子系统对账接口和市民卡页面查询功能
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/TradeErrorCode.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/TradeErrorCode.java
index 2ff2c01..84a951e 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/TradeErrorCode.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/TradeErrorCode.java
@@ -128,7 +128,7 @@
 
   public static final int SQL_EXCEPTION = 30004; // SQL处理异常
 
-  public static final int REQUEST_PARAM_EEROR = 30005; // 请求参数实体位置错误
+  public static final int NO_DEAL_EEROR = 30005; // 无交易记录
 
   //============= 交易错误 ============//
   public static final int CARD_NOT_EXISTS = 40000; //卡不存在
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/bean/CitizenCardSearchBean.java b/payapi/src/main/java/com/supwisdom/dlpay/system/bean/CitizenCardSearchBean.java
new file mode 100644
index 0000000..8505f39
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/bean/CitizenCardSearchBean.java
@@ -0,0 +1,60 @@
+package com.supwisdom.dlpay.system.bean;
+
+public class CitizenCardSearchBean {
+  private String cardno; //市民卡号
+  private String cardphyid; //物理ID
+  private String bankcardno; //银行卡号
+  private String username; //姓名
+  private String idno; //证件号
+
+  public CitizenCardSearchBean() {
+  }
+
+  public CitizenCardSearchBean(String cardno, String cardphyid, String bankcardno, String username, String idno) {
+    this.cardno = cardno;
+    this.cardphyid = cardphyid;
+    this.bankcardno = bankcardno;
+    this.username = username;
+    this.idno = idno;
+  }
+
+  public String getCardno() {
+    return cardno;
+  }
+
+  public void setCardno(String cardno) {
+    this.cardno = cardno;
+  }
+
+  public String getCardphyid() {
+    return cardphyid;
+  }
+
+  public void setCardphyid(String cardphyid) {
+    this.cardphyid = cardphyid;
+  }
+
+  public String getBankcardno() {
+    return bankcardno;
+  }
+
+  public void setBankcardno(String bankcardno) {
+    this.bankcardno = bankcardno;
+  }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  public String getIdno() {
+    return idno;
+  }
+
+  public void setIdno(String idno) {
+    this.idno = idno;
+  }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/bean/CitizenCardShowBean.java b/payapi/src/main/java/com/supwisdom/dlpay/system/bean/CitizenCardShowBean.java
new file mode 100644
index 0000000..b5c095d
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/bean/CitizenCardShowBean.java
@@ -0,0 +1,148 @@
+package com.supwisdom.dlpay.system.bean;
+
+public class CitizenCardShowBean {
+  private String cid; //市民卡tb_card的id
+  private String cardno;
+  private String cardtype;
+  private String cardphyid;
+  private String status;
+  private String expiredate;
+  private String bkid; //市民卡对应银行卡tb_card的id
+  private String bankcardno;
+  private Boolean signed;
+  private String lastsaved;
+  private String userid;
+  private String username;
+  private String idtype;
+  private String idno;
+  private String mobile;
+  private String email;
+
+  public String getCid() {
+    return cid;
+  }
+
+  public void setCid(String cid) {
+    this.cid = cid;
+  }
+
+  public String getCardno() {
+    return cardno;
+  }
+
+  public void setCardno(String cardno) {
+    this.cardno = cardno;
+  }
+
+  public String getCardtype() {
+    return cardtype;
+  }
+
+  public void setCardtype(String cardtype) {
+    this.cardtype = cardtype;
+  }
+
+  public String getCardphyid() {
+    return cardphyid;
+  }
+
+  public void setCardphyid(String cardphyid) {
+    this.cardphyid = cardphyid;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  public String getExpiredate() {
+    return expiredate;
+  }
+
+  public void setExpiredate(String expiredate) {
+    this.expiredate = expiredate;
+  }
+
+  public String getBkid() {
+    return bkid;
+  }
+
+  public void setBkid(String bkid) {
+    this.bkid = bkid;
+  }
+
+  public String getBankcardno() {
+    return bankcardno;
+  }
+
+  public void setBankcardno(String bankcardno) {
+    this.bankcardno = bankcardno;
+  }
+
+  public Boolean getSigned() {
+    return signed;
+  }
+
+  public void setSigned(Boolean signed) {
+    this.signed = signed;
+  }
+
+  public String getLastsaved() {
+    return lastsaved;
+  }
+
+  public void setLastsaved(String lastsaved) {
+    this.lastsaved = lastsaved;
+  }
+
+  public String getUserid() {
+    return userid;
+  }
+
+  public void setUserid(String userid) {
+    this.userid = userid;
+  }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  public String getIdtype() {
+    return idtype;
+  }
+
+  public void setIdtype(String idtype) {
+    this.idtype = idtype;
+  }
+
+  public String getIdno() {
+    return idno;
+  }
+
+  public void setIdno(String idno) {
+    this.idno = idno;
+  }
+
+  public String getMobile() {
+    return mobile;
+  }
+
+  public void setMobile(String mobile) {
+    this.mobile = mobile;
+  }
+
+  public String getEmail() {
+    return email;
+  }
+
+  public void setEmail(String email) {
+    this.email = email;
+  }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/controller/UserController.java b/payapi/src/main/java/com/supwisdom/dlpay/system/controller/UserController.java
index 31c7fae..fe353a1 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/system/controller/UserController.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/controller/UserController.java
@@ -10,6 +10,8 @@
 import com.supwisdom.dlpay.framework.util.PageResult;
 import com.supwisdom.dlpay.framework.util.StringUtil;
 import com.supwisdom.dlpay.framework.util.WebConstant;
+import com.supwisdom.dlpay.system.bean.CitizenCardSearchBean;
+import com.supwisdom.dlpay.system.bean.CitizenCardShowBean;
 import com.supwisdom.dlpay.system.bean.IdTypeBean;
 import com.supwisdom.dlpay.system.bean.PersonParamBean;
 import com.supwisdom.dlpay.system.service.DictionaryProxy;
@@ -161,4 +163,35 @@
     map.put("userid", userid);
     return "system/user/pointdtl";
   }
+
+  /**
+   * ====================================================
+   * 市民卡查询
+   * ====================================================
+   */
+  @GetMapping("/user/card")
+  public String card() {
+    return "system/user/card";
+  }
+
+  @GetMapping("/user/cardlist")
+  @PreAuthorize("hasPermission('/user/card','')")
+  @ResponseBody
+  public PageResult<CitizenCardShowBean> getBusinessDataList(@RequestParam("page") Integer pageNo,
+                                                             @RequestParam("limit") Integer pageSize,
+                                                             @RequestParam(value = "cardno", required = false) String cardno,
+                                                             @RequestParam(value = "cardphyid", required = false) String cardphyid,
+                                                             @RequestParam(value = "bankcardno", required = false) String bankcardno,
+                                                             @RequestParam(value = "username", required = false) String username,
+                                                             @RequestParam(value = "idno", required = false) String idno) {
+    try {
+      if (null == pageNo || pageNo < 1) pageNo = WebConstant.PAGENO_DEFAULT;
+      if (null == pageSize || pageSize < 1) pageSize = WebConstant.PAGESIZE_DEFAULT;
+      CitizenCardSearchBean param = new CitizenCardSearchBean(cardno, cardphyid, bankcardno, username, idno);
+      return userDataService.getUserCitizenCardPage(param, pageNo, pageSize);
+    } catch (Exception e) {
+      e.printStackTrace();
+      return new PageResult<>(99, "系统查询错误");
+    }
+  }
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/service/DtlDataService.java b/payapi/src/main/java/com/supwisdom/dlpay/system/service/DtlDataService.java
index 59afc6d..421d45b 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/system/service/DtlDataService.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/service/DtlDataService.java
@@ -1,5 +1,7 @@
 package com.supwisdom.dlpay.system.service;
 
+import com.supwisdom.dlpay.api.bean.DownloadShopBillData;
+import com.supwisdom.dlpay.api.bean.DownloadShopBillParam;
 import com.supwisdom.dlpay.api.domain.TPersondtl;
 import com.supwisdom.dlpay.api.domain.TShopdtl;
 import com.supwisdom.dlpay.framework.domain.TDictionary;
@@ -22,4 +24,10 @@
 
   @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = true)
   List<TreeSelectNode> getTreeSelectShops();
+
+  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = true)
+  long getDownloadShopBillCount(DownloadShopBillParam param);
+
+  @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, readOnly = true)
+  PageResult<DownloadShopBillData> getDownloadShopBillPage(DownloadShopBillParam param, int offset, int count);
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/service/UserDataService.java b/payapi/src/main/java/com/supwisdom/dlpay/system/service/UserDataService.java
index 942196e..3140f79 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/system/service/UserDataService.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/service/UserDataService.java
@@ -6,6 +6,8 @@
 import com.supwisdom.dlpay.api.domain.TPersonIdentity;
 import com.supwisdom.dlpay.api.domain.TPointsAccount;
 import com.supwisdom.dlpay.framework.util.PageResult;
