From: Tang Cheng Date: Wed, 14 Aug 2019 09:00:25 +0000 (+0800) Subject: refactor: 重构了对账任务 X-Git-Tag: 1.0.1^2~29 X-Git-Url: https://source.supwisdom.com/gerrit/gitweb?a=commitdiff_plain;h=62d2194044a3fa7469c6a7282fa7830b9800c390;p=epayment%2Ffood_payapi.git refactor: 重构了对账任务 --- diff --git a/payapi/src/main/java/com/supwisdom/dlpay/agent/CheckFileProvider.java b/payapi/src/main/java/com/supwisdom/dlpay/agent/CheckFileProvider.java index b8b3fa52..008dcd7f 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/agent/CheckFileProvider.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/agent/CheckFileProvider.java @@ -3,9 +3,25 @@ package com.supwisdom.dlpay.agent; import com.supwisdom.dlpay.api.domain.TSourceTypeCheckStatus; public interface CheckFileProvider { + /** + * 请求对账文件接口,由对账任务请求第三查询 checkStatus.checkFileDate 的对账文件 + * + * @param checkStatus + * @return AgentResponse.code == SUCCESS 时 ,有两种情况: 当 checkStatus.checkFileOk == true 表示对账文件明细已经 + * 写入数据库中,不需要 downloadCheckFile , 当为 false 时才需要 + * AgentResponse.code == REQUIRE_QUERY 时, 对账任务会调用 queryCheckFile 方法判断是否可以下载对账文件了 + * 其它值表示失败 + */ AgentResponse acquireCheckFile(TSourceTypeCheckStatus checkStatus); AgentResponse queryCheckFile(TSourceTypeCheckStatus checkStatus); + /** + * 下载checkStatus.checkDate 指定的日期的对账文件,由对账任务调用, + * 如果 checkStatus.chkFileUrl == 'none' 表示当日没有对账文件, + * + * @param checkStatus + * @return AgentResponse.code 返回值只有 SUCCESS 和 FAIL + */ AgentResponse downloadCheckFile(TSourceTypeCheckStatus checkStatus); } diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionChkdtlDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionChkdtlDao.java index 122ce76f..51301ce9 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionChkdtlDao.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionChkdtlDao.java @@ -23,9 +23,19 @@ public interface TransactionChkdtlDao extends JpaRepository 'equal' and a.chkfileId=?1 order by a.recordno ") List getCheckEqualDetails(String chkfileId); + @Query("select a from TTransactionChkdtl a,TPersondtl b where a.localRefno=b.refno and a.sourcetype=b.sourceType and a.accdate=b.accdate and a.amount=b.amount " + + " and a.otherStatus='success' and b.status='success' and a.chkresult <> 'equal' and a.chkfileId=?1 and a.recordno between ?2 and ?3 order by a.recordno ") + List findEqualDtlWithLimit(String chkfileId, Integer start, Integer end); + + @Query("select min(a.recordno) from TTransactionChkdtl a where a.chkfileId=?1 and a.chkresult=?2") + Integer getMinRecordNoByStatus(String chkFileId, String status); + + @Query("select min(a.recordno) from TTransactionChkdtl a where a.chkfileId=?1 ") + Integer getMinRecordNo(String chkFileId); + @Query("from TTransactionChkdtl t where t.chkfileId=?1 and t.chkresult='uncheck' order by t.recordno ") List getUncheckTransactionChkdtls(String chkfileId); @@ -34,6 +44,6 @@ public interface TransactionChkdtlDao extends JpaRepository findLocalMoreDtls(@Param("accdate")String accdate, @Param("sourcetype")String sourcetype, @Param("chkfileId") String chkfileId); + List findLocalMoreDtls(@Param("accdate") String accdate, @Param("sourcetype") String sourcetype, @Param("chkfileId") String chkfileId); } diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TSourceTypeCheckStatus.java b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TSourceTypeCheckStatus.java index bad55b9e..6a134672 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TSourceTypeCheckStatus.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TSourceTypeCheckStatus.java @@ -1,5 +1,7 @@ package com.supwisdom.dlpay.api.domain; +import com.sun.org.apache.xpath.internal.operations.Bool; + import javax.persistence.*; import javax.validation.constraints.NotNull; import java.sql.Timestamp; @@ -35,6 +37,13 @@ public class TSourceTypeCheckStatus { @NotNull private Boolean checkStatus; // 当前对账完成状态 + @Column(name = "repair_status") + private Boolean repairStatus; // 补帐完成状态 + + @Column(name = "settle_status") + @NotNull + private Boolean settleStatus; // 是否可以结算 + @Column(name = "force_recheck") @NotNull private Boolean forceRecheck; // 是否对当前日期强制对账 @@ -149,4 +158,20 @@ public class TSourceTypeCheckStatus { public void setRemark(String remark) { this.remark = remark; } + + public Boolean getSettleStatus() { + return settleStatus; + } + + public void setSettleStatus(Boolean settleStatus) { + this.settleStatus = settleStatus; + } + + public Boolean getRepairStatus() { + return repairStatus; + } + + public void setRepairStatus(Boolean repairStatus) { + this.repairStatus = repairStatus; + } } diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TTransactionChkdtl.java b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TTransactionChkdtl.java index f5dc80eb..ef5422a1 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TTransactionChkdtl.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TTransactionChkdtl.java @@ -3,11 +3,14 @@ package com.supwisdom.dlpay.api.domain; import org.hibernate.annotations.GenericGenerator; import javax.persistence.*; +import javax.validation.constraints.NotNull; import java.sql.Timestamp; @Entity @Table(name = "TB_TRANSACTION_CHKDTL", - indexes = {@Index(name = "UK_TRANSACTION_CHKDTL", unique = true, columnList = "ACCDATE,SOURCETYPE,OTHER_REFNO,LOCAL_REFNO")}) + indexes = {@Index(name = "UK_TRANSACTION_CHKDTL", unique = true, columnList = "ACCDATE,SOURCETYPE,OTHER_REFNO,LOCAL_REFNO"), + @Index(name = "TRANSACTION_CHKDTL_FILEID", columnList = "CHKFILE_ID"), + @Index(name = "TRANSACTION_CHKDTL_RECORDNO", columnList = "CHKFILE_ID, RECORDNO", unique = true)}) public class TTransactionChkdtl { @Id @GenericGenerator(name = "idGenerator", strategy = "uuid") @@ -16,6 +19,7 @@ public class TTransactionChkdtl { private String id; @Column(name = "CHKFILE_ID", nullable = false, length = 32) + @NotNull private String chkfileId; @Column(name = "ACCDATE", nullable = false, length = 8) @@ -25,6 +29,7 @@ public class TTransactionChkdtl { private String sourcetype; @Column(name = "RECORDNO", precision = 9) + @NotNull private Integer recordno; @Column(name = "AMOUNT", nullable = false, precision = 9, scale = 2) diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/citizencard_checkfile_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/citizencard_checkfile_service.kt index 615db409..46ae3401 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/citizencard_checkfile_service.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/citizencard_checkfile_service.kt @@ -43,21 +43,32 @@ class CitizenCardCheckFileProvider : CheckFileProvider { val billDate = checkStatus.checkFileDate logger.info("大理农商行对账单下载:download checkdate=【$billDate】") - val chkfile: TTransactionChkfile = transactionReconciliationService.getTransactionChkfile(billDate, checkStatus.sourceType)?.let { file -> - when (file.status) { - ConstantUtil.CHKFILE_STATUS_INIT -> file //初始化直接返回 - ConstantUtil.CHKFILE_STATUS_ERROR, ConstantUtil.CHKFILE_STATUS_UNCHECK -> reinitCheckFile(file) //错误或未对账的直接重新拉取 - else -> { - when (checkStatus.forceRecheck) { - true -> reinitCheckFile(file) //对账已完成但强制重新对账 - false -> throw TransactionException(-1, "大理农商行该天[$billDate]已对账完成") //对账已完成报错 + val chkfile: TTransactionChkfile = transactionReconciliationService + .getTransactionChkfile(billDate, checkStatus.sourceType)?.let { file -> + when (file.status) { + ConstantUtil.CHKFILE_STATUS_INIT -> file //初始化直接返回 + ConstantUtil.CHKFILE_STATUS_ERROR -> reinitCheckFile(file) //错误或未对账的直接重新拉取 + else -> { + when (checkStatus.forceRecheck) { + true -> reinitCheckFile(file) //对账已完成但强制重新对账 + false -> { + if (file.status == ConstantUtil.CHKFILE_STATUS_UNCHECK) { + file + } else { + throw TransactionException(-1, "大理农商行该天[$billDate]已对账完成") + } + } //对账已完成报错 + } + } } } - } - } ?: transactionReconciliationService.saveInitTransactionChkfile(billDate, checkStatus.sourceType) + ?: transactionReconciliationService.saveInitTransactionChkfile(billDate, checkStatus.sourceType) + if (chkfile.status == ConstantUtil.CHKFILE_STATUS_UNCHECK) { + return successDownload("[$billDate]日对账数据入库成功", checkStatus) + } val resp = citizencardPayService.getChkfilename(billDate, null) - when(resp.code) { + when (resp.code) { YnrccUtil.CODE_SUCCESS -> { val agentConfig = sourceTypeService.getChargePaytypeConfig(TradeDict.PAYTYPE_CITIZEN_CARD, true) val agentUrl = agentConfig[YnrccUtil.YNRCC_ANGENT_URL] + "/download?filename=" + resp.filename @@ -75,16 +86,10 @@ class CitizenCardCheckFileProvider : CheckFileProvider { YnrccUtil.NO_RECORDS_TODAY -> { //当日无交易明细,也创建空记录 transactionReconciliationService.doSuccessTransactionChkfile(chkfile, "请求银行返回:当日无交易明细") - //成功 - return AgentResponse().also { - it.code = AgentCode.SUCCESS - it.agentMsg = "当日无交易明细" - it.payload = sourceTypeService.saveOrUpdateSourceTypeCheckStatus(checkStatus.apply { - this.chkfileUrl = "none" - }) - } - + return successDownload("当日无交易明细", checkStatus.apply { + this.chkfileUrl = "none" + }) } else -> { //报错,退出对账单拉取 @@ -124,18 +129,11 @@ class CitizenCardCheckFileProvider : CheckFileProvider { } override fun downloadCheckFile(checkStatus: TSourceTypeCheckStatus): AgentResponse { - try{ + try { val billDate = checkStatus.checkFileDate val agentUrl = checkStatus.chkfileUrl if ("none" == agentUrl) { - return AgentResponse().also { - it.code = AgentCode.SUCCESS - it.agentMsg = "当日无交易明细" - it.payload = sourceTypeService.saveOrUpdateSourceTypeCheckStatus(checkStatus.apply { - this.checkFileOk = true - this.remark = "当日无交易明细" - }) - } + return successDownload("当日无交易明细", checkStatus) } val chkfile = transactionReconciliationService.doInitTransactionChkfile(billDate, checkStatus.sourceType) @@ -189,14 +187,7 @@ class CitizenCardCheckFileProvider : CheckFileProvider { //成功 transactionReconciliationService.doSuccessTransactionChkfile(chkfile, "对账单数据下载成功") - return AgentResponse().also { - it.code = AgentCode.SUCCESS - it.agentMsg = "[$billDate]日对账数据入库成功" - it.payload = sourceTypeService.saveOrUpdateSourceTypeCheckStatus(checkStatus.apply { - this.checkFileOk = true - this.remark = "[$billDate]日对账数据入库成功" - }) - } + return successDownload("[$billDate]日对账数据入库成功", checkStatus) } } else { logger.error("请求前置download[${chkfile.accdate}]对账单返回失败:httpStatus=[${respClient.status}]。获取对账文件数据失败") @@ -213,15 +204,26 @@ class CitizenCardCheckFileProvider : CheckFileProvider { } } - }catch (ex:Exception){ + } catch (ex: Exception) { return AgentResponse().also { it.code = AgentCode.FAIL - it.agentMsg = ex.message?:"按对账单路径获取对账数据入库异常" + it.agentMsg = ex.message ?: "按对账单路径获取对账数据入库异常" it.payload = checkStatus } } } + private fun successDownload(message: String, checkStatus: TSourceTypeCheckStatus): AgentResponse { + return AgentResponse().also { + it.code = AgentCode.SUCCESS + it.agentMsg = message + it.payload = sourceTypeService.saveOrUpdateSourceTypeCheckStatus(checkStatus.apply { + this.checkFileOk = true + this.remark = message + }) + } + } + private fun reinitCheckFile(chkfile: TTransactionChkfile): TTransactionChkfile { transactionReconciliationService.deleteTransactionChkdtls(chkfile.id) //删除明细 return transactionReconciliationService.saveOrUpdateTransactionChkfile(chkfile.apply { diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_sourcetype_chk.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_sourcetype_chk.kt index 4a552f82..2806109f 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_sourcetype_chk.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_sourcetype_chk.kt @@ -5,6 +5,7 @@ import com.supwisdom.dlpay.agent.AgentResponse import com.supwisdom.dlpay.agent.CheckFileProvider import com.supwisdom.dlpay.api.domain.TSourceType import com.supwisdom.dlpay.api.domain.TSourceTypeCheckStatus +import com.supwisdom.dlpay.api.domain.TTransactionChkdtl import com.supwisdom.dlpay.api.domain.TTransactionChkfile import com.supwisdom.dlpay.api.service.SourceTypeService import com.supwisdom.dlpay.api.service.TransactionReconciliationService @@ -63,7 +64,7 @@ class SourceTypeCheck { ?: newSourceTypeStatus(sourcetype, accdate) val interval = DateUtil.getIntervalDay(status.checkAccdate, accdate).toInt() - return if (status.checkStatus && interval == sourcetype.tplusN) { + return if (status.checkStatus && status.repairStatus && interval == sourcetype.tplusN) { // 当前日期对账已完成,并且已经对账到最后一天 null } else if (status.checkStatus && interval > sourcetype.tplusN) { @@ -72,8 +73,11 @@ class SourceTypeCheck { status.checkStatus = false status.checkFileDate = DateUtil.getNewDay(status.checkFileDate, 1) status.checkFileOk = false + status.repairStatus = false + status.settleStatus = false + status.forceRecheck = false sourceTypeService.saveOrUpdateSourceTypeCheckStatus(status) - } else if (!status.checkStatus && interval >= sourcetype.tplusN) { + } else if ((!status.checkStatus || !status.repairStatus) && interval >= sourcetype.tplusN) { // 当前对账并未完成 且满足对账条件 status } else { @@ -150,6 +154,44 @@ class SourceTypeCheck { } @Component +/** + * 对账业务主要由三张表组成 TSourceTypeCheckStatus(对账状态表), TTransactionChkfile(对账文件表), TTransactionChkDtl(对账明细表) + * 对账状态表有五个状态: + * 1. checkFileOk: 表示对账文件是否成功导入对账文件表和对账明细表中 + * 2. checkStatus: 表示核对流水任务是否完成,当核对任务完成后,同时根据对"对账文件表"的对账结果(result)来判断是否完成核对,只有当 result + * 是 "一致" 或 "不平" 时 checkStatus 才为 true + * 3. repairStatus: 表示核对完成,并且可以进行补帐交易。对账任务判断 repairStatus == true 之后将会对所有不一致的交易进行补帐。 + * 4. settleStatus: 表示本次对账完成(核对与补帐都完成),对账任务会在补帐完成后将 settleStatus 置为 true。 + * 同时 TTransactionChkDtl 的 resovled 状态全部为 'equal'。系统结算任务只有当 settleStatus == true 的时候才会进行 + * 5. forceRecheck : 表示对当前的 checkDate 的对账文件重新下载并对账 + * + * + * 对账文件表的 status 表示本次核对的结果。对账文件表 status 有几种情况: + * 1. 初始状态(init),表示对账任务已经触发; + * 2. 待核对状态(unchecked), 表示对账文件已存入数据库中; + * 3. 错误状态(error), 保存数据有错误 + * 4. 完成状态(finish) , 表示核对完成状态; + * + * 对账文件表的对账结果字段(result) 表示本次对账处理结果: + * 1. 未核对(none), 表示还未与流水进行比对 + * 2. 一致( equal), 表示对账完全一致 + * 3. 不平( notequal), 表示对账不一致,但可以通过补帐交易完成结算 + * 4. 错误( error ) , 表示核对时数据校验不正确,系统不能通过补帐交易完成结算。需人工审核对账文件后,重新下载对账单并核对。 + * + * + * 对账明细表(TTransactionChkDtl) 的 chkstatus 表示核对结果: + * 1. 未检查(uncheck), 表示记录还未核对 + * 2. 一直( equal) ,表示记录核对与流水一致 + * 3. 交易流水不存在(notexist), 表示本地交易流水不存在,影响对账文件表的 result 状态,为error。 + * 4. 支付未记账 ( nocharge) ,表示本地流水未成功,需要补帐 + * 5. 金额不相等(diff), 表示对账明细金额与本地流水不一致, 影响对账文件表的 result 状态,为error。 + * 6. 记账日期或支付方式错误 (error) , 其它类型错误,影响对账文件表的 result 状态,为error。 + * + * 对账明细表(TTransactionChkDtl) 的 result 表示处理结果 + * 1. 需要补帐("add") + * 2. 一致("equal") , 表示一致 + * 3. 挂起("hangup"), 表示补帐出现异常 + */ class SourceTypeCheckExecutor { companion object { const val SUCCESS = "success" @@ -213,7 +255,11 @@ class SourceTypeCheckExecutor { ExecutorResult(checkStatus, FAIL, "下载失败,未能查到对账文件") } } else if (acResp.code == AgentCode.SUCCESS) { - downloadFile(provider, acResp.payload) + if (!acResp.payload.checkFileOk) { + downloadFile(provider, acResp.payload) + } else { + ExecutorResult(checkStatus, SUCCESS, acResp.agentMsg) + } } else { ExecutorResult(checkStatus, FAIL, acResp.agentMsg) } @@ -228,51 +274,89 @@ class SourceTypeCheckExecutor { @Async("sourcetypeCheckTaskExecutor") fun reconciliation(checkStatus: TSourceTypeCheckStatus): Future { // 3. 完成对账 - val result = transactionReconciliationService.getTransactionChkfile(checkStatus.checkAccdate, checkStatus.sourceType)?.let { chkfile -> - if (ConstantUtil.CHKFILE_STATUS_UNCHECK == chkfile.status) { - //TODO:"实现对账逻辑" - try { - when (doCheckProcess(chkfile).result) { - ConstantUtil.CHKFILE_RESULT_EQUAL -> { - checkStatus.checkStatus = true //对账成功 - sourceTypeService.saveOrUpdateSourceTypeCheckStatus(checkStatus) - ExecutorResult(checkStatus, SUCCESS, "对账成功,双方一致") + val result = if (!checkStatus.checkStatus) { + transactionReconciliationService + .getTransactionChkfile(checkStatus.checkAccdate, checkStatus.sourceType)?.let { chkfile -> + try { + when (doCheckProcess(chkfile, checkStatus).result) { + ConstantUtil.CHKFILE_RESULT_EQUAL -> { + ExecutorResult(checkStatus, SUCCESS, "对账成功,双方一致") + } + else -> ExecutorResult(checkStatus, FAIL, "对账不平") + } + } catch (ex: Exception) { + ex.printStackTrace() + ExecutorResult(checkStatus, FAIL, "对账异常") } - else -> ExecutorResult(checkStatus, FAIL, "对账不平") - } - } catch (ex: Exception) { - ex.printStackTrace() - ExecutorResult(checkStatus, FAIL, "对账异常") - } - } else { - ExecutorResult(checkStatus, FAIL, "[${checkStatus.checkAccdate}]日入库的checkfile状态异常[${chkfile.status}]") - } - } ?: ExecutorResult(checkStatus, FAIL, "未找到[${checkStatus.checkAccdate}]日入库的对账数据") + } ?: ExecutorResult(checkStatus, FAIL, "未找到[${checkStatus.checkAccdate}]日入库的对账数据") + } else if (checkStatus.repairStatus) { + // 补帐逻辑 + ExecutorResult(checkStatus, SUCCESS, "对账成功,双方一致") + } else if (checkStatus.settleStatus) { + ExecutorResult(checkStatus, SUCCESS, "对账完成,可以结算") + } else { + ExecutorResult(checkStatus, FAIL, "未找到[${checkStatus.checkAccdate}]日入库的对账数据") + } return AsyncResult(result) } - fun doCheckProcess(chkfile: TTransactionChkfile): TTransactionChkfile { - try { - //统一处理交易一致的记录 - transactionReconciliationService.doCheckEqualTransdtls(chkfile) - } catch (e1: Exception) { + fun doCheckProcess(chkfile: TTransactionChkfile, checkStatus: TSourceTypeCheckStatus): TTransactionChkfile { + // 假设绝大多数记录都是一致的情况下,下面的逻辑采用每1000条批量处理提高性能 + //统一处理交易一致的记录, 对账 1 + (0..chkfile.othercnt step 1000).forEach { + try { + transactionReconciliationService.doCheckEqualTransdtls(chkfile, it) + } catch (e1: Exception) { + } } - - //剩余的不平记录逐一对比 + //剩余的未对账记录逐一对比, 对账 2 + // 这个规则业务可以处理 "对账1" 中可能漏掉的记录 + var errorDtl = false transactionReconciliationService.getUncheckTransactionChkdtls(chkfile.id).forEach { - transactionReconciliationService.doCheckTransactionChkdtl(it) + transactionReconciliationService.doCheckTransactionChkdtl(it).also { chkdtl -> + if (chkdtl.chkresult != ConstantUtil.CHKDTL_CHKRESULT_NOCHARGE + && chkdtl.chkresult != ConstantUtil.CHKDTL_CHKRESULT_EQUAL) { + errorDtl = true + } + } } - //判断chkdtl是否全部对账完成 + //判断chkdtl是否全部对账完成 , 对账 3 if (transactionReconciliationService.checkUncheckExists(chkfile.id)) { throw TransactionException(-2, "存在未对账的明细记录") } + //判断本地是否有多余成功流水,并保存 , 对账 4 + transactionReconciliationService.doCheckLocalMoreDtls(chkfile, checkStatus).also { + if (it.isNotEmpty()) { + errorDtl = true + } + } - //判断本地是否有多余成功流水,并保存 - transactionReconciliationService.doCheckLocalMoreDtls(chkfile) - - return transactionReconciliationService.doConfirmChkfileStatus(chkfile) - } + /** + * 当我们将每条明细进行核对后,需要明确是否对账完成、是否可以补帐和是否可以结算 + * 1. 对账完成是指每条对账明细只存在两种状态 CHKDTL_CHKRESULT_EQUAL , CHKDTL_CHKRESULT_NOCHARGE。其它状态都是异常状态 + * 2. 对"是否可以补帐"的定义是必须"对账完成" + * 3. 当所有明细完成对账或补帐才能结算 + */ + return if (!errorDtl) { + chkfile.status = ConstantUtil.CHKFILE_STATUS_finish + checkStatus.checkStatus = true + checkStatus.repairStatus = true + checkStatus.settleStatus = false + // udpate + transactionReconciliationService.doConfirmChkfileStatus(chkfile, checkStatus) + // 调用补帐业务进行补帐处理 + // 完成补帐业务进入结算状态 + checkStatus.settleStatus = true + transactionReconciliationService.doConfirmChkfileStatus(chkfile, checkStatus) + } else { + chkfile.status = ConstantUtil.CHKFILE_STATUS_ERROR + checkStatus.settleStatus = false + checkStatus.repairStatus = false + checkStatus.checkStatus = true + transactionReconciliationService.doConfirmChkfileStatus(chkfile, checkStatus) + } + } } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_reconciliation_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_reconciliation_service_impl.kt index be112d1f..2e2e89a4 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_reconciliation_service_impl.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_reconciliation_service_impl.kt @@ -6,8 +6,10 @@ import com.supwisdom.dlpay.api.dao.PersondtlDao import com.supwisdom.dlpay.api.dao.TransactionChkdtlDao import com.supwisdom.dlpay.api.dao.TransactionChkfileDao import com.supwisdom.dlpay.api.dao.TransactionMainDao +import com.supwisdom.dlpay.api.domain.TSourceTypeCheckStatus import com.supwisdom.dlpay.api.domain.TTransactionChkdtl import com.supwisdom.dlpay.api.domain.TTransactionChkfile +import com.supwisdom.dlpay.api.service.SourceTypeService import com.supwisdom.dlpay.api.service.TransactionReconciliationService import com.supwisdom.dlpay.api.service.TransactionServiceProxy import com.supwisdom.dlpay.exception.TransactionCheckException @@ -38,6 +40,9 @@ class TransactionReconciliationServiceImpl : TransactionReconciliationService { @Autowired private lateinit var transactionService: TransactionServiceProxy + @Autowired + private lateinit var sourceTypeService: SourceTypeService + override fun getTransactionChkfile(accdate: String, sourcetype: String): TTransactionChkfile? { return transactionChkfileDao.getByAccdateAndSourcetype(accdate, sourcetype) } @@ -46,7 +51,8 @@ class TransactionReconciliationServiceImpl : TransactionReconciliationService { return transactionChkfileDao.getByAccdateAndSourcetype(accdate, sourcetype).let { if (it != null) { if (ConstantUtil.CHKFILE_STATUS_INIT != it.status) - throw TransactionCheckException(TradeErrorCode.BUSINESS_DEAL_ERROR, "accdate=$accdate,sourcetype=$sourcetype 的chkfile已经存在") + throw TransactionCheckException(TradeErrorCode.BUSINESS_DEAL_ERROR, + "accdate=$accdate,sourcetype=$sourcetype 的chkfile已经存在") it } else { saveInitTransactionChkfile(accdate, sourcetype) //保存init对账文件 @@ -116,61 +122,69 @@ class TransactionReconciliationServiceImpl : TransactionReconciliationService { }) } - override fun doCheckEqualTransdtls(chkfile: TTransactionChkfile) { + override fun doCheckEqualTransdtls(chkfile: TTransactionChkfile, start: Int): Int { //所有一致的记录,一次任务处理 - transactionChkdtlDao.getCheckEqualDetails(chkfile.id)?.forEach { chkdtl -> - chkdtl.chkresult = ConstantUtil.CHKDTL_CHKRESULT_EQUAL - chkdtl.resolved = ConstantUtil.CHKDTL_RESOLVED_EQUAL - chkdtl.remark = "双方交易一致" - transactionChkdtlDao.save(chkdtl) //对账明细表更新 + return transactionChkdtlDao.findEqualDtlWithLimit(chkfile.id, start, start + 1000)?.also { + it.forEach { chkdtl -> + chkdtl.chkresult = ConstantUtil.CHKDTL_CHKRESULT_EQUAL + chkdtl.resolved = ConstantUtil.CHKDTL_RESOLVED_EQUAL + chkdtl.remark = "双方交易一致" + transactionChkdtlDao.save(chkdtl) //对账明细表更新 - transactionService.checkSuccessConfirm(chkdtl.localRefno, chkdtl.otherAccdate) + transactionService.checkSuccessConfirm(chkdtl.localRefno, chkdtl.otherAccdate) + } + }?.size ?: 0 + } + + override fun getMinChkDtlRecordNo(chkfile: TTransactionChkfile, status: String): Int { + return if (status.isEmpty()) { + transactionChkdtlDao.getMinRecordNo(chkfile.id) + } else { + transactionChkdtlDao.getMinRecordNoByStatus(chkfile.id, status) } } override fun getUncheckTransactionChkdtls(chkfileId: String): List { - return transactionChkdtlDao.getUncheckTransactionChkdtls(chkfileId) ?: ArrayList(0) + return transactionChkdtlDao.getUncheckTransactionChkdtls(chkfileId) + ?: ArrayList(0) } - override fun doCheckTransactionChkdtl(chkdtl: TTransactionChkdtl) { + override fun doCheckTransactionChkdtl(chkdtl: TTransactionChkdtl): TTransactionChkdtl { val transMain = transactionMainDao.findByRefno(chkdtl.localRefno) if (null == transMain || !transMain.person) { chkdtl.chkresult = ConstantUtil.CHKDTL_CHKRESULT_NOTEXIST chkdtl.resolved = ConstantUtil.CHKDTL_RESOLVED_HANGUP chkdtl.remark = "本地流水不存在" - transactionChkdtlDao.save(chkdtl) //对账明细表更新 + return transactionChkdtlDao.save(chkdtl) //对账明细表更新 } - if(!MoneyUtil.moneyEqual(chkdtl.amount,transMain.personDtl.amount)){ + if (!MoneyUtil.moneyEqual(chkdtl.amount, transMain.personDtl.amount)) { chkdtl.chkresult = ConstantUtil.CHKDTL_CHKRESULT_DIFF chkdtl.resolved = ConstantUtil.CHKDTL_RESOLVED_HANGUP chkdtl.remark = "交易金额不相等" - transactionChkdtlDao.save(chkdtl) //对账明细表更新 + return transactionChkdtlDao.save(chkdtl) //对账明细表更新 } - if(transMain.accdate!=chkdtl.accdate || transMain.sourceType != chkdtl.sourcetype){ - //TODO: 数据异常?? + if (transMain.accdate != chkdtl.accdate || transMain.sourceType != chkdtl.sourcetype) { + // 数据异常, 对账不能完成 chkdtl.chkresult = ConstantUtil.CHKDTL_CHKRESULT_ERROR chkdtl.resolved = ConstantUtil.CHKDTL_RESOLVED_HANGUP chkdtl.remark = "记账日期或支付方式错误" - transactionChkdtlDao.save(chkdtl) //对账明细表更新 + return transactionChkdtlDao.save(chkdtl) //对账明细表更新 } - if (transMain.status == TradeDict.DTL_STATUS_SUCCESS) { + return if (transMain.status == TradeDict.DTL_STATUS_SUCCESS) { chkdtl.chkresult = ConstantUtil.CHKDTL_CHKRESULT_EQUAL chkdtl.resolved = ConstantUtil.CHKDTL_RESOLVED_EQUAL chkdtl.remark = "双方交易一致" - transactionChkdtlDao.save(chkdtl) //对账明细表更新 - transactionService.checkSuccessConfirm(chkdtl.localRefno, chkdtl.otherAccdate) + transactionChkdtlDao.save(chkdtl) //对账明细表更新 } else { - //补账 - transactionService.repair(transMain.refno, chkdtl.otherAccdate, chkdtl.otherRefno, "补账成功") - + //需要补账 chkdtl.chkresult = ConstantUtil.CHKDTL_CHKRESULT_NOCHARGE - chkdtl.resolved = ConstantUtil.CHKDTL_RESOLVED_EQUAL - chkdtl.remark = "本地未入账,补账成功" + chkdtl.resolved = ConstantUtil.CHKDTL_RESOLVED_ADD + chkdtl.remark = "本地未入账,需要" transactionChkdtlDao.save(chkdtl) //对账明细表更新 } } @@ -179,11 +193,19 @@ class TransactionReconciliationServiceImpl : TransactionReconciliationService { return transactionChkdtlDao.getUncheckCount(chkfileId).existed > 0 } - override fun doCheckLocalMoreDtls(chkfile: TTransactionChkfile) { - transactionChkdtlDao.findLocalMoreDtls(chkfile.accdate, chkfile.sourcetype, chkfile.id)?.forEach { refno -> - //保存多余的记录 - transactionMainDao.findByRefno(refno)?.let { transMain -> - if (TradeDict.DTL_STATUS_SUCCESS == transMain.status && transMain.accdate == chkfile.accdate && transMain.sourceType == chkfile.sourcetype) { + private fun updateCheckStatusWhenError(message: String, checkStatus: TSourceTypeCheckStatus, + chkfile: TTransactionChkfile) { + checkStatus.checkStatus = false + checkStatus.remark = message + chkfile.status = ConstantUtil.CHKFILE_STATUS_ERROR + } + + override fun doCheckLocalMoreDtls(chkfile: TTransactionChkfile, checkStatus: TSourceTypeCheckStatus): List { + val result = mutableListOf() + transactionChkdtlDao.findLocalMoreDtls(chkfile.accdate, chkfile.sourcetype, chkfile.id)?.also { + it.forEach { refno -> + //保存多余的记录 + transactionMainDao.findByRefno(refno)?.also { transMain -> transactionChkdtlDao.save(TTransactionChkdtl().apply { this.chkfileId = chkfile.id this.accdate = chkfile.accdate @@ -200,30 +222,54 @@ class TransactionReconciliationServiceImpl : TransactionReconciliationService { this.otherStatus = "unknown" this.remark = "本地成功流水第三方流水不存在" this.extdata = transMain.personDtl.transdesc - this.chkresult = ConstantUtil.CHKDTL_CHKRESULT_NOTEXIST + this.chkresult = ConstantUtil.CHKDTL_CHKRESULT_ERROR this.resolved = ConstantUtil.CHKDTL_RESOLVED_HANGUP this.lastsaved = systemUtilService.sysdatetime.sysdate this.tenantid = chkfile.tenantid - }) //保存本地多出的记录 + }).also { dtl -> + result.add(dtl) + }//保存本地多出的记录 + }.also { transMain -> + if (transMain == null) { + chkfile.status = ConstantUtil.CHKFILE_STATUS_ERROR + updateCheckStatusWhenError("交易参考号<$refno>异常,未找到Main记录", + checkStatus, chkfile) + return result + } } } + }?.also { + if (it.isNotEmpty()) { + chkfile.status = ConstantUtil.CHKFILE_STATUS_ERROR + updateCheckStatusWhenError("本地有<${it.size}>条流水不在对账文件中,请联系管理员检查", + checkStatus, chkfile) + } } + return result } - override fun doConfirmChkfileStatus(chkfile: TTransactionChkfile): TTransactionChkfile { - val personSumInfo = persondtlDao.getPersondtlSumInfo(chkfile.accdate, chkfile.sourcetype, chkfile.tenantid) - chkfile.localcnt = personSumInfo.totalcnt ?: 0 - chkfile.localamt = personSumInfo.totalamt ?: 0.00 - chkfile.status = ConstantUtil.CHKFILE_STATUS_finish //对账结束 - if (chkfile.othercnt == chkfile.localcnt && MoneyUtil.moneyEqual(chkfile.otheramt, chkfile.localamt)) { - //fixme: 是否还需判断chkdtl中resolved是否都equal - chkfile.result = ConstantUtil.CHKFILE_RESULT_EQUAL - chkfile.remark = "对账完成,双方一致" - } else { - chkfile.result = ConstantUtil.CHKFILE_RESULT_UNEQUAL - chkfile.remark = "对账不平,请查看明细数据" - } - transactionChkfileDao.save(chkfile) - return chkfile + override fun doConfirmChkfileStatus(chkfile: TTransactionChkfile, checkStatus: TSourceTypeCheckStatus): TTransactionChkfile { +// if (chkfile.status == ConstantUtil.CHKFILE_STATUS_ERROR) { +// chkfile.remark = checkStatus.remark +// } else { +// val personSumInfo = persondtlDao.getPersondtlSumInfo(chkfile.accdate, chkfile.sourcetype, chkfile.tenantid) +// chkfile.localcnt = personSumInfo.totalcnt ?: 0 +// chkfile.localamt = personSumInfo.totalamt ?: 0.00 +// chkfile.status = ConstantUtil.CHKFILE_STATUS_finish //对账结束 +// if (chkfile.othercnt == chkfile.localcnt && MoneyUtil.moneyEqual(chkfile.otheramt, chkfile.localamt)) { +// //fixme: 是否还需判断chkdtl中resolved是否都equal +// chkfile.result = ConstantUtil.CHKFILE_RESULT_EQUAL +// chkfile.remark = "对账完成,双方一致" +// checkStatus.settleStatus = true +// } else { +// chkfile.result = ConstantUtil.CHKFILE_RESULT_UNEQUAL +// chkfile.remark = "对账不平,请查看明细数据" +// } +// checkStatus.checkStatus = true +// checkStatus.remark = "对账完成" +// } + + sourceTypeService.saveOrUpdateSourceTypeCheckStatus(checkStatus) + return transactionChkfileDao.save(chkfile) } } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_reconciliation_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_reconciliation_service.kt index c2935c5b..94299405 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_reconciliation_service.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_reconciliation_service.kt @@ -1,6 +1,7 @@ package com.supwisdom.dlpay.api.service import com.supwisdom.dlpay.api.bean.YnrccChkfileBean +import com.supwisdom.dlpay.api.domain.TSourceTypeCheckStatus import com.supwisdom.dlpay.api.domain.TTransactionChkdtl import com.supwisdom.dlpay.api.domain.TTransactionChkfile import org.springframework.transaction.annotation.Transactional @@ -31,21 +32,24 @@ interface TransactionReconciliationService { fun saveInitTransactionChkfile(accdate: String, sourcetype: String): TTransactionChkfile @Transactional(rollbackFor = [Exception::class]) - fun doCheckEqualTransdtls(chkfile: TTransactionChkfile) + fun getMinChkDtlRecordNo(chkfile: TTransactionChkfile, status: String): Int + + @Transactional(rollbackFor = [Exception::class]) + fun doCheckEqualTransdtls(chkfile: TTransactionChkfile, start: Int): Int @Transactional(rollbackFor = [Exception::class], readOnly = true) fun getUncheckTransactionChkdtls(chkfileId: String): List @Transactional(rollbackFor = [Exception::class]) - fun doCheckTransactionChkdtl(chkdtl: TTransactionChkdtl) + fun doCheckTransactionChkdtl(chkdtl: TTransactionChkdtl): TTransactionChkdtl @Transactional(rollbackFor = [Exception::class], readOnly = true) - fun checkUncheckExists(chkfileId: String):Boolean + fun checkUncheckExists(chkfileId: String): Boolean @Transactional(rollbackFor = [Exception::class]) - fun doCheckLocalMoreDtls(chkfile: TTransactionChkfile) + fun doCheckLocalMoreDtls(chkfile: TTransactionChkfile, checkStatus: TSourceTypeCheckStatus): List @Transactional(rollbackFor = [Exception::class]) - fun doConfirmChkfileStatus(chkfile: TTransactionChkfile): TTransactionChkfile + fun doConfirmChkfileStatus(chkfile: TTransactionChkfile, checkStatus: TSourceTypeCheckStatus): TTransactionChkfile } \ No newline at end of file