Explorar o código

OMS发票信息部分代码上传

徐滕 hai 2 días
pai
achega
db32cff695
Modificáronse 35 ficheiros con 6690 adicións e 5 borrados
  1. 297 5
      src/main/java/com/jeeplus/modules/ruralprojectrecords/web/RuralProjectSignatureOldMessageDisposeController.java
  2. 61 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/Additional.java
  3. 106 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/BuildingInfo.java
  4. 189 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/DifferenceVoucher.java
  5. 95 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/GoodsTransport.java
  6. 44 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/HarvesterInfo.java
  7. 48 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownAdditional.java
  8. 83 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownBuildingInfo.java
  9. 84 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownGoodsTransport.java
  10. 34 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownHarvesterInfo.java
  11. 228 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownItem.java
  12. 68 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownJointBuyer.java
  13. 130 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownRealPropertyRentInfo.java
  14. 155 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownRealPropertySellInfo.java
  15. 130 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownTravellerTransport.java
  16. 51 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownUsedCar.java
  17. 173 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownUsedCarSales.java
  18. 180 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownVehicle.java
  19. 106 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownVehicleVesselInfo.java
  20. 870 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSInvoiceDetailInfo.java
  21. 1467 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceOMSImportInfo.java
  22. 78 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/JointBuyer.java
  23. 131 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/OMSAccessTokenInfo.java
  24. 75 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/OMSInvoiceResultDownloadData.java
  25. 389 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/OrderItem.java
  26. 163 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/RealPropertyRentInfo.java
  27. 177 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/RealPropertySellInfo.java
  28. 148 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/TravellerTransport.java
  29. 56 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/UsedCar.java
  30. 187 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/UsedCarSale.java
  31. 194 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/Vehicle.java
  32. 157 0
      src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/VehicleVesselInfo.java
  33. 145 0
      src/main/java/com/jeeplus/modules/workinvoice/utils/HttpPostJsonUtil.java
  34. 161 0
      src/main/java/com/jeeplus/modules/workinvoice/utils/OMSNationUtil.java
  35. 30 0
      src/main/java/com/jeeplus/modules/workinvoice/utils/ThreadPoolUtil.java

+ 297 - 5
src/main/java/com/jeeplus/modules/ruralprojectrecords/web/RuralProjectSignatureOldMessageDisposeController.java

@@ -1,6 +1,7 @@
 package com.jeeplus.modules.ruralprojectrecords.web;
 
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.google.common.collect.Lists;
 import com.jeeplus.common.config.Global;
 import com.jeeplus.common.oss.OSSClientUtil;
@@ -9,7 +10,6 @@ import com.jeeplus.common.utils.excel.ExportExcel;
 import com.jeeplus.common.utils.excel.ImportExcel;
 import com.jeeplus.common.web.BaseController;
 import com.jeeplus.modules.militaryIndustryConfidentiality.service.MilitaryIndustryConfidentialityService;
-import com.jeeplus.modules.pojectMaterialsWarehouse.entity.ProjectMaterialCollectInfo;
 import com.jeeplus.modules.projectAccessory.dao.ProjectTemplateDao;
 import com.jeeplus.modules.projectAccessory.entity.ProjectTemplateInfo;
 import com.jeeplus.modules.projectcontentinfo.dao.ProjectReportRecordDao;
@@ -29,7 +29,6 @@ import com.jeeplus.modules.ruralprojectrecords.service.RuralProjectRecordsServic
 import com.jeeplus.modules.ruralprojectrecords.service.RuralProjectSignatureOldMessageDisposeService;
 import com.jeeplus.modules.statement.service.StatementCompanyComprehensiveService;
 import com.jeeplus.modules.sys.entity.User;
-import com.jeeplus.modules.sys.entity.Workattachment;
 import com.jeeplus.modules.sys.service.WorkattachmentService;
 import com.jeeplus.modules.sys.utils.UserUtils;
 import com.jeeplus.modules.tools.utils.SignaturePostUtil;
@@ -37,17 +36,21 @@ import com.jeeplus.modules.utils.SftpClientUtil;
 import com.jeeplus.modules.workactivity.entity.WorkActivityProcess;
 import com.jeeplus.modules.workactivity.service.WorkActivityProcessService;
 import com.jeeplus.modules.workclientinfo.entity.WorkClientAttachment;
+import com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown.OMSInvoiceDetailInfo;
+import com.jeeplus.modules.workinvoice.entity.OMS.InvoiceOMSImportInfo;
+import com.jeeplus.modules.workinvoice.entity.OMS.OMSAccessTokenInfo;
+import com.jeeplus.modules.workinvoice.entity.OMS.OMSInvoiceResultDownloadData;
 import com.jeeplus.modules.workinvoice.entity.TemporaryInvoiceInfo;
 import com.jeeplus.modules.workinvoice.service.WorkInvoiceService;
-import com.jeeplus.modules.workreimbursement.entity.ReimbursementVATTax;
+import com.jeeplus.modules.workinvoice.utils.HttpPostJsonUtil;
+import com.jeeplus.modules.workinvoice.utils.OMSNationUtil;
+import com.jeeplus.modules.workinvoice.utils.ThreadPoolUtil;
 import com.jeeplus.modules.workreimbursement.service.WorkReimbursementService;
 import com.jeeplus.modules.workstaff.service.WorkStaffBasicInfoService;
 import freemarker.template.Configuration;
 import freemarker.template.Template;
 import org.activiti.engine.HistoryService;
-import org.apache.shiro.authz.annotation.RequiresPermissions;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Controller;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -58,6 +61,7 @@ import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 import org.w3c.dom.Document;
 import org.w3c.dom.NodeList;
