Compare commits

...

10 Commits

17 changed files with 252 additions and 1952 deletions

1
.gitignore vendored
View File

@@ -31,3 +31,4 @@ build/
### VS Code ###
.vscode/

1
mvnw vendored
View File

@@ -308,3 +308,4 @@ exec "$JAVACMD" \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

1
mvnw.cmd vendored
View File

@@ -180,3 +180,4 @@ if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

View File

@@ -184,6 +184,7 @@
</dependency>
</dependencies>
<build>

View File

@@ -145,6 +145,40 @@ public class MedicalInsuranceReconciliationController {
return responseMap;
}
@ApiOperation(value = "更新医保对账备注", notes = "")
@PostMapping("/updateMedicalInsuranceReconciliationRemark")
@ResponseBody
public HashMap<Object, Object> updateMedicalInsuranceReconciliationRemark(
@ApiParam(value = "记录ID") @RequestParam String id,
@ApiParam(value = "备注") @RequestParam(required = false) String remark) {
HashMap<Object, Object> responseMap = new HashMap<>();
try {
if (id == null || "".equals(id.trim())) {
responseMap.put("code", 1);
responseMap.put("msg", "id不能为空");
return responseMap;
}
HashMap<Object, Object> updateMap = new HashMap<>();
updateMap.put("id", id.trim());
updateMap.put("remark", remark == null ? "" : remark.trim());
medicalInsuranceReconciliationService.updateMedicalInsuranceReconciliationRemark(updateMap);
responseMap.put("code", 0);
responseMap.put("msg", "OK");
} catch (Exception e) {
e.printStackTrace();
LogUtil.error(this.getClass(), "更新医保对账备注失败:" + e.getMessage());
responseMap.put("code", 1);
responseMap.put("msg", "更新失败:" + e.getMessage());
}
return responseMap;
}
}

View File

@@ -19,10 +19,12 @@ import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.File;
import java.math.BigDecimal;
import java.util.*;
/**
@@ -82,7 +84,8 @@ public class MilitaryInsuranceController {
try {
HashMap<Object, Object> map = new HashMap<Object, Object>();
map.put("payType", "3"); // 固定查询paytype=3的数据
List<String> payTypeList = Arrays.asList("3", "8");
map.put("payTypeList", payTypeList);
map.put("startTime", startTime);
map.put("endTime", endTime);
map.put("likeFiled", likeFiled);

View File

@@ -33,6 +33,11 @@ public interface MedicalInsuranceReconciliationMapper {
* 删除医保对账结果
*/
void deleteMedicalInsuranceReconciliationResult(HashMap<Object, Object> map) throws Exception;
/**
* 更新医保对账备注
*/
void updateMedicalInsuranceReconciliationRemark(HashMap<Object, Object> map) throws Exception;
}

View File