+import com.supwisdom.dlpay.system.bean.CitizenCardSearchBean;
+import com.supwisdom.dlpay.system.bean.CitizenCardShowBean;
 import com.supwisdom.dlpay.system.bean.PersonParamBean;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
@@ -39,4 +41,11 @@
 
     @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class,readOnly = true)
     PageResult<TPointsAccount> getUserPointDTL(PersonParamBean param);
+
+
+    /**
+     * 市民卡信息查询
+     * */
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class,readOnly = true)
+    PageResult<CitizenCardShowBean> getUserCitizenCardPage(CitizenCardSearchBean param, int pageNo, int pageSize);
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/DtlDataServiceImpl.java b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/DtlDataServiceImpl.java
index 82fbdc2..f50b0f6 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/DtlDataServiceImpl.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/DtlDataServiceImpl.java
@@ -1,5 +1,7 @@
 package com.supwisdom.dlpay.system.service.impl;
 
+import com.supwisdom.dlpay.api.bean.DownloadShopBillData;
+import com.supwisdom.dlpay.api.bean.DownloadShopBillParam;
 import com.supwisdom.dlpay.api.dao.PersondtlDao;
 import com.supwisdom.dlpay.api.dao.ShopdtlDao;
 import com.supwisdom.dlpay.api.domain.TPersondtl;
