@@ -30,6 +30,7 @@ import com.openhis.financial.domain.PaymentRecDetail;
import com.openhis.financial.domain.PaymentReconciliation ;
import com.openhis.financial.service.IPaymentRecDetailService ;
import com.openhis.financial.service.IPaymentReconciliationService ;
import com.openhis.web.paymentmanage.appservice.IChargeBillService ;
import com.openhis.web.paymentmanage.appservice.IEleInvoiceService ;
import com.openhis.web.paymentmanage.dto.* ;
import com.openhis.web.paymentmanage.mapper.EleInvoiceMapper ;
@@ -38,6 +39,11 @@ import com.openhis.yb.service.IClinicSettleService;
import com.openhis.yb.service.IRegService ;
import lombok.extern.slf4j.Slf4j ;
import org.apache.commons.codec.digest.DigestUtils ;
import org.apache.velocity.Template ;
import org.apache.velocity.VelocityContext ;
import org.apache.velocity.app.Velocity ;
import org.apache.velocity.runtime.RuntimeConstants ;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader ;
import org.apache.http.HttpResponse ;
import org.apache.http.client.config.RequestConfig ;
import org.apache.http.client.methods.CloseableHttpResponse ;
@@ -56,6 +62,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource ;
import java.io.IOException ;
import java.io.StringWriter ;
import java.math.BigDecimal ;
import java.math.RoundingMode ;
import java.nio.charset.Charset ;
@@ -64,6 +71,7 @@ import java.text.DecimalFormat;
import java.text.ParseException ;
import java.text.SimpleDateFormat ;
import java.util.* ;
import java.util.concurrent.ConcurrentHashMap ;
import java.util.stream.Collectors ;
/**
@@ -97,153 +105,180 @@ public class EleInvoiceServiceImpl implements IEleInvoiceService {
@Resource
IEncounterService encounterService ;
@Resource
IChargeBillService chargeBillService ;
@Resource
private AssignSeqUtil assignSeqUtil ;
@Autowired
private HttpConfig httpConfig ;
public static JSONObject PreInvoicePostForward ( JSONObject bill , String endpoint ) {
String resultString = " " ;
// JSONObject result = new JSONObject() ;
// 获取当前租户的option信息
static {
Properties p = new Properties ( ) ;
p . setProperty ( RuntimeConstants . RESOURCE_LOADER , " classpath " ) ;
p . setProperty ( " classpath.resource.loader.class " , ClasspathResourceLoader . class . getName ( ) ) ;
p . setProperty ( Velocity . INPUT_ENCODING , " UTF-8 " ) ;
Velocity . init ( p ) ;
}
private static final Map < String , JSONObject > invoiceDataMap = new ConcurrentHashMap < > ( ) ;
private JSONObject internalRegistration ( JSONObject bill ) {
return createInternalSuccessResponse ( bill , " REG " ) ;
}
private JSONObject internalOutpatient ( JSONObject bill ) {
return createInternalSuccessResponse ( bill , " OUT " ) ;
}
private JSONObject internalHospitalized ( JSONObject bill ) {
return createInternalSuccessResponse ( bill , " HOS " ) ;
}
private JSONObject internalWriteOff ( JSONObject bill ) {
JSONObject message = new JSONObject ( ) ;
message . put ( " eScarletBillBatchCode " , " SC " + System . currentTimeMillis ( ) ) ;
message . put ( " eScarletBillNo " , UUID . randomUUID ( ) . toString ( ) . substring ( 0 , 8 ) ) ;
message . put ( " eScarletRandom " , " 666888 " ) ;
message . put ( " createTime " , " 20251101143028 " ) ;
message . put ( " billQRCode " , " QR_DATA_SCARLET " ) ;
JSONObject optionJson = SecurityUtils . getLoginUser ( ) . getOptionJson ( ) ;
String baseUrl = optionJson . getString ( CommonConstants . Option . URL ) ;
String appID = optionJson . getString ( CommonConstants . Option . APP_ID ) ;
String appKey = optionJson . getString ( CommonConstants . Option . KEY ) ;
message . put ( " pictureUrl " , baseUrl + " /invoice/view?busNo=scarlet " ) ;
message . put ( " pictureNetUrl " , baseUrl + " /invoice/view?busNo=scarlet " ) ;
EleInvioceBillDto eleInvioceBillDto = new EleInvioceBillDto ( ) ;
eleInvioceBillDto . setBaseUrl ( baseUrl ) ;
eleInvioceBillDto . s etEndpo int ( endpoint ) ;
eleInvioceBillDto . setAppKey ( appKey ) ;
eleInvioceBillDto . setAppID ( appID ) ;
eleInvioceBillDto . setJsonObject ( bill ) ;
JSONObject result = new JSONObject ( ) ;
result . put ( " result " , " S0000 " ) ;
result . put ( " message " , Base64 . g etEncoder ( ) . encodeToStr ing ( message . toJSONString ( ) . getBytes ( StandardCharsets . UTF_8 ) ) ) ;
return result ;
}
// 创建Http请求
RequestConfig requestConfig = RequestConfig . custom ( ) . setConnectTimeout ( 30000 ) . setConnectionRequestTimeout ( 30000 )
. s etSocketTimeout ( 30000 ) . build ( ) ;
CloseableHttpClient httpClient = HttpClients . custom ( ) . s etDefaultRequestConfig ( requestConfig ) . build ( ) ;
CloseableHttpResponse response = null ;
// 发送请求
try {
HttpPost httpPost = new HttpPost ( optionJson . ge tString( " invoiceUrl " ) + " /eleInvoice/forward " ) ;
System . o ut. println ( optionJson . getString ( " invoiceUrl " ) + " /eleInvoice/forward " ) ;
StringEntity stringEntity = new StringEntity ( com . alibaba . fastjson2 . JSON . toJSONString ( eleInvioceBillDto ) ,
ContentType . APPLICATION_JSON ) ;
httpPost . setEntity ( stringEntity ) ;
// 执行http请求
response = httpClient . execute ( httpPost ) ;
if ( response = = null ) {
throw new ServiceException ( " Http请求异常, 未接受返回参数 " ) ;
private JSONObject createInternalSuccessResponse ( JSONObject bill , String prefix ) {
String busNo = bill . getString ( " busNo " ) ;
JSONObject optionJson = SecurityUtils . g etLoginUser ( ) . getOptionJson ( ) ;
String baseUrl = optionJson . g etString ( CommonConstants . Option . URL ) ;
JSONObject message = new JSONObject ( ) ;
message . put ( " billBatchCode " , prefix + " BC " + System . currentTimeMillis ( ) ) ;
message . put ( " billNo " , UUID . randomUUID ( ) . to String ( ) . substring ( 0 , 8 ) ) ;
message . p ut( " random " , " 123456 " ) ;
message . put ( " createTime " , new SimpleDateFormat ( " yyyyMMddHHmmssSSS " ) . format ( new Date ( ) ) ) ;
message . put ( " billQRCode " , " QR_ " + busNo ) ;
message . put ( " pictureUrl " , baseUrl + " /invoice/view?busNo= " + busNo ) ;
message . put ( " pictureNetUrl " , baseUrl + " /invoice/view?busNo= " + busNo ) ;
JSONObject result = new JSONObject ( ) ;
result . put ( " result " , " S0000 " ) ;
result . put ( " message " , Base64 . getEncoder ( ) . encodeToString ( message . toJSONString ( ) . getBytes ( StandardCharsets . UTF_8 ) ) ) ;
return result ;
}
private JSONObject createInternalErrorResponse ( String msg ) {
JSONObject result = new JSONObject ( ) ;
result . put ( " result " , " E0001 " ) ;
result . put ( " message " , Base64 . getEncoder ( ) . encodeToString ( msg . getBytes ( StandardCharsets . UTF_8 ) ) ) ;
return result ;
}
@Override
public String getInvoiceHtml ( String busNo ) {
JSONObject bill = invoiceDataMap . get ( busNo ) ;
if ( bill = = null ) {
return " <html><body><h2>未找到流水号为 " + busNo + " 的发票数据</h2></body></html> " ;
}
JSONObject receiptData = bill . getJSONObject ( " receiptData " ) ;
VelocityContext context = new VelocityContext ( ) ;
context . put ( " hospitalName " , receiptData ! = null ? receiptData . getString ( " fixmedinsName " ) : " HIS 医疗机构 " ) ;
context . put ( " patientName " , bill . getString ( " payer " ) ) ;
context . put ( " outpatientNo " , receiptData ! = null ? receiptData . getString ( " regNo " ) : bill . getString ( " busNo " ) ) ;
context . put ( " idCard " , bill . getString ( " cardNo " ) ) ;
context . put ( " tel " , bill . getString ( " tel " ) ) ;
context . put ( " deptName " , bill . getString ( " patientCategory " ) ) ;
context . put ( " doctorName " , receiptData ! = null ? receiptData . getString ( " doctor " ) : " - " ) ;
context . put ( " appointmentTime " , bill . getString ( " consultationDate " ) ) ;
context . put ( " totalAmt " , bill . getString ( " totalAmt " ) ) ;
context . put ( " busNo " , busNo ) ;
context . put ( " printTime " , new SimpleDateFormat ( " yyyy-MM-dd HH:mm:ss " ) . format ( new Date ( ) ) ) ;
List < Map < String , Object > > items = new ArrayList < > ( ) ;
if ( receiptData ! = null & & receiptData . containsKey ( " chargeItem " ) ) {
com . alibaba . fastjson2 . JSONArray chargeItems = receiptData . getJSONArray ( " chargeItem " ) ;
for ( int i = 0 ; i < chargeItems . size ( ) ; i + + ) {
JSONObject item = chargeItems . getJSONObject ( i ) ;
Map < String , Object > itemMap = new HashMap < > ( ) ;
itemMap . put ( " chargeItemName " , item . getString ( " chargeItemName " ) ) ;
itemMap . put ( " quantityValue " , item . getString ( " quantityValue " ) ) ;
itemMap . put ( " totalPrice " , item . getString ( " totalPrice " ) ) ;
items . add ( itemMap ) ;
}
resultString = EntityUtils . toString ( response . getEntity ( ) , " utf-8 " ) ;
} else {
Map < String , Object > itemMap = new HashMap < > ( ) ;
itemMap . put ( " chargeItemName " , " 挂号费 " ) ;
itemMap . put ( " quantityValue " , " 1 " ) ;
itemMap . put ( " totalPrice " , bill . getString ( " totalAmt " ) ) ;
items . add ( itemMap ) ;
}
context . put ( " items " , items ) ;
try {
Template template = Velocity . getTemplate ( " vm/invoice/invoice.vm " ) ;
StringWriter writer = new StringWriter ( ) ;
template . merge ( context , writer ) ;
return writer . toString ( ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
throw new ServiceException ( " Http请求异常, 请稍后再试。 " ) ;
} finally {
if ( response ! = null ) {
try {
response . close ( ) ;
} catch ( IOException e ) {
// logger.error("关闭响应异常", e) ;
throw new ServiceException ( " 未关闭系统资源: " + e . getStackTrace ( ) ) ;
}
log . error ( " 渲染发票模板失败 " , e ) ;
return " <html><body><h2>渲染发票凭条失败: " + e . getMessage ( ) + " </h2></body></html> " ;
}
}
public JSONObject PreInvoicePostForward ( JSONObject bill , String endpoint ) {
// 参考补打小票逻辑,动态获取小票详细信息
Long paymentId = bill . getLong ( " paymentId " ) ;
if ( paymentId ! = null ) {
try {
Map < String , Object > receiptDetail = chargeBillService . getDetail ( paymentId ) ;
bill . put ( " receiptData " , receiptDetail ) ;
log . info ( " 已成功获取并注入小票动态数据, paymentId: {} " , paymentId ) ;
} catch ( Exception e ) {
log . error ( " 获取小票数据失败, paymentId: {} " , paymentId , e ) ;
}
}
return JSONObject . parseObject ( resultString ) ;
// 内部调用逻辑:不再使用 Http 客户端,直接分发到本地逻辑
String busNo = bill . getString ( " busNo " ) ;
if ( busNo ! = null ) {
invoiceDataMap . put ( busNo , bill ) ;
}
JSONObject internalResult ;
if ( endpoint . contains ( " invEBillRegistration " ) ) {
internalResult = internalRegistration ( bill ) ;
} else if ( endpoint . contains ( " invoiceEBillOutpatient " ) ) {
internalResult = internalOutpatient ( bill ) ;
} else if ( endpoint . contains ( " invEBillHospitalized " ) ) {
internalResult = internalHospitalized ( bill ) ;
} else if ( endpoint . contains ( " writeOffEBill " ) ) {
internalResult = internalWriteOff ( bill ) ;
} else {
internalResult = createInternalErrorResponse ( " 未知接口: " + endpoint ) ;
}
JSONObject finalResponse = new JSONObject ( ) ;
finalResponse . put ( " success " , true ) ;
finalResponse . put ( " result " , internalResult ) ;
return finalResponse ;
}
/**
* 发送请求
* 发送请求 (内部调用版本)
*
* @param bill 请求参数
* @param endpoint 请求后缀url
* @return 返回值
*/
public static JSONObject PreInvoicePost ( JSONObject bill , String endpoint ) {
JSONObject result = new JSONObject ( ) ;
// 获取当前租户的option信息
JSONObject optionJson = SecurityUtils . getLoginUser ( ) . getOptionJson ( ) ;
String baseUrl = optionJson . getString ( CommonConstants . Option . URL ) ;
// 拼接成完整 URL( 作为路径)
String cleanUrl = baseUrl + " / " + endpoint ; // 确保用 "/" 分隔
String url = cleanUrl . trim ( ) . replaceAll ( " ^ \" | \" $ " , " " ) // 去除首尾引号
. replaceAll ( " \\ s+ " , " " ) // 去除首尾引号
. replaceAll ( " \" " , " " ) ; // 去除中间引号
String appID = optionJson . getString ( CommonConstants . Option . APP_ID ) ;
String appKey = optionJson . getString ( CommonConstants . Option . KEY ) ;
String data = bill . toJSONString ( ) ;
String version = " 1.0 " ;
// 请求随机标识 noise
String noise = UUID . randomUUID ( ) . toString ( ) ;
data = Base64 . getEncoder ( ) . encodeToString ( data . getBytes ( StandardCharsets . UTF_8 ) ) ;
StringBuilder str = new StringBuilder ( ) ;
str . append ( " appid= " ) . append ( appID ) ;
str . append ( " &data= " ) . append ( data ) ;
str . append ( " &noise= " ) . append ( noise ) ;
str . append ( " &key= " ) . append ( appKey ) ;
str . append ( " &version= " ) . append ( version ) ;
String sign = DigestUtils . md5Hex ( str . toString ( ) . getBytes ( Charset . forName ( " UTF-8 " ) ) ) . toUpperCase ( ) ;
Map < String , String > map = new HashMap < > ( ) ;
map . put ( " appid " , appID ) ;
map . put ( " data " , data ) ;
map . put ( " noise " , noise ) ;
map . put ( " sign " , sign ) ;
map . put ( " version " , version ) ;
try {
HttpPost httpPost = new HttpPost ( url ) ;
CloseableHttpClient client = HttpClients . createDefault ( ) ;
String respContent = null ;
// 请求参数转JOSN字符串
StringEntity entity = new StringEntity ( new ObjectMapper ( ) . writeValueAsString ( map ) , " utf-8 " ) ;
entity . setContentEncoding ( " UTF-8 " ) ;
entity . setContentType ( " application/json " ) ;
httpPost . setEntity ( entity ) ;
HttpResponse resp = client . execute ( httpPost ) ;
if ( resp . getStatusLine ( ) . getStatusCode ( ) = = 200 ) {
String rev = EntityUtils . toString ( resp . getEntity ( ) ) ;
// System.out.println("返回串--》"+rev);
Map resultData = new ObjectMapper ( ) . readValue ( rev , Map . class ) ;
String rdata = resultData . get ( " data " ) . toString ( ) ;
String rnoise = resultData . get ( " noise " ) . toString ( ) ;
// 1、拼接返回验签参数
StringBuilder str1 = new StringBuilder ( ) ;
str1 . append ( " appid= " ) . append ( appID ) ;
str1 . append ( " &data= " ) . append ( rdata ) ;
str1 . append ( " &noise= " ) . append ( rnoise ) ;
str1 . append ( " &key= " ) . append ( appKey ) ;
str1 . append ( " &version= " ) . append ( version ) ;
// 3.MD5加密 生成sign
String rmd5 = DigestUtils . md5Hex ( str1 . toString ( ) . getBytes ( Charset . forName ( " UTF-8 " ) ) ) . toUpperCase ( ) ;
String rsign = resultData . get ( " sign " ) . toString ( ) ;
System . out . println ( " 验签-》 " + ( StringUtils . equals ( rsign , rmd5 ) ) ) ;
String busData
= new String ( Base64 . getDecoder ( ) . decode ( resultData . get ( " data " ) . toString ( ) ) , StandardCharsets . UTF_8 ) ;
System . out . println ( " 返回业务数据--》 " + busData ) ;
Map busDataMap = new ObjectMapper ( ) . readValue ( busData , Map . class ) ;
System . out
. println ( " 业务信息解密--》 " + new String ( Base64 . getDecoder ( ) . decode ( busDataMap . get ( " message " ) . toString ( ) ) ,
StandardCharsets . UTF_8 ) ) ;
JSONObject resobj = JSONObject . parseObject ( busData ) ;
result . put ( " success " , true ) ;
result . put ( " result " , resobj ) ;
} else {
result . put ( " msg " , " web响应失败! " ) ;
result . put ( " success " , false ) ;
}
} catch ( Exception e ) {
result . put ( " msg " , e . getMessage ( ) ) ;
result . put ( " success " , false ) ;
}
return result ;
public JSONObject PreInvoicePost ( JSONObject bill , String endpoint ) {
return PreInvoicePostForward ( bill , endpoint ) ;
}
/**
@@ -339,6 +374,7 @@ public class EleInvoiceServiceImpl implements IEleInvoiceService {
// --------------------请求业务参数 data--------------------START
JSONObject bill = commomSet ( patientInfo , paymentInfo , clinicSettle ) ;
bill . put ( " paymentId " , paymentId ) ;
// ------票据信息------
// busType 业务标识 String 20 是 06, 标识挂号
@@ -580,6 +616,7 @@ public class EleInvoiceServiceImpl implements IEleInvoiceService {
// --------------------请求业务参数 data--------------------START
JSONObject bill = commomSet ( patientInfo , paymentInfo , clinicSettle ) ;
bill . put ( " paymentId " , paymentId ) ;
// ------票据信息------
// busType 业务标识 String 20 是 直接填写业务系统内部编码值, 由医疗平台配置对照, 例如: 附录5 业务标识列表
@@ -890,6 +927,7 @@ public class EleInvoiceServiceImpl implements IEleInvoiceService {
// --------------------请求业务参数 data--------------------START
JSONObject bill = commomSet ( patientInfo , paymentInfo , clinicSettle ) ;
bill . put ( " paymentId " , paymentId ) ;
// ------票据信息------
// busType 业务标识 String 20 是 直接填写业务系统内部编码值, 由医疗平台配置对照, 例如: 附录5 业务标识列表