@@ -1,7 +1,6 @@
package com.saye.hospitalgd.scheduler.jobMethod;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.saye.hospitalgd.commons.date.DateDUtil;
import com.saye.hospitalgd.commons.getBean.GetBeanUtil;
import com.saye.hospitalgd.commons.log.LogUtil;
@@ -24,14 +23,13 @@ import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.util.CollectionUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.*;
import java.net.URI;
import java.nio.charset.Charset;
@@ -512,25 +510,53 @@ public class BankGetDataMethodByJHLZF {
log.info("继续执行程序3");
List<BankbillHistory> bankbillHistoryList = new ArrayList<>();
// 跳过前24行Excel的前24行是标题和说明第24行是表头从第25行开始是数据
for (int i = 24; i < txtList.size(); i++) {
int startIndex = -1;
for (int i = 0; i < txtList.size(); i++) {
String line = txtList.get(i);
if (line == null || line.trim().isEmpty()) {
continue;
}
String[] cols = line.split("\t");
if (cols.length < 10) {
continue;
}
dateStr = cols.length > 13 ? cols[13].trim() : "";
if (dateStr == null || dateStr.isEmpty() || !dateStr.contains("-")) {
continue;
}
String firstAmountStr = cols.length > 20 ? cols[20].trim() : "";
if (firstAmountStr.isEmpty() || firstAmountStr.equals("null") || firstAmountStr.equals("0") || firstAmountStr.equals("0.00")) {
continue;
}
try {
String d = txtList.get(i);
double firstAmount = Double.parseDouble(firstAmountStr);
if (Math.abs(firstAmount) < 0.01) {
continue;
}
} catch (NumberFormatException e) {
continue;
}
startIndex = i;
break;
}
if (startIndex == -1) {
startIndex = 24;
}
for (int i = startIndex; i < txtList.size(); i++) {
try {
String d = txtList.get(i);
// 跳过空行
if (d == null || d.trim().isEmpty()) {
continue;
}
String[] s1 = d.split("\t");
String[] s1 = d.split("\t");
// 跳过列数不足的行至少需要10列才能有交易金额
if (s1.length < 10) {
log.debug("" + (i + 1) + " 行列数不足,跳过");
continue;
}
continue;
}
// 检查是否是汇总行(包含"小计"、"合计"、"终端小计"等关键字)
String rowText = d.toLowerCase();
if (rowText.contains("小计") || rowText.contains("合计") ||
rowText.contains("终端小计") || rowText.contains("pos编号") ||
@@ -540,7 +566,6 @@ public class BankGetDataMethodByJHLZF {
continue;
}
// 检查第13列交易日期Excel的N列是否为空
if (s1.length <= 13 || s1[13] == null || s1[13].trim().isEmpty()) {
log.debug("" + (i + 1) + " 行交易日期为空,跳过");
continue;
@@ -548,20 +573,17 @@ public class BankGetDataMethodByJHLZF {
String jyrq = s1[13].trim();
// 检查第13列是否包含日期简单检查是否包含"-"
if (!jyrq.contains("-")) {
log.debug("" + (i + 1) + " 行交易日期不包含日期分隔符,跳过");
continue;
}
// 检查交易金额列第20列对应Excel的U列是否有效
String amountStr = s1.length > 20 ? s1[20].trim() : "";
if (amountStr.isEmpty() || amountStr.equals("null") || amountStr.equals("0") || amountStr.equals("0.00")) {
log.debug("" + (i + 1) + " 行交易金额无效: [" + amountStr + "],跳过");
continue;
}
// 尝试解析金额,确保是有效数字
try {
double amount = Double.parseDouble(amountStr);
if (Math.abs(amount) < 0.01) {
@@ -575,7 +597,7 @@ public class BankGetDataMethodByJHLZF {
log.info("正在处理第 " + (i + 1) + " 行数据");
BankbillHistory bankbillHistory = new BankbillHistory();
BankbillHistory bankbillHistory = new BankbillHistory();
// 根据实际Excel列位置映射从日志分析得出
// 终端号: C列(索引2), 发卡行: E列(索引4), 卡种: I列(索引8),

View File

@@ -102,10 +102,12 @@ public class MedicalInsuranceReconciliationMethod {
prevQueryMap.put("trade_date", trade_date);
List<HashMap<Object, Object>> prevResults = reconciliationService.findMedicalInsuranceReconciliationResult(prevQueryMap);
HashMap<String, String> prevStatusMap = new HashMap<>();
HashMap<String, String> prevRemarkMap = new HashMap<>();
if (prevResults != null) {
for (HashMap<Object, Object> prev : prevResults) {
String key = StringDUtil.changeNullToEmpty(prev.get("insutype")) + "|" + StringDUtil.changeNullToEmpty(prev.get("clr_type"));
prevStatusMap.put(key, StringDUtil.changeNullToEmpty(prev.get("stmt_rslt")));
prevRemarkMap.put(key, StringDUtil.changeNullToEmpty(prev.get("recheck_user")));
}
}
@@ -165,7 +167,7 @@ public class MedicalInsuranceReconciliationMethod {
resultMap.put("recheck_flag", recheckFlag);
resultMap.put("prev_stmt_rslt", prevStmt);
resultMap.put("recheck_time", "1".equals(recheckFlag) ? DateDUtil.getCurrentDate(DateDUtil.yyyy_MM_dd_HH_mm_ss) : null);
resultMap.put("recheck_user", "");
resultMap.put("recheck_user", prevRemarkMap.getOrDefault(key, ""));
resultMap.put("infcode", callResult.get("infcode"));
resultMap.put("err_msg", callResult.get("err_msg"));
resultMap.put("warn_msg", callResult.get("warn_msg"));
@@ -199,7 +201,7 @@ public class MedicalInsuranceReconciliationMethod {
resultMap.put("recheck_flag", "0");
resultMap.put("prev_stmt_rslt", "");
resultMap.put("recheck_time", null);
resultMap.put("recheck_user", "");
resultMap.put("recheck_user", prevRemarkMap.getOrDefault(insutype + "|" + clrType, ""));
resultMap.put("api_result", "ERROR");
resultMap.put("create_time", DateDUtil.getCurrentDate(DateDUtil.yyyy_MM_dd_HH_mm_ss));
resultMap.put("modify_time", DateDUtil.getCurrentDate(DateDUtil.yyyy_MM_dd_HH_mm_ss));

View File

@@ -30,6 +30,11 @@ public interface MedicalInsuranceReconciliationService {
* 删除医保对账结果
*/
void deleteMedicalInsuranceReconciliationResult(HashMap<Object, Object> map) throws Exception;
/**
* 更新医保对账备注
*/
void updateMedicalInsuranceReconciliationRemark(HashMap<Object, Object> map) throws Exception;
}

View File

@@ -41,6 +41,11 @@ public class MedicalInsuranceReconciliationServiceImpl implements MedicalInsuran
public void deleteMedicalInsuranceReconciliationResult(HashMap<Object, Object> map) throws Exception {
medicalInsuranceReconciliationMapper.deleteMedicalInsuranceReconciliationResult(map);
}
@Override
public void updateMedicalInsuranceReconciliationRemark(HashMap<Object, Object> map) throws Exception {
medicalInsuranceReconciliationMapper.updateMedicalInsuranceReconciliationRemark(map);
}
}

View File

@@ -690,6 +690,28 @@ public class HisUtil {
Element ybtcAmountElm = item.element("YBTCAmount");
String ybtcAmount = ybtcAmountElm != null ? ybtcAmountElm.getText() : "";
// 退款场景下,确保医保账户金额和统筹金额为负数
try {
if (amount != null && amount.trim().length() > 0) {
java.math.BigDecimal amt = new java.math.BigDecimal(amount.trim());
if (amt.compareTo(java.math.BigDecimal.ZERO) < 0) {
if (ybzhAmount != null && ybzhAmount.trim().length() > 0) {
java.math.BigDecimal ybzh = new java.math.BigDecimal(ybzhAmount.trim());
if (ybzh.compareTo(java.math.BigDecimal.ZERO) > 0) {
ybzhAmount = ybzh.negate().toPlainString();
}
}
if (ybtcAmount != null && ybtcAmount.trim().length() > 0) {
java.math.BigDecimal ybtc = new java.math.BigDecimal(ybtcAmount.trim());
if (ybtc.compareTo(java.math.BigDecimal.ZERO) > 0) {
ybtcAmount = ybtc.negate().toPlainString();
}
}
}
}
} catch (Exception ignore) {
}
//收据号
Element receiptNOElm = item.element("ReceiptNO");
String receiptNO = receiptNOElm != null ? receiptNOElm.getText() : "";

View File

@@ -23,7 +23,13 @@
,ybtcAmount
from hisbill_history
<where>
<if test="payType!=null and payType!=''">
<if test="payTypeList != null and payTypeList.size() > 0">
and PayType in
<foreach collection="payTypeList" item="pt" open="(" separator="," close=")">
#{pt}
</foreach>
</if>
<if test="(payTypeList == null or payTypeList.size() == 0) and payType!=null and payType!=''">
and PayType = #{payType}
</if>
<if test="bizType!=null and bizType!=''">
@@ -47,7 +53,13 @@
select count(1)
from hisbill_history
<where>
<if test="payType!=null and payType!=''">
<if test="payTypeList != null and payTypeList.size() > 0">
and PayType in
<foreach collection="payTypeList" item="pt" open="(" separator="," close=")">
#{pt}
</foreach>
</if>
<if test="(payTypeList == null or payTypeList.size() == 0) and payType!=null and payType!=''">
and PayType = #{payType}
</if>
<if test="bizType!=null and bizType!=''">
@@ -201,7 +213,13 @@
,ybtcAmount
from hisbill_history
where trade_date=#{trade_date}
<if test="payType!=null and payType!=''">
<if test="payTypeList != null and payTypeList.size() > 0">
and PayType in
<foreach collection="payTypeList" item="pt" open="(" separator="," close=")">
#{pt}
</foreach>
</if>
<if test="(payTypeList == null or payTypeList.size() == 0) and payType!=null and payType!=''">
and PayType = #{payType}
</if>
<if test="tradingStatus!=null and tradingStatus!=''">
@@ -238,7 +256,13 @@
select count(1) as num,cast(IFNULL(sum(Amount),0) as decimal(19,2)) as money
from hisbill_history
<where>
<if test="payType!=null and payType!=''">
<if test="payTypeList != null and payTypeList.size() > 0">
and PayType in
<foreach collection="payTypeList" item="pt" open="(" separator="," close=")">
#{pt}
</foreach>
</if>
<if test="(payTypeList == null or payTypeList.size() == 0) and payType!=null and payType!=''">
and (PayType = #{payType} or PayType = CAST(#{payType} AS UNSIGNED))
</if>
<if test="startTime!=null and startTime!=''">
@@ -255,18 +279,14 @@
<!-- 医保对账按险种和清算类别分组统计去重规则HisTransId + ZFAmount 都相同才视为同一记录)
1. 医疗总金额MEDFEE_SUMAMT统计同一收据号HisTransId且同一自费金额ZFAmount下的金额累计统筹 + 非退款账户 + 自费
2. 统筹金额FUND_PAY_SUMAMT累计统筹金额不受退款影响
2. 统筹金额FUND_PAY_SUMAMT累计统筹金额(不受退款影响)
3. 账户金额ACCT_PAY累计账户金额包含退款 -->
<select id="findMedicalInsuranceGroupData" parameterType="HashMap" resultType="HashMap">
select
t.insutype as INSUTYPE,
t.clr_type as CLR_TYPE,
count(distinct t.HisTransId) as FIXMEDINS_SETL_CNT,
cast(
IFNULL(sum(t.ybtc),0)
+ IFNULL(sum(case when t.is_inpatient = 1 then 0 else t.ybzh_non_refund end),0)
+ IFNULL(sum(t.zf),0)
as decimal(19,2)) as MEDFEE_SUMAMT,
cast(IFNULL(sum(t.total_amt),0) as decimal(19,2)) as MEDFEE_SUMAMT,
cast(IFNULL(sum(t.ybtc),0) as decimal(19,2)) as FUND_PAY_SUMAMT,
cast(IFNULL(sum(t.ybzh),0) as decimal(19,2)) as ACCT_PAY
from (
@@ -274,6 +294,7 @@
insutype,
clr_type,
HisTransId,
sum(cast(IFNULL(Amount,0) as decimal(19,2))) as total_amt,
max(cast(IFNULL(ybtcAmount,0) as decimal(19,2))) as ybtc,
max(cast(IFNULL(ybzhAmount,0) as decimal(19,2))) as ybzh,
max(case when TradingStatus != '2' then cast(IFNULL(ybzhAmount,0) as decimal(19,2)) else 0 end) as ybzh_non_refund,

View File

@@ -114,6 +114,13 @@
order by create_time desc, trade_date desc, insutype, clr_type
</select>
<update id="updateMedicalInsuranceReconciliationRemark" parameterType="HashMap">
update medical_insurance_reconciliation_result
set recheck_user = #{remark},
modify_time = now()
where id = #{id}
</update>
<!-- 删除医保对账结果 -->
<delete id="deleteMedicalInsuranceReconciliationResult" parameterType="HashMap">
delete from medical_insurance_reconciliation_result

View File

@@ -74,7 +74,7 @@
<option value="340">新农合</option>
<option value="390">城乡居民基本医疗保险</option>
<option value="510">公务员医疗补助</option>
<option value="99410">保险</option>
<option value="99410">保险</option>
</select>
</div>
</div>
@@ -176,7 +176,13 @@
</tr>
<tr>
<td style="font-weight: bold;">备注</td>
<td id="detail_remark"></td>
<td>
<div id="detail_remark_flag" style="margin-bottom: 6px;"></div>
<textarea id="detail_remark_input" class="layui-textarea" style="min-height: 80px;"></textarea>
<div style="margin-top: 6px;">
<button type="button" class="layui-btn layui-btn-sm" onclick="saveDetailRemark()">保存备注</button>
</div>
</td>
</tr>
<tr>
<td style="font-weight: bold;">创建时间</td>
@@ -188,10 +194,55 @@
<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-xs" lay-event="getInfo">详细信息</a>
<a class="layui-btn layui-btn-xs layui-btn-primary" lay-event="editRemark">编辑备注</a>
</script>
</body>
<script th:inline="javascript">
let layer, laydate, table, form;
let currentDetailId = null;
function escapeHtml(value) {
if (value === null || value === undefined) return '';
return String(value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function buildRemarkDisplay(data) {
let parts = [];
if (data.recheck_flag === '1') {
parts.push('<span style="color:#409EFF;">二次核对</span>');
}
if (data.recheck_user) {
parts.push('<span>' + escapeHtml(data.recheck_user) + '</span>');
}
return parts.join(' / ');
}
function updateRemark(id, remark, onSuccess) {
let load = layer.load(1, {shade: [0.3, '#000']});
$.ajax({
url: '/medicalInsuranceReconciliation/updateMedicalInsuranceReconciliationRemark',
type: 'POST',
data: {id: id, remark: remark},
success: function (data) {
layer.close(load);
if (data.code === 0) {
layer.msg('保存成功!', {icon: 1, time: 1500});
if (onSuccess) onSuccess();
} else {
layer.alert('保存失败:' + (data.msg || ''), {icon: 2});
}
},
error: function () {
layer.close(load);
layer.alert('保存失败,请检查网络连接!', {icon: 2});
}
});
}
layui.use(['element', 'table', 'laydate', 'layer', 'form'], function () {
layer = layui.layer;
@@ -220,7 +271,7 @@
if(d.insutype === '340') return '新农合';
if(d.insutype === '390') return '居民医保';
if(d.insutype === '510') return '公务员补助';
if(d.insutype === '99410') return '工保险';
if(d.insutype === '99410') return '工保险';
return d.insutype;
}},
{field: 'clr_type', title: '清算类别', align: 'center', width: 100, templet: function(d){
@@ -238,8 +289,8 @@
{field: 'fund_pay_sumamt', title: '基金支付总额', align: 'center', width: 130},
{field: 'acct_pay', title: '账户支付金额', align: 'center', width: 130},
{field: 'fixmedins_setl_cnt', title: '结算笔数', align: 'center', width: 100},
{field: 'remark', title: '备注', align: 'center', width: 120, templet: function(d){
return d.recheck_flag === '1' ? '<span style="color:#409EFF;">二次核对</span>' : '';
{field: 'recheck_user', title: '备注', align: 'center', width: 220, templet: function(d){
return buildRemarkDisplay(d);
}},
{field: 'stmt_rslt', title: '对账结果', align: 'center', width: 100, templet: function(d){
if(d.stmt_rslt === '0') {
@@ -276,6 +327,19 @@
if (obj.event === 'getInfo') {
showDetail(data);
}
if (obj.event === 'editRemark') {
layer.prompt({
title: '请输入备注',
formType: 2,
value: data.recheck_user || '',
btn: ['保存', '取消']
}, function (value, index, elem) {
layer.close(index);
updateRemark(data.id, value, function () {
table.reload('test');
});
});
}
});
// 初始化:设置默认日期为前一天
@@ -407,6 +471,7 @@
// 显示详情
function showDetail(data) {
currentDetailId = data.id || null;
$("#detail_trade_date").text(data.trade_date || '-');
let insutypeText = data.insutype;
@@ -414,7 +479,7 @@
if(data.insutype === '340') insutypeText = '340-新农合';
if(data.insutype === '390') insutypeText = '390-城乡居民基本医疗保险';
if(data.insutype === '510') insutypeText = '510-公务员医疗补助';
if(data.insutype === '99410') insutypeText = '99410-工保险';
if(data.insutype === '99410') insutypeText = '99410-工保险';
$("#detail_insutype").text(insutypeText || '-');
let clrTypeText = data.clr_type;
@@ -438,7 +503,8 @@
$("#detail_stmt_rslt_dscr").text(data.stmt_rslt_dscr || '-');
$("#detail_api_result").text(data.api_result || '-');
$("#detail_remark").text(data.recheck_flag === '1' ? '二次核对' : '');
$("#detail_remark_flag").html(data.recheck_flag === '1' ? '<span style="color:#409EFF;">二次核对</span>' : '');
$("#detail_remark_input").val(data.recheck_user || '');
$("#detail_create_time").text(data.create_time || '-');
layer.open({
@@ -450,6 +516,16 @@
});
}
function saveDetailRemark() {
if (!currentDetailId) {
layer.alert('缺少记录ID无法保存备注', {icon: 2});
return;
}
updateRemark(currentDetailId, $("#detail_remark_input").val() || '', function () {
table.reload('test');
});
}
// 导出
function exportExcel() {
let url = "/medicalInsuranceReconciliation/exportMedicalInsuranceReconciliationResult";

View File

@@ -222,7 +222,12 @@
width: 120,
sort: false,
templet: function (d) {
return '医院垫支'; // 固定显示为医院垫支因为paytype=3
if (d.PAYTYPE === '3') {
return '医院垫支';
} else if (d.PAYTYPE === '8') {
return '军保支付';
}
return d.PAYTYPE;
}
},