@@ -18,6 +20,8 @@
 import com.supwisdom.dlpay.system.bean.UserdtlSearchBean;
 import com.supwisdom.dlpay.system.service.DictionaryProxy;
 import com.supwisdom.dlpay.system.service.DtlDataService;
+import org.hibernate.query.internal.NativeQueryImpl;
+import org.hibernate.transform.Transformers;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageRequest;
@@ -26,10 +30,14 @@
 import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
 
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
 import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.CriteriaQuery;
 import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Root;
+import java.math.BigInteger;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -47,6 +55,9 @@
   @Autowired
   private ShopDao shopDao;
 
+  @PersistenceContext
+  private EntityManager entityManager;
+
   @Override
   public PageResult<TPersondtl> getPersondtlPage(UserdtlSearchBean searchBean, int pageNo, int pageSize) {
     Pageable pageable = PageRequest.of(pageNo - 1, pageSize, Sort.by(Sort.Direction.DESC, "refno"));
@@ -162,4 +173,56 @@
     }
     return result;
   }
+
+  @Override
+  public long getDownloadShopBillCount(DownloadShopBillParam param) {
+    StringBuffer countSql = new StringBuffer("select count(t.refno) as cnt " +
+        "from tb_shopdtl t left join tb_transactionmain a on t.refno=a.refno where t.status='success' and t.accdate=:accdate and t.shopaccno=:shopaccno ");
+    if (!StringUtil.isEmpty(param.getSourcetype())) {
+      countSql.append(" and t.sourcetype=:sourcetype ");
+    }
+    if (!StringUtil.isEmpty(param.getDtltype())) {
+      countSql.append(" and t.dtltype=:dtltype ");
+    }
+    Query countQuery = entityManager.createNativeQuery(countSql.toString());
+    countQuery.setParameter("accdate", param.getCheckdate().trim());
+    countQuery.setParameter("shopaccno", param.getShopaccno().trim());
+    if (!StringUtil.isEmpty(param.getSourcetype())) {
+      countQuery.setParameter("sourcetype", param.getSourcetype().trim());
+    }
+    if (!StringUtil.isEmpty(param.getDtltype())) {
+      countQuery.setParameter("dtltype", param.getDtltype().trim());
+    }
+    BigInteger cnt = (BigInteger) countQuery.getSingleResult();
+    if (null != cnt) return cnt.longValue();
+    return 0;
+  }
+
+  @Override
+  public PageResult<DownloadShopBillData> getDownloadShopBillPage(DownloadShopBillParam param, int offset, int count) {
+    StringBuffer querySql = new StringBuffer("select a.outtradeno as billno,t.refno,cast(round(cast(t.amount as numeric)*100,0) as int4) as amount,to_char(a.end_time,'yyyyMMddHHmmss') as paytime,'SUCCESS' as status,t.sourcetype,t.dtltype \n" +
+        "from tb_shopdtl t left join tb_transactionmain a on t.refno=a.refno where t.status='success' and t.accdate=:accdate and t.shopaccno=:shopaccno ");
+    if (!StringUtil.isEmpty(param.getSourcetype())) {
+      querySql.append(" and t.sourcetype=:sourcetype ");
+    }
+    if (!StringUtil.isEmpty(param.getDtltype())) {
+      querySql.append(" and t.dtltype=:dtltype ");
+    }
+    querySql.append(" order by t.refno ");
+    Query query = entityManager.createNativeQuery(querySql.toString());
+    query.setParameter("accdate", param.getCheckdate().trim());
+    query.setParameter("shopaccno", param.getShopaccno().trim());
+    if (!StringUtil.isEmpty(param.getSourcetype())) {
+      query.setParameter("sourcetype", param.getSourcetype().trim());
+    }
+    if (!StringUtil.isEmpty(param.getDtltype())) {
+      query.setParameter("dtltype", param.getDtltype().trim());
+    }
+    query.setFirstResult(offset); //(pageNo - 1) * pageSize
+    query.setMaxResults(count); //分页显示
+    query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(DownloadShopBillData.class));
+    List<DownloadShopBillData> list = query.getResultList();
+    long cnt = getDownloadShopBillCount(param);
+    return new PageResult<>(cnt, list);
+  }
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/UserDataServiceImpl.java b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/UserDataServiceImpl.java
index 8b04848..a42e636 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/UserDataServiceImpl.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/UserDataServiceImpl.java
@@ -13,14 +13,22 @@
 import com.supwisdom.dlpay.framework.service.SystemUtilService;
 import com.supwisdom.dlpay.framework.tenant.TenantContext;
 import com.supwisdom.dlpay.framework.util.*;
+import com.supwisdom.dlpay.system.bean.CitizenCardSearchBean;
+import com.supwisdom.dlpay.system.bean.CitizenCardShowBean;
 import com.supwisdom.dlpay.system.bean.PersonParamBean;
 import com.supwisdom.dlpay.system.service.UserDataService;
