feat(system): 添加电票管理系统功能

- 新增发票记录的增删改查功能
- 实现发票状态管理(未处理、处理失败、已成功)
- 集成外部开票系统接口对接
- 添加批量处理发票功能
- 配置发票系统相关参数
- 创建发票管理前后端页面模板
- 实现发票数据与外部系统的同步机制
This commit is contained in:
Gjm
2026-04-10 10:26:01 +08:00
parent 79656d3019
commit 584cf4fd92
20 changed files with 1817 additions and 4 deletions

45
pom.xml
View File

@@ -247,7 +247,50 @@
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.8.0.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.nls</groupId>
<artifactId>orai18n</artifactId>
<version>19.8.0.0</version>
</dependency>
<!-- Java 21 必加:解决 JAXB 缺失报错 -->
<!-- JAXB API -->
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
<version>2.3.3</version>
</dependency>
<!-- JAXB 实现 -->
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<!-- 高版本 JDK 必须的激活依赖 -->
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>

Binary file not shown.

View File

@@ -0,0 +1,13 @@
package top.lidee.common.constant;
public class InvoiceConstants {
/** 未处理 */
public static final String UNTREATED = "0";
/** 已处理 但未成功 Processed but not successful*/
public static final String PROCESSED_NOT_SUCCESSFUL = "1";
/** 已成功 */
public static final String SUCCESSFUL = "2";
}

View File