+import redis.clients.jedis.Jedis;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -66,6 +70,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.*;
 
 /**
@@ -936,4 +941,291 @@ public class RuralProjectSignatureOldMessageDisposeController extends BaseContro
     }
 
 
+    /**
+     * OMS发票测试 完整最终版【最终最终定稿,完全匹配你的所有要求】
+     * 精准码值规则:
+     * 0000=成功执行下载 | 9998=5次重试/30秒 | 0003=清token从头执行 | 0001/0002/其他码=直接执行兜底方法修改系统信息
+     */
+    @RequestMapping(value = "/invoiceOMSView")
+    @ResponseBody
+    @Transactional(readOnly = false)
+    public Map<String,Object> invoiceOMSView(){
+        Map<String,Object> map = new HashMap<>();
+        // 调用抽离后的核心业务方法,实现流程复用(0003时可重新调用)
+        doInvoiceBusiness(map);
+        return map;
+    }
+
+    // ======================== 抽离核心业务流程:方便0003时从头重新调用 =========================
+    private void doInvoiceBusiness(Map<String,Object> map) {
+        int seconds = 86400;
+        Jedis jedis = null;
+        String accessToken = null;
+        try {
+            jedis = JedisUtils.getResource();
+            accessToken = jedis.get("OMSAccessToken");
+            if(StringUtils.isBlank(accessToken)){
+                // 获取AccessToken 9998重试5次
+                accessToken = getOmsAccessTokenWithRetry(5);
+                if(StringUtils.isNotBlank(accessToken)){
+                    jedis.setex("OMSAccessToken", seconds, accessToken);
+                    map.put("token状态", "重新获取token成功,存入Redis");
+                } else {
+                    accessToken = "";
+                    map.put("token状态", "获取token失败");
+                    return;
+                }
+            } else {
+                map.put("token状态", "从Redis获取token成功");
+            }
+
+            OMSNationUtil util = new OMSNationUtil();
+            String string = util.neatenData();
+
+            OMSAccessTokenInfo InvoiceTokenInfo = new OMSAccessTokenInfo();
+            InvoiceTokenInfo.setAppId("sscs");
+            InvoiceTokenInfo.setAppKey("sscs");
+            InvoiceTokenInfo.setExchangeId(UUID.randomUUID().toString());
+            InvoiceTokenInfo.setAccessToken(accessToken);
+            InvoiceTokenInfo.setData(string);
+            String jsonInvoiceStr = JSON.toJSONString(InvoiceTokenInfo);
+
+            String jsonInvoicResultStr = HttpPostJsonUtil.doPost("https://oms-sandbox.einvoice.js.cn:7079/prod-api/output/server/order/upload", jsonInvoiceStr);
+            System.out.println("✅ 订单提交接口返回值:" + jsonInvoicResultStr);
+            map.put("订单接口信息", jsonInvoicResultStr);
+
+            // 调用订单上传重试方法(包含所有码值规则)
+            if(StringUtils.isNotBlank(jsonInvoicResultStr)){
+                String finalAccessToken = accessToken;
+                String finalJsonInvoiceStr = jsonInvoiceStr;
+                executeOrderUploadRetry(5, jsonInvoicResultStr, finalJsonInvoiceStr, finalAccessToken, map);
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            map.put("errorMsg", "系统异常:" + e.getMessage());
+        } finally {
+            if(jedis != null){
+                jedis.close();
+            }
+        }
+    }
+
+    // ======================== 原有方法1:获取AccessToken 9998重试5次【无修改】 =========================
+    private String getOmsAccessTokenWithRetry(int remainRetryTimes) {
+        try {
+            OMSAccessTokenInfo tokenInfo = new OMSAccessTokenInfo();
+            tokenInfo.setAppId("sscs");
+            tokenInfo.setAppKey("sscs");
+            tokenInfo.setExchangeId(UUID.randomUUID().toString());
+            String jsonStr = JSON.toJSONString(tokenInfo);
+            String accessTokenStr = HttpPostJsonUtil.doPost("https://oms-sandbox.einvoice.js.cn:7079/prod-api/server/accessToken", jsonStr);
+
+            if(StringUtils.isBlank(accessTokenStr)){
+                System.err.println("获取AccessToken失败:接口返回空,剩余重试次数:"+remainRetryTimes);
+                return "";
+            }
+
+            OMSAccessTokenInfo resultTokenInfo = JSON.parseObject(accessTokenStr, OMSAccessTokenInfo.class);
+            if(null == resultTokenInfo || null == resultTokenInfo.getResult()){
+                System.err.println("获取AccessToken失败:返回结果解析异常,剩余重试次数:"+remainRetryTimes);
+                return "";
+            }
+
+            String code = resultTokenInfo.getResult().getCode();
+            if ("0000".equals(code)) {
+                String token = OMSNationUtil.extractAccessTokenFromBase64(resultTokenInfo.getData().toString());
+                System.out.println("✅ 获取AccessToken成功,重试次数剩余:"+remainRetryTimes);
+                return token;
+            } else if ("9998".equals(code)) {
+                if (remainRetryTimes > 1) {
+                    int nextRetry = remainRetryTimes - 1;
+                    System.err.println("⚠️ 获取AccessToken返回9998接口波动,30秒后重试,剩余次数:"+nextRetry);
+                    Thread.sleep(30 * 1000);
+                    return getOmsAccessTokenWithRetry(nextRetry);
+                } else {
+                    System.err.println("❌ 获取AccessToken失败:连续5次返回9998,重试次数耗尽!");
+                    return "";
+                }
+            } else {
+                System.err.println("❌ 获取AccessToken失败:返回业务错误码,code="+code);
+                return "";
+            }
+        } catch (InterruptedException e) {
+            System.err.println("❌ 获取AccessToken失败:重试延迟被中断");
+            Thread.currentThread().interrupt();
+            return "";
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println("❌ 获取AccessToken失败:调用接口异常,剩余重试次数:"+remainRetryTimes);
+            return "";
+        }
+    }
+
+    // ======================== 核心修改【仅这一处,完美匹配你的最新要求】 =========================
+    private void executeOrderUploadRetry(int remainRetryTimes, String jsonInvoicResultStr, String jsonInvoiceStr, String accessToken, Map<String,Object> map) {
+        String jsonInvoicResult = "";
+        try {
+            OMSAccessTokenInfo resultTokenInfo = JSON.parseObject(jsonInvoicResultStr, OMSAccessTokenInfo.class);
+            if(null == resultTokenInfo || null == resultTokenInfo.getResult()){
+                System.err.println("❌ 订单上传解析失败,剩余重试次数:"+remainRetryTimes);
+                handleInvoiceRetryAllFail(accessToken); // 解析失败也执行兜底方法
+                return;
+            }
+            String code = resultTokenInfo.getResult().getCode();
+
+            // ======================== 所有码值规则 全部在这里【最终定稿】=========================
+            if ("0000".equals(code)) {
+                // ✅ 0000 成功:解析数据+存入map+触发30秒后异步下载发票
+                jsonInvoicResult = OMSNationUtil.extractAccessTokenFromBase64(resultTokenInfo.getData().toString());
+                map.put("订单接口返回值",jsonInvoicResult);
+                System.out.println("✅ 订单上传返回0000成功,触发发票下载接口");
+                ThreadPoolUtil.executeDelay(30, () -> {
+                    executeInvoiceDownloadWithRetry(5, accessToken);
+                });
+            } else if ("9998".equals(code)) {
+                // ✅ 9998 接口波动:延迟30秒+重新调用接口+重试次数-1,最多5次
+                if (remainRetryTimes > 1) {
+                    int nextRetry = remainRetryTimes - 1;
+                    System.err.println("⚠️ 订单上传返回9998接口波动,30秒后重试,剩余次数:"+nextRetry);
+                    Thread.sleep(30 * 1000);
+                    String newResultStr = HttpPostJsonUtil.doPost("https://oms-sandbox.einvoice.js.cn:7079/prod-api/output/server/order/upload", jsonInvoiceStr);
+                    map.put("订单接口信息-重试"+(6-nextRetry), newResultStr);
+                    executeOrderUploadRetry(nextRetry, newResultStr, jsonInvoiceStr, accessToken, map);
+                } else {
+                    System.err.println("❌ 订单上传失败:连续5次9998,重试次数耗尽!");
+                    handleInvoiceRetryAllFail(accessToken); // 5次9998耗尽也执行兜底方法
+                    map.put("订单状态", "失败:接口波动次数超限");
+                }
+            } else if ("0003".equals(code)) {
+                // ✅ 0003 token失效:清除旧token → 重新获取token → 从头完整执行所有流程
+                System.err.println("⚠️ 订单上传返回0003(token失效),开始重新获取token并从头执行流程");
+                Jedis jedis = null;
+                try {
+                    jedis = JedisUtils.getResource();
+                    jedis.del("OMSAccessToken");
+                    map.put("0003处理", "已清除旧token,准备重新获取");
+                } catch (Exception e) {
+                    e.printStackTrace();
+                } finally {
+                    if(jedis != null) jedis.close();
+                }
+                doInvoiceBusiness(map);
+            } else {
+                // ✅ ✅ ✅ 核心修正:0001/0002/其他任意错误码 → 直接调用handleInvoiceRetryAllFail执行修改系统信息逻辑 ✅ ✅ ✅
+                System.err.println("❌ 订单上传返回业务错误码:"+code+"(0001/0002等),立即执行失败兜底逻辑修改系统信息!");
+                map.put("订单状态", "失败,错误码:"+code);
+                handleInvoiceRetryAllFail(accessToken); // 关键:直接执行你的修改逻辑,不抛异常、不重试
+            }
+        } catch (InterruptedException e) {
+            System.err.println("❌ 订单上传重试被中断");
+            Thread.currentThread().interrupt();
+            handleInvoiceRetryAllFail(accessToken); // 中断也执行兜底方法
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println("❌ 订单上传重试异常,剩余次数:"+remainRetryTimes);
+            handleInvoiceRetryAllFail(accessToken); // 异常也执行兜底方法
+        }
+    }
+
+    // ======================== 原有方法2:发票下载接口重试5次【无任何修改,一行未动】 =========================
+    private void executeInvoiceDownloadWithRetry(int remainingRetryTimes, String accessToken) {
+        try {
+            System.out.println("✅ 延迟执行成功,剩余重试次数:" + remainingRetryTimes + ",开始调用发票下载接口");
+
+            OMSInvoiceResultDownloadData getInvoiceInfo = new OMSInvoiceResultDownloadData();
+            getInvoiceInfo.setDeptCode("7777");
+            getInvoiceInfo.setOrderno("fff79669f3774c2a8be557e909a746d5");
+            getInvoiceInfo.setIsDetail("1");
+            String jsonInvoiceInfoStr = JSON.toJSONString(getInvoiceInfo);
+            String base64Str = Base64.getEncoder().encodeToString(jsonInvoiceInfoStr.getBytes(StandardCharsets.UTF_8));
+
+            OMSAccessTokenInfo invoiceDownInfo = new OMSAccessTokenInfo();
+            invoiceDownInfo.setAppId("sscs");
+            invoiceDownInfo.setAppKey("sscs");
+            invoiceDownInfo.setExchangeId(UUID.randomUUID().toString());
+            invoiceDownInfo.setAccessToken(accessToken);
+            invoiceDownInfo.setData(base64Str);
+            String jsonInvoiceDownStr = JSON.toJSONString(invoiceDownInfo);
+
+            String invoiceResultStr = HttpPostJsonUtil.doPost("https://oms-sandbox.einvoice.js.cn:7079/prod-api/output/server/invoice/download", jsonInvoiceDownStr);
+            System.out.println("发票开票结果invoiceResultStr:" + invoiceResultStr);
+
+            String invoceDownJsonStr = null;
+            boolean isSuccess = false;
+            if(StringUtils.isNotBlank(invoiceResultStr)){
+                OMSAccessTokenInfo resultDownInfo = JSON.parseObject(invoiceResultStr, OMSAccessTokenInfo.class);
+                if(null != resultDownInfo.getResult() && "0000".equals(resultDownInfo.getResult().getCode())){
+                    invoceDownJsonStr = OMSNationUtil.getDownFromBase64(resultDownInfo.getData().toString());
+                    if(StringUtils.isNotBlank(invoceDownJsonStr)){
+                        OMSInvoiceDetailInfo invoiceInfo = JSONObject.parseObject(invoceDownJsonStr, OMSInvoiceDetailInfo.class);
+                        if (invoiceInfo != null) {
+                            System.out.println("✅ 第"+(6-remainingRetryTimes)+"次调用发票接口成功,拿到有效数据!");
+                            System.out.println("getOfdUrl" + invoiceInfo.getOfdUrl());
+                            System.out.println("getPdfUrl" + invoiceInfo.getPdfUrl());
+                            System.out.println("getXmlUrl" + invoiceInfo.getXmlUrl());
+                            isSuccess = true;
+                        }
+                    }
+                }
+            }
+
+            if (isSuccess) {
+                System.out.println("✅ 发票接口调用成功,重试流程结束,执行后续业务");
+                return;
+            } else {
+                if (remainingRetryTimes > 1) {
+                    int nextRetryTimes = remainingRetryTimes - 1;
+                    System.out.println("❌ 发票接口返回结果异常/数据未就绪,准备重试!剩余重试次数:" + nextRetryTimes);
+                    ThreadPoolUtil.executeDelay(30, () -> {
+                        executeInvoiceDownloadWithRetry(nextRetryTimes, accessToken);
+                    });
+                } else {
+                    System.err.println("❌ 发票接口调用失败,已重试5次全部失败,触发系统信息修改逻辑!");
+                    handleInvoiceRetryAllFail(accessToken);
+                }
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.err.println("❌ 调用发票接口抛出异常,剩余重试次数:" + remainingRetryTimes);
+            if (remainingRetryTimes > 1) {
+                int nextRetryTimes = remainingRetryTimes - 1;
+                ThreadPoolUtil.executeDelay(30, () -> {
+                    executeInvoiceDownloadWithRetry(nextRetryTimes, accessToken);
+                });
+            } else {
+                System.err.println("❌ 发票接口调用抛出异常,已重试5次全部失败,触发系统信息修改逻辑!");
+                handleInvoiceRetryAllFail(accessToken);
+            }
+        }
+    }
+
+    // ======================== 原有方法3:失败兜底修改系统信息【无任何修改,你的业务逻辑写这里即可】 =========================
+    private void handleInvoiceRetryAllFail(String accessToken) {
+        // ============ 你的所有【修改系统信息/更新订单状态/写失败日志】逻辑 全部写在这里!!! ============
+        try {
+            System.err.println("============ 开始执行【失败兜底-系统信息修改逻辑】 ============");
+
+            // 示例1:更新订单表的发票状态为【开票失败】
+            // orderService.updateInvoiceStatusByToken(accessToken, "99");
+
+            // 示例2:写入失败日志到数据库,方便人工排查和补偿处理
+            // invoiceFailLogService.saveFailLog(accessToken, "开票失败,错误码非0000/9998/0003", new Date());
+
+            // 示例3:标记该单据为异常,方便后台运维查看
+            // abnormalOrderService.saveAbnormalOrder(accessToken, "开票失败");
+
+            // 示例4:调用其他系统接口,同步失败状态
+            // otherSystemApi.notifyInvoiceFail(accessToken);
+
+            System.err.println("============ 【失败兜底-系统信息修改逻辑】执行完成 ============");
+
+        } catch (Exception e) {
+            // 必加:捕获这个方法的异常,避免兜底方法报错导致线程异常
+            e.printStackTrace();
+            System.err.println("❌ 执行失败兜底方法时抛出异常:" + e.getMessage());
+        }
+    }
+
 }

+ 61 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/Additional.java

@@ -0,0 +1,61 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Pattern;
+
+
+/**
+ * 附加要素信息(按需传值,如果不为空则在发票备注展示附加要素信息;仅对开票类型为蓝票且发票类型为电子发票(增值税专用发票)、电子发票(普通发票)生效)
+ */
+public class Additional {
+    /**
+     * 附加要素名称
+     * 必填,40个字节(一个汉字占2个字节)
+     */
+    @NotBlank(message = "附加要素名称additionalName不能为空")
+    private String additionalName;
+
+    /**
+     * 附加要素类型
+     * 必填,可选值:string(字符型)、number(数值型)、date(日期型,格式yyyy-mm-dd)
+     */
+    @NotBlank(message = "附加要素类型additionalType不能为空")
+    @Pattern(regexp = "^string|number|date$",
+            message = "附加要素类型additionalType只能传入:string(字符型)、number(数值型)、date(日期型)")
+    private String additionalType;
+
+    /**
+     * 附加要素值
+     * 必填,500个字节
+     * 若类型为date时,格式必须为yyyy-mm-dd
+     */
+    @NotBlank(message = "附加要素值additionalValue不能为空")
+    @Pattern(regexp = "^$|^\\d{4}-\\d{2}-\\d{2}$",
+            message = "当附加要素类型为date时,值的格式必须为yyyy-mm-dd")
+    private String additionalValue;
+
+    public String getAdditionalName() {
+        return additionalName;
+    }
+
+    public void setAdditionalName(String additionalName) {
+        this.additionalName = additionalName;
+    }
+
+    public String getAdditionalType() {
+        return additionalType;
+    }
+
+    public void setAdditionalType(String additionalType) {
+        this.additionalType = additionalType;
+    }
+
+    public String getAdditionalValue() {
+        return additionalValue;
+    }
+
+    public void setAdditionalValue(String additionalValue) {
+        this.additionalValue = additionalValue;
+    }
+}

+ 106 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/BuildingInfo.java

@@ -0,0 +1,106 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+
+/**
+ * buildingInfo:开具建筑服务特定要素的数电发票才需要传(specificFactor 为 03 时);注:数电建筑服务发票 只能有 一条明细 且 规格型号、计量单位、数量、单价都不能有值
+ * (只有在开具建筑服务特定要素发票时需要传 buildingInfo 模块,不开具建筑服务特定要素不传buildingInfo 模块)
+ */
+public class BuildingInfo {
+    /**
+     * 建筑服务发生地
+     * 必填,需与行政区划名称一致,最大100字符
+     */
+    @NotBlank(message = "建筑服务发生地buildingAddress不能为空")
+    @Size(max = 100, message = "建筑服务发生地buildingAddress不能超过100字符")
+    private String buildingAddress;
+
+    /**
+     * 详细地址
+     * 建筑服务发生地+详细地址总长度最大120字符
+     */
+    @Size(max = 120, message = "详细地址detailedAddress不能超过120字符")
+    private String detailedAddress;
+
+    /**
+     * 土地增值税项目编号
+     * 最大16字符
+     */
+    @Size(max = 16, message = "土地增值税项目编号landVatItemNo不能超过16字符")
+    private String landVatItemNo;
+
+    /**
+     * 建筑项目名称
+     * 必填,最大80字符
+     */
+    @NotBlank(message = "建筑项目名称itemName不能为空")
+    @Size(max = 80, message = "建筑项目名称itemName不能超过80字符")
+    private String itemName;
+
+    /**
+     * 跨地(市)标志
+     * 必填,0:否 1:是
+     */
+    @NotBlank(message = "跨地(市)标志crossCityFlag不能为空")
+    @Pattern(regexp = "^0|1$", message = "跨地(市)标志crossCityFlag只能传入:0(否)、1(是)")
+    private String crossCityFlag;
+
+    /**
+     * 跨区域涉税事项报验管理编号
+     * 跨地(市)标志为1时必填,最大50字符
+     */
+    @Size(max = 50, message = "跨区域涉税事项报验管理编号crossCityCode不能超过50字符")
+    private String crossCityCode;
+
+    public String getBuildingAddress() {
+        return buildingAddress;
+    }
+
+    public void setBuildingAddress(String buildingAddress) {
+        this.buildingAddress = buildingAddress;
+    }
+
+    public String getDetailedAddress() {
+        return detailedAddress;
+    }
+
+    public void setDetailedAddress(String detailedAddress) {
+        this.detailedAddress = detailedAddress;
+    }
+
+    public String getLandVatItemNo() {
+        return landVatItemNo;
+    }
+
+    public void setLandVatItemNo(String landVatItemNo) {
+        this.landVatItemNo = landVatItemNo;
+    }
+
+    public String getItemName() {
+        return itemName;
+    }
+
+    public void setItemName(String itemName) {
+        this.itemName = itemName;
+    }
+
+    public String getCrossCityFlag() {
+        return crossCityFlag;
+    }
+
+    public void setCrossCityFlag(String crossCityFlag) {
+        this.crossCityFlag = crossCityFlag;
+    }
+
+    public String getCrossCityCode() {
+        return crossCityCode;
+    }
+
+    public void setCrossCityCode(String crossCityCode) {
+        this.crossCityCode = crossCityCode;
+    }
+}

+ 189 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/DifferenceVoucher.java

@@ -0,0 +1,189 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import javax.validation.constraints.Digits;
+import java.math.BigDecimal;
+
+/**
+ * 差额凭证信息子实体类
+ * 差额征税、差额开票时必传
+ */
+public class DifferenceVoucher {
+
+    /**
+     * 序号
+     * 必填,从1开始依次增加,3位字符
+     */
+    @NotBlank(message = "序号detailIndex不能为空")
+    @Pattern(regexp = "^[1-9]\\d{0,2}$",
+            message = "序号必须从1开始依次增加,且为1-3位数字")
+    private String detailIndex;
+
+    /**
+     * 凭证类型
+     * 必填,可选值:01(数电发票)、02(增值税专用发票)、03(增值税普通发票)、04(营业税发票)、05(财政票据)、06(法院裁决书)、07(契税完税凭证)、08(其他发票类)、09(其他扣除凭证)
+     */
+    @NotBlank(message = "凭证类型voucherType不能为空")
+    @Pattern(regexp = "^01|02|03|04|05|06|07|08|09$",
+            message = "凭证类型不在合法范围内")
+    private String voucherType;
+
+    /**
+     * 数电票号码
+     * 凭证类型为01时必传,最大20字符
+     */
+    @Size(max = 20, message = "数电票号码长度不能超过20字符")
+    private String allEinVno;
+
+    /**
+     * 发票代码
+     * 凭证类型为02、03、04时必传,最大12字符
+     */
+    @Size(max = 12, message = "发票代码长度不能超过12字符")
+    private String invcode;
+
+    /**
+     * 发票号码
+     * 凭证类型为02、03、04时必传,最大8字符
+     */
+    @Size(max = 8, message = "发票号码长度不能超过8字符")
+    private String invno;
+
+    /**
+     * 凭证号码
+     * 最大20字符
+     */
+    @Size(max = 20, message = "凭证号码长度不能超过20字符")
+    private String voucherNumber;
+
+    /**
+     * 开具日期
+     * 凭证类型为01、02、03、04时必传,格式为yyyy-MM-dd
+     */
+    @Pattern(regexp = "^$|^\\d{4}-\\d{2}-\\d{2}$",
+            message = "开具日期格式错误,必须为yyyy-MM-dd")
+    private String invoiceTime;
+
+    /**
+     * 凭证金额
+     * 必填,最多18位整数,保留2位小数
+     */
+    @NotNull(message = "凭证金额totalTax不能为空")
+    @Digits(integer = 18, fraction = 2, message = "凭证金额格式错误,最多18位整数,保留2位小数")
+    private BigDecimal totalTax;
+
+    /**
+     * 本次扣除金额
+     * 必填,不能大于凭证金额,最多18位整数,保留2位小数
+     */
+    @NotNull(message = "本次扣除金额deduction不能为空")
+    @Digits(integer = 18, fraction = 2, message = "本次扣除金额格式错误,最多18位整数,保留2位小数")
+    private BigDecimal deduction;
+
+    /**
+     * 备注
+     * 凭证类型为08、09时必传,最大100字符
+     */
+    @Size(max = 100, message = "备注长度不能超过100字符")
+    private String remarks;
+
+    /**
+     * 凭证来源
+     * 必填,可选值:1(手工录入)、2(勾选录入)、3(模板录入);同一张发票内保持一致
+     */
+    @NotBlank(message = "凭证来源voucherSource不能为空")
+    @Pattern(regexp = "^1|2|3$",
+            message = "凭证来源只能传入:1(手工录入)、2(勾选录入)、3(模板录入)")
+    private String voucherSource;
+
+    public String getDetailIndex() {
+        return detailIndex;
+    }
+
+    public void setDetailIndex(String detailIndex) {
+        this.detailIndex = detailIndex;
+    }
+
+    public String getVoucherType() {
+        return voucherType;
+    }
+
+    public void setVoucherType(String voucherType) {
+        this.voucherType = voucherType;
+    }
+
+    public String getAllEinVno() {
+        return allEinVno;
+    }
+
+    public void setAllEinVno(String allEinVno) {
+        this.allEinVno = allEinVno;
+    }
+
+    public String getInvcode() {
+        return invcode;
+    }
+
+    public void setInvcode(String invcode) {
+        this.invcode = invcode;
+    }
+
+    public String getInvno() {
+        return invno;
+    }
+
+    public void setInvno(String invno) {
+        this.invno = invno;
+    }
+
+    public String getVoucherNumber() {
+        return voucherNumber;
+    }
+
+    public void setVoucherNumber(String voucherNumber) {
+        this.voucherNumber = voucherNumber;
+    }
+
+    public String getInvoiceTime() {
+        return invoiceTime;
+    }
+
+    public void setInvoiceTime(String invoiceTime) {
+        this.invoiceTime = invoiceTime;
+    }
+
+    public BigDecimal getTotalTax() {
+        return totalTax;
+    }
+
+    public void setTotalTax(BigDecimal totalTax) {
+        this.totalTax = totalTax;
+    }
+
+    public BigDecimal getDeduction() {
+        return deduction;
+    }
+
+    public void setDeduction(BigDecimal deduction) {
+        this.deduction = deduction;
+    }
+
+    public String getRemarks() {
+        return remarks;
+    }
+
+    public void setRemarks(String remarks) {
+        this.remarks = remarks;
+    }
+
+    public String getVoucherSource() {
+        return voucherSource;
+    }
+
+    public void setVoucherSource(String voucherSource) {
+        this.voucherSource = voucherSource;
+    }
+}

+ 95 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/GoodsTransport.java

@@ -0,0 +1,95 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 货物运输服务特定要素子实体类
+ * 仅在开具货物运输服务特定要素的数电发票(specificFactor为04时)需要传入
+ * 最多2000行,至少1行
+ */
+public class GoodsTransport {
+
+    /**
+     * 运输工具种类
+     * 必填,可选值:1(铁路运输)、2(公路运输)、3(水路运输)、4(航空运输)、5(管道运输)
+     */
+    @NotBlank(message = "运输工具种类transportTool不能为空")
+    @Pattern(regexp = "^1|2|3|4|5$",
+            message = "运输工具种类transportTool只能传入:1-5的数字,对应铁路/公路/水路/航空/管道运输")
+    private String transportTool;
+
+    /**
+     * 运输工具牌号
+     * 必填,最大40字符
+     */
+    @NotBlank(message = "运输工具牌号transportToolNum不能为空")
+    @Size(max = 40, message = "运输工具牌号transportToolNum不能超过40字符")
+    private String transportToolNum;
+
+    /**
+     * 起运地
+     * 必填,最大80字符
+     */
+    @NotBlank(message = "起运地origin不能为空")
+    @Size(max = 80, message = "起运地origin不能超过80字符")
+    private String origin;
+
+    /**
+     * 到达地
+     * 必填,最大80字符
+     */
+    @NotBlank(message = "到达地destination不能为空")
+    @Size(max = 80, message = "到达地destination不能超过80字符")
+    private String destination;
+
+    /**
+     * 货物运输名称
+     * 必填,最大80字符
+     */
+    @NotBlank(message = "货物运输名称goodsName不能为空")
+    @Size(max = 80, message = "货物运输名称goodsName不能超过80字符")
+    private String goodsName;
+
+    public String getTransportTool() {
+        return transportTool;
+    }
+
+    public void setTransportTool(String transportTool) {
+        this.transportTool = transportTool;
+    }
+
+    public String getTransportToolNum() {
+        return transportToolNum;
+    }
+
+    public void setTransportToolNum(String transportToolNum) {
+        this.transportToolNum = transportToolNum;
+    }
+
+    public String getOrigin() {
+        return origin;
+    }
+
+    public void setOrigin(String origin) {
+        this.origin = origin;
+    }
+
+    public String getDestination() {
+        return destination;
+    }
+
+    public void setDestination(String destination) {
+        this.destination = destination;
+    }
+
+    public String getGoodsName() {
+        return goodsName;
+    }
+
+    public void setGoodsName(String goodsName) {
+        this.goodsName = goodsName;
+    }
+}

+ 44 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/HarvesterInfo.java

@@ -0,0 +1,44 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+import javax.validation.constraints.Size;
+
+/**
+ * 拖拉机和联合收割机信息子实体类
+ * 仅在开具拖拉机和联合收割机特定要素的数电发票(specificFactor为13时)需要传入
+ *
+ * 业务规则:
+ * 1. 当发票用于登记时:`engineCode`(发动机号码)和 `chassisCode`(底盘号/机架号)至少填写一个,且最多只能有一组;
+ *    同时发票明细只能有一行(或一对折扣明细行)。
+ * 2. 当发票不用于登记时:此模块必须为空,发票可以有多行明细。
+ */
+public class HarvesterInfo {
+
+    /**
+     * 发动机号码
+     * 选填,最大40字符;用于登记时与底盘号至少填一个
+     */
+    @Size(max = 40, message = "发动机号码长度不能超过40字符")
+    private String engineCode;
+
+    /**
+     * 底盘号/机架号
+     * 选填,最大40字符;用于登记时与发动机号码至少填一个
+     */
+    @Size(max = 40, message = "底盘号长度不能超过40字符")
+    private String chassisCode;
+
+    public String getEngineCode() {
+        return engineCode;
+    }
+
+    public void setEngineCode(String engineCode) {
+        this.engineCode = engineCode;
+    }
+
+    public String getChassisCode() {
+        return chassisCode;
+    }
+
+    public void setChassisCode(String chassisCode) {
+        this.chassisCode = chassisCode;
+    }
+}

+ 48 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownAdditional.java

@@ -0,0 +1,48 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+/**
+ * 附加要素信息列表项
+ * 按需传值,附加要素类型支持数值型、字符串型、日期型
+ */
+public class OMSDownAdditional {
+    /**
+     * 附加要素名称
+     * 非必填,40个字节
+     */
+    private String additionalName;
+
+    /**
+     * 附加要素类型
+     * 非必填,6个字节,可选值:number(数值型)、string(字符型)、date(日期型,格式yyyy-mm-dd)
+     */
+    private String additionalType;
+
+    /**
+     * 附加要素值
+     * 非必填,500个字节,根据附加要素类型传对应格式的值
+     */
+    private String additionalValue;
+
+    public String getAdditionalName() {
+        return additionalName;
+    }
+
+    public void setAdditionalName(String additionalName) {
+        this.additionalName = additionalName;
+    }
+
+    public String getAdditionalType() {
+        return additionalType;
+    }
+
+    public void setAdditionalType(String additionalType) {
+        this.additionalType = additionalType;
+    }
+
+    public String getAdditionalValue() {
+        return additionalValue;
+    }
+
+    public void setAdditionalValue(String additionalValue) {
+        this.additionalValue = additionalValue;
+    }
+}

+ 83 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownBuildingInfo.java

@@ -0,0 +1,83 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+/**
+ * 建筑服务信息实体类
+ * 用于发票中建筑服务相关的特定信息
+ */
+public class OMSDownBuildingInfo {
+    /**
+     * 建筑服务发生地
+     * 必填,100个字节
+     */
+    @NotBlank(message = "建筑服务发生地buildingAddress不能为空")
+    private String buildingAddress;
+
+    /**
+     * 详细地址
+     * 非必填,120个字节
+     */
+    private String detailedAddress;
+
+    /**
+     * 土地增值税项目编号
+     * 非必填,16个字节
+     */
+    private String landVatItemNo;
+
+    /**
+     * 建筑项目名称
+     * 必填,80个字节
+     */
+    @NotBlank(message = "建筑项目名称itemName不能为空")
+    private String itemName;
+
+    /**
+     * 跨地(市)标志
+     * 必填,1个字节,可选值:0-否,1-是
+     */
+    @NotBlank(message = "跨地(市)标志crossCityFlag不能为空")
+    @Pattern(regexp = "^[01]$", message = "跨地(市)标志crossCityFlag只能是0或1")
+    private String crossCityFlag;
+
+    public String getBuildingAddress() {
+        return buildingAddress;
+    }
+
+    public void setBuildingAddress(String buildingAddress) {
+        this.buildingAddress = buildingAddress;
+    }
+
+    public String getDetailedAddress() {
+        return detailedAddress;
+    }
+
+    public void setDetailedAddress(String detailedAddress) {
+        this.detailedAddress = detailedAddress;
+    }
+
+    public String getLandVatItemNo() {
+        return landVatItemNo;
+    }
+
+    public void setLandVatItemNo(String landVatItemNo) {
+        this.landVatItemNo = landVatItemNo;
+    }
+
+    public String getItemName() {
+        return itemName;
+    }
+
+    public void setItemName(String itemName) {
+        this.itemName = itemName;
+    }
+
+    public String getCrossCityFlag() {
+        return crossCityFlag;
+    }
+
+    public void setCrossCityFlag(String crossCityFlag) {
+        this.crossCityFlag = crossCityFlag;
+    }
+}

+ 84 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownGoodsTransport.java

@@ -0,0 +1,84 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+/**
+ * 货物运输信息实体类
+ * 用于发票中货物运输相关的特定信息
+ */
+public class OMSDownGoodsTransport {
+    /**
+     * 运输工具种类
+     * 必填,1个字节,可选值:1-铁路运输,2-公路运输,3-水路运输,4-航空运输,5-管道运输
+     */
+    @NotBlank(message = "运输工具种类transportTool不能为空")
+    @Pattern(regexp = "^[1-5]$", message = "运输工具种类transportTool只能是1-5的数字")
+    private String transportTool;
+
+    /**
+     * 运输工具牌号
+     * 必填,40个字节
+     */
+    @NotBlank(message = "运输工具牌号transportToolNum不能为空")
+    private String transportToolNum;
+
+    /**
+     * 起运地
+     * 必填,80个字节
+     */
+    @NotBlank(message = "起运地origin不能为空")
+    private String origin;
+
+    /**
+     * 到达地
+     * 必填,80个字节
+     */
+    @NotBlank(message = "到达地destination不能为空")
+    private String destination;
+
+    /**
+     * 货物运输名称
+     * 必填,80个字节
+     */
+    @NotBlank(message = "货物运输名称goodsName不能为空")
+    private String goodsName;
+
+    public String getTransportTool() {
+        return transportTool;
+    }
+
+    public void setTransportTool(String transportTool) {
+        this.transportTool = transportTool;
+    }
+
+    public String getTransportToolNum() {
+        return transportToolNum;
+    }
+
+    public void setTransportToolNum(String transportToolNum) {
+        this.transportToolNum = transportToolNum;
+    }
+
+    public String getOrigin() {
+        return origin;
+    }
+
+    public void setOrigin(String origin) {
+        this.origin = origin;
+    }
+
+    public String getDestination() {
+        return destination;
+    }
+
+    public void setDestination(String destination) {
+        this.destination = destination;
+    }
+
+    public String getGoodsName() {
+        return goodsName;
+    }
+
+    public void setGoodsName(String goodsName) {
+        this.goodsName = goodsName;
+    }
+}

+ 34 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownHarvesterInfo.java

@@ -0,0 +1,34 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+/**
+ * 拖拉机与联合收割机信息实体类
+ * 用于发票中拖拉机与联合收割机相关的特定信息
+ */
+public class OMSDownHarvesterInfo {
+    /**
+     * 发动机号码
+     * 非必填,40个字节
+     */
+    private String engineCode;
+
+    /**
+     * 底盘号
+     * 非必填,40个字节
+     */
+    private String chassisCode;
+
+    public String getEngineCode() {
+        return engineCode;
+    }
+
+    public void setEngineCode(String engineCode) {
+        this.engineCode = engineCode;
+    }
+
+    public String getChassisCode() {
+        return chassisCode;
+    }
+
+    public void setChassisCode(String chassisCode) {
+        this.chassisCode = chassisCode;
+    }
+}

+ 228 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownItem.java

@@ -0,0 +1,228 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+/**
+ * 发票下行明细信息实体类
+ * 发票核心明细项,与发票主表一一关联
+ */
+public class OMSDownItem {
+    /**
+     * 行号
+     * 必填,8个字节
+     */
+    @NotBlank(message = "行号lineCode不能为空")
+    private String lineCode;
+
+    /**
+     * 发票行性质
+     * 必填,2个字节
+     */
+    @NotBlank(message = "发票行性质lineType不能为空")
+    private String lineType;
+
+    /**
+     * 商品名称
+     * 必填,90个字节(一个汉字占2个字节)
+     */
+    @NotBlank(message = "商品名称goodsName不能为空")
+    private String goodsName;
+
+    /**
+     * 规格型号
+     * 非必填,40个字节
+     */
+    private String model;
+
+    /**
+     * 计量单位
+     * 非必填,20个字节
+     */
+    private String unit;
+
+    /**
+     * 数量
+     * 非必填,高精度数值,保留10位小数
+     */
+    private BigDecimal qty;
+
+    /**
+     * 单价
+     * 非必填,高精度数值,保留10位小数
+     */
+    private BigDecimal price;
+
+    /**
+     * 金额(不含税)
+     * 必填,高精度金额,保留2位小数
+     */
+    @NotNull(message = "金额amount不能为空")
+    private BigDecimal amount;
+
+    /**
+     * 税率
+     * 必填,高精度数值,保留3位小数
+     */
+    @NotNull(message = "税率taxrate不能为空")
+    private BigDecimal taxrate;
+
+    /**
+     * 税额
+     * 必填,高精度金额,保留2位小数
+     */
+    @NotNull(message = "税额tax不能为空")
+    private BigDecimal tax;
+
+    /**
+     * 含税金额
+     * 必填,高精度金额,保留2位小数
+     */
+    @NotNull(message = "含税金额taxamount不能为空")
+    private BigDecimal taxamount;
+
+    /**
+     * 优惠政策标识
+     * 非必填,2个字节
+     */
+    private String taxpre;
+
+    /**
+     * 税收分类编码
+     * 必填,19个字节
+     */
+    @NotBlank(message = "税收分类编码goodstaxno不能为空")
+    private String goodstaxno;
+
+    /**
+     * 扣除额
+     * 非必填,高精度金额,保留2位小数
+     */
+    private BigDecimal deduction;
+
+    /**
+     * 税收分类编码简称
+     * 非必填,100个字节
+     */
+    private String codeAbb;
+
+    public String getLineCode() {
+        return lineCode;
+    }
+
+    public void setLineCode(String lineCode) {
+        this.lineCode = lineCode;
+    }
+
+    public String getLineType() {
+        return lineType;
+    }
+
+    public void setLineType(String lineType) {
+        this.lineType = lineType;
+    }
+
+    public String getGoodsName() {
+        return goodsName;
+    }
+
+    public void setGoodsName(String goodsName) {
+        this.goodsName = goodsName;
+    }
+
+    public String getModel() {
+        return model;
+    }
+
+    public void setModel(String model) {
+        this.model = model;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public void setUnit(String unit) {
+        this.unit = unit;
+    }
+
+    public BigDecimal getQty() {
+        return qty;
+    }
+
+    public void setQty(BigDecimal qty) {
+        this.qty = qty;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public BigDecimal getTaxrate() {
+        return taxrate;
+    }
+
+    public void setTaxrate(BigDecimal taxrate) {
+        this.taxrate = taxrate;
+    }
+
+    public BigDecimal getTax() {
+        return tax;
+    }
+
+    public void setTax(BigDecimal tax) {
+        this.tax = tax;
+    }
+
+    public BigDecimal getTaxamount() {
+        return taxamount;
+    }
+
+    public void setTaxamount(BigDecimal taxamount) {
+        this.taxamount = taxamount;
+    }
+
+    public String getTaxpre() {
+        return taxpre;
+    }
+
+    public void setTaxpre(String taxpre) {
+        this.taxpre = taxpre;
+    }
+
+    public String getGoodstaxno() {
+        return goodstaxno;
+    }
+
+    public void setGoodstaxno(String goodstaxno) {
+        this.goodstaxno = goodstaxno;
+    }
+
+    public BigDecimal getDeduction() {
+        return deduction;
+    }
+
+    public void setDeduction(BigDecimal deduction) {
+        this.deduction = deduction;
+    }
+
+    public String getCodeAbb() {
+        return codeAbb;
+    }
+
+    public void setCodeAbb(String codeAbb) {
+        this.codeAbb = codeAbb;
+    }
+}

+ 68 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownJointBuyer.java

@@ -0,0 +1,68 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+
+/**
+ * 共同购买方集合实体类
+ * 用于发票中多方共同购买商品/服务的信息
+ */
+public class OMSDownJointBuyer {
+    /**
+     * 行号
+     * 必填,5个字节
+     */
+    @NotBlank(message = "行号lineCode不能为空")
+    private String lineCode;
+
+    /**
+     * 共同购买方名称
+     * 必填,100个字节
+     */
+    @NotBlank(message = "共同购买方名称buyerName不能为空")
+    private String buyerName;
+
+    /**
+     * 共同购买方证件类型
+     * 必填,3个字节,详见附录3证件类型代码表
+     */
+    @NotBlank(message = "共同购买方证件类型idCardType不能为空")
+    private String idCardType;
+
+    /**
+     * 共同购买方证件号码
+     * 必填,29个字节
+     */
+    @NotBlank(message = "共同购买方证件号码idCardNo不能为空")
+    private String idCardNo;
+
+    public String getLineCode() {
+        return lineCode;
+    }
+
+    public void setLineCode(String lineCode) {
+        this.lineCode = lineCode;
+    }
+
+    public String getBuyerName() {
+        return buyerName;
+    }
+
+    public void setBuyerName(String buyerName) {
+        this.buyerName = buyerName;
+    }
+
+    public String getIdCardType() {
+        return idCardType;
+    }
+
+    public void setIdCardType(String idCardType) {
+        this.idCardType = idCardType;
+    }
+
+    public String getIdCardNo() {
+        return idCardNo;
+    }
+
+    public void setIdCardNo(String idCardNo) {
+        this.idCardNo = idCardNo;
+    }
+}

+ 130 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownRealPropertyRentInfo.java

@@ -0,0 +1,130 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+/**
+ * 不动产租赁服务信息实体类
+ * 用于发票中不动产租赁相关的特定信息
+ */
+public class OMSDownRealPropertyRentInfo {
+    /**
+     * 不动产地址(省)
+     * 必填
+     */
+    @NotBlank(message = "不动产地址(省)realPropertyAddress不能为空")
+    private String realPropertyAddress;
+
+    /**
+     * 不动产地址(市)
+     * 非必填
+     */
+    private String realPropertyAddressCity;
+
+    /**
+     * 详细地址
+     * 非必填,120个字节
+     */
+    private String detailAddress;
+
+    /**
+     * 租赁开始日期
+     * 必填,10个字节,格式为yyyy-MM-dd或yyyy-MM-dd HH:mm
+     */
+    @NotBlank(message = "租赁开始日期rentStartDate不能为空")
+    @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}( \\d{2}:\\d{2})?$", message = "租赁开始日期格式必须为yyyy-MM-dd或yyyy-MM-dd HH:mm")
+    private String rentStartDate;
+
+    /**
+     * 租赁结束日期
+     * 必填,10个字节,格式为yyyy-MM-dd或yyyy-MM-dd HH:mm
+     */
+    @NotBlank(message = "租赁结束日期rentEndDate不能为空")
+    @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}( \\d{2}:\\d{2})?$", message = "租赁结束日期格式必须为yyyy-MM-dd或yyyy-MM-dd HH:mm")
+    private String rentEndDate;
+
+    /**
+     * 跨地(市)标志
+     * 必填,1个字节,可选值:0-否,1-是
+     */
+    @NotBlank(message = "跨地(市)标志crossCityFlag不能为空")
+    @Pattern(regexp = "^[01]$", message = "跨地(市)标志crossCityFlag只能是0或1")
+    private String crossCityFlag;
+
+    /**
+     * 产权证书/不动产权证号
+     * 非必填,40个字节
+     */
+    private String realPropertyCertificate;
+
+    /**
+     * 面积单位
+     * 必填,2个字节,可选值:1-平方千米,2-平方米,3-公顷,4-亩,5-hm²,6-km²,7-m²,8-米(铁路线与管道等使用)
+     */
+    @NotBlank(message = "面积单位unit不能为空")
+    @Pattern(regexp = "^[1-8]$", message = "面积单位unit只能是1-8的数字")
+    private String unit;
+
+    public String getRealPropertyAddress() {
+        return realPropertyAddress;
+    }
+
+    public void setRealPropertyAddress(String realPropertyAddress) {
+        this.realPropertyAddress = realPropertyAddress;
+    }
+
+    public String getRealPropertyAddressCity() {
+        return realPropertyAddressCity;
+    }
+
+    public void setRealPropertyAddressCity(String realPropertyAddressCity) {
+        this.realPropertyAddressCity = realPropertyAddressCity;
+    }
+
+    public String getDetailAddress() {
+        return detailAddress;
+    }
+
+    public void setDetailAddress(String detailAddress) {
+        this.detailAddress = detailAddress;
+    }
+
+    public String getRentStartDate() {
+        return rentStartDate;
+    }
+
+    public void setRentStartDate(String rentStartDate) {
+        this.rentStartDate = rentStartDate;
+    }
+
+    public String getRentEndDate() {
+        return rentEndDate;
+    }
+
+    public void setRentEndDate(String rentEndDate) {
+        this.rentEndDate = rentEndDate;
+    }
+
+    public String getCrossCityFlag() {
+        return crossCityFlag;
+    }
+
+    public void setCrossCityFlag(String crossCityFlag) {
+        this.crossCityFlag = crossCityFlag;
+    }
+
+    public String getRealPropertyCertificate() {
+        return realPropertyCertificate;
+    }
+
+    public void setRealPropertyCertificate(String realPropertyCertificate) {
+        this.realPropertyCertificate = realPropertyCertificate;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public void setUnit(String unit) {
+        this.unit = unit;
+    }
+}

+ 155 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownRealPropertySellInfo.java

@@ -0,0 +1,155 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import java.math.BigDecimal;
+
+/**
+ * 不动产销售信息实体类
+ * 用于发票中不动产销售相关的特定信息
+ */
+public class OMSDownRealPropertySellInfo {
+    /**
+     * 不动产单元代码/网签合同备案编号
+     * 非必填,28个字节
+     */
+    private String realPropertyContractNumber;
+
+    /**
+     * 不动产地址(省)
+     * 必填
+     */
+    @NotBlank(message = "不动产地址(省)realPropertyAddress不能为空")
+    private String realPropertyAddress;
+
+    /**
+     * 不动产地址(市)
+     * 非必填
+     */
+    private String realPropertyAddressCity;
+
+    /**
+     * 详细地址
+     * 非必填,120个字节
+     */
+    private String detailAddress;
+
+    /**
+     * 跨地(市)标志
+     * 必填,1个字节,可选值:0-否,1-是
+     */
+    @NotBlank(message = "跨地(市)标志crossCityFlag不能为空")
+    @Pattern(regexp = "^[01]$", message = "跨地(市)标志crossCityFlag只能是0或1")
+    private String crossCityFlag;
+
+    /**
+     * 土地增值税项目编号
+     * 非必填,18个字节
+     */
+    private String incrementTaxNumber;
+
+    /**
+     * 核定计税价格
+     * 非必填,20个字节
+     */
+    private BigDecimal price;
+
+    /**
+     * 实际成交税金额
+     * 非必填,20个字节
+     */
+    private BigDecimal taxAmount;
+
+    /**
+     * 产权证书/不动产权证号
+     * 非必填,40个字节
+     */
+    private String realPropertyCertificate;
+
+    /**
+     * 面积单位
+     * 必填,1个字节,可选值:1-平方千米,2-平方米,3-公顷,4-亩,5-hm²,6-km²,7-m²,8-米(铁路线与管道等使用)
+     */
+    @NotBlank(message = "面积单位unit不能为空")
+    @Pattern(regexp = "^[1-8]$", message = "面积单位unit只能是1-8的数字")
+    private String unit;
+
+    public String getRealPropertyContractNumber() {
+        return realPropertyContractNumber;
+    }
+
+    public void setRealPropertyContractNumber(String realPropertyContractNumber) {
+        this.realPropertyContractNumber = realPropertyContractNumber;
+    }
+
+    public String getRealPropertyAddress() {
+        return realPropertyAddress;
+    }
+
+    public void setRealPropertyAddress(String realPropertyAddress) {
+        this.realPropertyAddress = realPropertyAddress;
+    }
+
+    public String getRealPropertyAddressCity() {
+        return realPropertyAddressCity;
+    }
+
+    public void setRealPropertyAddressCity(String realPropertyAddressCity) {
+        this.realPropertyAddressCity = realPropertyAddressCity;
+    }
+
+    public String getDetailAddress() {
+        return detailAddress;
+    }
+
+    public void setDetailAddress(String detailAddress) {
+        this.detailAddress = detailAddress;
+    }
+
+    public String getCrossCityFlag() {
+        return crossCityFlag;
+    }
+
+    public void setCrossCityFlag(String crossCityFlag) {
+        this.crossCityFlag = crossCityFlag;
+    }
+
+    public String getIncrementTaxNumber() {
+        return incrementTaxNumber;
+    }
+
+    public void setIncrementTaxNumber(String incrementTaxNumber) {
+        this.incrementTaxNumber = incrementTaxNumber;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public BigDecimal getTaxAmount() {
+        return taxAmount;
+    }
+
+    public void setTaxAmount(BigDecimal taxAmount) {
+        this.taxAmount = taxAmount;
+    }
+
+    public String getRealPropertyCertificate() {
+        return realPropertyCertificate;
+    }
+
+    public void setRealPropertyCertificate(String realPropertyCertificate) {
+        this.realPropertyCertificate = realPropertyCertificate;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public void setUnit(String unit) {
+        this.unit = unit;
+    }
+}

+ 130 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownTravellerTransport.java

@@ -0,0 +1,130 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+
+/**
+ * 旅客运输信息实体类
+ * 用于发票中旅客运输相关的特定信息
+ */
+public class OMSDownTravellerTransport {
+    /**
+     * 出行人
+     * 必填,20个字节
+     */
+    @NotBlank(message = "出行人traveller不能为空")
+    private String traveller;
+
+    /**
+     * 出行日期
+     * 必填,10个字节,格式为yyyy-MM-dd
+     */
+    @NotBlank(message = "出行日期travelDate不能为空")
+    @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "出行日期格式必须为yyyy-MM-dd")
+    private String travelDate;
+
+    /**
+     * 出行人证件类型
+     * 必填,10个字节,可选值包括101-组织机构代码证、201-居民身份证等
+     */
+    @NotBlank(message = "出行人证件类型travellerCardType不能为空")
+    private String travellerCardType;
+
+    /**
+     * 出行人证件号码
+     * 必填,20个字节
+     */
+    @NotBlank(message = "出行人证件号码travellerCardNo不能为空")
+    private String travellerCardNo;
+
+    /**
+     * 出行地
+     * 必填,80个字节
+     */
+    @NotBlank(message = "出行地travelPlace不能为空")
+    private String travelPlace;
+
+    /**
+     * 到达地
+     * 必填,80个字节
+     */
+    @NotBlank(message = "到达地arrivePlace不能为空")
+    private String arrivePlace;
+
+    /**
+     * 交通工具类型
+     * 必填,2个字节,可选值:1-飞机,2-火车,3-长途汽车,4-公共交通,5-出租车,6-汽车,7-船舶,9-其他
+     */
+    @NotBlank(message = "交通工具类型vehicleType不能为空")
+    @Pattern(regexp = "^[1345679]$|^2$", message = "交通工具类型vehicleType只能是指定的数字")
+    private String vehicleType;
+
+    /**
+     * 交通工具等级
+     * 非必填,20个字节
+     */
+    private String vehicleLevel;
+
+    public String getTraveller() {
+        return traveller;
+    }
+
+    public void setTraveller(String traveller) {
+        this.traveller = traveller;
+    }
+
+    public String getTravelDate() {
+        return travelDate;
+    }
+
+    public void setTravelDate(String travelDate) {
+        this.travelDate = travelDate;
+    }
+
+    public String getTravellerCardType() {
+        return travellerCardType;
+    }
+
+    public void setTravellerCardType(String travellerCardType) {
+        this.travellerCardType = travellerCardType;
+    }
+
+    public String getTravellerCardNo() {
+        return travellerCardNo;
+    }
+
+    public void setTravellerCardNo(String travellerCardNo) {
+        this.travellerCardNo = travellerCardNo;
+    }
+
+    public String getTravelPlace() {
+        return travelPlace;
+    }
+
+    public void setTravelPlace(String travelPlace) {
+        this.travelPlace = travelPlace;
+    }
+
+    public String getArrivePlace() {
+        return arrivePlace;
+    }
+
+    public void setArrivePlace(String arrivePlace) {
+        this.arrivePlace = arrivePlace;
+    }
+
+    public String getVehicleType() {
+        return vehicleType;
+    }
+
+    public void setVehicleType(String vehicleType) {
+        this.vehicleType = vehicleType;
+    }
+
+    public String getVehicleLevel() {
+        return vehicleLevel;
+    }
+
+    public void setVehicleLevel(String vehicleLevel) {
+        this.vehicleLevel = vehicleLevel;
+    }
+}

+ 51 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownUsedCar.java

@@ -0,0 +1,51 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+
+/**
+ * 二手车发票关联的二手车销售统一发票信息实体类
+ * 用于关联二手车销售发票的数电/纸质发票信息
+ */
+public class OMSDownUsedCar {
+    /**
+     * 关联的二手车销售统一发票数电发票号码
+     * 必填,20个字节
+     */
+    @NotBlank(message = "关联的数电发票号码allEinVno不能为空")
+    private String allEinVno;
+
+    /**
+     * 关联的二手车销售统一发票纸质发票代码
+     * 非必填,12/10个字节
+     */
+    private String invcode;
+
+    /**
+     * 关联的二手车销售统一发票纸质发票号码
+     * 非必填,8个字节
+     */
+    private String invno;
+
+    public String getAllEinVno() {
+        return allEinVno;
+    }
+
+    public void setAllEinVno(String allEinVno) {
+        this.allEinVno = allEinVno;
+    }
+
+    public String getInvcode() {
+        return invcode;
+    }
+
+    public void setInvcode(String invcode) {
+        this.invcode = invcode;
+    }
+
+    public String getInvno() {
+        return invno;
+    }
+
+    public void setInvno(String invno) {
+        this.invno = invno;
+    }
+}

+ 173 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownUsedCarSales.java

@@ -0,0 +1,173 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+
+/**
+ * 二手车销售统一发票信息实体类
+ * 用于发票中二手车销售相关的特定信息
+ */
+public class OMSDownUsedCarSales {
+    /**
+     * 车牌照号
+     * 必填,20个字节
+     */
+    @NotBlank(message = "车牌照号carNo不能为空")
+    private String carNo;
+
+    /**
+     * 登记证号
+     * 必填,20个字节
+     */
+    @NotBlank(message = "登记证号registrationNo不能为空")
+    private String registrationNo;
+
+    /**
+     * 车辆识别代号/车架号码
+     * 必填,23个字节
+     */
+    @NotBlank(message = "车辆识别代号/车架号码frameNo不能为空")
+    private String frameNo;
+
+    /**
+     * 厂牌型号
+     * 必填,60个字节
+     */
+    @NotBlank(message = "厂牌型号brandModel不能为空")
+    private String brandModel;
+
+    /**
+     * 转入地车辆管理所名称
+     * 必填,80个字节
+     */
+    @NotBlank(message = "转入地车辆管理所名称transferInVehicleRegistrationOffice不能为空")
+    private String transferInVehicleRegistrationOffice;
+
+    /**
+     * 经营单位名称
+     * 必填,100个字节
+     */
+    @NotBlank(message = "经营单位名称businessEntityName不能为空")
+    private String businessEntityName;
+
+    /**
+     * 经营单位纳税人识别号
+     * 必填,20个字节
+     */
+    @NotBlank(message = "经营单位纳税人识别号businessEntityTaxno不能为空")
+    private String businessEntityTaxno;
+
+    /**
+     * 经营单位地址
+     * 必填,100个字节
+     */
+    @NotBlank(message = "经营单位地址businessEntityAddr不能为空")
+    private String businessEntityAddr;
+
+    /**
+     * 经营单位电话
+     * 必填,11个字节
+     */
+    @NotBlank(message = "经营单位电话businessEntityPhone不能为空")
+    private String businessEntityPhone;
+
+    /**
+     * 经营单位开户银行
+     * 必填,100个字节
+     */
+    @NotBlank(message = "经营单位开户银行businessEntityBank不能为空")
+    private String businessEntityBank;
+
+    /**
+     * 经营单位银行账号
+     * 必填,60个字节
+     */
+    @NotBlank(message = "经营单位银行账号businessEntityBankAccount不能为空")
+    private String businessEntityBankAccount;
+
+    public String getCarNo() {
+        return carNo;
+    }
+
+    public void setCarNo(String carNo) {
+        this.carNo = carNo;
+    }
+
+    public String getRegistrationNo() {
+        return registrationNo;
+    }
+
+    public void setRegistrationNo(String registrationNo) {
+        this.registrationNo = registrationNo;
+    }
+
+    public String getFrameNo() {
+        return frameNo;
+    }
+
+    public void setFrameNo(String frameNo) {
+        this.frameNo = frameNo;
+    }
+
+    public String getBrandModel() {
+        return brandModel;
+    }
+
+    public void setBrandModel(String brandModel) {
+        this.brandModel = brandModel;
+    }
+
+    public String getTransferInVehicleRegistrationOffice() {
+        return transferInVehicleRegistrationOffice;
+    }
+
+    public void setTransferInVehicleRegistrationOffice(String transferInVehicleRegistrationOffice) {
+        this.transferInVehicleRegistrationOffice = transferInVehicleRegistrationOffice;
+    }
+
+    public String getBusinessEntityName() {
+        return businessEntityName;
+    }
+
+    public void setBusinessEntityName(String businessEntityName) {
+        this.businessEntityName = businessEntityName;
+    }
+
+    public String getBusinessEntityTaxno() {
+        return businessEntityTaxno;
+    }
+
+    public void setBusinessEntityTaxno(String businessEntityTaxno) {
+        this.businessEntityTaxno = businessEntityTaxno;
+    }
+
+    public String getBusinessEntityAddr() {
+        return businessEntityAddr;
+    }
+
+    public void setBusinessEntityAddr(String businessEntityAddr) {
+        this.businessEntityAddr = businessEntityAddr;
+    }
+
+    public String getBusinessEntityPhone() {
+        return businessEntityPhone;
+    }
+
+    public void setBusinessEntityPhone(String businessEntityPhone) {
+        this.businessEntityPhone = businessEntityPhone;
+    }
+
+    public String getBusinessEntityBank() {
+        return businessEntityBank;
+    }
+
+    public void setBusinessEntityBank(String businessEntityBank) {
+        this.businessEntityBank = businessEntityBank;
+    }
+
+    public String getBusinessEntityBankAccount() {
+        return businessEntityBankAccount;
+    }
+
+    public void setBusinessEntityBankAccount(String businessEntityBankAccount) {
+        this.businessEntityBankAccount = businessEntityBankAccount;
+    }
+}

+ 180 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownVehicle.java

@@ -0,0 +1,180 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import java.math.BigDecimal;
+
+/**
+ * 机动车销售统一发票信息实体类
+ * 用于发票中机动车销售相关的特定信息
+ */
+public class OMSDownVehicle {
+    /**
+     * 车架号码
+     * 必填,17个字节
+     */
+    @NotBlank(message = "车架号码frameNo不能为空")
+    private String frameNo;
+
+    /**
+     * 产地
+     * 非必填,10个字节
+     */
+    private String origin;
+
+    /**
+     * 进口证明书号
+     * 非必填,16个字节
+     */
+    private String importCertificateNo;
+
+    /**
+     * 商检单号
+     * 非必填,60个字节
+     */
+    private String inspectionNo;
+
+    /**
+     * 吨位
+     * 非必填,10个字节
+     */
+    private BigDecimal tonnage;
+
+    /**
+     * 限乘人数
+     * 非必填,11个字节
+     */
+    private Integer limitNum;
+
+    /**
+     * 完税凭证号码
+     * 非必填,100个字节
+     */
+    private String taxPaymentReceiptNo;
+
+    /**
+     * 厂牌型号
+     * 非必填,100个字节
+     */
+    private String brandModel;
+
+    /**
+     * 合格证号
+     * 非必填,100个字节
+     */
+    private String passCertificate;
+
+    /**
+     * 发动机号码
+     * 非必填,100个字节
+     */
+    private String engineCode;
+
+    /**
+     * 生产企业名称
+     * 非必填,300个字节
+     */
+    private String productionCompanyName;
+
+    /**
+     * 制造或进口日期
+     * 非必填,10个字节,格式为yyyy-MM-dd
+     */
+    @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$", message = "制造或进口日期格式必须为yyyy-MM-dd")
+    private String vehicleProdDate;
+
+    public String getFrameNo() {
+        return frameNo;
+    }
+
+    public void setFrameNo(String frameNo) {
+        this.frameNo = frameNo;
+    }
+
+    public String getOrigin() {
+        return origin;
+    }
+
+    public void setOrigin(String origin) {
+        this.origin = origin;
+    }
+
+    public String getImportCertificateNo() {
+        return importCertificateNo;
+    }
+
+    public void setImportCertificateNo(String importCertificateNo) {
+        this.importCertificateNo = importCertificateNo;
+    }
+
+    public String getInspectionNo() {
+        return inspectionNo;
+    }
+
+    public void setInspectionNo(String inspectionNo) {
+        this.inspectionNo = inspectionNo;
+    }
+
+    public BigDecimal getTonnage() {
+        return tonnage;
+    }
+
+    public void setTonnage(BigDecimal tonnage) {
+        this.tonnage = tonnage;
+    }
+
+    public Integer getLimitNum() {
+        return limitNum;
+    }
+
+    public void setLimitNum(Integer limitNum) {
+        this.limitNum = limitNum;
+    }
+
+    public String getTaxPaymentReceiptNo() {
+        return taxPaymentReceiptNo;
+    }
+
+    public void setTaxPaymentReceiptNo(String taxPaymentReceiptNo) {
+        this.taxPaymentReceiptNo = taxPaymentReceiptNo;
+    }
+
+    public String getBrandModel() {
+        return brandModel;
+    }
+
+    public void setBrandModel(String brandModel) {
+        this.brandModel = brandModel;
+    }
+
+    public String getPassCertificate() {
+        return passCertificate;
+    }
+
+    public void setPassCertificate(String passCertificate) {
+        this.passCertificate = passCertificate;
+    }
+
+    public String getEngineCode() {
+        return engineCode;
+    }
+
+    public void setEngineCode(String engineCode) {
+        this.engineCode = engineCode;
+    }
+
+    public String getProductionCompanyName() {
+        return productionCompanyName;
+    }
+
+    public void setProductionCompanyName(String productionCompanyName) {
+        this.productionCompanyName = productionCompanyName;
+    }
+
+    public String getVehicleProdDate() {
+        return vehicleProdDate;
+    }
+
+    public void setVehicleProdDate(String vehicleProdDate) {
+        this.vehicleProdDate = vehicleProdDate;
+    }
+}

+ 106 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSDownVehicleVesselInfo.java

@@ -0,0 +1,106 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import java.math.BigDecimal;
+
+/**
+ * 代收车船税信息实体类
+ * 用于发票中代收车船税相关的特定信息
+ */
+public class OMSDownVehicleVesselInfo {
+    /**
+     * 保险单号
+     * 非必填,40个字节
+     */
+    private String policyNo;
+
+    /**
+     * 车牌号
+     * 非必填,40个字节
+     */
+    private String carNo;
+
+    /**
+     * 税款所属期
+     * 非必填,15个字节
+     */
+    private String taxPeriod;
+
+    /**
+     * 代收车船税金额
+     * 非必填,保留2位小数
+     */
+    private BigDecimal amount;
+
+    /**
+     * 滞纳金金额
+     * 非必填,保留2位小数
+     */
+    private BigDecimal lateFeeAmount;
+
+    /**
+     * 金额合计
+     * 非必填,保留2位小数
+     */
+    private BigDecimal totalamount;
+
+    /**
+     * 车辆识别代码/车架号码
+     * 非必填,17个字节
+     */
+    private String frameNo;
+
+    public String getPolicyNo() {
+        return policyNo;
+    }
+
+    public void setPolicyNo(String policyNo) {
+        this.policyNo = policyNo;
+    }
+
+    public String getCarNo() {
+        return carNo;
+    }
+
+    public void setCarNo(String carNo) {
+        this.carNo = carNo;
+    }
+
+    public String getTaxPeriod() {
+        return taxPeriod;
+    }
+
+    public void setTaxPeriod(String taxPeriod) {
+        this.taxPeriod = taxPeriod;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public BigDecimal getLateFeeAmount() {
+        return lateFeeAmount;
+    }
+
+    public void setLateFeeAmount(BigDecimal lateFeeAmount) {
+        this.lateFeeAmount = lateFeeAmount;
+    }
+
+    public BigDecimal getTotalamount() {
+        return totalamount;
+    }
+
+    public void setTotalamount(BigDecimal totalamount) {
+        this.totalamount = totalamount;
+    }
+
+    public String getFrameNo() {
+        return frameNo;
+    }
+
+    public void setFrameNo(String frameNo) {
+        this.frameNo = frameNo;
+    }
+}

+ 870 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceDown/OMSInvoiceDetailInfo.java

@@ -0,0 +1,870 @@
+package com.jeeplus.modules.workinvoice.entity.OMS.InvoiceDown;
+import com.google.common.collect.Lists;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * OMS发票详情主信息实体类
+ * 发票主表信息,与OMSDownItem发票明细表为一对多关系
+ */
+public class OMSInvoiceDetailInfo {
+    /**
+     * 发票代码
+     * 非必填,12个字节
+     */
+    private String invcode;
+
+    /**
+     * 发票号码
+     * 非必填,8个字节
+     */
+    private String invno;
+
+    /**
+     * 数电发票号码
+     * 必填,20个字节
+     */
+    @NotBlank(message = "数电发票号码allEinVno不能为空")
+    private String allEinVno;
+
+    /**
+     * 订单号
+     * 必填,50个字节
+     */
+    @NotBlank(message = "订单号orderno不能为空")
+    private String orderno;
+
+    /**
+     * 购买方名称
+     * 必填,100个字节
+     */
+    @NotBlank(message = "购买方名称buyerName不能为空")
+    private String buyerName;
+
+    /**
+     * 购买方纳税人识别号
+     * 非必填,20个字节
+     */
+    private String buyerTaxno;
+
+    /**
+     * 购买方地址
+     * 非必填,140个字节
+     */
+    private String buyerAddr;
+
+    /**
+     * 购买方电话
+     * 非必填,20个字节
+     */
+    private String buyerPhone;
+
+    /**
+     * 购买方开户行名称
+     * 非必填,50个字节
+     */
+    private String buyerBank;
+
+    /**
+     * 购买方开户行银行账号
+     * 非必填,50个字节
+     */
+    private String buyerBankaccount;
+
+    /**
+     * 购买方经办人
+     * 非必填,16个字节
+     */
+    private String buyerHandler;
+
+    /**
+     * 经办人身份证件号码
+     * 非必填,20个字节
+     */
+    private String buyerHandlerIdcardno;
+
+    /**
+     * 经办人联系电话
+     * 非必填,20个字节
+     */
+    private String buyerHandlerPhone;
+
+    /**
+     * 销售方名称
+     * 必填,100个字节
+     */
+    @NotBlank(message = "销售方名称sellerName不能为空")
+    private String sellerName;
+
+    /**
+     * 销售方纳税人识别号
+     * 非必填,20个字节
+     */
+    private String sellerTaxno;
+
+    /**
+     * 销售方地址
+     * 非必填,140个字节
+     */
+    private String sellerAddr;
+
+    /**
+     * 销售方电话
+     * 非必填,20个字节
+     */
+    private String sellerPhone;
+
+    /**
+     * 销售方开户行名称
+     * 非必填,50个字节
+     */
+    private String sellerBank;
+
+    /**
+     * 销售方开户行银行账号
+     * 非必填,50个字节
+     */
+    private String sellerBankaccount;
+
+    /**
+     * 订单来源
+     * 非必填,2个字节
+     */
+    private String orderSource;
+
+    /**
+     * 开票类型
+     * 必填,2个字节
+     */
+    @NotBlank(message = "开票类型invKind不能为空")
+    private String invKind;
+
+    /**
+     * 发票类型
+     * 必填,2个字节
+     */
+    @NotBlank(message = "发票类型invType不能为空")
+    private String invType;
+
+    /**
+     * 开票日期
+     * 必填
+     */
+    @NotNull(message = "开票日期invDate不能为空")
+    private Date invDate;
+
+    /**
+     * 原蓝票发票代码
+     * 非必填,12个字节
+     */
+    private String originalInvcode;
+
+    /**
+     * 原蓝票发票号码
+     * 非必填,20个字节
+     */
+    private String originalInvno;
+
+    /**
+     * 红字确认信息单编号
+     * 非必填,32个字节
+     */
+    private String redNo;
+
+    /**
+     * 红字发票标签
+     * 非必填,2个字节
+     */
+    private String redLabel;
+
+    /**
+     * 备注
+     * 非必填,200个字节
+     */
+    private String remarks;
+
+    /**
+     * 合计金额(不含税)
+     * 必填,保留2位小数
+     */
+    @NotNull(message = "合计金额(不含税)totalAmount不能为空")
+    private BigDecimal totalAmount;
+
+    /**
+     * 合计税额
+     * 必填,保留2位小数
+     */
+    @NotNull(message = "合计税额totalTax不能为空")
+    private BigDecimal totalTax;
+
+    /**
+     * 价税合计
+     * 必填,保留2位小数
+     */
+    @NotNull(message = "价税合计totalTaxamount不能为空")
+    private BigDecimal totalTaxamount;
+
+    /**
+     * 差额征税标签
+     * 非必填,2个字节
+     */
+    private String diffFlag;
+
+    /**
+     * 开票人
+     * 必填,8个字节
+     */
+    @NotBlank(message = "开票人drawer不能为空")
+    private String drawer;
+
+    /**
+     * 收款人
+     * 非必填,20个字节
+     */
+    private String payee;
+
+    /**
+     * 复核人
+     * 非必填,20个字节
+     */
+    private String checker;
+
+    /**
+     * 开票人证件号码
+     * 非必填,30个字节
+     */
+    private String drawerIdcardno;
+
+    /**
+     * 开票人证件类型
+     * 非必填,40个字节
+     */
+    private String drawerIdcardtype;
+
+    /**
+     * 收款银行名称
+     * 非必填,40个字节
+     */
+    private String payeeBank;
+
+    /**
+     * 收款银行账号
+     * 非必填,40个字节
+     */
+    private String payeeBankaccount;
+
+    /**
+     * 收购标识
+     * 非必填,2个字节
+     */
+    private String purchaseLabel;
+
+    /**
+     * 结算方式
+     * 非必填,2个字节
+     */
+    private String settlementType;
+
+    /**
+     * 特定要素
+     * 非必填,2个字节
+     */
+    private String specificFactor;
+
+    /**
+     * 发票状态
+     * 必填,1个字节
+     */
+    @NotBlank(message = "发票状态invStatus不能为空")
+    private String invStatus;
+
+    /**
+     * 发票pdf文件地址
+     * 非必填
+     */
+    private String pdfUrl;
+
+    /**
+     * 发票ofd文件地址
+     * 非必填
+     */
+    private String ofdUrl;
+
+    /**
+     * 发票xml文件地址
+     * 非必填
+     */
+    private String xmlUrl;
+
+    /**
+     * 失败原因
+     * 非必填
+     */
+    private String failCause;
+
+
+    // ======================== 以下所有关联实体 全部改为 List集合类型 ========================
+
+
+    /**
+     * 发票明细行信息集合
+     * 非必填 发票核心明细项 一对多关联
+     */
+    private List<OMSDownItem> items =  Lists.newArrayList();
+
+    /**
+     * 附加要素信息列表
+     * 非必填 发票备注展示附加要素信息
+     */
+    private List<OMSDownAdditional> additionalList =  Lists.newArrayList();
+
+    /**
+     * 建筑服务信息集合
+     * 非必填 建筑服务类发票专用
+     */
+    private List<OMSDownBuildingInfo> buildingInfo =  Lists.newArrayList();
+
+    /**
+     * 货物运输信息集合
+     * 非必填 货物运输类发票专用
+     */
+    private List<OMSDownGoodsTransport> goodsTransports =  Lists.newArrayList();
+
+    /**
+     * 旅客运输信息集合
+     * 非必填 旅客运输类发票专用
+     */
+    private List<OMSDownTravellerTransport> travellerTransports =  Lists.newArrayList();
+
+    /**
+     * 不动产租赁服务信息集合
+     * 非必填 不动产租赁类发票专用
+     */
+    private List<OMSDownRealPropertyRentInfo> realPropertyRentInfo =  Lists.newArrayList();
+
+    /**
+     * 不动产销售信息集合
+     * 非必填 不动产销售类发票专用
+     */
+    private List<OMSDownRealPropertySellInfo> realPropertySellInfo =  Lists.newArrayList();
+
+    /**
+     * 代收车船税信息集合
+     * 非必填 车船税代收类发票专用
+     */
+    private List<OMSDownVehicleVesselInfo> vehicleVesselInfo =  Lists.newArrayList();
+
+    /**
+     * 拖拉机与联合收割机信息集合
+     * 非必填 农机类发票专用
+     */
+    private List<OMSDownHarvesterInfo> harvesterInfo =  Lists.newArrayList();
+
+    /**
+     * 机动车销售统一发票信息集合
+     * 非必填 机动车销售类发票专用
+     */
+    private List<OMSDownVehicle> vehicle =  Lists.newArrayList();
+
+    /**
+     * 二手车销售统一发票信息集合
+     * 非必填 二手车销售类发票专用
+     */
+    private List<OMSDownUsedCarSales> usedCarSales =  Lists.newArrayList();
+
+    /**
+     * 二手车发票关联信息集合
+     * 非必填 二手车发票关联专用
+     */
+    private List<OMSDownUsedCar> usedCar =  Lists.newArrayList();
+
+    /**
+     * 共同购买方信息集合
+     * 非必填 多方共同购买时传入
+     */
+    private List<OMSDownJointBuyer> jointBuyer =  Lists.newArrayList();
+
+
+    public String getInvcode() {
+        return invcode;
+    }
+
+    public void setInvcode(String invcode) {
+        this.invcode = invcode;
+    }
+
+    public String getInvno() {
+        return invno;
+    }
+
+    public void setInvno(String invno) {
+        this.invno = invno;
+    }
+
+    public String getAllEinVno() {
+        return allEinVno;
+    }
+
+    public void setAllEinVno(String allEinVno) {
+        this.allEinVno = allEinVno;
+    }
+
+    public String getOrderno() {
+        return orderno;
+    }
+
+    public void setOrderno(String orderno) {
+        this.orderno = orderno;
+    }
+
+    public String getBuyerName() {
+        return buyerName;
+    }
+
+    public void setBuyerName(String buyerName) {
+        this.buyerName = buyerName;
+    }
+
+    public String getBuyerTaxno() {
+        return buyerTaxno;
+    }
+
+    public void setBuyerTaxno(String buyerTaxno) {
+        this.buyerTaxno = buyerTaxno;
+    }
+
+    public String getBuyerAddr() {
+        return buyerAddr;
+    }
+
+    public void setBuyerAddr(String buyerAddr) {
+        this.buyerAddr = buyerAddr;
+    }
+
+    public String getBuyerPhone() {
+        return buyerPhone;
+    }
+
+    public void setBuyerPhone(String buyerPhone) {
+        this.buyerPhone = buyerPhone;
+    }
+
+    public String getBuyerBank() {
+        return buyerBank;
+    }
+
+    public void setBuyerBank(String buyerBank) {
+        this.buyerBank = buyerBank;
+    }
+
+    public String getBuyerBankaccount() {
+        return buyerBankaccount;
+    }
+
+    public void setBuyerBankaccount(String buyerBankaccount) {
+        this.buyerBankaccount = buyerBankaccount;
+    }
+
+    public String getBuyerHandler() {
+        return buyerHandler;
+    }
+
+    public void setBuyerHandler(String buyerHandler) {
+        this.buyerHandler = buyerHandler;
+    }
+
+    public String getBuyerHandlerIdcardno() {
+        return buyerHandlerIdcardno;
+    }
+
+    public void setBuyerHandlerIdcardno(String buyerHandlerIdcardno) {
+        this.buyerHandlerIdcardno = buyerHandlerIdcardno;
+    }
+
+    public String getBuyerHandlerPhone() {
+        return buyerHandlerPhone;
+    }
+
+    public void setBuyerHandlerPhone(String buyerHandlerPhone) {
+        this.buyerHandlerPhone = buyerHandlerPhone;
+    }
+
+    public String getSellerName() {
+        return sellerName;
+    }
+
+    public void setSellerName(String sellerName) {
+        this.sellerName = sellerName;
+    }
+
+    public String getSellerTaxno() {
+        return sellerTaxno;
+    }
+
+    public void setSellerTaxno(String sellerTaxno) {
+        this.sellerTaxno = sellerTaxno;
+    }
+
+    public String getSellerAddr() {
+        return sellerAddr;
+    }
+
+    public void setSellerAddr(String sellerAddr) {
+        this.sellerAddr = sellerAddr;
+    }
+
+    public String getSellerPhone() {
+        return sellerPhone;
+    }
+
+    public void setSellerPhone(String sellerPhone) {
+        this.sellerPhone = sellerPhone;
+    }
+
+    public String getSellerBank() {
+        return sellerBank;
+    }
+
+    public void setSellerBank(String sellerBank) {
+        this.sellerBank = sellerBank;
+    }
+
+    public String getSellerBankaccount() {
+        return sellerBankaccount;
+    }
+
+    public void setSellerBankaccount(String sellerBankaccount) {
+        this.sellerBankaccount = sellerBankaccount;
+    }
+
+    public String getOrderSource() {
+        return orderSource;
+    }
+
+    public void setOrderSource(String orderSource) {
+        this.orderSource = orderSource;
+    }
+
+    public String getInvKind() {
+        return invKind;
+    }
+
+    public void setInvKind(String invKind) {
+        this.invKind = invKind;
+    }
+
+    public String getInvType() {
+        return invType;
+    }
+
+    public void setInvType(String invType) {
+        this.invType = invType;
+    }
+
+    public Date getInvDate() {
+        return invDate;
+    }
+
+    public void setInvDate(Date invDate) {
+        this.invDate = invDate;
+    }
+
+    public String getOriginalInvcode() {
+        return originalInvcode;
+    }
+
+    public void setOriginalInvcode(String originalInvcode) {
+        this.originalInvcode = originalInvcode;
+    }
+
+    public String getOriginalInvno() {
+        return originalInvno;
+    }
+
+    public void setOriginalInvno(String originalInvno) {
+        this.originalInvno = originalInvno;
+    }
+
+    public String getRedNo() {
+        return redNo;
+    }
+
+    public void setRedNo(String redNo) {
+        this.redNo = redNo;
+    }
+
+    public String getRedLabel() {
+        return redLabel;
+    }
+
+    public void setRedLabel(String redLabel) {
+        this.redLabel = redLabel;
+    }
+
+    public String getRemarks() {
+        return remarks;
+    }
+
+    public void setRemarks(String remarks) {
+        this.remarks = remarks;
+    }
+
+    public BigDecimal getTotalAmount() {
+        return totalAmount;
+    }
+
+    public void setTotalAmount(BigDecimal totalAmount) {
+        this.totalAmount = totalAmount;
+    }
+
+    public BigDecimal getTotalTax() {
+        return totalTax;
+    }
+
+    public void setTotalTax(BigDecimal totalTax) {
+        this.totalTax = totalTax;
+    }
+
+    public BigDecimal getTotalTaxamount() {
+        return totalTaxamount;
+    }
+
+    public void setTotalTaxamount(BigDecimal totalTaxamount) {
+        this.totalTaxamount = totalTaxamount;
+    }
+
+    public String getDiffFlag() {
+        return diffFlag;
+    }
+
+    public void setDiffFlag(String diffFlag) {
+        this.diffFlag = diffFlag;
+    }
+
+    public String getDrawer() {
+        return drawer;
+    }
+
+    public void setDrawer(String drawer) {
+        this.drawer = drawer;
+    }
+
+    public String getPayee() {
+        return payee;
+    }
+
+    public void setPayee(String payee) {
+        this.payee = payee;
+    }
+
+    public String getChecker() {
+        return checker;
+    }
+
+    public void setChecker(String checker) {
+        this.checker = checker;
+    }
+
+    public String getDrawerIdcardno() {
+        return drawerIdcardno;
+    }
+
+    public void setDrawerIdcardno(String drawerIdcardno) {
+        this.drawerIdcardno = drawerIdcardno;
+    }
+
+    public String getDrawerIdcardtype() {
+        return drawerIdcardtype;
+    }
+
+    public void setDrawerIdcardtype(String drawerIdcardtype) {
+        this.drawerIdcardtype = drawerIdcardtype;
+    }
+
+    public String getPayeeBank() {
+        return payeeBank;
+    }
+
+    public void setPayeeBank(String payeeBank) {
+        this.payeeBank = payeeBank;
+    }
+
+    public String getPayeeBankaccount() {
+        return payeeBankaccount;
+    }
+
+    public void setPayeeBankaccount(String payeeBankaccount) {
+        this.payeeBankaccount = payeeBankaccount;
+    }
+
+    public String getPurchaseLabel() {
+        return purchaseLabel;
+    }
+
+    public void setPurchaseLabel(String purchaseLabel) {
+        this.purchaseLabel = purchaseLabel;
+    }
+
+    public String getSettlementType() {
+        return settlementType;
+    }
+
+    public void setSettlementType(String settlementType) {
+        this.settlementType = settlementType;
+    }
+
+    public String getSpecificFactor() {
+        return specificFactor;
+    }
+
+    public void setSpecificFactor(String specificFactor) {
+        this.specificFactor = specificFactor;
+    }
+
+    public String getInvStatus() {
+        return invStatus;
+    }
+
+    public void setInvStatus(String invStatus) {
+        this.invStatus = invStatus;
+    }
+
+    public String getPdfUrl() {
+        return pdfUrl;
+    }
+
+    public void setPdfUrl(String pdfUrl) {
+        this.pdfUrl = pdfUrl;
+    }
+
+    public String getOfdUrl() {
+        return ofdUrl;
+    }
+
+    public void setOfdUrl(String ofdUrl) {
+        this.ofdUrl = ofdUrl;
+    }
+
+    public String getXmlUrl() {
+        return xmlUrl;
+    }
+
+    public void setXmlUrl(String xmlUrl) {
+        this.xmlUrl = xmlUrl;
+    }
+
+    public String getFailCause() {
+        return failCause;
+    }
+
+    public void setFailCause(String failCause) {
+        this.failCause = failCause;
+    }
+
+    public List<OMSDownItem> getItems() {
+        return items;
+    }
+
+    public void setItems(List<OMSDownItem> items) {
+        this.items = items;
+    }
+
+    public List<OMSDownAdditional> getAdditionalList() {
+        return additionalList;
+    }
+
+    public void setAdditionalList(List<OMSDownAdditional> additionalList) {
+        this.additionalList = additionalList;
+    }
+
+    public List<OMSDownBuildingInfo> getBuildingInfo() {
+        return buildingInfo;
+    }
+
+    public void setBuildingInfo(List<OMSDownBuildingInfo> buildingInfo) {
+        this.buildingInfo = buildingInfo;
+    }
+
+    public List<OMSDownGoodsTransport> getGoodsTransports() {
+        return goodsTransports;
+    }
+
+    public void setGoodsTransports(List<OMSDownGoodsTransport> goodsTransports) {
+        this.goodsTransports = goodsTransports;
+    }
+
+    public List<OMSDownTravellerTransport> getTravellerTransports() {
+        return travellerTransports;
+    }
+
+    public void setTravellerTransports(List<OMSDownTravellerTransport> travellerTransports) {
+        this.travellerTransports = travellerTransports;
+    }
+
+    public List<OMSDownRealPropertyRentInfo> getRealPropertyRentInfo() {
+        return realPropertyRentInfo;
+    }
+
+    public void setRealPropertyRentInfo(List<OMSDownRealPropertyRentInfo> realPropertyRentInfo) {
+        this.realPropertyRentInfo = realPropertyRentInfo;
+    }
+
+    public List<OMSDownRealPropertySellInfo> getRealPropertySellInfo() {
+        return realPropertySellInfo;
+    }
+
+    public void setRealPropertySellInfo(List<OMSDownRealPropertySellInfo> realPropertySellInfo) {
+        this.realPropertySellInfo = realPropertySellInfo;
+    }
+
+    public List<OMSDownVehicleVesselInfo> getVehicleVesselInfo() {
+        return vehicleVesselInfo;
+    }
+
+    public void setVehicleVesselInfo(List<OMSDownVehicleVesselInfo> vehicleVesselInfo) {
+        this.vehicleVesselInfo = vehicleVesselInfo;
+    }
+
+    public List<OMSDownHarvesterInfo> getHarvesterInfo() {
+        return harvesterInfo;
+    }
+
+    public void setHarvesterInfo(List<OMSDownHarvesterInfo> harvesterInfo) {
+        this.harvesterInfo = harvesterInfo;
+    }
+
+    public List<OMSDownVehicle> getVehicle() {
+        return vehicle;
+    }
+
+    public void setVehicle(List<OMSDownVehicle> vehicle) {
+        this.vehicle = vehicle;
+    }
+
+    public List<OMSDownUsedCarSales> getUsedCarSales() {
+        return usedCarSales;
+    }
+
+    public void setUsedCarSales(List<OMSDownUsedCarSales> usedCarSales) {
+        this.usedCarSales = usedCarSales;
+    }
+
+    public List<OMSDownUsedCar> getUsedCar() {
+        return usedCar;
+    }
+
+    public void setUsedCar(List<OMSDownUsedCar> usedCar) {
+        this.usedCar = usedCar;
+    }
+
+    public List<OMSDownJointBuyer> getJointBuyer() {
+        return jointBuyer;
+    }
+
+    public void setJointBuyer(List<OMSDownJointBuyer> jointBuyer) {
+        this.jointBuyer = jointBuyer;
+    }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1467 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/InvoiceOMSImportInfo.java


+ 78 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/JointBuyer.java

@@ -0,0 +1,78 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 共同购买方集合子实体类
+ * 仅在不动产销售特定要素(specificFactor为05)且多方共同购买标签(jointBuyerFlag为Y)时需传值
+ * 不可超过100行,行号从1开始升序
+ */
+public class JointBuyer {
+
+    /**
+     * 行号
+     * 必填,从1开始依次增加,最大5位字符
+     */
+    @NotBlank(message = "共同购买方行号lineCode不能为空")
+    @Pattern(regexp = "^[1-9]\\d{0,4}$",
+            message = "行号必须从1开始依次增加,且为1-5位数字")
+    private String lineCode;
+
+    /**
+     * 共同购买方名称
+     * 必填,最大100字符
+     */
+    @NotBlank(message = "共同购买方名称buyerName不能为空")
+    @Size(max = 100, message = "共同购买方名称长度不能超过100字符")
+    private String buyerName;
+
+    /**
+     * 共同购买方证件类型
+     * 必填,最大3字符,详见附录3身份证件类型代码表
+     */
+    @NotBlank(message = "共同购买方证件类型idCardType不能为空")
+    @Size(max = 3, message = "证件类型长度不能超过3字符")
+    private String idCardType;
+
+    /**
+     * 共同购买方证件号码
+     * 必填,最大20字符
+     */
+    @NotBlank(message = "共同购买方证件号码idCardNo不能为空")
+    @Size(max = 20, message = "证件号码长度不能超过20字符")
+    private String idCardNo;
+
+    public String getLineCode() {
+        return lineCode;
+    }
+
+    public void setLineCode(String lineCode) {
+        this.lineCode = lineCode;
+    }
+
+    public String getBuyerName() {
+        return buyerName;
+    }
+
+    public void setBuyerName(String buyerName) {
+        this.buyerName = buyerName;
+    }
+
+    public String getIdCardType() {
+        return idCardType;
+    }
+
+    public void setIdCardType(String idCardType) {
+        this.idCardType = idCardType;
+    }
+
+    public String getIdCardNo() {
+        return idCardNo;
+    }
+
+    public void setIdCardNo(String idCardNo) {
+        this.idCardNo = idCardNo;
+    }
+}

+ 131 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/OMSAccessTokenInfo.java

@@ -0,0 +1,131 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+
+import com.alibaba.fastjson.annotation.JSONField;
+
+import java.io.Serializable;
+
+public class OMSAccessTokenInfo implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    @JSONField(name = "appId")
+    private String appId;
+
+    @JSONField(name = "appKey")
+    private String appKey;
+
+    @JSONField(name = "exchangeId")
+    private String exchangeId;
+
+    @JSONField(name = "accessToken")
+    private String accessToken;
+
+    @JSONField(name = "result")
+    private Result result;
+
+    @JSONField(name = "data")
+    private Object data;
+
+    // 无参构造方法 (JSON反序列化必须有)
+    public OMSAccessTokenInfo() {
+    }
+
+    // ==================== 所有字段的getter & setter ====================
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getAppKey() {
+        return appKey;
+    }
+
+    public void setAppKey(String appKey) {
+        this.appKey = appKey;
+    }
+
+    public String getExchangeId() {
+        return exchangeId;
+    }
+
+    public void setExchangeId(String exchangeId) {
+        this.exchangeId = exchangeId;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+    }
+
+    public Result getResult() {
+        return result;
+    }
+
+    public void setResult(Result result) {
+        this.result = result;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+
+    // toString 方便日志打印和调试
+    @Override
+    public String toString() {
+        return "OMSAccessTokenInfo{" +
+                "appId='" + appId + '\'' +
+                ", appKey='" + appKey + '\'' +
+                ", exchangeId='" + exchangeId + '\'' +
+                ", accessToken='" + accessToken + '\'' +
+                ", result=" + result +
+                ", data=" + data +
+                '}';
+    }
+
+    // ==================== 嵌套的Result子实体类 ====================
+    public static class Result implements Serializable {
+        private static final long serialVersionUID = 1L;
+
+        @JSONField(name = "code")
+        private String code;
+
+        @JSONField(name = "message")
+        private String message;
+
+        public Result() {
+        }
+
+        public String getCode() {
+            return code;
+        }
+
+        public void setCode(String code) {
+            this.code = code;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public void setMessage(String message) {
+            this.message = message;
+        }
+
+        @Override
+        public String toString() {
+            return "Result{" +
+                    "code='" + code + '\'' +
+                    ", message='" + message + '\'' +
+                    '}';
+        }
+    }
+}

+ 75 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/OMSInvoiceResultDownloadData.java

@@ -0,0 +1,75 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+import com.alibaba.fastjson.annotation.JSONField;
+import java.io.Serializable;
+
+/**
+ * OMS发票结果下载数据实体类
+ * 字段对应表格定义:组织代码、订单号、是否查询明细
+ */
+public class OMSInvoiceResultDownloadData implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 组织代码(所属组织唯一标识,长度20,必填)
+     */
+    @JSONField(name = "deptCode")
+    private String deptCode;
+
+    /**
+     * 订单号(订单唯一标识,长度50,必填)
+     */
+    @JSONField(name = "orderno")
+    private String orderno;
+
+    /**
+     * 是否查询明细(0:否,1:是,长度1,必填)
+     */
+    @JSONField(name = "isDetail")
+    private String isDetail;
+
+    // 无参构造(JSON反序列化必备)
+    public OMSInvoiceResultDownloadData() {
+    }
+
+    // 全参构造(方便快速创建对象)
+    public OMSInvoiceResultDownloadData(String deptCode, String orderno, String isDetail) {
+        this.deptCode = deptCode;
+        this.orderno = orderno;
+        this.isDetail = isDetail;
+    }
+
+    // ==================== 字段对应的getter & setter ====================
+    public String getDeptCode() {
+        return deptCode;
+    }
+
+    public void setDeptCode(String deptCode) {
+        this.deptCode = deptCode;
+    }
+
+    public String getOrderno() {
+        return orderno;
+    }
+
+    public void setOrderno(String orderno) {
+        this.orderno = orderno;
+    }
+
+    public String getIsDetail() {
+        return isDetail;
+    }
+
+    public void setIsDetail(String isDetail) {
+        this.isDetail = isDetail;
+    }
+
+    // toString方法(方便日志打印/调试)
+    @Override
+    public String toString() {
+        return "OMSInvoiceResultDownloadData{" +
+                "deptCode='" + deptCode + '\'' +
+                ", orderno='" + orderno + '\'' +
+                ", isDetail='" + isDetail + '\'' +
+                '}';
+    }
+}

+ 389 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/OrderItem.java

@@ -0,0 +1,389 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Digits;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import java.math.BigDecimal;
+
+/**
+ * 订单明细表信息
+ */
+public class OrderItem {
+
+    /**
+     * 行号
+     * 明细行行号;仅作为唯一标识使用;最终发票中的明细顺序以请求报文中的明细顺序为准
+     * 非空必填,字符串类型
+     */
+    @NotBlank(message = "明细行号lineCode不能为空")
+    private String lineCode;
+
+    /**
+     * 对应蓝字发票明细行号
+     * 红票必传 蓝字票非必填
+     */
+    private String oriLineCode;
+
+    /**
+     * 发票行性质
+     * 00:正常行 01:折扣行 02:被折扣行
+     * 必填 + 只能传指定的3个值
+     */
+    @NotBlank(message = "发票行性质lineType不能为空")
+    @Pattern(regexp = "^00|01|02$", message = "发票行性质lineType只能传入:00(正常行)、01(折扣行)、02(被折扣行)")
+    private String lineType;
+
+    /**
+     * 商品代码
+     * 商品唯一标识为商品代码时必传
+     */
+    private String goodsCode;
+
+    /**
+     * 商品名称
+     * 商品唯一标识包含商品名称时必传 | 业务核心字段 非空必填
+     */
+    @NotBlank(message = "商品名称goodsName不能为空")
+    private String goodsName;
+
+    /**
+     * 规格型号
+     * 商品唯一标识包含规格型号时必传;特定要素为14、15、31时,此字段必填,填写车辆识别代号/车架号码
+     */
+    private String model;
+
+    /**
+     * 计量单位
+     * 特定要素为14、15、31时,填写“辆”
+     */
+    private String unit;
+
+    /**
+     * 数量
+     * 数字,最大12位整数,小数最多4位;红字发票时为负数;特定要素为14、15、31时,填写1
+     * 必填 + 数值精度严格校验
+     */
+    @NotNull(message = "商品数量qty不能为空")
+    @Digits(integer = 12, fraction = 4, message = "数量qty格式错误,最大12位整数,最多保留4位小数")
+    private BigDecimal qty;
+
+    /**
+     * 单价
+     * 数字,最大12位整数,小数最多4位
+     * 必填 + 数值精度严格校验
+     */
+    @NotNull(message = "商品单价price不能为空")
+    @Digits(integer = 12, fraction = 4, message = "单价price格式错误,最大12位整数,最多保留4位小数")
+    private BigDecimal price;
+
+    /**
+     * 单价含税标识
+     * 0:不含税 1:含税;传0时price字段代表不含税单价,传1时price字段代表含税单价;默认为0 不含税
+     * 必填 + 只能传0/1
+     */
+    @NotBlank(message = "单价含税标识priceTaxFlag不能为空")
+    @Pattern(regexp = "^0|1$", message = "单价含税标识priceTaxFlag只能传入:0(不含税)、1(含税)")
+    private String priceTaxFlag;
+
+    /**
+     * 税率
+     * 必填 + 小数点后三位有效数字
+     */
+    @NotNull(message = "税率taxrate不能为空")
+    @Digits(integer = 3, fraction = 3, message = "税率taxrate格式错误,最多3位整数,保留3位小数")
+    private BigDecimal taxrate;
+
+    /**
+     * 金额
+     * 不可为空 + 小数点后两位有效数字
+     */
+    @NotNull(message = "金额amount不能为空")
+    @Digits(integer = 12, fraction = 2, message = "金额amount格式错误,最多12位整数,保留2位小数")
+    private BigDecimal amount;
+
+    /**
+     * 税额
+     * 不可为空 + 小数点后两位有效数字
+     */
+    @NotNull(message = "税额tax不能为空")
+    @Digits(integer = 12, fraction = 2, message = "税额tax格式错误,最多12位整数,保留2位小数")
+    private BigDecimal tax;
+
+    /**
+     * 含税金额
+     * 小数点后两位有效数字
+     */
+    @Digits(integer = 12, fraction = 2, message = "含税金额taxamount格式错误,最多12位整数,保留2位小数")
+    private BigDecimal taxamount;
+
+    /**
+     * 优惠政策标识
+     * 01:简易征收;02:稀土产品;03:免税 等指定值,非必填但传值则校验合法性
+     */
+    @Pattern(regexp = "^$|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18",
+            message = "优惠政策标识taxpre传入值不在合法范围内")
+    private String taxpre;
+
+    /**
+     * 税收分类编码
+     * 商品唯一标识包含税收分类编码时必传
+     */
+    private String goodstaxno;
+
+    /**
+     * 扣除额
+     * 保留两位小数
+     */
+    @Digits(integer = 12, fraction = 2, message = "扣除额deduction格式错误,最多12位整数,保留2位小数")
+    private BigDecimal deduction;
+
+    /**
+     * 煤炭种类
+     * 煤炭商品必填; 0100:政府保供煤 0201:长协煤等,非必填但传值则校验合法性
+     */
+    @Pattern(regexp = "^$|0100|0201|0202|0203|0204|0300", message = "煤炭种类coalType传入值不在合法范围内")
+    private String coalType;
+
+    /**
+     * 明细备用1-10 预留字段
+     */
+    private String itemspare1;
+    private String itemspare2;
+    private String itemspare3;
+    private String itemspare4;
+    private String itemspare5;
+    private String itemspare6;
+    private String itemspare7;
+    private String itemspare8;
+    private String itemspare9;
+    private String itemspare10;
+
+
+
+    public String getLineCode() {
+        return lineCode;
+    }
+
+    public void setLineCode(String lineCode) {
+        this.lineCode = lineCode;
+    }
+
+    public String getOriLineCode() {
+        return oriLineCode;
+    }
+
+    public void setOriLineCode(String oriLineCode) {
+        this.oriLineCode = oriLineCode;
+    }
+
+    public String getLineType() {
+        return lineType;
+    }
+
+    public void setLineType(String lineType) {
+        this.lineType = lineType;
+    }
+
+    public String getGoodsCode() {
+        return goodsCode;
+    }
+
+    public void setGoodsCode(String goodsCode) {
+        this.goodsCode = goodsCode;
+    }
+
+    public String getGoodsName() {
+        return goodsName;
+    }
+
+    public void setGoodsName(String goodsName) {
+        this.goodsName = goodsName;
+    }
+
+    public String getModel() {
+        return model;
+    }
+
+    public void setModel(String model) {
+        this.model = model;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public void setUnit(String unit) {
+        this.unit = unit;
+    }
+
+    public BigDecimal getQty() {
+        return qty;
+    }
+
+    public void setQty(BigDecimal qty) {
+        this.qty = qty;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public String getPriceTaxFlag() {
+        return priceTaxFlag;
+    }
+
+    public void setPriceTaxFlag(String priceTaxFlag) {
+        this.priceTaxFlag = priceTaxFlag;
+    }
+
+    public BigDecimal getTaxrate() {
+        return taxrate;
+    }
+
+    public void setTaxrate(BigDecimal taxrate) {
+        this.taxrate = taxrate;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public BigDecimal getTax() {
+        return tax;
+    }
+
+    public void setTax(BigDecimal tax) {
+        this.tax = tax;
+    }
+
+    public BigDecimal getTaxamount() {
+        return taxamount;
+    }
+
+    public void setTaxamount(BigDecimal taxamount) {
+        this.taxamount = taxamount;
+    }
+
+    public String getTaxpre() {
+        return taxpre;
+    }
+
+    public void setTaxpre(String taxpre) {
+        this.taxpre = taxpre;
+    }
+
+    public String getGoodstaxno() {
+        return goodstaxno;
+    }
+
+    public void setGoodstaxno(String goodstaxno) {
+        this.goodstaxno = goodstaxno;
+    }
+
+    public BigDecimal getDeduction() {
+        return deduction;
+    }
+
+    public void setDeduction(BigDecimal deduction) {
+        this.deduction = deduction;
+    }
+
+    public String getCoalType() {
+        return coalType;
+    }
+
+    public void setCoalType(String coalType) {
+        this.coalType = coalType;
+    }
+
+    public String getItemspare1() {
+        return itemspare1;
+    }
+
+    public void setItemspare1(String itemspare1) {
+        this.itemspare1 = itemspare1;
+    }
+
+    public String getItemspare2() {
+        return itemspare2;
+    }
+
+    public void setItemspare2(String itemspare2) {
+        this.itemspare2 = itemspare2;
+    }
+
+    public String getItemspare3() {
+        return itemspare3;
+    }
+
+    public void setItemspare3(String itemspare3) {
+        this.itemspare3 = itemspare3;
+    }
+
+    public String getItemspare4() {
+        return itemspare4;
+    }
+
+    public void setItemspare4(String itemspare4) {
+        this.itemspare4 = itemspare4;
+    }
+
+    public String getItemspare5() {
+        return itemspare5;
+    }
+
+    public void setItemspare5(String itemspare5) {
+        this.itemspare5 = itemspare5;
+    }
+
+    public String getItemspare6() {
+        return itemspare6;
+    }
+
+    public void setItemspare6(String itemspare6) {
+        this.itemspare6 = itemspare6;
+    }
+
+    public String getItemspare7() {
+        return itemspare7;
+    }
+
+    public void setItemspare7(String itemspare7) {
+        this.itemspare7 = itemspare7;
+    }
+
+    public String getItemspare8() {
+        return itemspare8;
+    }
+
+    public void setItemspare8(String itemspare8) {
+        this.itemspare8 = itemspare8;
+    }
+
+    public String getItemspare9() {
+        return itemspare9;
+    }
+
+    public void setItemspare9(String itemspare9) {
+        this.itemspare9 = itemspare9;
+    }
+
+    public String getItemspare10() {
+        return itemspare10;
+    }
+
+    public void setItemspare10(String itemspare10) {
+        this.itemspare10 = itemspare10;
+    }
+}

+ 163 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/RealPropertyRentInfo.java

@@ -0,0 +1,163 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 不动产经营租赁服务特定要素子实体类
+ * 仅在开具不动产经营租赁服务特定要素的数电发票(specificFactor为06时)需要传入
+ * 订单明细的规格型号和计量单位不能有值;特定要素信息需与订单明细行数量、顺序保持一致
+ */
+public class RealPropertyRentInfo {
+
+    /**
+     * 不动产地址(省)
+     * 必填,需以省、自治区、直辖市、北京市、天津市、上海市、重庆市任意一个关键词结尾
+     */
+    @NotBlank(message = "不动产地址(省)realPropertyAddress不能为空")
+    @Size(max = 100, message = "不动产地址(省)不能超过100字符")
+    @Pattern(regexp = ".+(省|自治区|直辖市|北京市|天津市|上海市|重庆市)$",
+            message = "不动产地址(省)必须以省、自治区、直辖市、北京市、天津市、上海市、重庆市任意一个关键词结尾")
+    private String realPropertyAddress;
+
+    /**
+     * 不动产地址(市)
+     * 需以市、盟、自治州、地区、区、县任意一个关键词结尾
+     */
+    @Size(max = 20, message = "不动产地址(市)不能超过20字符")
+    @Pattern(regexp = "^$|.+(市|盟|自治州|地区|区|县)$",
+            message = "不动产地址(市)必须以市、盟、自治州、地区、区、县任意一个关键词结尾")
+    private String realPropertyAddressCity;
+
+    /**
+     * 详细地址
+     * 必填,需包含街、路、村、乡、镇、道、巷、号关键词
+     */
+    @NotBlank(message = "详细地址detailAddress不能为空")
+    @Size(max = 120, message = "详细地址不能超过120字符")
+    @Pattern(regexp = ".+(街|路|村|乡|镇|道|巷|号)",
+            message = "详细地址必须包含街、路、村、乡、镇、道、巷、号任意一个关键词")
+    private String detailAddress;
+
+    /**
+     * 租赁开始日期
+     * 必填,格式为yyyy-MM-dd HH:mm;不能晚于租赁结束日期
+     */
+    @NotBlank(message = "租赁开始日期rentStartDate不能为空")
+    @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$",
+            message = "租赁开始日期格式错误,应为yyyy-MM-dd HH:mm")
+    private String rentStartDate;
+
+    /**
+     * 租赁结束日期
+     * 必填,格式为yyyy-MM-dd HH:mm;不能早于租赁开始日期
+     */
+    @NotBlank(message = "租赁结束日期rentEndDate不能为空")
+    @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$",
+            message = "租赁结束日期格式错误,应为yyyy-MM-dd HH:mm")
+    private String rentEndDate;
+
+    /**
+     * 跨地(市)标志
+     * 必填,0:否 1:是
+     */
+    @NotBlank(message = "跨地(市)标志crossCityFlag不能为空")
+    @Pattern(regexp = "^0|1$", message = "跨地(市)标志只能传入:0(否)、1(是)")
+    private String crossCityFlag;
+
+    /**
+     * 产权证书/不动产权证号
+     * 最大40字符
+     */
+    @Size(max = 40, message = "产权证书/不动产权证号不能超过40字符")
+    private String realPropertyCertificate;
+
+    /**
+     * 面积单位
+     * 必填,可选值:1(平方千米)、2(平方米)、3(公顷)、4(亩)、5(hm²)、6(km²)、7(m²)、8(米)
+     */
+    @NotBlank(message = "面积单位unit不能为空")
+    @Pattern(regexp = "^1|2|3|4|5|6|7|8$",
+            message = "面积单位只能传入:1(平方千米)、2(平方米)、3(公顷)、4(亩)、5(hm²)、6(km²)、7(m²)、8(米)")
+    private String unit;
+
+    /**
+     * 车牌号
+     * 税收分类编码为3040502020200000000时必填,最大20字符
+     */
+    @Size(max = 20, message = "车牌号不能超过20字符")
+    private String carNo;
+
+    public String getRealPropertyAddress() {
+        return realPropertyAddress;
+    }
+
+    public void setRealPropertyAddress(String realPropertyAddress) {
+        this.realPropertyAddress = realPropertyAddress;
+    }
+
+    public String getRealPropertyAddressCity() {
+        return realPropertyAddressCity;
+    }
+
+    public void setRealPropertyAddressCity(String realPropertyAddressCity) {
+        this.realPropertyAddressCity = realPropertyAddressCity;
+    }
+
+    public String getDetailAddress() {
+        return detailAddress;
+    }
+
+    public void setDetailAddress(String detailAddress) {
+        this.detailAddress = detailAddress;
+    }
+
+    public String getRentStartDate() {
+        return rentStartDate;
+    }
+
+    public void setRentStartDate(String rentStartDate) {
+        this.rentStartDate = rentStartDate;
+    }
+
+    public String getRentEndDate() {
+        return rentEndDate;
+    }
+
+    public void setRentEndDate(String rentEndDate) {
+        this.rentEndDate = rentEndDate;
+    }
+
+    public String getCrossCityFlag() {
+        return crossCityFlag;
+    }
+
+    public void setCrossCityFlag(String crossCityFlag) {
+        this.crossCityFlag = crossCityFlag;
+    }
+
+    public String getRealPropertyCertificate() {
+        return realPropertyCertificate;
+    }
+
+    public void setRealPropertyCertificate(String realPropertyCertificate) {
+        this.realPropertyCertificate = realPropertyCertificate;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public void setUnit(String unit) {
+        this.unit = unit;
+    }
+
+    public String getCarNo() {
+        return carNo;
+    }
+
+    public void setCarNo(String carNo) {
+        this.carNo = carNo;
+    }
+}

+ 177 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/RealPropertySellInfo.java

@@ -0,0 +1,177 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Digits;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.math.BigDecimal;
+
+/**
+ * 不动产销售特定要素子实体类
+ * 仅在开具不动产销售特定要素的数电发票(specificFactor为05时)需要传入
+ * 此时商品只能有一行明细,且规格型号、计量单位都不能有值
+ */
+public class RealPropertySellInfo {
+
+    /**
+     * 不动产单元代码/网签合同备案编号
+     * 最大28字符
+     */
+    @Size(max = 28, message = "不动产单元代码/网签合同备案编号不能超过28字符")
+    private String realPropertyContractNumber;
+
+    /**
+     * 不动产地址(省)
+     * 必填,需以省、自治区、直辖市、北京市、天津市、上海市、重庆市任意一个关键词结尾
+     */
+    @NotBlank(message = "不动产地址(省)realPropertyAddress不能为空")
+    @Size(max = 100, message = "不动产地址(省)不能超过100字符")
+    @Pattern(regexp = ".+(省|自治区|直辖市|北京市|天津市|上海市|重庆市)$",
+            message = "不动产地址(省)必须以省、自治区、直辖市、北京市、天津市、上海市、重庆市任意一个关键词结尾")
+    private String realPropertyAddress;
+
+    /**
+     * 不动产地址(市)
+     * 需以市、盟、自治州、地区、区、县任意一个关键词结尾
+     */
+    @Size(max = 20, message = "不动产地址(市)不能超过20字符")
+    @Pattern(regexp = "^$|.+(市|盟|自治州|地区|区|县)$",
+            message = "不动产地址(市)必须以市、盟、自治州、地区、区、县任意一个关键词结尾")
+    private String realPropertyAddressCity;
+
+    /**
+     * 详细地址
+     * 必填,需包含街、路、村、乡、镇、道、巷、号关键词
+     */
+    @NotBlank(message = "详细地址detailAddress不能为空")
+    @Size(max = 120, message = "详细地址不能超过120字符")
+    @Pattern(regexp = ".+(街|路|村|乡|镇|道|巷|号)",
+            message = "详细地址必须包含街、路、村、乡、镇、道、巷、号任意一个关键词")
+    private String detailAddress;
+
+    /**
+     * 跨地(市)标志
+     * 必填,0:否 1:是
+     */
+    @NotBlank(message = "跨地(市)标志crossCityFlag不能为空")
+    @Pattern(regexp = "^0|1$", message = "跨地(市)标志只能传入:0(否)、1(是)")
+    private String crossCityFlag;
+
+    /**
+     * 土地增值税项目编号
+     * 最大18字符
+     */
+    @Size(max = 18, message = "土地增值税项目编号不能超过18字符")
+    private String incrementTaxNumber;
+
+    /**
+     * 核定计税价格
+     * 小数点后保留两位小数
+     */
+    @Digits(integer = 20, fraction = 2, message = "核定计税价格格式错误,最多20位整数,保留2位小数")
+    private BigDecimal price;
+
+    /**
+     * 实际成交税金额
+     * 小数点后保留两位小数;当核定计税价格有值时必填
+     */
+    @Digits(integer = 20, fraction = 2, message = "实际成交税金额格式错误,最多20位整数,保留2位小数")
+    private BigDecimal taxAmount;
+
+    /**
+     * 产权证书/不动产权证号
+     * 最大40字符
+     */
+    @Size(max = 40, message = "产权证书/不动产权证号不能超过40字符")
+    private String realPropertyCertificate;
+
+    /**
+     * 面积单位
+     * 必填,可选值:1(平方千米)、2(平方米)、3(公顷)、4(亩)、5(hm²)、6(km²)、7(m²)、8(米)
+     */
+    @NotBlank(message = "面积单位unit不能为空")
+    @Pattern(regexp = "^1|2|3|4|5|6|7|8$",
+            message = "面积单位只能传入:1(平方千米)、2(平方米)、3(公顷)、4(亩)、5(hm²)、6(km²)、7(m²)、8(米)")
+    private String unit;
+
+    public String getRealPropertyContractNumber() {
+        return realPropertyContractNumber;
+    }
+
+    public void setRealPropertyContractNumber(String realPropertyContractNumber) {
+        this.realPropertyContractNumber = realPropertyContractNumber;
+    }
+
+    public String getRealPropertyAddress() {
+        return realPropertyAddress;
+    }
+
+    public void setRealPropertyAddress(String realPropertyAddress) {
+        this.realPropertyAddress = realPropertyAddress;
+    }
+
+    public String getRealPropertyAddressCity() {
+        return realPropertyAddressCity;
+    }
+
+    public void setRealPropertyAddressCity(String realPropertyAddressCity) {
+        this.realPropertyAddressCity = realPropertyAddressCity;
+    }
+
+    public String getDetailAddress() {
+        return detailAddress;
+    }
+
+    public void setDetailAddress(String detailAddress) {
+        this.detailAddress = detailAddress;
+    }
+
+    public String getCrossCityFlag() {
+        return crossCityFlag;
+    }
+
+    public void setCrossCityFlag(String crossCityFlag) {
+        this.crossCityFlag = crossCityFlag;
+    }
+
+    public String getIncrementTaxNumber() {
+        return incrementTaxNumber;
+    }
+
+    public void setIncrementTaxNumber(String incrementTaxNumber) {
+        this.incrementTaxNumber = incrementTaxNumber;
+    }
+
+    public BigDecimal getPrice() {
+        return price;
+    }
+
+    public void setPrice(BigDecimal price) {
+        this.price = price;
+    }
+
+    public BigDecimal getTaxAmount() {
+        return taxAmount;
+    }
+
+    public void setTaxAmount(BigDecimal taxAmount) {
+        this.taxAmount = taxAmount;
+    }
+
+    public String getRealPropertyCertificate() {
+        return realPropertyCertificate;
+    }
+
+    public void setRealPropertyCertificate(String realPropertyCertificate) {
+        this.realPropertyCertificate = realPropertyCertificate;
+    }
+
+    public String getUnit() {
+        return unit;
+    }
+
+    public void setUnit(String unit) {
+        this.unit = unit;
+    }
+}

+ 148 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/TravellerTransport.java

@@ -0,0 +1,148 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 旅客运输服务特定要素子实体类
+ * 仅在开具旅客运输服务特定要素的数电发票(specificFactor为09时)需要传入
+ * 最多2000行,至少1行;若任意字段填写则触发全量必填校验
+ */
+public class TravellerTransport {
+
+    /**
+     * 出行人
+     * 必填,最大20字符
+     */
+    @NotBlank(message = "出行人traveller不能为空")
+    @Size(max = 20, message = "出行人长度不能超过20字符")
+    private String traveller;
+
+    /**
+     * 出行日期
+     * 必填,格式为yyyy-MM-dd
+     */
+    @NotBlank(message = "出行日期travelDate不能为空")
+    @Pattern(regexp = "^\\d{4}-\\d{2}-\\d{2}$",
+            message = "出行日期格式错误,必须为yyyy-MM-dd")
+    private String travelDate;
+
+    /**
+     * 出行人证件类型
+     * 必填,可选值:101(组织机构代码证)、102(营业执照)、103(税务登记证)、199(其他单位证件)、201(居民身份证)等
+     */
+    @NotBlank(message = "出行人证件类型travellerCardType不能为空")
+    @Pattern(regexp = "^101|102|103|199|201|202|203|204|205|206|207|208|210|212|213|214|215|216|299$",
+            message = "出行人证件类型不在合法范围内")
+    private String travellerCardType;
+
+    /**
+     * 出行人证件号码
+     * 必填,最大20字符
+     */
+    @NotBlank(message = "出行人证件号码travellerCardNo不能为空")
+    @Size(max = 20, message = "出行人证件号码长度不能超过20字符")
+    private String travellerCardNo;
+
+    /**
+     * 出行地
+     * 必填,最大80字符
+     */
+    @NotBlank(message = "出行地travelPlace不能为空")
+    @Size(max = 80, message = "出行地长度不能超过80字符")
+    private String travelPlace;
+
+    /**
+     * 到达地
+     * 必填,最大80字符
+     */
+    @NotBlank(message = "到达地arrivePlace不能为空")
+    @Size(max = 80, message = "到达地长度不能超过80字符")
+    private String arrivePlace;
+
+    /**
+     * 交通工具类型
+     * 必填,可选值:1(飞机)、2(火车)、3(长途汽车)、4(公共交通)、5(出租车)、6(汽车)、7(船舶)、9(其他)
+     */
+    @NotBlank(message = "交通工具类型vehicleType不能为空")
+    @Pattern(regexp = "^1|2|3|4|5|6|7|9$",
+            message = "交通工具类型只能传入:1(飞机)、2(火车)、3(长途汽车)、4(公共交通)、5(出租车)、6(汽车)、7(船舶)、9(其他)")
+    private String vehicleType;
+
+    /**
+     * 交通工具等级
+     * 当交通工具类型为火车、飞机、船舶时必填,对应可选值:
+     * 火车:一等座/二等座/软席/硬席
+     * 飞机:公务舱/头等舱/经济舱
+     * 船舶:一等舱/二等舱/三等舱
+     */
+    @Pattern(regexp = "^$|一等座|二等座|软席|硬席|公务舱|头等舱|经济舱|一等舱|二等舱|三等舱",
+            message = "交通工具等级不在合法范围内")
+    private String vehicleLevel;
+
+    public String getTraveller() {
+        return traveller;
+    }
+
+    public void setTraveller(String traveller) {
+        this.traveller = traveller;
+    }
+
+    public String getTravelDate() {
+        return travelDate;
+    }
+
+    public void setTravelDate(String travelDate) {
+        this.travelDate = travelDate;
+    }
+
+    public String getTravellerCardType() {
+        return travellerCardType;
+    }
+
+    public void setTravellerCardType(String travellerCardType) {
+        this.travellerCardType = travellerCardType;
+    }
+
+    public String getTravellerCardNo() {
+        return travellerCardNo;
+    }
+
+    public void setTravellerCardNo(String travellerCardNo) {
+        this.travellerCardNo = travellerCardNo;
+    }
+
+    public String getTravelPlace() {
+        return travelPlace;
+    }
+
+    public void setTravelPlace(String travelPlace) {
+        this.travelPlace = travelPlace;
+    }
+
+    public String getArrivePlace() {
+        return arrivePlace;
+    }
+
+    public void setArrivePlace(String arrivePlace) {
+        this.arrivePlace = arrivePlace;
+    }
+
+    public String getVehicleType() {
+        return vehicleType;
+    }
+
+    public void setVehicleType(String vehicleType) {
+        this.vehicleType = vehicleType;
+    }
+
+    public String getVehicleLevel() {
+        return vehicleLevel;
+    }
+
+    public void setVehicleLevel(String vehicleLevel) {
+        this.vehicleLevel = vehicleLevel;
+    }
+}

+ 56 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/UsedCar.java

@@ -0,0 +1,56 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Size;
+
+/**
+ * 二手车发票关联的二手车销售统一发票信息子实体类
+ * 特定要素为31时必传,15时选传,只能有一组信息;二手车发票明细只能有一行
+ */
+public class UsedCar {
+    /**
+     * 关联的二手车销售统一发票数电发票号码
+     * 必填,最大20字符
+     */
+    @NotBlank(message = "关联的二手车销售统一发票数电发票号码allEinVno不能为空")
+    @Size(max = 20, message = "关联的数电发票号码长度不能超过20字符")
+    private String allEinVno;
+
+    /**
+     * 关联的二手车销售统一发票纸质发票代码
+     * 选填,最大12字符(或10字符)
+     */
+    @Size(max = 12, message = "关联的纸质发票代码长度不能超过12字符")
+    private String invcode;
+
+    /**
+     * 关联的二手车销售统一发票纸质发票号码
+     * 选填,最大8字符
+     */
+    @Size(max = 8, message = "关联的纸质发票号码长度不能超过8字符")
+    private String invno;
+
+    public String getAllEinVno() {
+        return allEinVno;
+    }
+
+    public void setAllEinVno(String allEinVno) {
+        this.allEinVno = allEinVno;
+    }
+
+    public String getInvcode() {
+        return invcode;
+    }
+
+    public void setInvcode(String invcode) {
+        this.invcode = invcode;
+    }
+
+    public String getInvno() {
+        return invno;
+    }
+
+    public void setInvno(String invno) {
+        this.invno = invno;
+    }
+}

+ 187 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/UsedCarSale.java

@@ -0,0 +1,187 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Size;
+
+/**
+ * 二手车销售统一发票信息子实体类
+ * 发票类型为84(数电票二手车销售统一发票)、88(数电纸票二手车销售统一发票)时必传
+ */
+public class UsedCarSale {
+
+    /**
+     * 车牌照号
+     * 必填,最大20字符
+     */
+    @NotBlank(message = "车牌照号carNo不能为空")
+    @Size(max = 20, message = "车牌照号长度不能超过20字符")
+    private String carNo;
+
+    /**
+     * 登记证号
+     * 必填,最大20字符
+     */
+    @NotBlank(message = "登记证号registrationNo不能为空")
+    @Size(max = 20, message = "登记证号长度不能超过20字符")
+    private String registrationNo;
+
+    /**
+     * 车辆识别代号/车架号码
+     * 必填,最大23字符
+     */
+    @NotBlank(message = "车辆识别代号/车架号码frameNo不能为空")
+    @Size(max = 23, message = "车辆识别代号长度不能超过23字符")
+    private String frameNo;
+
+    /**
+     * 厂牌型号
+     * 必填,最大60字符
+     */
+    @NotBlank(message = "厂牌型号brandModel不能为空")
+    @Size(max = 60, message = "厂牌型号长度不能超过60字符")
+    private String brandModel;
+
+    /**
+     * 转入地车辆管理所名称
+     * 必填,最大80字符
+     */
+    @NotBlank(message = "转入地车辆管理所名称transferInVehicleRegistrationOffice不能为空")
+    @Size(max = 80, message = "转入地车辆管理所名称长度不能超过80字符")
+    private String transferInVehicleRegistrationOffice;
+
+    /**
+     * 经营单位名称
+     * 必填,最大100字符
+     */
+    @NotBlank(message = "经营单位名称businessEntityName不能为空")
+    @Size(max = 100, message = "经营单位名称长度不能超过100字符")
+    private String businessEntityName;
+
+    /**
+     * 经营单位纳税人识别号
+     * 必填,最大20字符
+     */
+    @NotBlank(message = "经营单位纳税人识别号businessEntityTaxno不能为空")
+    @Size(max = 20, message = "经营单位纳税人识别号长度不能超过20字符")
+    private String businessEntityTaxno;
+
+    /**
+     * 经营单位地址
+     * 必填,最大100字符
+     */
+    @NotBlank(message = "经营单位地址businessEntityAddr不能为空")
+    @Size(max = 100, message = "经营单位地址长度不能超过100字符")
+    private String businessEntityAddr;
+
+    /**
+     * 经营单位电话
+     * 必填,最大11字符
+     */
+    @NotBlank(message = "经营单位电话businessEntityPhone不能为空")
+    @Size(max = 11, message = "经营单位电话长度不能超过11字符")
+    private String businessEntityPhone;
+
+    /**
+     * 经营单位开户银行
+     * 必填,最大100字符
+     */
+    @NotBlank(message = "经营单位开户银行businessEntityBank不能为空")
+    @Size(max = 100, message = "经营单位开户银行长度不能超过100字符")
+    private String businessEntityBank;
+
+    /**
+     * 经营单位银行账号
+     * 必填,最大60字符
+     */
+    @NotBlank(message = "经营单位银行账号businessEntityBankAccount不能为空")
+    @Size(max = 60, message = "经营单位银行账号长度不能超过60字符")
+    private String businessEntityBankAccount;
+
+    public String getCarNo() {
+        return carNo;
+    }
+
+    public void setCarNo(String carNo) {
+        this.carNo = carNo;
+    }
+
+    public String getRegistrationNo() {
+        return registrationNo;
+    }
+
+    public void setRegistrationNo(String registrationNo) {
+        this.registrationNo = registrationNo;
+    }
+
+    public String getFrameNo() {
+        return frameNo;
+    }
+
+    public void setFrameNo(String frameNo) {
+        this.frameNo = frameNo;
+    }
+
+    public String getBrandModel() {
+        return brandModel;
+    }
+
+    public void setBrandModel(String brandModel) {
+        this.brandModel = brandModel;
+    }
+
+    public String getTransferInVehicleRegistrationOffice() {
+        return transferInVehicleRegistrationOffice;
+    }
+
+    public void setTransferInVehicleRegistrationOffice(String transferInVehicleRegistrationOffice) {
+        this.transferInVehicleRegistrationOffice = transferInVehicleRegistrationOffice;
+    }
+
+    public String getBusinessEntityName() {
+        return businessEntityName;
+    }
+
+    public void setBusinessEntityName(String businessEntityName) {
+        this.businessEntityName = businessEntityName;
+    }
+
+    public String getBusinessEntityTaxno() {
+        return businessEntityTaxno;
+    }
+
+    public void setBusinessEntityTaxno(String businessEntityTaxno) {
+        this.businessEntityTaxno = businessEntityTaxno;
+    }
+
+    public String getBusinessEntityAddr() {
+        return businessEntityAddr;
+    }
+
+    public void setBusinessEntityAddr(String businessEntityAddr) {
+        this.businessEntityAddr = businessEntityAddr;
+    }
+
+    public String getBusinessEntityPhone() {
+        return businessEntityPhone;
+    }
+
+    public void setBusinessEntityPhone(String businessEntityPhone) {
+        this.businessEntityPhone = businessEntityPhone;
+    }
+
+    public String getBusinessEntityBank() {
+        return businessEntityBank;
+    }
+
+    public void setBusinessEntityBank(String businessEntityBank) {
+        this.businessEntityBank = businessEntityBank;
+    }
+
+    public String getBusinessEntityBankAccount() {
+        return businessEntityBankAccount;
+    }
+
+    public void setBusinessEntityBankAccount(String businessEntityBankAccount) {
+        this.businessEntityBankAccount = businessEntityBankAccount;
+    }
+}

+ 194 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/Vehicle.java

@@ -0,0 +1,194 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+import org.hibernate.validator.constraints.NotBlank;
+
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+
+/**
+ * 机动车销售统一发票信息子实体类
+ * 发票类型为83、87时必传
+ */
+public class Vehicle {
+
+    /**
+     * 厂牌型号
+     * 选填,最大140字符;进口车选填,为空时使用车架号码关联的厂牌型号;国产车不填写
+     */
+    @Size(max = 140, message = "厂牌型号长度不能超过140字符")
+    private String brandModel;
+
+    /**
+     * 合格证号
+     * 选填,最大50字符;乐企通道不填写,为空时使用车架号码关联的合格证号
+     */
+    @Size(max = 50, message = "合格证号长度不能超过50字符")
+    private String passCertificate;
+
+    /**
+     * 发动机号码
+     * 选填,最大140字符;乐企通道不填写,为空时使用车架号码关联的发动机号码
+     */
+    @Size(max = 140, message = "发动机号码长度不能超过140字符")
+    private String engineCode;
+
+    /**
+     * 车架号码
+     * 必填,17字符
+     */
+    @NotBlank(message = "车架号码frameNo不能为空")
+    @Size(min = 17, max = 17, message = "车架号码必须为17字符")
+    private String frameNo;
+
+    /**
+     * 产地
+     * 选填,最大10字符
+     */
+    @Size(max = 10, message = "产地长度不能超过10字符")
+    private String origin;
+
+    /**
+     * 进口证明书号
+     * 选填,最大16字符;乐企通道不填写,当进口证明书号不为空时,合格证号将为空
+     */
+    @Size(max = 16, message = "进口证明书号长度不能超过16字符")
+    private String importCertificateNo;
+
+    /**
+     * 商检单号
+     * 选填,最大60字符
+     */
+    @Size(max = 60, message = "商检单号长度不能超过60字符")
+    private String inspectionNo;
+
+    /**
+     * 吨位
+     * 选填,最大10字符;乐企通道不填写
+     */
+    @Size(max = 10, message = "吨位长度不能超过10字符")
+    private String tonnage;
+
+    /**
+     * 限乘人数
+     * 选填,最大11字符;乐企通道不填写
+     */
+    @Size(max = 11, message = "限乘人数长度不能超过11字符")
+    private String limitNum;
+
+    /**
+     * 完税凭证号码
+     * 选填,最大100字符
+     */
+    @Size(max = 100, message = "完税凭证号码长度不能超过100字符")
+    private String taxPaymentReceiptNo;
+
+    /**
+     * 生产企业名称
+     * 选填,最大300字符;为空时使用车架号码关联的生产企业名称
+     */
+    @Size(max = 300, message = "生产企业名称长度不能超过300字符")
+    private String productionCompanyName;
+
+    /**
+     * 制造或进口日期
+     * 选填,格式为yyyy-mm-dd;为空时使用车架号码关联的车辆制造日期
+     */
+    @Pattern(regexp = "^$|^\\d{4}-\\d{2}-\\d{2}$",
+            message = "制造或进口日期格式错误,必须为yyyy-mm-dd")
+    private String vehicleProdDate;
+
+    public String getBrandModel() {
+        return brandModel;
+    }
+
+    public void setBrandModel(String brandModel) {
+        this.brandModel = brandModel;
+    }
+
+    public String getPassCertificate() {
+        return passCertificate;
+    }
+
+    public void setPassCertificate(String passCertificate) {
+        this.passCertificate = passCertificate;
+    }
+
+    public String getEngineCode() {
+        return engineCode;
+    }
+
+    public void setEngineCode(String engineCode) {
+        this.engineCode = engineCode;
+    }
+
+    public String getFrameNo() {
+        return frameNo;
+    }
+
+    public void setFrameNo(String frameNo) {
+        this.frameNo = frameNo;
+    }
+
+    public String getOrigin() {
+        return origin;
+    }
+
+    public void setOrigin(String origin) {
+        this.origin = origin;
+    }
+
+    public String getImportCertificateNo() {
+        return importCertificateNo;
+    }
+
+    public void setImportCertificateNo(String importCertificateNo) {
+        this.importCertificateNo = importCertificateNo;
+    }
+
+    public String getInspectionNo() {
+        return inspectionNo;
+    }
+
+    public void setInspectionNo(String inspectionNo) {
+        this.inspectionNo = inspectionNo;
+    }
+
+    public String getTonnage() {
+        return tonnage;
+    }
+
+    public void setTonnage(String tonnage) {
+        this.tonnage = tonnage;
+    }
+
+    public String getLimitNum() {
+        return limitNum;
+    }
+
+    public void setLimitNum(String limitNum) {
+        this.limitNum = limitNum;
+    }
+
+    public String getTaxPaymentReceiptNo() {
+        return taxPaymentReceiptNo;
+    }
+
+    public void setTaxPaymentReceiptNo(String taxPaymentReceiptNo) {
+        this.taxPaymentReceiptNo = taxPaymentReceiptNo;
+    }
+
+    public String getProductionCompanyName() {
+        return productionCompanyName;
+    }
+
+    public void setProductionCompanyName(String productionCompanyName) {
+        this.productionCompanyName = productionCompanyName;
+    }
+
+    public String getVehicleProdDate() {
+        return vehicleProdDate;
+    }
+
+    public void setVehicleProdDate(String vehicleProdDate) {
+        this.vehicleProdDate = vehicleProdDate;
+    }
+}

+ 157 - 0
src/main/java/com/jeeplus/modules/workinvoice/entity/OMS/VehicleVesselInfo.java

@@ -0,0 +1,157 @@
+package com.jeeplus.modules.workinvoice.entity.OMS;
+import org.hibernate.validator.constraints.NotBlank;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import javax.validation.constraints.Digits;
+import java.math.BigDecimal;
+
+/**
+ * 代收车船税信息子实体类
+ * 仅在开具代收车船税特定要素的数电发票(specificFactor为07时)需要传入
+ * 且只能有一组,发票明细只能有一行(或一对折扣明细行)
+ */
+public class VehicleVesselInfo {
+
+    /**
+     * 保险单号
+     * 必填,最大40字符
+     */
+    @NotBlank(message = "保险单号policyNo不能为空")
+    @Size(max = 40, message = "保险单号长度不能超过40字符")
+    private String policyNo;
+
+    /**
+     * 车牌号
+     * 必填,最大40字符
+     */
+    @NotBlank(message = "车牌号carNo不能为空")
+    @Size(max = 40, message = "车牌号长度不能超过40字符")
+    private String carNo;
+
+    /**
+     * 税款所属期
+     * 必填,格式为:YYYY-MM YYYY-MM(如2023-11 2023-12),用空格分隔
+     */
+    @NotBlank(message = "税款所属期taxPeriod不能为空")
+    @Pattern(regexp = "^\\d{4}-\\d{2} \\d{4}-\\d{2}$",
+            message = "税款所属期格式错误,必须为YYYY-MM YYYY-MM(如2023-11 2023-12)")
+    private String taxPeriod;
+
+    /**
+     * 代收车船税金额
+     * 必填,最多16位整数,保留2位小数
+     */
+    @NotBlank(message = "代收车船税金额amount不能为空")
+    @Digits(integer = 16, fraction = 2, message = "代收车船税金额格式错误,最多16位整数,保留2位小数")
+    private BigDecimal amount;
+
+    /**
+     * 滞纳金金额
+     * 必填,最多16位整数,保留2位小数
+     */
+    @NotBlank(message = "滞纳金金额lateFeeAmount不能为空")
+    @Digits(integer = 16, fraction = 2, message = "滞纳金金额格式错误,最多16位整数,保留2位小数")
+    private BigDecimal lateFeeAmount;
+
+    /**
+     * 金额合计
+     * 必填,最多16位整数,保留2位小数
+     */
+    @NotBlank(message = "金额合计totalamount不能为空")
+    @Digits(integer = 16, fraction = 2, message = "金额合计格式错误,最多16位整数,保留2位小数")
+    private BigDecimal totalamount;
+
+    /**
+     * 车辆识别代码/车架号码
+     * 必填,17字符
+     */
+    @NotBlank(message = "车辆识别代码frameNo不能为空")
+    @Size(min = 17, max = 17, message = "车辆识别代码必须为17字符")
+    private String frameNo;
+
+    /**
+     * 发动机号码
+     * 选填,最大40字符
+     */
+    @Size(max = 40, message = "发动机号码长度不能超过40字符")
+    private String engineCode;
+
+    /**
+     * 底盘号
+     * 选填,最大40字符
+     */
+    @Size(max = 40, message = "底盘号长度不能超过40字符")
+    private String chassisCode;
+
+    public String getPolicyNo() {
+        return policyNo;
+    }
+
+    public void setPolicyNo(String policyNo) {
+        this.policyNo = policyNo;
+    }
+
+    public String getCarNo() {
+        return carNo;
+    }
+
+    public void setCarNo(String carNo) {
+        this.carNo = carNo;
+    }
+
+    public String getTaxPeriod() {
+        return taxPeriod;
+    }
+
+    public void setTaxPeriod(String taxPeriod) {
+        this.taxPeriod = taxPeriod;
+    }
+
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    public void setAmount(BigDecimal amount) {
+        this.amount = amount;
+    }
+
+    public BigDecimal getLateFeeAmount() {
+        return lateFeeAmount;
+    }
+
+    public void setLateFeeAmount(BigDecimal lateFeeAmount) {
+        this.lateFeeAmount = lateFeeAmount;
+    }
+
+    public BigDecimal getTotalamount() {
+        return totalamount;
+    }
+
+    public void setTotalamount(BigDecimal totalamount) {
+        this.totalamount = totalamount;
+    }
+
+    public String getFrameNo() {
+        return frameNo;
+    }
+
+    public void setFrameNo(String frameNo) {
+        this.frameNo = frameNo;
+    }
+
+    public String getEngineCode() {
+        return engineCode;
+    }
+
+    public void setEngineCode(String engineCode) {
+        this.engineCode = engineCode;
+    }
+
+    public String getChassisCode() {
+        return chassisCode;
+    }
+
+    public void setChassisCode(String chassisCode) {
+        this.chassisCode = chassisCode;
+    }
+}

+ 145 - 0
src/main/java/com/jeeplus/modules/workinvoice/utils/HttpPostJsonUtil.java

@@ -0,0 +1,145 @@
+package com.jeeplus.modules.workinvoice.utils;
+import javax.net.ssl.*;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+
+/**
+ * 纯原生POST请求工具类 - 仅支持JSON格式请求体 + UTF-8编码
+ * 无任何冗余代码、无第三方依赖、支持HTTP/HTTPS、异常兜底返回空JSON
+ */
+public class HttpPostJsonUtil {
+
+    /**
+     * 核心方法:发送POST请求
+     * @param requestUrl 接口请求地址 (HTTP/HTTPS均可)
+     * @param outputStr  JSON格式的请求参数字符串
+     * @return 接口返回的JSON字符串,异常则返回 {}
+     */
+    public static String doPost(String requestUrl, String outputStr) {
+        HttpURLConnection httpUrlConn = null;
+        try {
+            URL url = new URL(requestUrl);
+            httpUrlConn = (HttpURLConnection) url.openConnection();
+
+            // HTTPS 证书信任配置 (必须保留,兼容HTTPS请求)
+            if (httpUrlConn instanceof HttpsURLConnection) {
+                SSLContext sslContext = SSLContext.getInstance("TLS");
+                sslContext.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new SecureRandom());
+                ((HttpsURLConnection) httpUrlConn).setSSLSocketFactory(sslContext.getSocketFactory());
+                ((HttpsURLConnection) httpUrlConn).setHostnameVerifier((hostname, session) -> true);
+            }
+
+            // 核心配置
+            httpUrlConn.setRequestMethod("POST");
+            httpUrlConn.setDoOutput(true);
+            httpUrlConn.setDoInput(true);
+            httpUrlConn.setUseCaches(false);
+            httpUrlConn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
+
+            // 写入JSON参数 并 UTF-8编码
+            if (outputStr != null && !outputStr.trim().isEmpty()) {
+                OutputStream os = httpUrlConn.getOutputStream();
+                os.write(outputStr.getBytes("UTF-8"));
+                os.flush();
+                os.close();
+            }
+
+            // 读取返回结果 并 UTF-8解码
+            BufferedReader br = new BufferedReader(new InputStreamReader(httpUrlConn.getInputStream(), "UTF-8"));
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+            br.close();
+            return sb.toString();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (httpUrlConn != null) {
+                httpUrlConn.disconnect();
+            }
+        }
+        return "{}";
+    }
+
+    /**
+     * 重载方法:带token请求头的POST请求 (你的业务必用,无冗余新增)
+     * @param requestUrl 接口地址
+     * @param outputStr   JSON参数
+     * @param token       令牌(accessToken/Authorization)
+     * @return 接口返回的JSON字符串
+     */
+    public static String doPost(String requestUrl, String outputStr, String token) {
+        HttpURLConnection httpUrlConn = null;
+        try {
+            URL url = new URL(requestUrl);
+            httpUrlConn = (HttpURLConnection) url.openConnection();
+
+            if (httpUrlConn instanceof HttpsURLConnection) {
+                SSLContext sslContext = SSLContext.getInstance("TLS");
+                sslContext.init(null, new TrustManager[]{new TrustAnyTrustManager()}, new SecureRandom());
+                ((HttpsURLConnection) httpUrlConn).setSSLSocketFactory(sslContext.getSocketFactory());
+                ((HttpsURLConnection) httpUrlConn).setHostnameVerifier((hostname, session) -> true);
+            }
+
+            httpUrlConn.setRequestMethod("POST");
+            httpUrlConn.setDoOutput(true);
+            httpUrlConn.setDoInput(true);
+            httpUrlConn.setUseCaches(false);
+            httpUrlConn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
+            // 添加token请求头
+            if (token != null && !token.trim().isEmpty()) {
+                httpUrlConn.setRequestProperty("accessToken", token);
+                // 如果接口要求 Bearer 前缀,放开下面这行注释即可
+                // httpUrlConn.setRequestProperty("Authorization", "Bearer " + token);
+            }
+
+            if (outputStr != null && !outputStr.trim().isEmpty()) {
+                OutputStream os = httpUrlConn.getOutputStream();
+                os.write(outputStr.getBytes("UTF-8"));
+                os.flush();
+                os.close();
+            }
+
+            BufferedReader br = new BufferedReader(new InputStreamReader(httpUrlConn.getInputStream(), "UTF-8"));
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while ((line = br.readLine()) != null) {
+                sb.append(line);
+            }
+            br.close();
+            return sb.toString();
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (httpUrlConn != null) {
+                httpUrlConn.disconnect();
+            }
+        }
+        return "{}";
+    }
+
+    /**
+     * 内部类:HTTPS证书信任管理器 (极简实现,无冗余代码)
+     */
+    private static class TrustAnyTrustManager implements X509TrustManager {
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType) {}
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType) {}
+
+        @Override
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+    }
+}

+ 161 - 0
src/main/java/com/jeeplus/modules/workinvoice/utils/OMSNationUtil.java

@@ -0,0 +1,161 @@
+package com.jeeplus.modules.workinvoice.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.collect.Lists;
+import com.jeeplus.modules.workinvoice.entity.OMS.InvoiceOMSImportInfo;
+import com.jeeplus.modules.workinvoice.entity.OMS.OrderItem;
+import org.apache.commons.lang3.StringUtils;
+
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+
+public class OMSNationUtil {
+
+    public static void main(String[] args) {
+        OMSNationUtil util = new OMSNationUtil();
+        String string = util.neatenData();
+        String accessToken = extractAccessTokenFromBase64(string);
+        String string1 = HttpPostJsonUtil.doPost("https://oms-sandbox.einvoice.js.cn:7079/prod-api/output/server/order/upload", accessToken);
+        System.out.println("✅ 提取到的accessToken:" + accessToken);
+        System.out.println("✅ 提取到的string1:" + string1);
+    }
+
+    /**
+     * 从Base64编码的JSON响应中提取accessToken
+     */
+    public static String extractAccessTokenFromBase64(String base64Str) {
+        if (base64Str == null || com.jeeplus.common.utils.StringUtils.isBlank(base64Str)) {
+            return "";
+        }
+        // 1. 解码Base64得到JSON字符串
+        byte[] decodeBytes = Base64.getDecoder().decode(base64Str);
+        String jsonStr = new String(decodeBytes, StandardCharsets.UTF_8);
+        // 2. 解析JSON提取accessToken
+        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
+        return jsonObject.getString("accessToken");
+    }
+
+    public static InvoiceOMSImportInfo getInvoiceInfo(String base64Str) {
+        // 1. 调用你的解码方法,获取JSON字符串
+        String jsonResult = getDownFromBase64(base64Str);
+
+        // 2. 非空校验
+        if (StringUtils.isBlank(jsonResult)) {
+            System.err.println("解码后返回的JSON字符串为空,无法转换实体类");
+            return null;
+        }
+
+        // 3. 转换实体类+异常捕获
+        InvoiceOMSImportInfo invoiceInfo = null;
+        try {
+            invoiceInfo = JSONObject.parseObject(jsonResult, InvoiceOMSImportInfo.class);
+        } catch (Exception e) {
+            System.err.println("JSON字符串转InvoiceOMSImportInfo实体类失败:" + e.getMessage());
+            e.printStackTrace();
+        }
+        return invoiceInfo;
+    }
+
+    // ========== 你的原方法(无需修改,保持不变) ==========
+    public static String getDownFromBase64(String base64Str) {
+        if (StringUtils.isBlank(base64Str)) {
+            return "";
+        }
+        byte[] decodeBytes = null;
+        String jsonStr = null;
+        try {
+            decodeBytes = Base64.getDecoder().decode(base64Str);
+            jsonStr = new String(decodeBytes, StandardCharsets.UTF_8);
+        } catch (IllegalArgumentException e) {
+            System.err.println("Base64解码失败,密文格式错误:" + e.getMessage());
+            return "";
+        }
+
+        try {
+            JSONArray jsonArray = JSONArray.parseArray(jsonStr);
+            if (jsonArray == null || jsonArray.size() == 0) {
+                System.err.println("解码后的JSON数组为空");
+                return "";
+            }
+            JSONObject jsonObject = jsonArray.getJSONObject(0);
+            if (jsonObject == null) {
+                System.err.println("JSON数组第一个元素不是JSONObject对象");
+                return "";
+            }
+            return jsonObject.toJSONString();
+        } catch (Exception e) {
+            System.err.println("JSON解析失败:" + e.getMessage());
+            e.printStackTrace();
+            return "";
+        }
+    }
+
+    /**
+     * Base64加密(编码)方法 - 配套上面的解码方法
+     * 入参:原始JSON字符串
+     * 出参:Base64编码后的字符串 (UTF-8编码,防中文乱码,和解码逻辑完全匹配)
+     */
+    public static String encryptJsonToBase64(String jsonStr) {
+        if (jsonStr == null || com.jeeplus.common.utils.StringUtils.isBlank(jsonStr)) {
+            return "";
+        }
+        // 1. JSON字符串转字节数组(必须指定UTF-8,你的JSON有中文,防止乱码)
+        byte[] jsonBytes = jsonStr.getBytes(StandardCharsets.UTF_8);
+        // 2. 进行Base64编码(加密)
+        return Base64.getEncoder().encodeToString(jsonBytes);
+    }
+
+
+    /**
+     * 整理数据明文
+     */
+    public String neatenData() {
+        List<OrderItem> orderItems = Lists.newArrayList();
+
+        InvoiceOMSImportInfo omsImportInfo = new InvoiceOMSImportInfo();
+        omsImportInfo.setDeptCode("7777");
+        omsImportInfo.setOrderno("fff79669f3774c2a8be557e909a746d5");
+        omsImportInfo.setSellerName("深圳市松胜电子有限公司");
+        omsImportInfo.setSellerTaxno("500102204228315131");
+        omsImportInfo.setInvKind("02");
+        omsImportInfo.setInvType("01");
+
+        //添加购买方信息
+        omsImportInfo.setBuyerName("江苏兴光项目管理有限公司");
+        omsImportInfo.setBuyerTaxno("91320000746823994F");
+
+
+        OrderItem orderItem = new OrderItem();
+        orderItem.setLineCode("1");
+        orderItem.setLineType("00");
+        orderItem.setLineType("00");
+        //添加商品名称
+        //orderItem.setGoodsCode("1001");
+        orderItem.setGoodsName("电脑");
+        orderItem.setQty(BigDecimal.valueOf(1));
+        orderItem.setPrice(BigDecimal.valueOf(100000.00));
+        orderItem.setPriceTaxFlag("0"); //含税状态
+        orderItem.setTaxrate(BigDecimal.valueOf(0.01)); //税率
+        orderItem.setAmount(BigDecimal.valueOf(100000.00)); //金额
+        orderItem.setTax(BigDecimal.valueOf(1000.00));//税额
+        orderItem.setTaxamount(BigDecimal.valueOf(101000.00));//含税金额
+        orderItem.setGoodstaxno("109050901");//税收分类编码
+
+
+        orderItems.add(orderItem);
+        omsImportInfo.setOrderItems(orderItems);
+        // 核心一行代码:Java对象 转 JSON格式字符串
+        String jsonStr = JSON.toJSONString(omsImportInfo);
+
+        // 2. 核心新增:JSON字符串 转 标准Base64编码字符串【解决你的核心问题】
+        String base64Str = Base64.getEncoder().encodeToString(jsonStr.getBytes(StandardCharsets.UTF_8));
+
+        return base64Str;
+    }
+
+
+}

+ 30 - 0
src/main/java/com/jeeplus/modules/workinvoice/utils/ThreadPoolUtil.java

@@ -0,0 +1,30 @@
+package com.jeeplus.modules.workinvoice.utils;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 传统MVC项目 全局单例 延迟任务线程池工具类
+ * 纯JDK原生,无任何依赖
+ */
+public class ThreadPoolUtil {
+    // 全局唯一的延迟任务线程池实例(单例)
+    private static final ScheduledExecutorService DELAY_POOL;
+
+    static {
+        DELAY_POOL = Executors.newScheduledThreadPool(10);
+    }
+
+    // ===== 原方法:保留不动 =====
+    public static void executeDelay(int delaySeconds, Runnable runnable) {
+        DELAY_POOL.schedule(runnable, delaySeconds, TimeUnit.SECONDS);
+    }
+
+    // ===== 新增重载方法:支持传入【自定义业务对象】,给异步线程用 =====
+    public static void executeDelay(int delaySeconds, Runnable runnable, Object... params) {
+        DELAY_POOL.schedule(runnable, delaySeconds, TimeUnit.SECONDS);
+    }
+
+    // 私有化构造方法,禁止外部new对象
+    private ThreadPoolUtil(){}
+}