+import org.hibernate.query.internal.NativeQueryImpl;
+import org.hibernate.transform.Transformers;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Pageable;
 import org.springframework.data.domain.Sort;
 import org.springframework.stereotype.Service;
 
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigInteger;
 import java.util.List;
 import java.util.Optional;
 
@@ -37,6 +45,9 @@
   @Autowired
   private PersonIdentityDao personIdentityDao;
 
+  @PersistenceContext
+  private EntityManager entityManager;
+
   @Override
   public PageResult<TPerson> getPersonsByKey(PersonParamBean param) {
     Pageable pageable = PageRequest.of(param.getPageNo() - 1, param.getPageSize()
@@ -181,4 +192,63 @@
   public PageResult<TPointsAccount> getUserPointDTL(PersonParamBean param) {
     return null;
   }
+
+  @Override
+  public PageResult<CitizenCardShowBean> getUserCitizenCardPage(CitizenCardSearchBean param, int pageNo, int pageSize) {
+    StringBuffer querySql = new StringBuffer("select t.id as cid,t.cardno,t.cardtype,t.cardphyid,case when t.status='closed' then 'closed' else t.trans_status end as status,t.expiredate,a.id as bkid,a.cardno as bankcardno,a.signed,t.lastsaved,t.userid,p.name as username,p.idtype,p.idno,p.mobile,p.email \n" +
+        "from tb_card t left join tb_card a on a.cardtype='bankcard' and a.cardphyid=t.cardphyid and a.userid=t.userid left join tb_person p on p.userid=t.userid \n" +
+        "where t.cardtype='citizencard' ");
+    StringBuffer countSql = new StringBuffer("select count(t.id) as cnt \n" +
+        "from tb_card t left join tb_card a on a.cardtype='bankcard' and a.cardphyid=t.cardphyid and a.userid=t.userid left join tb_person p on p.userid=t.userid \n" +
+        "where t.cardtype='citizencard' ");
+    if (!StringUtil.isEmpty(param.getCardno())) {
+      querySql.append(" and t.cardno=:cardno ");
+      countSql.append(" and t.cardno=:cardno ");
+    }
+    if (!StringUtil.isEmpty(param.getCardphyid())) {
+      querySql.append(" and t.cardphyid=:cardphyid ");
+      countSql.append(" and t.cardphyid=:cardphyid ");
+    }
+    if (!StringUtil.isEmpty(param.getBankcardno())) {
+      querySql.append(" and a.cardno=:bankcardno ");
+      countSql.append(" and a.cardno=:bankcardno ");
+    }
+    if (!StringUtil.isEmpty(param.getUsername())) {
+      querySql.append(" and p.name like :uname ");
+      countSql.append(" and p.name like :uname ");
+    }
+    if (!StringUtil.isEmpty(param.getIdno())) {
+      querySql.append(" and p.idno like :idno ");
+      countSql.append(" and p.idno like :idno ");
+    }
+    querySql.append(" order by t.cardno ");
+    Query query = entityManager.createNativeQuery(querySql.toString());
+    Query countQuery = entityManager.createNativeQuery(countSql.toString());
+    if (!StringUtil.isEmpty(param.getCardno())) {
+      query.setParameter("cardno", param.getCardno().trim());
+      countQuery.setParameter("cardno", param.getCardno().trim());
+    }
+    if (!StringUtil.isEmpty(param.getCardphyid())) {
+      query.setParameter("cardphyid", param.getCardphyid().trim());
+      countQuery.setParameter("cardphyid", param.getCardphyid().trim());
+    }
+    if (!StringUtil.isEmpty(param.getBankcardno())) {
+      query.setParameter("bankcardno", param.getBankcardno().trim());
+      countQuery.setParameter("bankcardno", param.getBankcardno().trim());
+    }
+    if (!StringUtil.isEmpty(param.getUsername())) {
+      query.setParameter("uname", "%" + param.getUsername().trim() + "%");
+      countQuery.setParameter("uname", "%" + param.getUsername().trim() + "%");
+    }
+    if (!StringUtil.isEmpty(param.getIdno())) {
+      query.setParameter("idno", "%" + param.getIdno().trim() + "%");
+      countQuery.setParameter("idno", "%" + param.getIdno().trim() + "%");
+    }
+    query.setFirstResult((pageNo - 1) * pageSize);
+    query.setMaxResults(pageSize); //分页显示
+    query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(CitizenCardShowBean.class));
+    List<CitizenCardShowBean> list = query.getResultList();
+    BigInteger cnt = (BigInteger) countQuery.getSingleResult();
+    return new PageResult<>(cnt.longValue(), list);
+  }
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/ConstantUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/util/ConstantUtil.java
index 873e619..ba129e6 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/util/ConstantUtil.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/util/ConstantUtil.java
@@ -89,4 +89,6 @@
   public static final String OPERCHK_CHKMODE_ADD = "新增";
   public static final String OPERCHK_CHKMODE_MODIFY = "修改";
   public static final String OPERCHK_CHKMODE_DELETE = "删除";
+
+  public static final String CHKFILE_DELIMITER = "|";
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/shop_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/shop_api_controller.kt
index a722aec..c1dbf6a 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/shop_api_controller.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/shop_api_controller.kt
@@ -1,5 +1,7 @@
 package com.supwisdom.dlpay.api.controller
 
+import com.supwisdom.dlpay.api.bean.DownloadShopBillData
+import com.supwisdom.dlpay.api.bean.DownloadShopBillParam
 import com.supwisdom.dlpay.api.bean.OpenShopParam
 import com.supwisdom.dlpay.api.bean.QueryShopParam
 import com.supwisdom.dlpay.api.exception.RequestParamCheckException
@@ -9,12 +11,15 @@
 import com.supwisdom.dlpay.framework.service.CommonService
 import com.supwisdom.dlpay.framework.tenant.TenantContext
 import com.supwisdom.dlpay.framework.util.TradeErrorCode
+import com.supwisdom.dlpay.system.service.DtlDataService
+import com.supwisdom.dlpay.util.ConstantUtil
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.http.ResponseEntity
 import org.springframework.web.bind.annotation.*
 import java.net.URLDecoder
-import javax.servlet.http.HttpServletRequest
+import java.nio.charset.StandardCharsets
 import javax.servlet.http.HttpServletResponse
+import java.io.OutputStream as OutputStream
 
 @RestController
 @RequestMapping("/api/shop")
@@ -23,6 +28,8 @@
     private lateinit var shopService: ShopService
     @Autowired
     private lateinit var commonService: CommonService
+    @Autowired
+    private lateinit var dtlDataService: DtlDataService
 
     @PostMapping("/open")
     fun openAccount(@RequestBody param: OpenShopParam): ResponseEntity<Any> {
@@ -71,4 +78,52 @@
                     .transException(et, "业务处理错误"))
         }
     }