@@ -0,0 +1,203 @@
package top.lidee.project.monitor.job.task;
/*
* 发票引入定时任务
* */
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import top.lidee.common.constant.InvoiceConstants;
import top.lidee.project.monitor.job.util.HttpXml;
import top.lidee.project.monitor.job.util.InvoiceXmlSy;
import top.lidee.project.system.record.domain.GrInvoiceRecord;
import top.lidee.project.system.record.mapper.GrInvoiceRecordMapper;
import javax.annotation.Resource;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 发票定时任务(更新发票状态)
*
*/
@Component("suYuanScheduledTask")
public class SuYuanScheduledTask {
private static final Logger log = LoggerFactory.getLogger(SuYuanScheduledTask.class);
// 数据库连接信息
@Value("${invoice.url}")
private String URL;
@Value("${invoice.eiInvWebService}")
private String eiInvWebServiceUrl;
/**
* 数据库用户名
*/
@Value("${invoice.username}")
private String USERNAME;
/**
* 数据库密码
*/
@Value("${invoice.password}")
private String PASSWORD;
@Value("${invoice.taxId}")
private String taxId;
@Value("${invoice.authCode}")
private String authCode;
@Value("${invoice.updateCode}")
private String interfaceCode;
private static final String fPath = "C:\\log\\fplog.log";
@Resource
private GrInvoiceRecordMapper grInvoiceRecordMapper;
public void updateInvoice() {
GrInvoiceRecord grInvoiceRecord = new GrInvoiceRecord();
grInvoiceRecord.setStatus(InvoiceConstants.UNTREATED);
List<GrInvoiceRecord> grInvoiceRecords = grInvoiceRecordMapper.selectGrInvoiceRecordList(grInvoiceRecord);
if (CollectionUtils.isEmpty(grInvoiceRecords)) {
System.out.println("没有待请求数据");
return;
}
try {
for (GrInvoiceRecord invoiceRecord : grInvoiceRecords) {
String key = invoiceRecord.getDjh();
String txtCon = "";
List<String> billNos = new ArrayList<>();
billNos.add(key);
System.out.println("溯源 Id" + billNos);
txtCon += "溯源 Id" + billNos + "\n";
//封装 请求报文
String xmldata = InvoiceXmlSy.generateDwzsXml(billNos);
// 组装接口需要的请求报文
String payload = HttpXml.bulidSoapXML(interfaceCode, taxId, authCode, xmldata, "eiInterface");
System.out.println(payload);
boolean hasError = false;
try {
// 调用接口
Map<String, Object> res = HttpXml.webServiceExecute(eiInvWebServiceUrl, payload);
System.out.println("接口请求状态:" + res.get("httpStatus"));
System.out.println("接口返回的原始报文:" + res.get("data"));
txtCon += "接口请求状态:" + res.get("httpStatus") + "\n";
txtCon += "接口返回的原始报文:" + res.get("data") + "\n";
if (200 == (int) res.get("httpStatus")) {
try {
// 解析接口返回的报文
HashMap<String, String> serviceData = HttpXml.parseResponseXML((String) res.get("data"));
System.out.println("业务处理返回代码:" + serviceData.get("returnCode"));
System.out.println("业务处理返回提示:" + serviceData.get("returnMessage"));
System.out.println("业务处理返回数据:" + serviceData.get("data"));
txtCon += "业务处理返回代码:" + serviceData.get("returnCode") + "\n";
txtCon += "业务处理返回提示:" + serviceData.get("returnMessage") + "\n";
txtCon += "业务处理返回数据:" + serviceData.get("data") + "\n";
// appendToFile(fPath, txtCon);
String invNumber = "1";
String xml = serviceData.get("data");
Pattern pattern = Pattern.compile("<INV_NUMBER>(.*?)</INV_NUMBER>");
Matcher matcher = pattern.matcher(xml);
if (matcher.find()) {
invNumber = matcher.group(1);
}
// 如果没有被开发票invNumber 为 1
if (invNumber.equals("1")) {
log.error("单据号 " + key + " 还未开票");
continue;
}
if (serviceData.get("returnCode").equals("0000")) {
//更新数据库
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
System.out.println("更新视图开始");
// 1. 加载 Oracle JDBC 驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2. 建立数据库连接
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 3. 创建 Statement 对象
stmt = conn.createStatement();
// 4. 执行查询语句
String sql = "update gr_dp_saset_sdhm_v a set a.zxcolumn1 = '" + invNumber + "' where a.sasettleid =" + key;
PreparedStatement pstmt = conn.prepareStatement(sql);
int rowsA = pstmt.executeUpdate();
System.out.println("完成更新视图:" + rowsA);
invoiceRecord.setStatus(InvoiceConstants.SUCCESSFUL);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
invoiceRecord.setKpsj(sdf.format(new Date()));
invoiceRecord.setInvNumber(invNumber);
grInvoiceRecordMapper.updateGrInvoiceRecord(invoiceRecord);
} catch (Exception e) {
e.printStackTrace();
// 发生错误,标记为已尝试但不需要重试
hasError = true;
// 6. 关闭资源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e11) {
e11.printStackTrace();
}
}
} else {
// 业务处理失败,标记为已尝试但不需要重试
hasError = true;
log.error("业务处理失败:" + serviceData.get("returnMessage"));
}
} catch (Exception e) {
log.error("解析报文出错的,保持业务为处理中状态,需要检查原因");
e.printStackTrace();
hasError = true;
}
} else {
// HTTP 请求状态不是 200标记为已尝试但不需要重试
hasError = true;
log.error("HTTP 请求失败,状态码:" + res.get("httpStatus"));
}
} catch (Exception e) {
e.printStackTrace();
hasError = true;
}
// 如果发生错误,更新状态为 1已尝试但不需要重试
if (hasError) {
invoiceRecord.setStatus(InvoiceConstants.PROCESSED_NOT_SUCCESSFUL);
grInvoiceRecordMapper.updateGrInvoiceRecord(invoiceRecord);
log.error("单据号 " + key + " 处理失败,标记为已尝试状态");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 追加写入模式
public static void appendToFile(String filePath, String content) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) {
writer.newLine(); // 换行后再追加
writer.write(content);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,228 @@
package top.lidee.project.monitor.job.task;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;
import top.lidee.common.constant.InvoiceConstants;
import top.lidee.project.monitor.job.util.HttpXml;
import top.lidee.project.monitor.job.util.InvoiceXmlGenerator;
import top.lidee.project.system.record.domain.GrInvoiceRecord;
import top.lidee.project.system.record.mapper.GrInvoiceRecordMapper;
import javax.annotation.Resource;
import javax.xml.bind.JAXBException;
import java.sql.*;
import java.util.*;
import java.util.Date;
/**
* 发票定时任务
*
*/
@Component("yinEuScheduledTask")
public class YinEuScheduledTask {
@Value("${invoice.url}")
private String URL;
/**
* 数据库用户名
*/
@Value("${invoice.username}")
private String USERNAME;
/**
* 数据库密码
*/
@Value("${invoice.password}")
private String PASSWORD;
@Value("${invoice.taxId}")
private String taxId;
@Value("${invoice.authCode}")
private String authCode;
@Value("${invoice.interfaceCode}")
private String interfaceCode;
@Value("${invoice.iBillInterfaceWebService}")
private String iBillInterfaceWebServiceUrl;
@Resource
private GrInvoiceRecordMapper grInvoiceRecordMapper;
// 每隔5秒执行一次
public void insertInvoice() {
System.out.println("单据引入开始执行");
//查询是否有待处理数据
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1. 加载Oracle JDBC驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2. 建立数据库连接
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 3. 创建Statement对象
stmt = conn.createStatement();
// 4. 执行查询语句
String sql = "SELECT g.*,COUNT(*) OVER (PARTITION BY g.SASETTLEID) AS MXS FROM GR_DP_SASET_V g";
sql = "SELECT DJH,DJRQ,FPZL,KPLX,GMF_MC,HSBZ,XMMC,GGXH,DW,XMSL,XMDJ,XMJE,SL,ZKJE,CREATE_PERSON_NAME,BZ,SASETTLEID,SASETTLEDTLID,MXS FROM (SELECT t.*,COUNT(*) OVER (PARTITION BY DJH) AS MXS,\n" +
"ROW_NUMBER() OVER (PARTITION BY DJH ORDER BY SASETTLEID) AS rn FROM GR_DP_SASET_V t) WHERE rn = 1";
rs = stmt.executeQuery(sql);
if (!rs.isBeforeFirst()) {
System.out.println("警告:查询结果为空,没有待处理的单据数据!");
} else {
System.out.println("查询成功,开始处理单据数据...");
}
// 5. 处理查询结果
while (rs.next()) {
String SASETTLEID = rs.getString("SASETTLEID");
// 1. 构造根对象
InvoiceXmlGenerator.RequestCommonDjlr request = new InvoiceXmlGenerator.RequestCommonDjlr();
// 2. 构造发票集合
InvoiceXmlGenerator.CommonDjlrFpts fpts = new InvoiceXmlGenerator.CommonDjlrFpts();
List<InvoiceXmlGenerator.CommonDjlrFpt> fptList = new ArrayList<>();
// 3. 构造一张发票头
InvoiceXmlGenerator.CommonDjlrFpt fpt = new InvoiceXmlGenerator.CommonDjlrFpt();
String djh = rs.getString("DJH");
String djrq = rs.getString("DJRQ");
String fpzl = rs.getString("FPZL");
String gmfMc = rs.getString("GMF_MC");
GrInvoiceRecord grInvoiceRecord = new GrInvoiceRecord();
grInvoiceRecord.setDjh(djh);
grInvoiceRecord.setDjrq(djrq);
grInvoiceRecord.setFpzl(fpzl);
grInvoiceRecord.setGmfMc(gmfMc);
grInvoiceRecord.setCreateTime(new Date());
fpt.setDjh(djh);
fpt.setDjrq(djrq);
fpt.setFpzl(fpzl);
fpt.setKplx(rs.getString("KPLX"));
fpt.setGmfMc(gmfMc);
fpt.setHsbz(rs.getString("HSBZ"));
fpt.setKpr("方国旭");
fpt.setSkr("方国旭");
fpt.setFhr("方国旭");
fpt.setBz(rs.getString("BZ"));
fpt.setZzbm("ORG001");
// fpt.setCreatePersonName(rs.getString("CREATE_PERSON_NAME"));
fpt.setCreatePersonName("陈志");
// 4. 构造明细集合
InvoiceXmlGenerator.CommonDjlrXmxxs xmxxs = new InvoiceXmlGenerator.CommonDjlrXmxxs();
List<InvoiceXmlGenerator.CommonDjlrXmxx> xmxxList = new ArrayList<>();
if (rs.getString("MXS").toString().equals("1")) {
System.out.println("查询明细数据1条");
InvoiceXmlGenerator.CommonDjlrXmxx xm1 = new InvoiceXmlGenerator.CommonDjlrXmxx();
xm1.setXh("1");
xm1.setXmmc(rs.getString("XMMC"));
xm1.setGgxh(rs.getString("GGXH"));
xm1.setDw(rs.getString("DW"));
xm1.setXmsl(rs.getString("XMSL"));
xm1.setXmje(rs.getString("XMJE"));
xm1.setSl(rs.getString("SL"));
xm1.setZkje(rs.getString("ZKJE"));
xmxxList.add(xm1);
} else {
//多条明细
ResultSet rsmx = null;
String sqlmx = "SELECT * FROM GR_DP_SASET_V where SASETTLEID=" + rs.getString("SASETTLEID");
rsmx = stmt.executeQuery(sqlmx);
System.out.println("查询明细数据:" + rsmx.getFetchSize() + "");
int i = 1;
while (rsmx.next()) {
InvoiceXmlGenerator.CommonDjlrXmxx xm1 = new InvoiceXmlGenerator.CommonDjlrXmxx();
xm1.setXh(i + "");
xm1.setXmmc(rsmx.getString("XMMC"));
xm1.setGgxh(rsmx.getString("GGXH"));
xm1.setDw(rsmx.getString("DW"));
xm1.setXmsl(rsmx.getString("XMSL"));
xm1.setXmje(rsmx.getString("XMJE"));
xm1.setSl(rsmx.getString("SL"));
xm1.setZkje(rsmx.getString("ZKJE"));
xmxxList.add(xm1);
i++;
}
}
xmxxs.setCommonDjlrXmxxList(xmxxList);
fpt.setCommonDjlrXmxxs(xmxxs);
fptList.add(fpt);
fpts.setCommonDjlrFptList(fptList);
request.setCommonDjlrFpts(fpts);
// 5. 生成 XML 字符串
String xmldata = InvoiceXmlGenerator.generateXml(request).replace("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", "");
//封装 请求报文
// 组装接口需要的请求报文
String payload = HttpXml.bulidSoapXML(interfaceCode, taxId, authCode, xmldata, "impSoHeaderInterface");
try {
// 调用接口
Map<String, Object> res = HttpXml.webServiceExecute(iBillInterfaceWebServiceUrl, payload);
System.out.println("接口请求状态:" + res.get("httpStatus"));
System.out.println("接口返回的原始报文:" + res.get("data"));
if (200 == (int) res.get("httpStatus")) {
try {
// 解析接口返回的报文
// HashMap<String, String> serviceData= parseResponseXML((String) res.get("data"));
// System.out.println("业务处理返回代码:"+serviceData.get("returnCode"));
// System.out.println("业务处理返回提示:"+serviceData.get("returnMessage"));
// System.out.println("业务处理返回数据:"+serviceData.get("data"));
if (res.get("data").toString().contains("returnCode&gt;0000&lt;/returnCode&gt")) {
//更新视图
System.out.println("更新视图");
String sqlupdate = "update GR_DP_SASET_V a set a.zxcolumn2 = '1' where a.sasettleid =" + SASETTLEID;
PreparedStatement pstmt = conn.prepareStatement(sqlupdate);
pstmt.execute();
System.out.println("完成更新视图");
grInvoiceRecord.setStatus(InvoiceConstants.UNTREATED);
grInvoiceRecord.setCreateTime(new Date());
grInvoiceRecordMapper.insertGrInvoiceRecord(grInvoiceRecord);
}
} catch (Exception e) {
System.out.println("解析报文出错的,保持业务为处理中状态,需要检查原因");
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("数据库操作失败");
}
System.out.println(xmldata);
}
} catch (ClassNotFoundException e) {
System.out.println("Oracle JDBC驱动未找到");
e.printStackTrace();
} catch (SQLException | JAXBException e) {
System.out.println("数据库访问异常!");
e.printStackTrace();
} finally {
// 6. 关闭资源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// // 1. XML构造根对象
// InvoiceXmlGenerator.RequestCommonDjlr request = new InvoiceXmlGenerator.RequestCommonDjlr();
//
// // 2. 构造发票集合
// InvoiceXmlGenerator.CommonDjlrFpts fpts = new InvoiceXmlGenerator.CommonDjlrFpts();
// List<InvoiceXmlGenerator.CommonDjlrFpt> fptList = new ArrayList<>();
System.out.println("定时任务完成");
}
}

View File

@@ -0,0 +1,140 @@
package top.lidee.project.monitor.job.util;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class HttpXml {
/*
* 数据请求
* @param requesturl 请求地址
* @param soapXML 请求报文
* */
public static Map<String, Object> webServiceExecute(String requesturl, String soapXML) throws Exception {
HttpURLConnection conn = null;
OutputStream os = null;
Map<String, Object> reslut = new HashMap<>();
try {
//1创建连接
URL url = new URL(requesturl);
conn = (HttpURLConnection) url.openConnection();
//2设置连接的配置
conn.setRequestMethod("POST");
conn.setRequestProperty("content-type", "text/xml;charset=utf-8");
conn.setDoInput(true);
conn.setDoOutput(true);
//3打开连接发送数据
os = conn.getOutputStream();
os.write(soapXML.getBytes());
String temp = null;
//4接收服务端的响应
//4.1http的响应码
int responseCode = conn.getResponseCode();
//4.2获取返回报文
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
StringBuilder response = new StringBuilder();
String responseLine;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
//简单模拟封装返回结果
reslut.put("httpStatus", responseCode);
reslut.put("data", response.toString());
} catch (Exception e) {
throw new RuntimeException("调用"+requesturl+"webservice出错", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return reslut;
}
/**
* 构建Soap请求报文
* @param interfaceCode 接口编码
* @param taxId 税号
* @param authCode 授权码
* @param content 内层业务报文的明文
*/
public static String bulidSoapXML(String interfaceCode, String taxId, String authCode, String content, String ffmc) {
//数据报文通过base64编码
String payload = Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8));
return "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ws=\"http://ws.aisino.com\">\n" +
"<soapenv:Header/>\n" +
"<soapenv:Body>\n" +
"<ws:"+ffmc+">\n" +
"\t<ws:in0><![CDATA[<?xml version=\"1.0\" encoding=\"utf-8\"?> <interface xmlns=\"\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"> \t\n" +
"\t<globalInfo> \t\t\n" +
"\t\t<version>1.0</version> \t\t\n" +
"\t\t<taxpayerId>" + taxId + "</taxpayerId> \t\t\n" +
"\t\t<interfaceCode>" + interfaceCode + "</interfaceCode> \t\t\n" +
"\t\t<authorizationCode>" + authCode + "</authorizationCode> \t\n" +
"\t</globalInfo> \t\n" +
"\t\t<returnStateInfo> \t\t\n" +
"\t\t<returnCode>0000</returnCode> \t\t\n" +
"\t\t<returnMessage></returnMessage> \t\n" +
"\t</returnStateInfo> \t\n" +
"\t<Data> \t\t\n" +
"\t\t<dataDescription> \t\t\t\n" +
"\t\t\t<zipCode>0</zipCode> \t\t\t\n" +
"\t\t\t<encryptCode>0</encryptCode> \t\t\t\n" +
"\t\t\t<codeType>0</codeType> \t\t\n" +
"\t\t</dataDescription> \t\t\n" +
"\t\t<content>" + payload + "</content>\t\n" +
"\t</Data>\n" +
"\t</interface>]]>\n" +
"\t</ws:in0>\n" +
"</ws:"+ffmc+">\n" +
"</soapenv:Body>\n" +
"</soapenv:Envelope>";
}
public static HashMap<String, String> parseResponseXML(String resData){
try {
// 获取DocumentBuilderFactory实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 配置安全选项防止XXE攻击
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// 创建DocumentBuilder
DocumentBuilder builder = factory.newDocumentBuilder();
// 解析XML文件
Document document = builder.parse(new ByteArrayInputStream(resData.getBytes(StandardCharsets.UTF_8)));
// 获取根元素
Element root = document.getDocumentElement();
// 获取out节点
Node out = root.getFirstChild().getFirstChild().getFirstChild();
Document outXML = builder.parse(new ByteArrayInputStream(out.getTextContent().getBytes(StandardCharsets.UTF_8)));
// 解析返回的业务处理结果
String returnCode = outXML.getElementsByTagName("returnCode").item(0).getTextContent();
String returnMessage = outXML.getElementsByTagName("returnMessage").item(0).getTextContent();
String data = outXML.getElementsByTagName("content").item(0).getTextContent();
HashMap<String, String> resMap = new HashMap<>();
resMap.put("returnCode", returnCode);
resMap.put("returnMessage", new String(Base64.getDecoder().decode(returnMessage)));
resMap.put("data", new String(Base64.getDecoder().decode(data)));
return resMap;
}catch (Exception e){
throw new RuntimeException("解析返回报文报错",e);
}
}
}

View File

@@ -0,0 +1,86 @@
package top.lidee.project.monitor.job.util;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import java.io.StringWriter;
import java.util.List;
public class InvoiceXmlGenerator {
// ========== XML 映射类 ==========
@XmlRootElement(name = "REQUEST_COMMON_DJLR")
@XmlType(propOrder = {"commonDjlrFpts"})
public static class RequestCommonDjlr {
private CommonDjlrFpts commonDjlrFpts;
@XmlElement(name = "COMMON_DJLR_FPTS")
public CommonDjlrFpts getCommonDjlrFpts() { return commonDjlrFpts; }
public void setCommonDjlrFpts(CommonDjlrFpts commonDjlrFpts) { this.commonDjlrFpts = commonDjlrFpts; }
}
@XmlType(propOrder = {"commonDjlrFptList"})
public static class CommonDjlrFpts {
private List<CommonDjlrFpt> commonDjlrFptList;
@XmlElement(name = "COMMON_DJLR_FPT")
public List<CommonDjlrFpt> getCommonDjlrFptList() { return commonDjlrFptList; }
public void setCommonDjlrFptList(List<CommonDjlrFpt> commonDjlrFptList) { this.commonDjlrFptList = commonDjlrFptList; }
}
@XmlType(propOrder = {"djh", "djrq", "fpzl", "kplx", "gmfMc", "hsbz", "kpr", "skr", "fhr", "bz", "zzbm", "createPersonName", "commonDjlrXmxxs"})
public static class CommonDjlrFpt {
private String djh, djrq, fpzl, kplx, gmfMc, hsbz, kpr, skr, fhr, bz, zzbm, createPersonName;
private CommonDjlrXmxxs commonDjlrXmxxs;
// getter/setter (均使用 @XmlElement 指定标签名)
@XmlElement(name = "DJH") public String getDjh() { return djh; } public void setDjh(String djh) { this.djh = djh; }
@XmlElement(name = "DJRQ") public String getDjrq() { return djrq; } public void setDjrq(String djrq) { this.djrq = djrq; }
@XmlElement(name = "FPZL") public String getFpzl() { return fpzl; } public void setFpzl(String fpzl) { this.fpzl = fpzl; }
@XmlElement(name = "KPLX") public String getKplx() { return kplx; } public void setKplx(String kplx) { this.kplx = kplx; }
@XmlElement(name = "GMF_MC") public String getGmfMc() { return gmfMc; } public void setGmfMc(String gmfMc) { this.gmfMc = gmfMc; }
@XmlElement(name = "HSBZ") public String getHsbz() { return hsbz; } public void setHsbz(String hsbz) { this.hsbz = hsbz; }
@XmlElement(name = "KPR") public String getKpr() { return kpr; } public void setKpr(String kpr) { this.kpr = kpr; }
@XmlElement(name = "SKR") public String getSkr() { return skr; } public void setSkr(String skr) { this.skr = skr; }
@XmlElement(name = "FHR") public String getFhr() { return fhr; } public void setFhr(String fhr) { this.fhr = fhr; }
@XmlElement(name = "BZ") public String getBz() { return bz; } public void setBz(String bz) { this.bz = bz; }
@XmlElement(name = "ZZBM") public String getZzbm() { return zzbm; } public void setZzbm(String zzbm) { this.zzbm = zzbm; }
@XmlElement(name = "CREATE_PERSON_NAME") public String getCreatePersonName() { return createPersonName; } public void setCreatePersonName(String createPersonName) { this.createPersonName = createPersonName; }
@XmlElement(name = "COMMON_DJLR_XMXXS") public CommonDjlrXmxxs getCommonDjlrXmxxs() { return commonDjlrXmxxs; } public void setCommonDjlrXmxxs(CommonDjlrXmxxs commonDjlrXmxxs) { this.commonDjlrXmxxs = commonDjlrXmxxs; }
}
@XmlType(propOrder = {"commonDjlrXmxxList"})
public static class CommonDjlrXmxxs {
private List<CommonDjlrXmxx> commonDjlrXmxxList;
@XmlElement(name = "COMMON_DJLR_XMXX")
public List<CommonDjlrXmxx> getCommonDjlrXmxxList() { return commonDjlrXmxxList; }
public void setCommonDjlrXmxxList(List<CommonDjlrXmxx> commonDjlrXmxxList) { this.commonDjlrXmxxList = commonDjlrXmxxList; }
}
@XmlType(propOrder = {"xh", "xmmc", "ggxh", "dw", "xmsl", "xmje", "sl", "zkje"})
public static class CommonDjlrXmxx {
private String xh, xmmc, ggxh, dw, xmsl, xmje, sl, zkje;
@XmlElement(name = "XH") public String getXh() { return xh; } public void setXh(String xh) { this.xh = xh; }
@XmlElement(name = "XMMC") public String getXmmc() { return xmmc; } public void setXmmc(String xmmc) { this.xmmc = xmmc; }
@XmlElement(name = "GGXH") public String getGgxh() { return ggxh; } public void setGgxh(String ggxh) { this.ggxh = ggxh; }
@XmlElement(name = "DW") public String getDw() { return dw; } public void setDw(String dw) { this.dw = dw; }
@XmlElement(name = "XMSL") public String getXmsl() { return xmsl; } public void setXmsl(String xmsl) { this.xmsl = xmsl; }
@XmlElement(name = "XMJE") public String getXmje() { return xmje; } public void setXmje(String xmje) { this.xmje = xmje; }
@XmlElement(name = "SL") public String getSl() { return sl; } public void setSl(String sl) { this.sl = sl; }
@XmlElement(name = "ZKJE") public String getZkje() { return zkje; } public void setZkje(String zkje) { this.zkje = zkje; }
}
// ========== 生成 XML 字符串的方法 ==========
public static String generateXml(RequestCommonDjlr request) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(RequestCommonDjlr.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
StringWriter sw = new StringWriter();
marshaller.marshal(request, sw);
return sw.toString();
}
}

View File

@@ -0,0 +1,68 @@
package top.lidee.project.monitor.job.util;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.StringWriter;
import java.util.List;
public class InvoiceXmlSy {
/**
* 生成 XML 字符串
* @param billNoList 单据号列表,每个元素将生成一个 <BILLNO> 节点
* @return 格式化的 XML 字符串(不含 XML 声明)
* @throws ParserConfigurationException XML 解析器配置异常
* @throws TransformerException XML 转换异常
*/
public static String generateDwzsXml(List<String> billNoList) throws ParserConfigurationException, TransformerException {
// 1. 创建 Document 对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
// 2. 创建根元素 REQUEST_COMMON_DWZS并设置 class 属性
Element root = doc.createElement("REQUEST_COMMON_DWZS");
root.setAttribute("class", "REQUEST_COMMON_DWZS");
doc.appendChild(root);
// 3. 创建 COMMON_DWZS_HEADERS 元素
Element headers = doc.createElement("COMMON_DWZS_HEADERS");
root.appendChild(headers);
// 4. 遍历单据号列表,为每个单据号创建一个 COMMON_DWZS_HEADER
for (String billNo : billNoList) {
Element header = doc.createElement("COMMON_DWZS_HEADER");
header.setAttribute("class", "COMMON_DWZS_HEADER");
headers.appendChild(header);
Element billNoElem = doc.createElement("BILLNO");
// 如果单据号为 null则设为空字符串避免 null 值导致异常
billNoElem.setTextContent(billNo != null ? billNo : "");
header.appendChild(billNoElem);
}
// 5. 将 Document 对象转换为格式化的 XML 字符串
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
// 设置输出属性:省略 XML 声明启用缩进4空格
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
return writer.toString();
}
}

View File

@@ -0,0 +1,136 @@
package top.lidee.project.system.record.controller;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import top.lidee.framework.aspectj.lang.annotation.Log;
import top.lidee.framework.aspectj.lang.enums.BusinessType;
import top.lidee.framework.web.controller.BaseController;
import top.lidee.framework.web.domain.AjaxResult;
import top.lidee.common.utils.poi.ExcelUtil;
import top.lidee.framework.web.page.TableDataInfo;
import top.lidee.project.system.record.domain.GrInvoiceRecord;
import top.lidee.project.system.record.service.IGrInvoiceRecordService;
/**
* 发票Controller
*
* @author yuheng
* @date 2026-04-02
*/
@Controller
@RequestMapping("/system/record")
public class GrInvoiceRecordController extends BaseController
{
private String prefix = "system/record";
@Autowired
private IGrInvoiceRecordService grInvoiceRecordService;
@RequiresPermissions("system:record:view")
@GetMapping()
public String record()
{
return prefix + "/record";
}
/**
* 查询发票列表
*/
@RequiresPermissions("system:record:list")
@PostMapping("/list")
@ResponseBody
public TableDataInfo list(GrInvoiceRecord grInvoiceRecord)
{
startPage();
List<GrInvoiceRecord> list = grInvoiceRecordService.selectGrInvoiceRecordList(grInvoiceRecord);
return getDataTable(list);
}
/**
* 导出发票列表
*/
@RequiresPermissions("system:record:export")
@Log(title = "发票", businessType = BusinessType.EXPORT)
@PostMapping("/export")
@ResponseBody
public AjaxResult export(GrInvoiceRecord grInvoiceRecord)
{
List<GrInvoiceRecord> list = grInvoiceRecordService.selectGrInvoiceRecordList(grInvoiceRecord);
ExcelUtil<GrInvoiceRecord> util = new ExcelUtil<GrInvoiceRecord>(GrInvoiceRecord.class);
return util.exportExcel(list, "发票数据");
}
/**
* 新增发票
*/
@GetMapping("/add")
public String add()
{
return prefix + "/add";
}
/**
* 新增保存发票
*/
@RequiresPermissions("system:record:add")
@Log(title = "发票", businessType = BusinessType.INSERT)
@PostMapping("/add")
@ResponseBody
public AjaxResult addSave(GrInvoiceRecord grInvoiceRecord)
{
return toAjax(grInvoiceRecordService.insertGrInvoiceRecord(grInvoiceRecord));
}
/**
* 修改发票
*/
@RequiresPermissions("system:record:edit")
@GetMapping("/edit/{id}")
public String edit(@PathVariable("id") String id, ModelMap mmap)
{
GrInvoiceRecord grInvoiceRecord = grInvoiceRecordService.selectGrInvoiceRecordById(id);
mmap.put("grInvoiceRecord", grInvoiceRecord);
return prefix + "/edit";
}
/**
* 修改保存发票
*/
@RequiresPermissions("system:record:edit")
@Log(title = "发票", businessType = BusinessType.UPDATE)
@PostMapping("/edit")
@ResponseBody
public AjaxResult editSave(GrInvoiceRecord grInvoiceRecord)
{
return toAjax(grInvoiceRecordService.updateGrInvoiceRecord(grInvoiceRecord));
}
/**
* 删除发票
*/
@RequiresPermissions("system:record:remove")
@Log(title = "发票", businessType = BusinessType.DELETE)
@PostMapping( "/remove")
@ResponseBody
public AjaxResult remove(String ids)
{
return toAjax(grInvoiceRecordService.deleteGrInvoiceRecordByIds(ids));
}
/**
* 批量处理发票
*/
@RequiresPermissions("system:record:list")
@Log(title = "发票", businessType = BusinessType.UPDATE)
@PostMapping("/batchProcess")
@ResponseBody
public AjaxResult batchProcess(@RequestParam List<String> idList)
{
return toAjax(grInvoiceRecordService.batchProcess(idList));
}
}

View File

@@ -0,0 +1,51 @@
package top.lidee.project.system.record.domain;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import top.lidee.framework.aspectj.lang.annotation.Excel;
import top.lidee.framework.web.domain.BaseEntity;
import java.util.List;
/**
* 发票对象 gr_invoice_record
*
* @author yuheng
* @date 2026-04-02
*/
@Data
public class GrInvoiceRecord extends BaseEntity {
private static final long serialVersionUID = 1L;
/** */
private Long id;
/**单据号*/
@Excel(name = "")
private String djh;
/**单据日期*/
@Excel(name = "")
private String djrq;
/**发票种类*/
@Excel(name = "")
private String fpzl;
/**开票时间*/
@Excel(name = "")
private String kpsj;
/**开票状态*/
@Excel(name = "")
private String status;
private List<String> statusList;
/**客户名称*/
private String gmfMc;
private String invNumber;
}

View File

@@ -0,0 +1,65 @@
package top.lidee.project.system.record.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import top.lidee.project.system.record.domain.GrInvoiceRecord;
/**
* 发票Mapper接口
*
* @author yuheng
* @date 2026-04-02
*/
public interface GrInvoiceRecordMapper
{
/**
* 查询发票
*
* @param id 发票主键
* @return 发票
*/
public GrInvoiceRecord selectGrInvoiceRecordById(String id);
/**
* 查询发票列表
*
* @param grInvoiceRecord 发票
* @return 发票集合
*/
public List<GrInvoiceRecord> selectGrInvoiceRecordList(GrInvoiceRecord grInvoiceRecord);
/**
* 新增发票
*
* @param grInvoiceRecord 发票
* @return 结果
*/
public int insertGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord);
/**
* 修改发票
*
* @param grInvoiceRecord 发票
* @return 结果
*/
public int updateGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord);
/**
* 删除发票
*
* @param id 发票主键
* @return 结果
*/
public int deleteGrInvoiceRecordById(String id);
/**
* 批量删除发票
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
public int deleteGrInvoiceRecordByIds(String[] ids);
List<GrInvoiceRecord> selectGrInvoiceRecordListByIdList(@Param("list") List<String> idList);
}

View File

@@ -0,0 +1,63 @@
package top.lidee.project.system.record.service;
import java.util.List;
import top.lidee.project.system.record.domain.GrInvoiceRecord;
/**
* 发票Service接口
*
* @author yuheng
* @date 2026-04-02
*/
public interface IGrInvoiceRecordService
{
/**
* 查询发票
*
* @param id 发票主键
* @return 发票
*/
public GrInvoiceRecord selectGrInvoiceRecordById(String id);
/**
* 查询发票列表
*
* @param grInvoiceRecord 发票
* @return 发票集合
*/
public List<GrInvoiceRecord> selectGrInvoiceRecordList(GrInvoiceRecord grInvoiceRecord);
/**
* 新增发票
*
* @param grInvoiceRecord 发票
* @return 结果
*/
public int insertGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord);
/**
* 修改发票
*
* @param grInvoiceRecord 发票
* @return 结果
*/
public int updateGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord);
/**
* 批量删除发票
*
* @param ids 需要删除的发票主键集合
* @return 结果
*/
public int deleteGrInvoiceRecordByIds(String ids);
/**
* 删除发票信息
*
* @param id 发票主键
* @return 结果
*/
public int deleteGrInvoiceRecordById(String id);
int batchProcess(List<String> idList);
}

View File

@@ -0,0 +1,280 @@
package top.lidee.project.system.record.service.impl;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import top.lidee.common.constant.InvoiceConstants;
import top.lidee.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.lidee.common.utils.StringUtils;
import top.lidee.project.monitor.job.util.HttpXml;
import top.lidee.project.monitor.job.util.InvoiceXmlSy;
import top.lidee.project.system.record.mapper.GrInvoiceRecordMapper;
import top.lidee.project.system.record.domain.GrInvoiceRecord;
import top.lidee.project.system.record.service.IGrInvoiceRecordService;
import top.lidee.common.utils.text.Convert;
import javax.annotation.Resource;
/**
* 发票Service业务层处理
*
* @author yuheng
* @date 2026-04-02
*/
@Service
public class GrInvoiceRecordServiceImpl implements IGrInvoiceRecordService {
@Autowired
private GrInvoiceRecordMapper grInvoiceRecordMapper;
@Value("${invoice.url}")
private String URL;
@Value("${invoice.eiInvWebService}")
private String eiInvWebServiceUrl;
/**
* 数据库用户名
*/
@Value("${invoice.username}")
private String USERNAME;
/**
* 数据库密码
*/
@Value("${invoice.password}")
private String PASSWORD;
@Value("${invoice.taxId}")
private String taxId;
@Value("${invoice.authCode}")
private String authCode;
@Value("${invoice.updateCode}")
private String interfaceCode;
private static final String fPath = "C:\\log\\fplog.log";
/**
* 查询发票
*
* @param id 发票主键
* @return 发票
*/
@Override
public GrInvoiceRecord selectGrInvoiceRecordById(String id) {
return grInvoiceRecordMapper.selectGrInvoiceRecordById(id);
}
/**
* 查询发票列表
*
* @param grInvoiceRecord 发票
* @return 发票
*/
@Override
public List<GrInvoiceRecord> selectGrInvoiceRecordList(GrInvoiceRecord grInvoiceRecord) {
if (StringUtils.isNotBlank(grInvoiceRecord.getStatus()) && InvoiceConstants.UNTREATED.equals(grInvoiceRecord.getStatus())) {
List<String> statusList = new ArrayList<>();
statusList.add(InvoiceConstants.UNTREATED);
statusList.add(InvoiceConstants.PROCESSED_NOT_SUCCESSFUL);
grInvoiceRecord.setStatus(null);
grInvoiceRecord.setStatusList(statusList);
}
return grInvoiceRecordMapper.selectGrInvoiceRecordList(grInvoiceRecord);
}
/**
* 新增发票
*
* @param grInvoiceRecord 发票
* @return 结果
*/
@Override
public int insertGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord) {
grInvoiceRecord.setCreateTime(DateUtils.getNowDate());
return grInvoiceRecordMapper.insertGrInvoiceRecord(grInvoiceRecord);
}
/**
* 修改发票
*
* @param grInvoiceRecord 发票
* @return 结果
*/
@Override
public int updateGrInvoiceRecord(GrInvoiceRecord grInvoiceRecord) {
grInvoiceRecord.setUpdateTime(DateUtils.getNowDate());
return grInvoiceRecordMapper.updateGrInvoiceRecord(grInvoiceRecord);
}
/**
* 批量删除发票
*
* @param ids 需要删除的发票主键
* @return 结果
*/
@Override
public int deleteGrInvoiceRecordByIds(String ids) {
return grInvoiceRecordMapper.deleteGrInvoiceRecordByIds(Convert.toStrArray(ids));
}
/**
* 删除发票信息
*
* @param id 发票主键
* @return 结果
*/
@Override
public int deleteGrInvoiceRecordById(String id) {
return grInvoiceRecordMapper.deleteGrInvoiceRecordById(id);
}
@Override
public int batchProcess(List<String> idList) {
if (CollectionUtils.isEmpty(idList)) {
return 1;
}
List<GrInvoiceRecord> grInvoiceRecords = grInvoiceRecordMapper.selectGrInvoiceRecordListByIdList(idList);
if (CollectionUtils.isEmpty(grInvoiceRecords)) {
System.out.println("没有待请求数据");
return 1;
}
try {
for (GrInvoiceRecord invoiceRecord : grInvoiceRecords) {
String key = invoiceRecord.getDjh();
String txtCon = "";
List<String> billNos = new ArrayList<>();
billNos.add(key);
System.out.println("溯源 Id" + billNos);
txtCon += "溯源 Id" + billNos + "\n";
//封装 请求报文
String xmldata = InvoiceXmlSy.generateDwzsXml(billNos);
// 组装接口需要的请求报文
String payload = HttpXml.bulidSoapXML(interfaceCode, taxId, authCode, xmldata, "eiInterface");
System.out.println(payload);
boolean hasError = false;
try {
// 调用接口
Map<String, Object> res = HttpXml.webServiceExecute(eiInvWebServiceUrl, payload);
System.out.println("接口请求状态:" + res.get("httpStatus"));
System.out.println("接口返回的原始报文:" + res.get("data"));
txtCon += "接口请求状态:" + res.get("httpStatus") + "\n";
txtCon += "接口返回的原始报文:" + res.get("data") + "\n";
if (200 == (int) res.get("httpStatus")) {
try {
// 解析接口返回的报文
HashMap<String, String> serviceData = HttpXml.parseResponseXML((String) res.get("data"));
System.out.println("业务处理返回代码:" + serviceData.get("returnCode"));
System.out.println("业务处理返回提示:" + serviceData.get("returnMessage"));
System.out.println("业务处理返回数据:" + serviceData.get("data"));
txtCon += "业务处理返回代码:" + serviceData.get("returnCode") + "\n";
txtCon += "业务处理返回提示:" + serviceData.get("returnMessage") + "\n";
txtCon += "业务处理返回数据:" + serviceData.get("data") + "\n";
// appendToFile(fPath, txtCon);
String invNumber = "1";
String xml = serviceData.get("data");
Pattern pattern = Pattern.compile("<INV_NUMBER>(.*?)</INV_NUMBER>");
Matcher matcher = pattern.matcher(xml);
if (matcher.find()) {
invNumber = matcher.group(1);
}
// 如果没有被开发票invNumber 为 1
if (invNumber.equals("1")) {
System.out.println("单据号 " + key + " 还未开票");
continue;
}
if (serviceData.get("returnCode").equals("0000")) {
//更新数据库
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
System.out.println("更新视图开始");
// 1. 加载 Oracle JDBC 驱动
Class.forName("oracle.jdbc.driver.OracleDriver");
// 2. 建立数据库连接
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
// 3. 创建 Statement 对象
stmt = conn.createStatement();
// 4. 执行查询语句
String sql = "update gr_dp_saset_sdhm_v a set a.zxcolumn1 = '" + invNumber + "' where a.sasettleid =" + key;
PreparedStatement pstmt = conn.prepareStatement(sql);
int rowsA = pstmt.executeUpdate();
System.out.println("完成更新视图:" + rowsA);
invoiceRecord.setStatus(InvoiceConstants.SUCCESSFUL);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
invoiceRecord.setKpsj(sdf.format(new Date()));
invoiceRecord.setInvNumber(invNumber);
grInvoiceRecordMapper.updateGrInvoiceRecord(invoiceRecord);
} catch (Exception e) {
e.printStackTrace();
// 发生错误,标记为已尝试但不需要重试
hasError = true;
// 6. 关闭资源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e11) {
e11.printStackTrace();
}
}
} else {
// 业务处理失败,标记为已尝试但不需要重试
hasError = true;
System.out.println("业务处理失败:" + serviceData.get("returnMessage"));
}
} catch (Exception e) {
System.out.println("解析报文出错的,保持业务为处理中状态,需要检查原因");
e.printStackTrace();
hasError = true;
}
} else {
// HTTP 请求状态不是 200标记为已尝试但不需要重试
hasError = true;
System.out.println("HTTP 请求失败,状态码:" + res.get("httpStatus"));
}
} catch (Exception e) {
e.printStackTrace();
hasError = true;
}
// 如果发生错误,更新状态为 1已尝试但不需要重试
if (hasError) {
invoiceRecord.setStatus(InvoiceConstants.PROCESSED_NOT_SUCCESSFUL);
grInvoiceRecordMapper.updateGrInvoiceRecord(invoiceRecord);
System.out.println("单据号 " + key + " 处理失败,标记为已尝试状态");
}
}
} catch (Exception e) {
e.printStackTrace();
}
return 1;
}
// 追加写入模式
public static void appendToFile(String filePath, String content) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, true))) {
writer.newLine(); // 换行后再追加
writer.write(content);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -7,7 +7,8 @@ spring:
druid:
# 主库数据源 root
master:
url: jdbc:sqlite:D:/uploadPath/SQLite/sqlite3.db?date_string_format=yyyy-MM-dd HH:mm:ss
# url: jdbc:sqlite:D:/work/gr_fapiao/sql/sqlite3.db?date_string_format=yyyy-MM-dd HH:mm:ss
url: jdbc:sqlite:c:/SQLite/sql/sqlite3.db?date_string_format=yyyy-MM-dd HH:mm:ss
username:
password:
# 从库数据源

View File

@@ -150,4 +150,23 @@ gen:
# 自动去除表前缀默认是true
autoRemovePre: true
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
tablePrefix: sys_
tablePrefix: sys_
invoice:
# 开票系统地址
url: jdbc:oracle:thin:@192.168.1.247:1521:gryy
# 开票系统用户名
username: GRYYINV
# 开票系统密码
password: xxb147258368
taxId: 9134040072334670XN
authCode: 7G5X2YSSMA
interfaceCode: DJLR
updateCode: DWZS
eiInvWebService: https://csxtqd.nuocity.com/open/services/eiInvWebService?wsdl
iBillInterfaceWebService: https://csxtqd.nuocity.com/open/services/iBillInterfaceWebService?wsdl

View File

@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.lidee.project.system.record.mapper.GrInvoiceRecordMapper">
<resultMap type="GrInvoiceRecord" id="GrInvoiceRecordResult">
<result property="id" column="id"/>
<result property="djh" column="djh"/>
<result property="djrq" column="djrq"/>
<result property="fpzl" column="fpzl"/>
<result property="kpsj" column="kpsj"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="status" column="status"/>
<result property="gmfMc" column="gmf_mc"/>
<result property="invNumber" column="inv_number"/>
</resultMap>
<sql id="selectGrInvoiceRecordVo">
select id,
djh,
djrq,
fpzl,
kpsj,
create_by,
create_time,
update_by,
update_time,
status,
gmf_mc,
inv_number
from gr_invoice_record
</sql>
<select id="selectGrInvoiceRecordList" parameterType="GrInvoiceRecord" resultMap="GrInvoiceRecordResult">
<include refid="selectGrInvoiceRecordVo"/>
<where>
<if test="djh != null and djh != ''">AND djh LIKE '%' || #{djh} || '%'</if>
<if test="status != null and status != ''">and status = #{status}</if>
<if test="gmfMc != null and gmfMc != ''">AND gmf_mc LIKE '%' || #{gmfMc} || '%'</if>
<if test="invNumber != null and invNumber != ''">AND inv_number LIKE '%' || #{invNumber} || '%'</if>
<if test="statusList != null">
AND status in
<foreach item="statusItem" collection="statusList" open="(" separator="," close=")">
#{statusItem}
</foreach>
</if>
</where>
ORDER BY create_time DESC
</select>
<select id="selectGrInvoiceRecordById" parameterType="String" resultMap="GrInvoiceRecordResult">
<include refid="selectGrInvoiceRecordVo"/>
where id = #{id}
</select>
<insert id="insertGrInvoiceRecord" parameterType="GrInvoiceRecord">
insert into gr_invoice_record
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="djh != null">djh,</if>
<if test="djrq != null">djrq,</if>
<if test="fpzl != null">fpzl,</if>
<if test="kpsj != null">kpsj,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="status != null ">status,</if>
<if test="gmfMc != null">gmf_mc,</if>
<if test="invNumber != null">inv_number,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="djh != null">#{djh},</if>
<if test="djrq != null">#{djrq},</if>
<if test="fpzl != null">#{fpzl},</if>
<if test="kpsj != null">#{kpsj},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="status != null">#{status},</if>
<if test="gmfMc != null">#{gmfMc},</if>
<if test="invNumber != null">#{invNumber},</if>
</trim>
</insert>
<update id="updateGrInvoiceRecord" parameterType="GrInvoiceRecord">
update gr_invoice_record
<trim prefix="SET" suffixOverrides=",">
<if test="djh != null">djh = #{djh},</if>
<if test="djrq != null">djrq = #{djrq},</if>
<if test="fpzl != null">fpzl = #{fpzl},</if>
<if test="kpsj != null">kpsj = #{kpsj},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="status != null">status = #{status},</if>
<if test="gmfMc != null">gmf_mc = #{gmfMc},</if>
<if test="invNumber != null">inv_number = #{invNumber},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteGrInvoiceRecordById" parameterType="String">
delete
from gr_invoice_record
where id = #{id}
</delete>
<delete id="deleteGrInvoiceRecordByIds" parameterType="String">
delete from gr_invoice_record where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="selectGrInvoiceRecordListByIdList" parameterType="java.util.List" resultMap="GrInvoiceRecordResult">
<include refid="selectGrInvoiceRecordVo"/>
where id in
<foreach item="id" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
</mapper>

View File

@@ -26,7 +26,7 @@
</div>
<a th:href="@{/index}">
<li class="logo hidden-xs">
<span class="logo-lg">lidee</span>
<span class="logo-lg">电票系统</span>
</li>
</a>
<div class="sidebar-collapse">

View File

@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('新增发票')" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-record-add">
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<input name="DJH" class="form-control" type="text">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<input name="DJRQ" class="form-control" type="text">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<input name="FPZL" class="form-control" type="text">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<input name="KPSJ" class="form-control" type="text">
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var prefix = ctx + "system/record"
$("#form-record-add").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/add", $('#form-record-add').serialize());
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" >
<head>
<th:block th:include="include :: header('修改发票')" />
</head>
<body class="white-bg">
<div class="wrapper wrapper-content animated fadeInRight ibox-content">
<form class="form-horizontal m" id="form-record-edit" th:object="${grInvoiceRecord}">
<input name="id" th:field="*{id}" type="hidden">
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<input name="DJH" th:field="*{DJH}" class="form-control" type="text">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<input name="DJRQ" th:field="*{DJRQ}" class="form-control" type="text">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<input name="FPZL" th:field="*{FPZL}" class="form-control" type="text">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label"></label>
<div class="col-sm-8">
<input name="KPSJ" th:field="*{KPSJ}" class="form-control" type="text">
</div>
</div>
</form>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">
var prefix = ctx + "system/record";
$("#form-record-edit").validate({
focusCleanup: true
});
function submitHandler() {
if ($.validate.form()) {
$.operate.save(prefix + "/edit", $('#form-record-edit').serialize());
}
}
</script>
</body>
</html>

View File

@@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<th:block th:include="include :: header('发票列表')"/>
</head>
<body class="gray-bg">
<div class="container-div">
<div class="row">
<div class="col-sm-12 search-collapse">
<form id="formId">
<div class="select-list">
<ul>
<li>
<label>单据号:</label>
<input type="text" name="djh" style="width: 200px;"/>
</li>
<li>
<label style="width: 120px;">客户名称:</label>
<input type="text" name="gmfMc" style="width: 200px;"/>
</li>
<li>
<label style="width: 120px;">发票号码:</label>
<input type="text" name="invNumber" style="width: 200px;"/>
</li>
<li style="margin-left: 40px;">
<label>开票状态:</label>
<select name="status" style="width: 120px;">
<option value="">全部</option>
<option value="0">未开票</option>
<option value="2">已开票</option>
</select>
</li>
<li>
<a class="btn btn-primary btn-rounded btn-sm" onclick="$.table.search()"><i
class="fa fa-search"></i>&nbsp;搜索</a>
<a class="btn btn-warning btn-rounded btn-sm" onclick="$.form.reset()"><i
class="fa fa-refresh"></i>&nbsp;重置</a>
</li>
</ul>
</div>
</form>
</div>
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success multiple disabled" onclick="batchProcess()" shiro:hasPermission="system:record:list">
<i class="fa fa-edit"></i> 批量处理
</a>
</div>
<div class="col-sm-12 select-table table-striped">
<table id="bootstrap-table"></table>
</div>
</div>
</div>
<th:block th:include="include :: footer"/>
<script th:inline="javascript">
var editFlag = [[${@permission.hasPermi('system:record:edit')}]];
var removeFlag = [[${@permission.hasPermi('system:record:remove')}]];
var prefix = ctx + "system/record";
// 批量处理函数
function batchProcess() {
var rows = $.common.isEmpty(table.options.uniqueId) ? $.table.selectFirstColumns() : $.table.selectColumns(table.options.uniqueId);
if (rows.length == 0) {
$.modal.alertWarning("请至少选择一条记录");
return;
}
$.modal.confirm("确认要处理选中的" + rows.length + "条数据吗?", function() {
$.modal.loading("正在处理,请稍候...");
$.ajax({
url: prefix + "/batchProcess",
type: "post",
dataType: "json",
traditional: true,
data: {
"idList": rows
},
success: function(result) {
if (result.code == web_status.SUCCESS) {
$.modal.msgSuccess(result.msg);
$.table.refresh();
} else {
$.modal.alertError(result.msg);
}
},
complete: function() {
$.modal.closeLoading();
}
});
});
}
$(function () {
var options = {
url: prefix + "/list",
createUrl: prefix + "/add",
updateUrl: prefix + "/edit/{id}",
removeUrl: prefix + "/remove",
exportUrl: prefix + "/export",
modalName: "发票",
columns: [{
checkbox: true
},
{
field: 'id',
title: '',
visible: false
},
{
field: 'djh',
title: '单据号',
align: 'center'
},
{
field: 'djrq',
title: '单据日期',
align: 'center'
},
{
field: 'invNumber',
title: '发票号码',
align: 'center'
},
{
field: 'fpzl',
title: '发票种类',
align: 'center',
formatter: function(value, row, index) {
if (value === "81") {
return '<span>数电专票</span>';
} else if (value === "82") {
return '<span>数电普票</span>';
}
return value;
}
},
{
field: 'gmfMc',
title: '客户名称',
align: 'center'
},
{
field: 'kpsj',
title: '开票时间',
align: 'center'
},
{
field: 'createTime',
title: '上传时间',
align: 'center'
},
{
field: 'status',
title: '开票状态',
align: 'center',
formatter: function(value, row, index) {
if (value === "2") {
return '<span style="color: #28a745;">已开票</span>';
} else if (value === "1") {
return '<span style="color: #17a2b8;">处理中</span>';
} else if (value === "0") {
return '<span style="color: #dc3545;">未开票</span>';
}
return value;
}
}
// {
// title: '操作',
// align: 'center',
// formatter: function (value, row, index) {
// var actions = [];
// actions.push('<a class="btn btn-success btn-xs ' + editFlag + '" href="javascript:void(0)" onclick="$.operate.edit(\'' + row.id + '\')"><i class="fa fa-edit"></i>编辑</a> ');
// actions.push('<a class="btn btn-danger btn-xs ' + removeFlag + '" href="javascript:void(0)" onclick="$.operate.remove(\'' + row.id + '\')"><i class="fa fa-remove"></i>删除</a>');
// return actions.join('');
// }
// }
]
};
$.table.init(options);
});
</script>
</body>
</html>