Browse Source

销售订单明细新增附件

wzy 3 weeks ago
parent
commit
d9c53b0113

+ 2 - 0
mjava-jinlun/src/main/java/com/malk/jinlun/entity/SaleOrderEntry.java

@@ -48,4 +48,6 @@ public class SaleOrderEntry {
     boolean FDeliveryControl;//控制发货数量
     Double FDeliveryMaxQty;//发货上限
     Double FDeliveryMinQty;//发货下限
+
+    String F_Sl_Attachment;//订单图纸附件
 }

+ 326 - 6
mjava-jinlun/src/main/java/com/malk/jinlun/service/impl/JinlunServiceImpl.java

@@ -20,11 +20,17 @@ import com.malk.utils.UtilMap;
 import com.malk.utils.UtilToken;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.util.Strings;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.web.bind.annotation.RequestBody;
 
+import java.net.CookieManager;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
 import java.util.*;
 
 import static org.junit.Assert.fail;
@@ -98,10 +104,15 @@ public class JinlunServiceImpl implements JinlunService {
 
         for (Map address : addressList) {
             CustomerContactEntry customerContactEntry = new CustomerContactEntry();
-            customerContactEntry.setFNUMBER1(UtilMap.getString(address,"ShortText1758199808715"));//地点编码
-            customerContactEntry.setFNAME1(UtilMap.getString(address,"ShortText1758199824395"));//地点名称
+            String addressCode = UtilMap.getString(address, "ShortText1758199808715");
+            String addressName = UtilMap.getString(address, "ShortText1758199824395");
 
-            FT_BD_CUSTCONTACT.add(customerContactEntry);
+            if (Strings.isNotBlank(addressCode) && Strings.isNotBlank(addressName)){
+                customerContactEntry.setFNUMBER1(addressCode);//地点编码
+                customerContactEntry.setFNAME1(addressName);//地点名称
+
+                FT_BD_CUSTCONTACT.add(customerContactEntry);
+            }
         }
 
         customerModel.setFName(FName);
@@ -128,7 +139,10 @@ public class JinlunServiceImpl implements JinlunService {
         customerModel.setFSOCIALCRECODE(FSOCIALCRECODE);
         customerModel.setFINVOICETEL(FINVOICETEL);
         customerModel.setFINVOICEADDRESS(FINVOICEADDRESS);
-        customerModel.setFT_BD_CUSTCONTACT(FT_BD_CUSTCONTACT);
+
+        if (!FT_BD_CUSTCONTACT.isEmpty()){
+            customerModel.setFT_BD_CUSTCONTACT(FT_BD_CUSTCONTACT);
+        }
 
         save.setModel(customerModel);
         Gson gson2 = new Gson();
@@ -168,7 +182,7 @@ public class JinlunServiceImpl implements JinlunService {
             isTrue(repoRet2);
             customerCode = repoRet2.getResult().getResponseStatus().getSuccessEntitys().get(0).getNumber();
             //回写云枢客户编号
-            cpClient.updateCpBo("ZLKHLC",UtilMap.map("id, ShortText1758195840127",objectId,customerCode),null);
+//            cpClient.updateCpBo("ZLKHLC",UtilMap.map("id, ShortText1758195840127",objectId,customerCode),null);
 
             if (isTrue(repoRet2)){
                 Audit audit = new Audit();
@@ -413,6 +427,7 @@ public class JinlunServiceImpl implements JinlunService {
         List<Map> saleDetailList = UtilMap.getList(saleOrderData, "Sheet1758341252878");
         List<SaleOrderEntry> saleOrderEntryList = new ArrayList<>();
 
+
         for (Map saleDetail : saleDetailList) {
             FNumber FMaterialId = new FNumber(UtilMap.getString(saleDetail, "ShortText1758341332222"));//物料编码
             FNumber FUnitID = new FNumber(UtilMap.getString(saleDetail, "Text1758341368975"));//销售单位
@@ -441,7 +456,7 @@ public class JinlunServiceImpl implements JinlunService {
             Double FDeliveryMaxQty = UtilMap.getDouble(saleDetail, "Number1763091294543");//发货上限
             Double FDeliveryMinQty = UtilMap.getDouble(saleDetail, "Number1763091312359");//发货下限
 
-            //todo 订单图纸附件上传
+
 
 
             SaleOrderEntry saleOrderEntry = new SaleOrderEntry();
@@ -469,6 +484,25 @@ public class JinlunServiceImpl implements JinlunService {
             saleOrderEntry.setFDeliveryMaxQty(FDeliveryMaxQty);
             saleOrderEntry.setFDeliveryMinQty(FDeliveryMinQty);
 
+            //todo 订单图纸附件上传
+            List<Map> attachments = UtilMap.getList(saleDetail, "Attachment1763102109764");
+            if (!attachments.isEmpty()){
+                Map attachment = attachments.get(0);
+                String refId = UtilMap.getString(attachment, "refId");
+                String fileName = UtilMap.getString(attachment, "name");
+
+                String escapeRefId = new String(Base64.getUrlDecoder().decode(refId), StandardCharsets.UTF_8).substring(5);
+
+                String filePath = "/home/data/cloudpivot/file/"+escapeRefId.substring(0,2)+"/"+escapeRefId.substring(2,4)+"/"+escapeRefId;
+
+//                String fileName2 = "4.png";
+//                String filePath2 = "C:\\Users\\EDY\\Pictures\\4.png";
+
+                String fileId = kdUpload(fileName, filePath);
+
+                saleOrderEntry.setF_Sl_Attachment(fileId);
+            }
+
             saleOrderEntryList.add(saleOrderEntry);
         }
 
@@ -615,4 +649,290 @@ public class JinlunServiceImpl implements JinlunService {
         }
     }
 
+    private String kdUpload(String fileName,String filePath){
+        try {
+            // 1. 读取文件为字节数组
+            byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
+
+            // 2. 转换为Base64字符串
+            String base64String = "";
+
+            K3CloudApi client = new K3CloudApi(initIden());
+
+            // 定义分块大小(4MB)
+            final int CHUNK_SIZE = 4 * 1024 * 1024;
+
+            long fileSize = fileBytes.length;
+
+            String fileId = null;
+
+            //sdk无直接上传附件接口 临时将附件上传并绑定固定单据 得到fileId
+            Map map = new HashMap();
+            map.put("FileName", fileName);//文件名
+            map.put("FormId", "SAL_SaleOrder");//表单id
+            map.put("InterId", "104784");//单据内码
+            map.put("BillNO", "S301-251118005");//单据编号
+
+
+            // 如果文件小于等于4MB,直接上传
+            if (fileSize <= CHUNK_SIZE) {
+                base64String = Base64.getEncoder().encodeToString(fileBytes);
+
+                map.put("IsLast", true);//是否最后一次上传
+                map.put("SendByte", base64String);//文件字节数组转base64后的字符串
+
+                String resultJson = client.attachmentUpload(JSONObject.toJSONString(map));
+
+                Map parse = (Map) JSONObject.parse(resultJson);
+
+                Map map1 = UtilMap.getMap(parse, "Result");
+
+                fileId = UtilMap.getString(map1, "FileId");
+
+                return fileId;
+            }
+
+            // 分块上传逻辑
+            int totalChunks = (int) Math.ceil((double) fileSize / CHUNK_SIZE);
+
+            for (int i = 0; i < totalChunks; i++) {
+                // 计算当前块的起始和结束位置
+                int start = i * CHUNK_SIZE;
+                int end = (int) Math.min(start + CHUNK_SIZE, fileSize);
+                int chunkLength = end - start;
+
+                // 提取当前分块的数据
+                byte[] chunkBytes = new byte[chunkLength];
+                System.arraycopy(fileBytes, start, chunkBytes, 0, chunkLength);
+
+                // 转换为Base64字符串
+                base64String = Base64.getEncoder().encodeToString(chunkBytes);
+
+                // 第一个块需要获取FileId,后续块需要携带FileId
+                boolean isLastChunk = (i == totalChunks - 1);
+
+                if (i == 0) {
+                    // 第一个块,IsLast为false
+                    map.put("IsLast", false);
+                    map.put("SendByte", base64String);//文件字节数组转base64后的字符串
+
+                    String resultJson = client.attachmentUpload(JSONObject.toJSONString(map));
+
+                    Map parse = (Map) JSONObject.parse(resultJson);
+
+                    Map map1 = UtilMap.getMap(parse, "Result");
+
+                    fileId = UtilMap.getString(map1, "FileId");
+                } else {
+                    // 后续块,需要携带FileId
+                    map.put("FileId", fileId);
+                    map.put("IsLast", isLastChunk);
+                    map.put("SendByte", base64String);//文件字节数组转base64后的字符串
+
+                    String resultJson = client.attachmentUpload(JSONObject.toJSONString(map));
+                }
+            }
+
+            System.out.println(fileId);
+
+            return fileId;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /*private String kdUpload(String fileName, String filePath) {
+        try {
+            // 定义分块大小(4MB)
+            final int CHUNK_SIZE = 4 * 1024 * 1024;
+
+            // 1. 读取文件为字节数组
+            byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
+            long fileSize = fileBytes.length;
+
+            String fileId = null;
+
+            // 如果文件小于等于4MB,直接上传
+            if (fileSize <= CHUNK_SIZE) {
+                String base64String = Base64.getEncoder().encodeToString(fileBytes);
+
+                Map<String, Object> body = new HashMap<>();
+                Map<String, Object> data = new HashMap<>();
+                data.put("FileName", fileName);
+                data.put("SendByte", base64String);
+                data.put("IsLast", true);
+                body.put("data",data);
+
+                // 调用上传接口(带会话检查)
+                Map<String, Object> result = uploadWithSessionCheck(body, true);
+
+                // 获取FileId
+                Map<String, Object> resultData = UtilMap.getMap(result, "Result");
+                fileId = UtilMap.getString(resultData, "FileId");
+
+                return fileId;
+            }
+
+            // 分块上传逻辑
+            int totalChunks = (int) Math.ceil((double) fileSize / CHUNK_SIZE);
+
+            for (int i = 0; i < totalChunks; i++) {
+                // 计算当前块的起始和结束位置
+                int start = i * CHUNK_SIZE;
+                int end = (int) Math.min(start + CHUNK_SIZE, fileSize);
+                int chunkLength = end - start;
+
+                // 提取当前分块的数据
+                byte[] chunkBytes = new byte[chunkLength];
+                System.arraycopy(fileBytes, start, chunkBytes, 0, chunkLength);
+
+                // 转换为Base64字符串
+                String base64String = Base64.getEncoder().encodeToString(chunkBytes);
+
+                // 准备请求体
+                Map<String, Object> body = new HashMap<>();
+                Map<String,Object> data = new HashMap<>();
+                data.put("FileName", fileName);
+                data.put("SendByte", base64String);
+                body.put("data",data);
+
+                // 第一个块需要获取FileId,后续块需要携带FileId
+                boolean isLastChunk = (i == totalChunks - 1);
+
+                if (i == 0) {
+                    // 第一个块,IsLast为false
+                    body.put("IsLast", false);
+
+                    // 调用上传接口(带会话检查)
+                    Map<String, Object> result = uploadWithSessionCheck(body, false);
+
+                    // 获取FileId
+                    Map<String, Object> resultData = UtilMap.getMap(result, "Result");
+                    fileId = UtilMap.getString(resultData, "FileId");
+                } else {
+                    // 后续块,需要携带FileId
+                    body.put("FileId", fileId);
+                    body.put("IsLast", isLastChunk);
+
+                    // 调用上传接口(带会话检查)
+                    uploadWithSessionCheck(body, isLastChunk);
+                }
+            }
+
+            return fileId;
+        } catch (Exception e) {
+            throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
+        }
+    }
+
+    // 带会话检查的上传方法(支持单块和多块上传)
+    private Map<String, Object> uploadWithSessionCheck(Map<String, Object> body, boolean isLastChunk) {
+        while (true) {
+            try {
+                // 调用上传接口
+                Map<String, Object> result = (Map<String, Object>) JSONObject.parse(UtilHttp.doPost(kdWebApiConf.getXKDApiServerUrl() + "Kingdee.BOS.WebApi.ServicesStub.DynamicFormService.UploadFile.common.kdsvc", null, null, body));
+
+                // 检查响应状态
+                checkResponseStatus(result);
+
+                return result;
+            } catch (RuntimeException e) {
+                // 解析错误响应
+                try {
+                    Map<String, Object> responseStatus = (Map<String, Object>) JSONObject.parse(e.getMessage());
+
+                    // 如果是会话丢失错误
+                    if (responseStatus != null && UtilMap.getString(((List<Map>)UtilMap.getList(responseStatus, "Errors")).get(0),"Message").equals("会话信息已丢失,请重新登录")) {
+                        // 重新登录
+                        kdLogin();
+
+                        // 如果是最后一块上传失败,需要设置IsLast为true
+                        if (isLastChunk) {
+                            body.put("IsLast", true);
+                        }
+
+                        // 重试上传
+                        continue;
+                    }
+                } catch (Exception parseException) {
+                    // 如果不是会话丢失错误,直接抛出原始异常
+                    throw e;
+                }
+
+                // 如果不是会话丢失错误,直接抛出原始异常
+                throw e;
+            }
+        }
+    }
+
+    // 检查响应状态
+    private void checkResponseStatus(Map<String, Object> result) {
+        if (result == null || !result.containsKey("Result")) {
+            throw new RuntimeException("无效的响应格式");
+        }
+
+        Map<String, Object> resultData = UtilMap.getMap(result, "Result");
+        Map<String, Object> responseStatus = UtilMap.getMap(resultData, "ResponseStatus");
+
+        if (responseStatus == null || !UtilMap.getBoolean(responseStatus, "IsSuccess")) {
+            String message = UtilMap.getString(resultData, "Message");
+            // 如果有错误码,也包含在异常信息中
+            int errorCode = UtilMap.getInt(responseStatus, "ErrorCode");
+            if (errorCode != 0) {
+                message = "错误码: " + errorCode + ", 消息: " + message;
+                log.info("message: {}", message);
+            }
+            throw new RuntimeException(JSONObject.toJSONString(responseStatus));
+        }
+    }
+    private void kdLogin(){
+        try {
+            //金蝶登录
+            String acctID = kdWebApiConf.getXKDApiAcctID();
+            String username = kdWebApiConf.getXKDApiUserName();
+            String appId = kdWebApiConf.getXKDApiAppID();
+            String appSec = kdWebApiConf.getXKDApiAppSec();
+            String timestamp = System.currentTimeMillis()/1000 + "";
+
+            String[] arr = {acctID,username,appId,appSec,timestamp};
+
+            Arrays.sort(arr, String::compareTo);
+
+            String str = String.join("", arr);
+
+            // 1. 创建 MessageDigest 实例,指定 SHA-256 算法
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+
+            // 2. 输入字符串转换为字节数组,并计算哈希值
+            byte[] hashBytes = digest.digest(str.getBytes(StandardCharsets.UTF_8));
+
+            // 3. 将字节数组转换为十六进制字符串
+            StringBuilder sign = new StringBuilder();
+            for (byte b : hashBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    sign.append('0'); // 补零
+                }
+                sign.append(hex);
+            }
+
+            // 4. 输出结果
+            System.out.println("SHA-256 哈希值: " + sign.toString());
+
+            Map body = new HashMap();
+            body.put("acctID",acctID);
+            body.put("username",username);
+            body.put("appId",appId);
+            body.put("timestamp",timestamp);
+            body.put("sign",sign.toString());
+            body.put("lcid",2052);
+
+            UtilHttp.doPost(kdWebApiConf.getXKDApiServerUrl() + "Kingdee.BOS.WebApi.ServicesStub.AuthService.LoginBySign.common.kdsvc", null, null, body);
+
+        }catch (Exception e){
+            throw new RuntimeException(e);
+        }
+
+    }*/
+
 }

+ 288 - 3
mjava-jinlun/src/test/java/com/malk/jinlun/DdTest.java

@@ -3,6 +3,15 @@ package com.malk.jinlun;
 import com.alibaba.fastjson.JSONObject;
 import com.auth0.jwt.JWT;
 import com.auth0.jwt.algorithms.Algorithm;
+import com.google.gson.Gson;
+import com.kingdee.bos.webapi.entity.IdentifyInfo;
+import com.kingdee.bos.webapi.entity.RepoResult;
+import com.kingdee.bos.webapi.entity.RepoRet;
+import com.kingdee.bos.webapi.sdk.K3CloudApi;
+import com.kingdee.bos.webapi.sdk.WebApiClient;
+import com.malk.jinlun.config.KDWebApiConf;
+import com.malk.jinlun.entity.*;
+import com.malk.jinlun.service.CpClient;
 import com.malk.jinlun.service.JinlunService;
 import com.malk.jinlun.service.JinlunTaskService;
 import com.malk.utils.UtilHttp;
@@ -16,14 +25,22 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.junit4.SpringRunner;
 
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.MessageDigest;
+import java.util.*;
 
 @Slf4j
 @SpringBootTest
 @RunWith(SpringRunner.class)
 public class DdTest {
+    @Autowired
+    private KDWebApiConf kdWebApiConf;
+
+    @Autowired
+    private CpClient cpClient;
+
     @Autowired
     private JinlunService jinlunService;
 
@@ -54,6 +71,274 @@ public class DdTest {
         jinlunTaskService.syncSaleReturn();
     }
 
+    @Test
+    public void test8() {
+        try {
+            String filePath = "C:\\Users\\EDY\\Pictures\\4.png"; // 替换为你的文件路径
+
+            // 1. 读取文件为字节数组
+            byte[] fileBytes = Files.readAllBytes(Paths.get(filePath));
+
+            // 2. 转换为Base64字符串
+            String base64String = Base64.getEncoder().encodeToString(fileBytes);
+
+            System.out.println("Base64编码结果:");
+            System.out.println(base64String);
+
+
+            K3CloudApi client = new K3CloudApi(initIden());
+
+
+
+            Map map = new HashMap();
+            map.put("FileName", "4.png");//文件名
+            map.put("FormId", "SAL_SaleOrder");//表单id
+            map.put("IsLast", true);//是否最后一次上传
+            map.put("InterId", "104784");//单据内码
+//            map.put("Entrykey", "");//单据体标识
+//            map.put("EntryinterId", "-1");//分录内码
+            map.put("BillNO", "S301-251118005");//单据编号
+//        map.put("AliasFileName", "test");
+//        map.put("FileId", "");
+            map.put("SendByte", base64String);//文件字节数组转base64后的字符串
+
+            String resultJson = client.attachmentUpload(JSONObject.toJSONString(map));
+
+            Map parse = (Map) JSONObject.parse(resultJson);
+
+            Map map1 = UtilMap.getMap(parse, "Result");
+
+            String fileId = UtilMap.getString(map1, "FileId");
+
+            System.out.println(fileId);
+
+
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Test
+    public void test9() {
+        //查询云枢客户单据详情
+        String objectId = "264ae110db184912a63af53738cd4a0f";
+
+        Map saleOrder = cpClient.getCpBo("XSDD", objectId, null);
+
+        Map saleOrderData = cpClient.getCpBoResultData(saleOrder);
+
+        //请求参数,要求为json字符串
+        Save save = new Save();
+        SaleOrderModel saleOrderModel = new SaleOrderModel();
+
+        //基本信息
+        FNumber FBillTypeID = new FNumber(UtilMap.getString(saleOrderData, "Dropdown1758339062589_key"));//单据类型
+        String FDate = "2025-11-18 00:00:00";//日期
+        FNumber FCustId = new FNumber(UtilMap.getString(saleOrderData, "ShortText1760426148831"));//客户
+        String F_NLD_DDTYPE = UtilMap.getString(saleOrderData, "Dropdown1758340059699_key");//订单类型
+        FNumber FSalerId = new FNumber(UtilMap.getString(saleOrderData, "ShortText1760421121913"));//销售员
+        FSTAFFNUMBER F_Sl_gendanyuan = new FSTAFFNUMBER(UtilMap.getString(saleOrderData, "Text1760421147555"));//跟单员
+        String FBusinessType = UtilMap.getString(saleOrderData,"Dropdown1758339265813_key");//业务类型
+        String F_WGR_Combo_re5 = UtilMap.getString(saleOrderData,"Dropdown1758340112723_key");//订单要求
+        FNumber FHEADLOCID = new FNumber(UtilMap.getString(saleOrderData, "ShortText1760507577966"));//交货地点
+        String FNote = UtilMap.getString(saleOrderData,"LongText1758339516625");//备注
+        String F_Sl_customerbillno = UtilMap.getString(saleOrderData,"ShortText1758340044946");//客户订单号
+
+        saleOrderModel.setFBillTypeID(FBillTypeID);
+        saleOrderModel.setFDate(FDate);
+        saleOrderModel.setFCustId(FCustId);
+        saleOrderModel.setF_NLD_DDTYPE(F_NLD_DDTYPE);
+        saleOrderModel.setFSalerId(FSalerId);
+        saleOrderModel.setF_Sl_gendanyuan(F_Sl_gendanyuan);
+        saleOrderModel.setFBusinessType(FBusinessType);
+        saleOrderModel.setF_WGR_Combo_re5(F_WGR_Combo_re5);
+        saleOrderModel.setFHEADLOCID(FHEADLOCID);
+        saleOrderModel.setFNote(FNote);
+        saleOrderModel.setF_Sl_customerbillno(F_Sl_customerbillno);
+
+        //财务信息
+        SaleOrderFinance saleOrderFinance = new SaleOrderFinance();
+        FNumber FSettleCurrId = new FNumber(UtilMap.getString(saleOrderData, "Dropdown1758339227503_key"));//结算币别
+        FNumber FRecConditionId = new FNumber(UtilMap.getString(saleOrderData, "Dropdown1758339314401_key"));//收款条件
+        FNumber FSettleModeId = new FNumber(UtilMap.getString(saleOrderData, "Dropdown1758340854092_key"));//结算方式
+        boolean FIsIncludedTax = UtilMap.getBoolean(saleOrderData,"Logic1758340099868");//是否含税
+        boolean FIsPriceExcludeTax = UtilMap.getBoolean(saleOrderData,"Logic1758340955492");//价外税
+
+        saleOrderFinance.setFSettleCurrId(FSettleCurrId);
+        saleOrderFinance.setFRecConditionId(FRecConditionId);
+        saleOrderFinance.setFSettleModeId(FSettleModeId);
+        saleOrderFinance.setFIsIncludedTax(FIsIncludedTax);
+        saleOrderFinance.setFIsPriceExcludeTax(FIsPriceExcludeTax);
+
+        saleOrderModel.setFSaleOrderFinance(saleOrderFinance);
+
+        //明细信息
+        List<Map> saleDetailList = UtilMap.getList(saleOrderData, "Sheet1758341252878");
+        List<SaleOrderEntry> saleOrderEntryList = new ArrayList<>();
+
+        String[] fileIds = {"276d028be0d5478695673cc985f058f2","70ae9854204f4365a8ac3baa48a5b9bd"};
+
+        int i=0;
+        for (Map saleDetail : saleDetailList) {
+            FNumber FMaterialId = new FNumber(UtilMap.getString(saleDetail, "ShortText1758341332222"));//物料编码
+            FNumber FUnitID = new FNumber("Kg");//销售单位
+            Double FQty = 1.0;//销售数量
+            FNumber FPriceUnitId = new FNumber("Kg");//计价单位
+            Double FPriceUnitQty = 1.0;//计价数量
+
+            String F_Sl_XSTYPE = UtilMap.getString(saleDetail, "Dropdown1758341293022_key");//产品类别
+
+            Double FTaxPrice = 1.0;//含税单价
+            boolean FIsFree = UtilMap.getBoolean(saleDetail,"Logic1758341430353");//是否赠品
+            Double FEntryTaxRate = UtilMap.getDouble(saleDetail, "Number1758349078527");//税率
+//            String FDeliveryDate = UtilMap.getString(saleDetail,"Date1758341499356");//客户交期
+//            String FEntryNote = UtilMap.getString(saleDetail,"Date1758341499356");//备注
+            Double F_VMKV_MPPRICE = 1.0;//报价价格
+//            String F_VMKV_SCCQ1 = UtilMap.getString(saleDetail,"Dropdown1760425152219_key");//生产厂区
+//            String F_VMKV_JSYQ = UtilMap.getString(saleDetail,"ShortText1762135907570");//技术要求
+//            String F_NLD_BR = UtilMap.getString(saleDetail,"ShortText1762135907570");//剩磁(Br)
+//            String F_NLD_Hcj = UtilMap.getString(saleDetail,"ShortText1762135907570");//内禀矫顽力(Hcj)
+//            String F_NLD_Hcb = UtilMap.getString(saleDetail,"ShortText1762135907570");//磁感矫顽力(Hcb)
+//            String F_NLD_BH = UtilMap.getString(saleDetail,"ShortText1762135907570");//最大磁能积(BH)max
+//            String F_NLD_Hk = UtilMap.getString(saleDetail,"ShortText1762135907570");//方形度Hk/Hcj
+//            String F_VMKV_GXYQ = UtilMap.getString(saleDetail,"ShortText1762135907570");//公差要求
+//
+//            boolean FDeliveryControl = UtilMap.getBoolean(saleDetail,"Logic1763091193161");//控制发货数量
+//            Double FDeliveryMaxQty = UtilMap.getDouble(saleDetail, "Number1763091294543");//发货上限
+//            Double FDeliveryMinQty = UtilMap.getDouble(saleDetail, "Number1763091312359");//发货下限
+
+            //todo 订单图纸附件上传
+            String file = fileIds[i];
+
+            i++;
+
+
+            SaleOrderEntry saleOrderEntry = new SaleOrderEntry();
+            saleOrderEntry.setFMaterialId(FMaterialId);
+            saleOrderEntry.setFUnitID(FUnitID);
+            saleOrderEntry.setFPriceUnitId(FPriceUnitId);
+            saleOrderEntry.setFPriceUnitQty(FPriceUnitQty);
+            saleOrderEntry.setF_Sl_XSTYPE(F_Sl_XSTYPE);
+            saleOrderEntry.setFQty(FQty);
+            saleOrderEntry.setFTaxPrice(FTaxPrice);
+            saleOrderEntry.setFIsFree(FIsFree);
+            saleOrderEntry.setFEntryTaxRate(FEntryTaxRate);
+//            saleOrderEntry.setFDeliveryDate(FDeliveryDate);
+//            saleOrderEntry.setFEntryNote(FEntryNote);
+            saleOrderEntry.setF_VMKV_MPPRICE(F_VMKV_MPPRICE);
+//            saleOrderEntry.setF_VMKV_SCCQ1(F_VMKV_SCCQ1);
+//            saleOrderEntry.setF_VMKV_JSYQ(F_VMKV_JSYQ);
+//            saleOrderEntry.setF_NLD_BR(F_NLD_BR);
+//            saleOrderEntry.setF_NLD_Hcj(F_NLD_Hcj);
+//            saleOrderEntry.setF_NLD_Hcb(F_NLD_Hcb);
+//            saleOrderEntry.setF_NLD_BH(F_NLD_BH);
+//            saleOrderEntry.setF_NLD_Hk(F_NLD_Hk);
+//            saleOrderEntry.setF_VMKV_GXYQ(F_VMKV_GXYQ);
+//            saleOrderEntry.setFDeliveryControl(FDeliveryControl);
+//            saleOrderEntry.setFDeliveryMaxQty(FDeliveryMaxQty);
+//            saleOrderEntry.setFDeliveryMinQty(FDeliveryMinQty);
+
+            saleOrderEntry.setF_Sl_Attachment(file);
+
+            saleOrderEntryList.add(saleOrderEntry);
+        }
+
+        saleOrderModel.setFSaleOrderEntry(saleOrderEntryList);
+
+        save.setModel(saleOrderModel);
+        Gson gson2 = new Gson();
+        String jsonData = gson2.toJson(save);
+
+        log.info(jsonData);
+
+        K3CloudApi client = new K3CloudApi(initIden());
+        //业务对象标识
+        String formId = "SAL_SaleOrder";
+        //调用接口
+        String resultJson = "";
+        try {
+            resultJson = client.save(formId,jsonData);
+        }catch (Exception e){
+            throw new RuntimeException(e);
+        }
+        //用于记录结果
+        Gson gson = new Gson();
+        //对返回结果进行解析和校验
+        RepoRet repoRet = gson.fromJson(resultJson, RepoRet.class);
+
+    }
+
+    @Test
+    public void test10(){
+        try {
+            String acctID = "66816d8a115ffc";
+            String username = "Administrator";
+            String appId = "295561_7e1I0bHHVqGaSZxJ7Z4pVxxs7L5bxMMI";
+            String appSec = "0f51467e39764b548ec3d75159dfbd2b";
+            String timestamp = System.currentTimeMillis()/1000 + "";
+
+            String[] arr = {acctID,username,appId,appSec,timestamp};
+
+            Arrays.sort(arr, (a, b) -> a.compareTo(b));
+
+            System.out.println("排序结果:");
+
+            for (String s : arr) {
+                System.out.println(s);
+            }
+
+
+            String str = String.join("", arr);
+
+            // 1. 创建 MessageDigest 实例,指定 SHA-256 算法
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+
+            // 2. 输入字符串转换为字节数组,并计算哈希值
+            byte[] hashBytes = digest.digest(str.getBytes(StandardCharsets.UTF_8));
+
+            // 3. 将字节数组转换为十六进制字符串
+            StringBuilder hexString = new StringBuilder();
+            for (byte b : hashBytes) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    hexString.append('0'); // 补零
+                }
+                hexString.append(hex);
+            }
+
+            // 4. 输出结果
+            System.out.println("SHA-256 哈希值: " + hexString.toString());
+        }catch (Exception e){
+            throw new RuntimeException(e);
+        }
+
+
+    }
+
+    @Test
+    public void test11(){
+//        String input = "RElSRV82NjdmODEyMmJkYzM0NDUyYWMyYjM3Mjg5NTAzYjhhMjEucG5n";
+        String input = "RElSRV9iNWViMTdlZTA1NTI0OTU1YmM3MjM3YWEyMzU5OTY5MDIucG5n";
+        String s = new String(Base64.getUrlDecoder().decode(input), StandardCharsets.UTF_8);
+
+        System.out.println(s);
+
+    }
+
+    private IdentifyInfo initIden(){
+        //注意 1:此处不再使用参数形式传入用户名及密码等敏感信息,改为在登录配置文件中设置。
+        //注意 2:必须先配置第三方系统登录授权信息后,再进行业务操作,详情参考各语言版本SDK介绍中的登录配置文件说明。
+        //读取配置,初始化SDK
+        IdentifyInfo iden = new IdentifyInfo();
+        iden.setUserName(kdWebApiConf.getXKDApiUserName());
+        iden.setAppId(kdWebApiConf.getXKDApiAppID());
+        iden.setdCID(kdWebApiConf.getXKDApiAcctID());
+        iden.setAppSecret(kdWebApiConf.getXKDApiAppSec());
+        iden.setServerUrl(kdWebApiConf.getXKDApiServerUrl());
+
+        return iden;
+    }
+
 
 
     @Test