+
+    /**
+     * 获取商户对账单接口
+     * 错误时返回json串{""}
+     */
+    @PostMapping("/downloadshopbill")
+    fun downloadShopBill(@RequestBody param: DownloadShopBillParam, response: HttpServletResponse): ResponseEntity<Any> {
+        val outputStream = response.outputStream
+        try {
+            shopService.checkDownloadShopBillParam(param) //判断该天是否已结算,未结算直接报错
+            val count = dtlDataService.getDownloadShopBillCount(param)
+            if (count <= 0) {
+                return ResponseEntity.ok(ResponseBodyBuilder.create().fail(TradeErrorCode.NO_DEAL_EEROR, "当天无交易记录"))
+            }
+            parseFile(dtlDataService, param, count, outputStream)
+            return ResponseEntity.ok(outputStream)
+        } catch (ex: RequestParamCheckException) {
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .requestException(ex, "请求参数错误"))
+        } catch (et: TransactionException) {
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .transException(et, "业务处理错误"))
+        } finally {
+            outputStream?.close()
+        }
+    }
+
+    private fun parseFile(dtlDataService: DtlDataService, param: DownloadShopBillParam, count: Long, output: OutputStream) {
+        writeLine(output, "billno", "refno", "amount", "paytime", "status", "sourcetype", "dtltype") //第一行输出标题
+        val pageSize = 5000 //每次查询笔数
+        var offset = 0
+        while (offset < count) {
+            var page = dtlDataService.getDownloadShopBillPage(param, offset, pageSize)
+            if (null == page || page.data.isNullOrEmpty()) break
+            page!!.data.forEach {
+                writeLine(output, it.billno, it.refno, it.amount, it.paytime, it.status, it.sourcetype, it.dtltype) //输出数据
+            }
+            offset += pageSize
+        }
+    }
+
+    private fun writeLine(output: OutputStream, vararg columns: Any) {
+        for (item in columns) {
+            output.write(item.toString().toByteArray(StandardCharsets.UTF_8))
+            output.write(ConstantUtil.CHKFILE_DELIMITER.toByteArray(StandardCharsets.UTF_8))
+        }
+        output.write("\n".toByteArray())
+    }
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/shop_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/shop_service_impl.kt
index ff890fb..fde2ac5 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/shop_service_impl.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/shop_service_impl.kt
@@ -1,21 +1,25 @@
 package com.supwisdom.dlpay.api.service.impl
 
+import com.supwisdom.dlpay.api.bean.DownloadShopBillParam
 import com.supwisdom.dlpay.api.bean.OpenShopParam
+import com.supwisdom.dlpay.api.exception.RequestParamCheckException
 import com.supwisdom.dlpay.api.service.ShopService
+import com.supwisdom.dlpay.api.service.SourceTypeService
 import com.supwisdom.dlpay.api.types.ShopTypes
 import com.supwisdom.dlpay.exception.TransactionProcessException
+import com.supwisdom.dlpay.framework.dao.DictionaryDao
+import com.supwisdom.dlpay.framework.dao.SettleCtlDao
 import com.supwisdom.dlpay.framework.dao.ShopDao
 import com.supwisdom.dlpay.framework.dao.ShopaccDao
 import com.supwisdom.dlpay.framework.domain.TShop
 import com.supwisdom.dlpay.framework.domain.TShopacc
 import com.supwisdom.dlpay.framework.service.SystemUtilService
-import com.supwisdom.dlpay.framework.util.StringUtil
-import com.supwisdom.dlpay.framework.util.Subject
-import com.supwisdom.dlpay.framework.util.TradeDict
-import com.supwisdom.dlpay.framework.util.TradeErrorCode
+import com.supwisdom.dlpay.framework.util.*
 import com.supwisdom.dlpay.util.EnumCheck
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.stereotype.Service
+import javax.persistence.EntityManager
+import javax.persistence.PersistenceContext
 
 /**
  * Created by shuwei on 2019/4/15.
@@ -28,6 +32,12 @@
     private lateinit var shopaccDao: ShopaccDao
     @Autowired
     private lateinit var systemUtilService: SystemUtilService
+    @Autowired
+    private lateinit var dictionaryDao: DictionaryDao
+    @Autowired
+    private lateinit var settleCtlDao: SettleCtlDao
+    @Autowired
+    private lateinit var sourceTypeService: SourceTypeService
 
     private val shoptypeCheck = EnumCheck<ShopTypes, String>()
 
@@ -98,4 +108,27 @@
             shopDao.getByThirdUniqueIdenty(shopUniqueId)
         }
     }
+
+    override fun checkDownloadShopBillParam(param: DownloadShopBillParam): Boolean {
+        if (!StringUtil.isEmpty(param.checkdate)) {
+            val ctl = settleCtlDao.findByTenantId(param.tenantid)
+                    ?: throw TransactionProcessException(TradeErrorCode.BUSINESS_DEAL_ERROR, "[tenantid=${param.tenantid}]未找到结算状态表")
+            if (DateUtil.compareDatetime(param.checkdate, "${ctl.settledate}", "yyyyMMdd") >= 0) {
+                throw TransactionProcessException(TradeErrorCode.INPUT_DATA_ERROR, "对账单未生成") //未结算,不返回对账单
+            }
+        }
+        if (!StringUtil.isEmpty(param.shopaccno)) {
+            shopaccDao.findByShopaccno(param.shopaccno.trim())
+                    ?: throw RequestParamCheckException("指定商户不存在!")
+        }
+        if (!StringUtil.isEmpty(param.sourcetype)) {
+            sourceTypeService.getBySourceType(param.sourcetype.trim())
+                    ?: throw RequestParamCheckException("支付类型错误")
+        }
+        if (!StringUtil.isEmpty(param.dtltype)) {
+            dictionaryDao.getByDicttypeAndDictval(Dictionary.DTLTYPES, param.dtltype.trim())
+                    ?: throw RequestParamCheckException("流水类型错误")
+        }
+        return true
+    }
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/shop_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/shop_service.kt
index 697bc11..f02c2e2 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/shop_service.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/shop_service.kt
@@ -1,5 +1,6 @@
 package com.supwisdom.dlpay.api.service
 
+import com.supwisdom.dlpay.api.bean.DownloadShopBillParam
 import com.supwisdom.dlpay.api.bean.OpenShopParam
 import com.supwisdom.dlpay.framework.domain.TShop
 import org.springframework.transaction.annotation.Propagation
@@ -20,4 +21,7 @@
 
     @Transactional(propagation = Propagation.REQUIRED, rollbackFor = arrayOf(Exception::class), readOnly = true)
     fun findByShopBySearch(shopid: Int?, shopaccno: String?, shopUniqueId: String?): TShop?
+
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = arrayOf(Exception::class), readOnly = true)
+    fun checkDownloadShopBillParam(param: DownloadShopBillParam): Boolean
 }
\ No newline at end of file
diff --git a/payapi/src/main/resources/data.sql b/payapi/src/main/resources/data.sql
index 32cfd2f..446650b 100644
--- a/payapi/src/main/resources/data.sql
+++ b/payapi/src/main/resources/data.sql
@@ -73,6 +73,9 @@
 VALUES (35, NULL, 1, NULL, '', '/thirdchk/chkfile', '对账明细查询', 332, 33, '{tenantid}');
 INSERT INTO "tb_function" ("id", "createtime", "isleaf", "lastsaved", "menuicon", "menuurl", "name", "ordernum", "parentid", tenantid)
 VALUES (36, NULL, 1, NULL, '', '/shop/shopcheck', '商户审批', 2, 7, '{tenantid}');
+INSERT INTO "tb_function" ("id", "createtime", "isleaf", "lastsaved", "menuicon", "menuurl", "name", "ordernum", "parentid", tenantid)
+VALUES (37, NULL, 1, NULL, '', '/user/card', '市民卡查询', 1, 19, '{tenantid}');
+
 
 INSERT INTO "tb_role_function" ("id", "functionid", "roleid", tenantid)
 VALUES ('ff8080816b7947ed016b7955772c0032', 1, 'd1yctWs5+ks0iQN3m9bUvRHus6HbKbrs', '{tenantid}');
@@ -136,6 +139,8 @@
 VALUES ('ff8083816c8468e8016c846d7a570017', 35, 'd1yctWs5+ks0iQN3m9bUvRHus6HbKbrs', '{tenantid}');
 INSERT INTO "tb_role_function" ("id", "functionid", "roleid", tenantid)
 VALUES ('ff8080816db87e27016db88be41a0014', 36, 'd1yctWs5+ks0iQN3m9bUvRHus6HbKbrs', '{tenantid}');
+INSERT INTO "tb_role_function" ("id", "functionid", "roleid", tenantid)
+VALUES ('ff8080816db87e27016db88be41f0015', 37, 'd1yctWs5+ks0iQN3m9bUvRHus6HbKbrs', '{tenantid}');
 
 
 INSERT INTO "tb_resource" ("id", "code", "function_id", "name", "uri", tenantid)
@@ -290,6 +295,8 @@
 VALUES (88, '', 36, '审核跳转', '/shop/opercheck', '{tenantid}');
 INSERT INTO "tb_resource" ("id", "code", "function_id", "name", "uri", tenantid)
 VALUES (89, '', 36, '审核', '/shop/docheckshop', '{tenantid}');
+INSERT INTO "tb_resource" ("id", "code", "function_id", "name", "uri", tenantid)
+VALUES (90, '', 37, '查询', '/user/card', '{tenantid}');
 
 
 INSERT INTO "tb_permission" ("id", "resid", "role_func_id", "roleid", tenantid)
@@ -444,6 +451,8 @@
 VALUES ('ff8080816db87e27016db94468da0089', 88, NULL, 'd1yctWs5+ks0iQN3m9bUvRHus6HbKbrs', '{tenantid}');
 INSERT INTO  "tb_permission" ("id", "resid", "role_func_id", "roleid", "tenantid")
 VALUES ('ff8080816db87e27016db94468da008a', 89, NULL, 'd1yctWs5+ks0iQN3m9bUvRHus6HbKbrs', '{tenantid}');
+INSERT INTO  "tb_permission" ("id", "resid", "role_func_id", "roleid", "tenantid")
+VALUES ('ff8080816db87e27016db9446fda008b', 90, NULL, 'd1yctWs5+ks0iQN3m9bUvRHus6HbKbrs', '{tenantid}');
 
 
 INSERT INTO "tb_subject" ("subjid","subjno", "balflag", "displayflag", "endflag", "fsubjno", "opendate", "subjlevel", "subjname", "subjtype", "tenantid")
diff --git a/payapi/src/main/resources/templates/system/user/card.html b/payapi/src/main/resources/templates/system/user/card.html
new file mode 100644
index 0000000..138d2b9
--- /dev/null
+++ b/payapi/src/main/resources/templates/system/user/card.html
@@ -0,0 +1,163 @@
+<div class="layui-card">
+    <div class="layui-card-header">
+        <h2 class="header-title">市民卡查询</h2>
+        <span class="layui-breadcrumb pull-right">
+          <a href="#!_user_index">用户中心</a>
+          <a><cite>市民卡查询</cite></a>
+        </span>
+    </div>
+    <div class="layui-card-body">
+        <div class="layui-form" lay-filter="citizencard-search-form">
+            <div class="layui-form-item" style="margin-bottom: 0;">
+                <div class="layui-inline" style="margin-right: 20px;">
+                    <label class="layui-form-label">市民卡号</label>
+                    <div class="layui-input-block" style="width: 265px;">
+                        <input type="text" name="cardno" id="citizencard-search-cardno" placeholder="市民卡号" maxlength="10"
+                               autocomplete="off" class="layui-input"/>
+                    </div>
+                </div>
+                <div class="layui-inline" style="margin-right: 20px;">
+                    <label class="layui-form-label">物理卡号</label>
+                    <div class="layui-input-block" style="width: 265px;">
+                        <input type="text" name="cardphyid" id="citizencard-search-cardphyid" placeholder="物理卡号" maxlength="8"
+                               autocomplete="off" class="layui-input"/>
+                    </div>
+                </div>
+                <div class="layui-inline" style="margin-right: 20px;">
+                    <label class="layui-form-label">银行卡号</label>
+                    <div class="layui-input-block" style="width: 265px;">
+                        <input type="text" name="bankcardno" id="citizencard-search-bankcardno" placeholder="银行卡号" maxlength="19"
+                               autocomplete="off" class="layui-input"/>
+                    </div>
+                </div>
+                <div class="layui-inline" style="margin-right: 20px;margin-top: 10px;">
+                    <label class="layui-form-label">姓名</label>
+                    <div class="layui-input-block" style="width: 265px;">
+                        <input type="text" name="username" id="citizencard-search-username" placeholder="姓名" maxlength="20"
+                               autocomplete="off" class="layui-input"/>
+                    </div>
+                </div>
+                <div class="layui-inline" style="margin-right: 20px;margin-top: 10px;">
+                    <label class="layui-form-label">证件号</label>
+                    <div class="layui-input-block" style="width: 265px;">
+                        <input type="text" name="idno" id="citizencard-search-idno" placeholder="证件号" maxlength="18"
+                               autocomplete="off" class="layui-input"/>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="layui-card-body">
+        <table class="layui-table" id="citizencardSearchTable" lay-filter="citizencardSearchTable-filter"></table>
+    </div>
+</div>
+
+<script type="text/html" id="citizencard-table-toolbar">
+    <div class="layui-btn-container">
+        <button class="layui-btn layui-btn-sm" id="btn-citizencard-search-ok" lay-event="search"><i
+                class="layui-icon">&#xe615;</i>搜 索</button>
+        <button class="layui-btn layui-btn-sm" id="btn-citizencard-search-clear" lay-event="clear"><i
+                class="layui-icon"></i>清 空</button>
+    </div>
+</script>
+
+<script>
+    layui.use(['form', 'table', 'layer', 'admin'], function () {
+        var form = layui.form;
+        var table = layui.table;
+        var admin = layui.admin;
+
+        table.render({
+            id: 'citizencardSearchTable',
+            title: '市民卡信息查询',
+            elem: '#citizencardSearchTable',
+            url: '[[@{/user/cardlist}]]',
+            page: true,
+            toolbar: '#citizencard-table-toolbar',
+            cols: [
+                [
+                    {field:'cardno',title:'市民卡号', align: 'center', width: 120, fixed: 'left', sort: true},
+                    {field:'cardphyid',title:'物理卡号', align: 'center', width: 120, fixed: 'left'},
+                    {
+                        field: 'expiredate', title: '有效期', align: 'center', width: 120, templet: function (d) {
+                            return dateFormat('' + d.expiredate);
+                        }
+                    },
+                    {
+                        field: 'status', title: '状态', align: 'center', width: 80, sort: true, templet: function (d) {
+                            if (d.status == 'closed') {
+                                return '<span class="layui-badge layui-bg-gray">注销</span>';
+                            } else if (d.status == 'normal') {
+                                return '<span class="layui-badge layui-bg-green">正常</span>';
+                            } else if (d.status == 'unuse') {
+                                return '<span class="layui-badge layui-bg-blue">未启用</span>';
+                            } else if (d.status == 'abnormal') {
+                                return '<span class="layui-badge">异常</span>';
+                            } else if (d.status == 'lost') {
+                                return '<span class="layui-badge">挂失</span>';
+                            } else if (d.status == 'locked') {
+                                return '<span class="layui-badge">锁定</span>';
+                            } else if (d.status == 'frozen') {
+                                return '<span class="layui-badge">冻结</span>';
+                            } else {
+                                return d.status;
+                            }
+                        }
+                    },
+                    {field: 'bankcardno', title: '银行卡号', align: 'center', width: 200, sort: true},
+                    {
+                        field: 'signed', title: '是否签约', align: 'center', width: 110, sort: true, templet: function (d) {
+                            if (d.signed) {
+                                return '<span class="layui-badge layui-bg-green">已签约</span>';
+                            } else {
+                                return '<span class="layui-badge layui-bg-gray">未签约</span>';
+                            }
+                        }
+                    },
+                    {field: 'username', title: '用户名', align: 'center', width: 120},
+                    {
+                        field: 'idtype', title: '证件类型', align: 'center', width: 100, templet: function (d) {
+                            return getTempDictValue('idtypeList', d.idtype);
+                        }
+                    },
+                    {field: 'idno', title: '证件号', align: 'center', width: 180},
+                    {field: 'mobile', title: '手机号', align: 'center', width: 120},
+                    {field: 'email', title: '电子邮箱', align: 'center', width: 200},
+                    {
+                        field: 'lastsaved',
+                        title: '更新时间',
+                        align: 'center',
+                        width: 200,
+                        sort: true,
+                        templet: function (d) {
+                            return admin.formatDate(d.lastsaved);
+                        }
+                    }
+                ]
+            ]
+        });
+
+        table.on('toolbar(citizencardSearchTable-filter)', function (obj) {
+            switch (obj.event) {
+                case 'search':
+                    table.reload('citizencardSearchTable', {
+                        where: {
+                            cardno: $("#citizencard-search-cardno").val(),
+                            cardphyid: $("#citizencard-search-cardphyid").val(),
+                            bankcardno: $("#citizencard-search-bankcardno").val(),
+                            username: $("#citizencard-search-username").val(),
+                            idno: $("#citizencard-search-idno").val()
+                        }, page: {curr: 1}
+                    });
+                    break;
+                case 'clear':
+                    $("#citizencard-search-cardno").val("");
+                    $("#citizencard-search-cardphyid").val("");
+                    $("#citizencard-search-bankcardno").val("");
+                    $("#citizencard-search-username").val("");
+                    $("#citizencard-search-idno").val("");
+                    break;
+            }
+        });
+    });
+</script>
diff --git a/payapi/src/main/resources/templates/system/user/index.html b/payapi/src/main/resources/templates/system/user/index.html
index 5507955..a1d07a8 100644
--- a/payapi/src/main/resources/templates/system/user/index.html
+++ b/payapi/src/main/resources/templates/system/user/index.html
@@ -56,7 +56,7 @@
                             return getTempDictValue('idtypeList', item.idtype);
                         }
                     },
-                    {field: 'idno', title: '证件号', width: 120, align: 'center', sort: true},
+                    {field: 'idno', title: '证件号', width: 180, align: 'center', sort: true},
                     {field: 'email', title: '邮箱', width: 100, align: 'center',  sort: true},
                     {field: 'mobile', title: '手机', width: 120, align: 'center', sort: true},
                     {field: 'tel', title: '电话', align: 'center'},