在Salesforce中搭建集成日志管理集成信息

172 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。
【前言】:我们知道做集成时,由于不能预知什么时候会出现意料之外的异常,在异常出现时如果没有对用户或者某个类设置Debug Log,很难重现问题并排错。因为不能永久性的检测同步时系统debug的数据。所以,新建一个集成日志对象来管理集成信息就显得尤为重要。

在介绍自定义集成日志前,不妨先看下sf处理Test Class异常的日志页面:
【搭建步骤】:

  1. 创建Integration Log数据模型:

  2. 调整相关布局:

  3. 360view 3.1 主记录视图

3.2 最近查看视图

3.3 列表视图 - 根据apex class做filter

【集成Sample】: 实例1:ACC_QuoteSyncApprovalResult

/**
 * @author SetsuNa.Wang
 */
@RestResource(urlMapping = '/ACC_QuoteSyncApprovalResult/*')
global class ACC_QuoteSyncApprovalResult {
	// request parameters
    global class ParameterData {
    	public String QuoteNumber;
		public String RISQuoteApproveNumber;
		public String ApproveResult;
		public String ApproveRemarks;
    }
    global class ParameterDataList {
    	public List<ParameterData> paraList;
    }
	// response result
	global class SyncReturnResult {
		public String QUOTENUMBER;
		public String FAILREASON;
		public String ISSUCCESS;// String True/False
    }

    @HttpPost
    global static List<SyncReturnResult> doPost() {
    	List<SyncReturnResult> rt = new List<SyncReturnResult>();
    	Integration_Log__c log = new Integration_Log__c();
    	log.Integration_Log__c = '';
    	log.Integration_Apex__c = 'ACC_QuoteSyncApprovalResult';
    	log.Start_Time__c = datetime.now().format('yyyy-MM-dd HH:mm:ss');

        try{
        	RestRequest req = RestContext.request;
        	
	        //Convert the requestBody into a string
	        String bodyStr = req.requestBody.toString();
	        System.debug('bodyStr: ' + bodyStr);
            log.Integration_Log__c = 'Request Body: ' + bodyStr;
	        ParameterDataList parameter = new ParameterDataList();
	        
	        //Deserialize the received Json data to the Apex object 
	        parameter = (ParameterDataList)JSON.deserialize(bodyStr, ParameterDataList.class);
	        System.debug('parameter: ' + parameter + ' - parameter.paraList: ' + parameter.paraList);
	        
        	if(!parameter.paraList.isEmpty()) {
        		List<String> quoteIdList = new List<String>();
        		// Get quote id's list from params.
		        for(ParameterData para : parameter.paraList) {
        			System.debug('start loop...');
        			quoteIdList.add(para.QuoteNumber);
		        }
		        
		        // Query the quote list by id list from params.
		        List<Quote> queryList = new List<Quote>();
		        Map<String, Quote> updateMap = new Map<String, Quote>();
		        List<String> invalidData = new List<String>();
		        queryList = [SELECT Id, ACC_Judgement_ID__c, ACC_Quote_Number_Ext__c, Status FROM Quote WHERE ACC_Quote_Number_Ext__c IN: quoteIdList];
		        //queryList = [SELECT Id, ACC_Judgement_ID__c, Status FROM Quote];
		        system.debug('queryList:' + queryList);
		        
		        // Create map of update quote
		        if (queryList.size() > 0) {
			        for (Quote quote : queryList) {
			        	updateMap.put(quote.ACC_Quote_Number_Ext__c, quote);
			        }
			    // If query result is empty, return fail reason and insert log.
		        } else {
		        	SyncReturnResult syncResult = new SyncReturnResult();
	        		syncResult.IsSuccess = 'False';
	        		syncResult.FailReason = Label.ACC_Interface_No_Valid_Data_Error_Msg;
	        		rt.add(syncResult);
	        		
		        	log.IsSuccess__c = false;
		        	log.Excepition__c = Label.ACC_Interface_No_Valid_Data_Error_Msg;
			        log.End_Time__c = datetime.now().format('yyyy-MM-dd HH:mm:ss');
			        system.debug('log:' + log);
			        insert log;
			        
			        RestResponse resp = RestContext.response;
			        String respJson = json.serialize(rt);
			        system.debug('json string ------------>' + respJson);
			        String respBody = EncodingUtil.urlDecode(respJson, 'utf-8');
			        resp.responseBody = Blob.valueof(respBody);
			        return rt;
		        }
		        
		        Map<String, Quote> updateQuoteMap = new Map<String, Quote>();
		        // Loop the parameters, divide update quote and invalid quote.
		        for(ParameterData para : parameter.paraList) {
		        	SyncReturnResult syncResult = new SyncReturnResult();
		        	
		        	// Add exist quote into update list.
		        	if (updateMap.get(para.QuoteNumber) != null) {
		        		Quote quote = updateMap.get(para.QuoteNumber);
		        		
		        		if (String.isBlank(para.ApproveRemarks) && !'Approved'.equals(para.ApproveResult)) {
		        			syncResult.IsSuccess = 'false';
		        			syncResult.FailReason = 'Approval remarks cannot be empty.';
		        		} else {
			        		quote.ACC_Judgement_ID__c = para.RISQuoteApproveNumber;
			        		quote.Status = 'Approved'.equals(para.ApproveResult) ? Label.ACC_Quote_Status_Approved_In_OA : 'Rejected';
			        		quote.ACC_Approval_Remarks__c = para.ApproveRemarks; // TODO waiting for BA confirm.OA
                            quote.ACC_Need_Send_Rejection_Email__c = true;	//trigger workflow to send email notification
			        		updateQuoteMap.put(quote.Id, quote);
			        		syncResult.IsSuccess = 'True';
		        		}
		        	// add invalid quote into invalidData list.
		        	} else {
		        		invalidData.add(para.QuoteNumber);
		        		syncResult.IsSuccess = 'False';
		        		syncResult.FailReason = 'QuoteNumber is not exist.';
		        	}
	        		syncResult.QuoteNumber = para.QuoteNumber;
	        		// response body.
		        	rt.add(syncResult);
		        }
	        	List<Quote> updateList = updateQuoteMap.values();
	        	if (updateList.size() > 0) {
	        		update updateList;
	        		log.Integration_Log__c += 'Successed:' + updateList;
	        	}
        		log.Integration_Log__c += 'Failed:' + invalidData;
	        	log.IsSuccess__c = true;
	        }else {
	        	log.IsSuccess__c = false;
        		log.Integration_Log__c += 'The parameter cannot be null.';
	        }
        }catch(Exception e) {
        	system.debug(e.getMessage());
        	log.IsSuccess__c = false;
        	log.Excepition__c = e.getMessage();
        }
        log.End_Time__c = datetime.now().format('yyyy-MM-dd HH:mm:ss');
        system.debug('log:' + log);
        insert log;

        RestResponse resp = RestContext.response;
        String respJson = json.serialize(rt);
        system.debug('json string ------------>' + respJson);
        String respBody = EncodingUtil.urlDecode(respJson, 'utf-8');
        resp.responseBody = Blob.valueof(respBody);
        return rt;
    }
}

Test Class:

@isTest
private class ACC_QuoteSyncApprovalResultTest {
	@testSetUp static void setup() {
		//insert custom setting 
        Running_number__c objseq = new Running_number__c(name = 'number chk', seq__c = 1);
        insert objseq;

        Account a = new Account();
        a.Name = 'testAccount';
        insert a;

    	Opportunity opp = new Opportunity();
    	opp.Name = 'opp1';
    	opp.StageName = 'Design';
    	opp.CloseDate = System.Today();
    	insert opp;

        Quote q = new Quote();
        q.Name = 'syncQuoteResult';
        q.RecordTypeId = ACC_Utility.getRecordTypeMapBySobj('Quote').get('ACC_PAPAGZ');
        q.Quote_To_Account__c = a.Id;
        q.OpportunityId = opp.Id;
        insert q;
        
        Quote quote = new Quote(
            Name = 'TestQuote-Click01',
            RecordTypeId = ACC_Utility.getRecordTypeMapBySobj('Quote').get('ACC_PAPAGZ'),
            OpportunityId = opp.Id,
            Status = 'Approved in OA'
            //IsSyncing = false
        );
         
    //    insert quote;
	}

    static testMethod void testParameterNull() {
    	ACC_QuoteSyncApprovalResult.ParameterDataList jsonData = new ACC_QuoteSyncApprovalResult.ParameterDataList();
    	jsonData.paraList = new List<ACC_QuoteSyncApprovalResult.ParameterData>();

        RestRequest req = new RestRequest(); 
	    RestResponse res = new RestResponse();
        String reqJson = json.serialize(jsonData);
        System.debug('reqJson = ' + reqJson);
        req.requestBody = Blob.valueof(reqJson);
	    req.requestURI = 'https://cac-pa--partial.cs97.my.salesforce.com/services/apexrest/ACC_QuoteSyncApprovalResult/';  
	    req.httpMethod = 'POST';
	    RestContext.request = req;
	    RestContext.response = res;
	    
	    ACC_QuoteSyncApprovalResult.doPost();
    }

    static testMethod void testQueryResultEmpty() {
    	ACC_QuoteSyncApprovalResult.ParameterDataList jsonData = new ACC_QuoteSyncApprovalResult.ParameterDataList();
    	jsonData.paraList = new List<ACC_QuoteSyncApprovalResult.ParameterData>();

    	Test.startTest();
        ACC_QuoteSyncApprovalResult.ParameterData item = new ACC_QuoteSyncApprovalResult.ParameterData();
        jsonData.paraList.add(item);

        RestRequest req = new RestRequest(); 
	    RestResponse res = new RestResponse();
        String reqJson = json.serialize(jsonData);
        System.debug('reqJson = ' + reqJson);
        req.requestBody = Blob.valueof(reqJson);
	    req.requestURI = 'https://cac-pa--partial.cs97.my.salesforce.com/services/apexrest/ACC_QuoteSyncApprovalResult/';  
	    req.httpMethod = 'POST';
	    RestContext.request = req;
	    RestContext.response = res;
	    
	    ACC_QuoteSyncApprovalResult.doPost();

	    Test.stopTest();
    }

    static testMethod void testRejectedRemarksEmpty() {
        ACC_QuoteSyncApprovalResult.ParameterDataList jsonData = new ACC_QuoteSyncApprovalResult.ParameterDataList();
        jsonData.paraList = new List<ACC_QuoteSyncApprovalResult.ParameterData>();

        Quote q = [select Id, ACC_Quote_Number_Ext__c from Quote where Name = 'syncQuoteResult' limit 1];

        Test.startTest();
        ACC_QuoteSyncApprovalResult.ParameterData item = new ACC_QuoteSyncApprovalResult.ParameterData();
        item.QuoteNumber = q.ACC_Quote_Number_Ext__c;
        item.RISQuoteApproveNumber = '123456';
        item.ApproveResult = 'Rejected';
        jsonData.paraList.add(item);

        RestRequest req = new RestRequest(); 
        RestResponse res = new RestResponse();
        String reqJson = json.serialize(jsonData);
        System.debug('reqJson = ' + reqJson);
        req.requestBody = Blob.valueof(reqJson);
        req.requestURI = 'https://cac-pa--partial.cs97.my.salesforce.com/services/apexrest/ACC_QuoteSyncApprovalResult/';  
        req.httpMethod = 'POST';
        RestContext.request = req;
        RestContext.response = res;
        
        ACC_QuoteSyncApprovalResult.doPost();

        Test.stopTest();
    }

    static testMethod void testQuoteSuccess() {
        ACC_QuoteSyncApprovalResult.ParameterDataList jsonData = new ACC_QuoteSyncApprovalResult.ParameterDataList();
        jsonData.paraList = new List<ACC_QuoteSyncApprovalResult.ParameterData>();

        Quote q = [select Id, ACC_Quote_Number_Ext__c from Quote where Name = 'syncQuoteResult' limit 1];

        Test.startTest();
        ACC_QuoteSyncApprovalResult.ParameterData item = new ACC_QuoteSyncApprovalResult.ParameterData();
        item.QuoteNumber = q.ACC_Quote_Number_Ext__c;
        item.RISQuoteApproveNumber = '123456';
        item.ApproveResult = 'Approved';
        item.ApproveRemarks = 'ok';
        jsonData.paraList.add(item);

        RestRequest req = new RestRequest(); 
        RestResponse res = new RestResponse();
        String reqJson = json.serialize(jsonData);
        System.debug('reqJson = ' + reqJson);
        req.requestBody = Blob.valueof(reqJson);
        req.requestURI = 'https://cac-pa--partial.cs97.my.salesforce.com/services/apexrest/ACC_QuoteSyncApprovalResult/';  
        req.httpMethod = 'POST';
        RestContext.request = req;
        RestContext.response = res;
        
        ACC_QuoteSyncApprovalResult.doPost();

        Test.stopTest();
    }
}

实例2:ACC_QuoteSyncToRIS

@RestResource(urlMapping = '/ACC_QuoteSyncToRIS/*')
global class ACC_QuoteSyncToRIS{
	
	/* Input Quote Information */
    public class QuoteInfo {
    	public String OPPORTUNITYNUMBER;
    	public String OPPORTUNITYNAME;
    	public String OPPORTUNITYSTAGE;
    	public String QUOTENUMBER;
    	public String DEALERCODE;
    	public String DEALERNAME;
    	public String OWNERNAME;
    	public String ESTIMATEDDELIVERYDATE;
    	public String SALESREPRESENTATIVE;
    	public String SALESREPRESENTATIVENO;
    	// ----- Add by SIT R2 2018.01.25 Start -----
    	public String SALESORG;
    	public String SALESCHANNEL;
    	// ----- Add by SIT R2 2018.01.25 End -------
    	public String COSTCENTERCODE;
    	public String QUOTEABOUT;
    	public String ATTCHMENTURL;
    	public String REBATE;
    	public String TOTALLISTPRICE;
    	public String TOTALSALESPRICE;
    	public String TOTALLISTPRICEDISCOUNTRATE;
    	public String TOTALINDICATIVEPRICEDISCOUNTRATE;
    	public String TOTALBPPRICEPROFIT;
    	public String TOTALCOSTPRICEPROFIT;
    	public String TOTALBPPRICEPROFITABILITY;
    	public String TOTALCOSTPRICEPROFITABILITY;
    	public List<ProductItem> PRODUCTITEMS;
    }
    
    /* QuoteLineItem Information */
    public class ProductItem {
    	public String ITEMNUMBER;
    	public String PRODUCT;
    	public String PRODUCTGROUP;
    	public String PRODUCTSUBGROUP1;
    	public String PRODUCTSUBGROUP2;
    	public String QUANTITY;
    	public String LISTPRICE;
    	public String INDICATIVEPRICE;
    	public String SALESPRICE;
    	public String LISTPRICEDISCOUNTRATE;
    	public String INDICATIVEPRICEDISCOUNTRATE;
    	public String INDICATIVEPRICEDISCOUNTAMOUNT;
    	public String TOTALINDICATIVEPRICEDISCOUNTAMOUNT;
    	public String BPPRICEPROFIT;
    	public String COSTPRICEPROFIT;
    	public String BPPRICEPROFITABILITY;
    	public String COSTPRICEPROFITABILITY;
    }
    
    public class ResponseData {
    	public String RS;
    	public String ERRMSG;
    	public ResDetail DATA;
    	
    	public String errorCode;
    	public String msg;
    	public String result;
    	public String success;
    }
    
    public class ResDetail {
    	public String FailReason;
    	public String IsSuccess;
    	public String RISQuoteApproveID;
    }
    
    @HttpPost
    Webservice static String doPost(String quoteId, String description){
    	// new Integration Log
    	Integration_Log__c integrationLog = new Integration_Log__c();
    	integrationLog.Integration_Apex__c = 'ACC_QuoteSyncToRIS';
    	integrationLog.Start_Time__c = DateTime.now().format('yyyy-MM-dd HH:mm:ss');
    	integrationLog.Integration_Log__c = '';
    	integrationLog.Excepition__c = '';
    	system.debug('Sync start...time: ' + integrationLog.Start_Time__c);
    	QuoteInfo quoteInfo = new QuoteInfo();
    	
    	try {
    		// Query quote data from salesforce.
	    	Quote quote = [SELECT Id,
	    						  Opportunity.Id,
	    						  Opportunity.Name,
	    						  Opportunity.ACC_Opportunity_Number_Ext__c,
	    						  ACC_Opportunity_Stage__c,
	    						  QuoteNumber,
	    						  ACC_Quote_Number_Ext__c,
	    						  Opportunity.ACC_Partner_Id__c,
	    						  Opportunity.ACC_Partner__r.SAP_ID__c, 
	    						  Opportunity.ACC_Partner__r.Name, 
	    						  Opportunity.ACC_Partner__r.ACC_Sales_Org__r.ACC_Org_Number__c, 
	    						  Opportunity.ACC_Partner__r.ACC_Sales_Channel__r.ACC_Channel_Number__c, 
	    						  Opportunity.ACC_Building_Owner_Name__c,
	    						  Opportunity.Estimated_Delivery_Date__c,
	    						  Opportunity.ACC_Sales_Representative__c,
	    						  Opportunity.ACC_Sales_Representative__r.UserNo__c,
	    						  Opportunity.ACC_Sales_Representative_ID__c,
	    						  ACC_Cost_Center__c,
	    						  ACC_Judgement_Description__c,
	    						  (SELECT Id FROM Attachments LIMIT 1),
	    						  ACC_Rebate_1000__c,
	    						  ACC_Total_List_Price__c,
	    						  ACC_Total_Sales_Price__c,
	    						  ACC_Total_Discount_Rate_List_Price__c,
	    						  ACC_Total_Discount_Rate_Indicative_Price__c,
								  ACC_Total_BP_Price_Profit_1000__c,
								  ACC_Total_Cost_Price_Profit_1000__c,
								  ACC_Profitability_Compared_To_BP_Price__c,
								  ACC_Profitability_Compared_To_Cost_Price__c,
								  ACC_Judgement_ID__c,
								  (SELECT Id,
										QuoteId,
										LineNumber,
										Product2.Name,
										Product2.Product_Group__c,
										Product2.Product_Subgroup1__c,
										Product2.Product_Subgroup2__c,
										Quantity,
										ListPrice,
										ACC_Indicative_Price__c,
										UnitPrice,
										ACC_Unit_Discount_Rate__c,
										ACC_Indicative_Discount_Rate__c,
										ACC_Differences_in_Indicative_Price__c,
										ACC_Total_Difference_Price__c,
										ACC_Profit_CT_BP_Price__c,
										ACC_Profit_CT_Cost_Price__c,
										ACC_Unit_Profitability_CT_BP_Price__c,
										ACC_Unit_Profitability_CT_Cost_Price__c
									FROM QuoteLineItems)
							FROM Quote
							WHERE Status =: Label.ACC_Quote_Status_Approved_In_Salesforce
							AND Id =: quoteId];
			// Quote and QuoteLineItem is not null.
			// Push data into request body objects.
			if (quote != null) {
	    		quoteInfo.OPPORTUNITYNUMBER = quote.Opportunity.ACC_Opportunity_Number_Ext__c;
	    		quoteInfo.OPPORTUNITYNAME = quote.Opportunity.Name;
	    		quoteInfo.OPPORTUNITYSTAGE = quote.ACC_Opportunity_Stage__c;
	    		quoteInfo.QUOTENUMBER = quote.ACC_Quote_Number_Ext__c;
	    		quoteInfo.DEALERCODE = quote.Opportunity.ACC_Partner__r.SAP_ID__c;
	    		quoteInfo.DEALERNAME = quote.Opportunity.ACC_Partner__r.Name;
	    		if (String.isNotBlank(quote.Opportunity.ACC_Building_Owner_Name__c)) {
	    			quoteInfo.OWNERNAME = quote.Opportunity.ACC_Building_Owner_Name__c;
	    		} else {
	    			quoteInfo.OWNERNAME = '';
	    		}
	    		quoteInfo.ESTIMATEDDELIVERYDATE = quote.Opportunity.Estimated_Delivery_Date__c == null ? '' : String.valueOf(quote.Opportunity.Estimated_Delivery_Date__c);
	    		quoteInfo.SALESREPRESENTATIVE = quote.Opportunity.ACC_Sales_Representative__c;
	    		quoteInfo.SALESREPRESENTATIVENO = quote.Opportunity.ACC_Sales_Representative__r.UserNo__c;
	    		quoteInfo.SALESORG = quote.Opportunity.ACC_Partner__r.ACC_Sales_Org__r.ACC_Org_Number__c == null ? '':quote.Opportunity.ACC_Partner__r.ACC_Sales_Org__r.ACC_Org_Number__c;
	    		quoteInfo.SALESCHANNEL = quote.Opportunity.ACC_Partner__r.ACC_Sales_Channel__r.ACC_Channel_Number__c == null ? '':quote.Opportunity.ACC_Partner__r.ACC_Sales_Channel__r.ACC_Channel_Number__c;
	    		quoteInfo.COSTCENTERCODE = quote.ACC_Cost_Center__c;
    			quoteInfo.QUOTEABOUT = description;
    			if (!quote.Attachments.isEmpty()) {
    				for (Attachment att : quote.Attachments) {
    					quoteInfo.ATTCHMENTURL = '&startURL=/content/session?url=' + Label.ACC_Common_File_Domain + att.Id;
    				}
    			} else {
	    			quoteInfo.ATTCHMENTURL = '';
    			}
	    		quoteInfo.REBATE = quote.ACC_Rebate_1000__c == null ? '' : String.valueOf(quote.ACC_Rebate_1000__c);
	    		quoteInfo.TOTALLISTPRICE = quote.ACC_Total_List_Price__c == null ? '' : String.valueOf(quote.ACC_Total_List_Price__c);
	    		quoteInfo.TOTALSALESPRICE = quote.ACC_Total_Sales_Price__c == null ? '' : String.valueOf(quote.ACC_Total_Sales_Price__c);
	    		quoteInfo.TOTALLISTPRICEDISCOUNTRATE = quote.ACC_Total_Discount_Rate_List_Price__c == null ? '' : String.valueOf(quote.ACC_Total_Discount_Rate_List_Price__c.setscale(4));
	    		quoteInfo.TOTALINDICATIVEPRICEDISCOUNTRATE = quote.ACC_Total_Discount_Rate_Indicative_Price__c == null ? '' : String.valueOf(quote.ACC_Total_Discount_Rate_Indicative_Price__c.setscale(4));
	    		quoteInfo.TOTALBPPRICEPROFIT = quote.ACC_Total_BP_Price_Profit_1000__c == null ? '' : String.valueOf(quote.ACC_Total_BP_Price_Profit_1000__c);
	    		quoteInfo.TOTALCOSTPRICEPROFIT = quote.ACC_Total_Cost_Price_Profit_1000__c == null ? '' : String.valueOf(quote.ACC_Total_Cost_Price_Profit_1000__c);
	    		quoteInfo.TOTALBPPRICEPROFITABILITY = quote.ACC_Profitability_Compared_To_BP_Price__c == null ? '' : String.valueOf(quote.ACC_Profitability_Compared_To_BP_Price__c.setscale(4));
	    		quoteInfo.TOTALCOSTPRICEPROFITABILITY = quote.ACC_Profitability_Compared_To_Cost_Price__c == null ? '' : String.valueOf(quote.ACC_Profitability_Compared_To_Cost_Price__c.setscale(4));

				quoteInfo.PRODUCTITEMS = new List<ProductItem>();
				if (!quote.QuoteLineItems.isEmpty()) {
		    		for (QuoteLineItem item : quote.QuoteLineItems) {
		    			ProductItem productItem = new ProductItem();
		    			productItem.ITEMNUMBER = item.LineNumber;
		    			productItem.PRODUCT = item.Product2.Name;
		    			productItem.PRODUCTGROUP = item.Product2.Product_Group__c;
		    			productItem.PRODUCTSUBGROUP1 = item.Product2.Product_Subgroup1__c;
		    			productItem.PRODUCTSUBGROUP2 = item.Product2.Product_Subgroup2__c;
		    			productItem.QUANTITY = item.Quantity == null ? '' : String.valueOf(item.Quantity);
		    			productItem.LISTPRICE = item.ListPrice == null ? '' : String.valueOf(item.ListPrice);
		    			productItem.INDICATIVEPRICE = item.ACC_Indicative_Price__c == null ? '' : String.valueOf(item.ACC_Indicative_Price__c);
		    			productItem.SALESPRICE = item.UnitPrice == null ? '' : String.valueOf(item.UnitPrice);
		    			productItem.LISTPRICEDISCOUNTRATE = item.ACC_Unit_Discount_Rate__c == null ? '' : String.valueOf(item.ACC_Unit_Discount_Rate__c);
		    			productItem.INDICATIVEPRICEDISCOUNTRATE = item.ACC_Indicative_Discount_Rate__c == null ? '' : String.valueOf(item.ACC_Indicative_Discount_Rate__c);
		    			productItem.INDICATIVEPRICEDISCOUNTAMOUNT = item.ACC_Differences_in_Indicative_Price__c == null ? '' : String.valueOf(item.ACC_Differences_in_Indicative_Price__c);
		    			productItem.TOTALINDICATIVEPRICEDISCOUNTAMOUNT = item.ACC_Total_Difference_Price__c == null ? '' : String.valueOf(item.ACC_Total_Difference_Price__c);
		    			productItem.BPPRICEPROFIT = item.ACC_Profit_CT_BP_Price__c == null ? '' : String.valueOf(item.ACC_Profit_CT_BP_Price__c);
		    			productItem.COSTPRICEPROFIT = item.ACC_Profit_CT_Cost_Price__c == null ? '' : String.valueOf(item.ACC_Profit_CT_Cost_Price__c);
		    			productItem.BPPRICEPROFITABILITY = item.ACC_Unit_Profitability_CT_BP_Price__c == null ? '' : String.valueOf(item.ACC_Unit_Profitability_CT_BP_Price__c);
		    			productItem.COSTPRICEPROFITABILITY = item.ACC_Unit_Profitability_CT_Cost_Price__c == null ? '' : String.valueOf(item.ACC_Unit_Profitability_CT_Cost_Price__c);
	
		    			quoteInfo.PRODUCTITEMS.add(productItem);
		    		}
				}
			}
    	} catch (Exception ex) {
    		integrationLog.Integration_Log__c += ex.getMessage();
    	}
    	HttpRequest req = new HttpRequest();
    	
    	// API server url.
		String apiServer = Label.ACC_Interface_ApiServer;
		// API method name.
		String cmd = Label.ACC_Interface_CMD;
		String access_key = Label.ACC_Interface_Access_Key;
		String secret = Label.ACC_Interface_Secret;
		
		
		String timestamp = String.valueOf(DateTime.now().getTime());
		//String REQ_ID = ACC_Utility.generateRandomString(36) + '_SF_' + DateTime.now().format('yyyyMMdd');
		//中间件log ID 这个参数的是否有必要,去现场提问
		String REQ_ID = 'b77c4886-851a-4ff0-b160-4c9e4494e51b' + '_SF_' + DateTime.now().format('yyyyMMdd');
		String REQ_TIME_beforeEncode = DateTime.now().format('yyyy-MM-dd HH:mm:ss');
		String REQ_TIME = EncodingUtil.urlEncode(String.valueOf(DateTime.now()), 'UTF-8');
		//String sig = 'DDDA902E8A35F3CA7C86FE121ECCF54B';
		String SRCSYSTEM = Label.ACC_Interface_SRCSYSTEM;
		String DESSYSTEM = Label.ACC_Interface_DESSYSTEM;
		String APINAME = Label.ACC_Interface_APINAME;
		String APICODE = Label.ACC_Interface_APICODE;
		String INJSONSTR_beforeEncode = JSON.serialize(quoteInfo);
		String INJSONSTR = ACC_Utility.UrlEncodeObject(quoteInfo);
		system.debug(timestamp);
		String sig_method = 'HmacMD5';
		
		// Create signature string
		String secretString = secret + 'APICODE' + APICODE + 'APINAME' + APINAME + 'DESSYSTEM' + DESSYSTEM +
							  'INJSONSTR' + INJSONSTR_beforeEncode + 'REQ_ID' + REQ_ID + 'REQ_TIME' + REQ_TIME_beforeEncode + 'SRCSYSTEM' + SRCSYSTEM +
							  'access_key' + access_key +
							  'cmd' + cmd + 'formatjson' + 'sig_method' + sig_method + 'timestamp' + timestamp;
		system.debug('secretString:' + secretString);
		
		// get signature
		Blob sigBlob = crypto.generateMac('hmacMD5', Blob.valueOf(secretString), Blob.valueOf(secret));
		system.debug('sign size:' + sigBlob.size());
		system.debug('sign common:' + EncodingUtil.convertToHex(sigBlob));
		String sig = EncodingUtil.convertToHex(sigBlob).toUpperCase();
		system.debug(sig);
		
		system.debug('quoteInfo:' + quoteInfo);
		system.debug('INJSONSTR:' + INJSONSTR);
		
        /*
		String endpoint = apiServer + 
    					'?REQ_ID=' + REQ_ID + 
    					'&APINAME=' + EncodingUtil.urlEncode(APINAME, 'UTF-8') +
    					'&REQ_TIME=' + REQ_TIME + 
    					'&INJSONSTR=' + INJSONSTR + 
    					'&format=json' + 
    					'&sig=' + sig + 
    					'&sig_method=' + sig_method + 
    					'&SRCSYSTEM=' + SRCSYSTEM + 
    					'&DESSYSTEM=' + DESSYSTEM + 
    					'&access_key=' + access_key + 
    					'&APICODE=' + APICODE + 
    					'&cmd=' + cmd + 
    					//'&secret=' + secret + 
    					'&timestamp=' + timestamp;
		req.setEndpoint(endpoint);
		*/
        String reqBody = 'REQ_ID=' + REQ_ID + 
    					'&APINAME=' + EncodingUtil.urlEncode(APINAME, 'UTF-8') +
    					'&REQ_TIME=' + REQ_TIME + 
    					'&INJSONSTR=' + INJSONSTR + 
    					'&format=json' + 
    					'&sig=' + sig + 
    					'&sig_method=' + sig_method + 
    					'&SRCSYSTEM=' + SRCSYSTEM + 
    					'&DESSYSTEM=' + DESSYSTEM + 
    					'&access_key=' + access_key + 
    					'&APICODE=' + APICODE + 
    					'&cmd=' + cmd + 
    					//'&secret=' + secret + 
    					'&timestamp=' + timestamp;
        req.setBody(reqBody);
        system.debug('req body: ' + reqBody);
        req.setEndpoint(apiServer);
    	
    	system.debug('endpoint:' + req.getEndpoint());
    	
		req.setMethod('POST');
		system.debug('request:' + req);
		
		Http http = new Http();
		try {
			// send request and get response data.
		    HttpResponse res = http.send(req);
		    system.debug('STATUS:'+res.getStatus());
		    system.debug('STATUS_CODE:'+res.getStatusCode());
		    system.debug('BODY: '+res.getBody());
	    	integrationLog.Integration_Log__c += 'Response:' + res.getBody();
		    ResponseData resData = (ResponseData)JSON.deserialize(res.getBody(), ACC_QuoteSyncToRIS.ResponseData.class);
		    if (String.isNotBlank(resData.errorCode)) {
		    	integrationLog.IsSuccess__c = false;
		    	integrationLog.Integration_Log__c += 'ResponseError:' + resData.msg;
		    	integrationLog.Excepition__c += 'Failed';
		    } else if ('N'.equals(resData.RS)) {
		    	integrationLog.IsSuccess__c = false;
		    	integrationLog.Integration_Log__c += 'ResponseError:' + resData.ERRMSG;
		    	integrationLog.Excepition__c += resData.ERRMSG;
		    // RS:"Y", Data.IsSuccess:"True", update quote Judgement id with RISQuoteApproveID.
		    } else if ('Y'.equals(resData.RS)) {
		    	if ('True'.equals(resData.DATA.IsSuccess)) {
		    		Quote updateQuote = new Quote();
		    		updateQuote.Id = quoteId;
		    		updateQuote.ACC_Judgement_ID__c = resData.DATA.RISQuoteApproveID;
		    		update updateQuote;
		    		
			    	integrationLog.IsSuccess__c = true;
			    	integrationLog.Integration_Log__c += 'updateQuote:' + updateQuote.Id + ';' + 'JudgementId:' + updateQuote.ACC_Judgement_ID__c;
		    	} else {
			    	integrationLog.IsSuccess__c = false;
			    	integrationLog.Integration_Log__c += 'ResponseError:' + resData.DATA.FailReason;
			    	integrationLog.Excepition__c += resData.DATA.FailReason;
		    	}
		    }
		    
	    	integrationLog.End_Time__c = DateTime.now().format('yyyy-MM-dd HH:mm:ss');
	    	system.debug('Sync end...time: ' + integrationLog.End_Time__c);
	    	upsert(integrationLog);
	    	
	    	if (String.isBlank(integrationLog.Excepition__c)) {
				return 'success';
	    	} else {
	    		return 'failed';
	    	}
		} catch (Exception ex) {
		    system.debug('ERROR: '+ ex.getMessage());
		    integrationLog.Excepition__c += ex.getMessage();
		}
    	integrationLog.End_Time__c = DateTime.now().format('yyyy-MM-dd HH:mm:ss');
    	system.debug('Sync end...time: ' + integrationLog.End_Time__c);
    	upsert(integrationLog);
    	
		return '';
    }
}

Test Class:

@isTest
public class ACC_QuoteSyncToRISTest {
    @isTest static void myUnitTest(){
        //insert custom setting 
        Running_number__c objseq = new Running_number__c(name = 'number chk', seq__c = 1);
        insert objseq;

        Id pricebookId = Test.getStandardPricebookId();
        // Pricebook2 - custom
        Pricebook2 pb = new Pricebook2(
            Name = 'PAPAGZ',
            IsActive = true
        );
        insert pb;
        // Product2 - product1
        Product2 product1 = new Product2(
            IsActive = true,
            Name = 'Test_product1',
            Product_Group__c = 'PAC'
        );
        insert product1;
        // Product2 - product2
        Product2 product2 = new Product2(
            IsActive = true,
            Name = 'Test_product2',
            Product_Group__c = 'PAC'
        );
        insert product2;
        // PricebookEntry standardprice1
        PricebookEntry standardprice1 = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id = product1.Id,
            UnitPrice = 1000,
            IsActive = true
        );
        insert standardprice1;
        // PricebookEntry standardprice2
        PricebookEntry standardprice2 = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id = product2.Id,
            UnitPrice = 1000,
            IsActive = true
        );
        insert standardprice2;
         // PricebookEntry pbe1
        PricebookEntry pbe1 = new PricebookEntry(
            Pricebook2Id = pb.Id,
            Product2Id = product1.Id,
            UnitPrice = 1000,
            Cost_Price__c = 900,
            ACC_Cost_Price_A__c = 950,
            ACC_Cost_Price_B__c = 940,
            ACC_Indicative_Price_1__c = 880,
            ACC_Indicative_Price_2__c = 870,
            ACC_Indicative_Price_3__c = 860,
            ACC_Indicative_Price_4__c = 850,
            IsActive = true,
            UseStandardPrice = false
        );
        insert pbe1;
        // PricebookEntry pbe2
        PricebookEntry pbe2 = new PricebookEntry(
            Pricebook2Id = pb.Id,
            Product2Id = product2.Id,
            UnitPrice = 1000,
            Cost_Price__c = 900,
            ACC_Cost_Price_A__c = 950,
            ACC_Cost_Price_B__c = 940,
            ACC_Indicative_Price_1__c = 880,
            ACC_Indicative_Price_2__c = 870,
            ACC_Indicative_Price_3__c = 860,
            ACC_Indicative_Price_4__c = 850,
            IsActive = true,
            UseStandardPrice = false
        );
        insert pbe2;
        Account acc=new Account(
            name='acc1',
            SAP_ID__c = 'test'
        );
        insert acc;
        List<User> userList =  ACC_TestDataUtility.getUserList();
        userList[0].UserNo__c = 'test';
        update userList[0];
        Opportunity opp = new Opportunity(
            Name = 'TestOppOnClick-Opportunity01',
            RecordTypeId = ACC_Utility.getRecordTypeMapBySobj('Opportunity').get('ACC_PAPAGZ'),
            Pricebook2Id = pb.Id,
            StageName = 'Identification',
            ACC_Partner__C = acc.Id,
            ACC_Sales_Representative__c = userList[0].id,
            ACC_Planning_Purposes__c = 'Public Building',
         //   ACC_Rank__c = 'A',
            CloseDate = System.Today().addDays(10),
                Corporate_Discount__c = 0.15
         //   LastModifiedDate = ''
        );
        insert opp;
        Opportunity opp1 = new Opportunity(
            Name = 'TestOppOnClick-Opportunity01',
            RecordTypeId = ACC_Utility.getRecordTypeMapBySobj('Opportunity').get('ACC_PAPAGZ'),
            Pricebook2Id = pb.Id,
            StageName = 'Identification',
            ACC_Partner__C = acc.Id,
            ACC_Sales_Representative__c = userList[0].id,
            ACC_Planning_Purposes__c = 'Public Building',
            ACC_Estimated_Delivery_Start_Month__c = System.Today(),
            ACC_Estimated_Delivery_End_Month__c = System.Today(),
            ACC_Estimated_Tender_Date__c = System.Today(),
            ACC_Authorization_Date__c = System.Today(),
         //   ACC_Rank__c = 'S',
            CloseDate = System.Today().addDays(10)
         //   LastModifiedDate = ''
        );
        insert opp1;
        OpportunityLineItem opp2LineItem = new OpportunityLineItem(
            OpportunityId = opp.Id,
            Product2Id = product1.Id,
            PricebookEntryId = pbe1.Id,
            Quantity = 10,
            TotalPrice = 10000,
            Category__c = 'Others',
            Category_if_others__c = 'Others',
            Discount = 60
        );
        insert opp2LineItem;
        Quote quote = new Quote(
            Name = 'TestQuote99',
            RecordTypeId = ACC_Utility.getRecordTypeMapBySobj('Quote').get('ACC_PAPAGZ'),
            Pricebook2Id = pb.Id,
            OpportunityId = opp.Id,
            Status = 'Approved in Salesforce',
            Approver__c = 'Sarwanto Dedi',
            Progress_Payment__c = 'pay1\npay2',
            Note__c = 'note1_note2',
            IsPPNDisplayed__c = true,
            seq__c = 7632 
        );
        insert quote;
        QuoteLineItem qlt1 = new QuoteLineItem(
            QuoteId = quote.Id, 
            Product2Id = product1.Id,
            PricebookEntryId = pbe1.Id,
            Quantity = 100,
            UnitPrice = 8000,
            System_CategoryPGI__c = 'test Category',
            FloorPGI__c = 'All'
        );
        insert qlt1;
        
       /* ACC_QuoteSyncToRIS.ResponseData jsonData = new ACC_QuoteSyncToRIS.ResponseData();
        RestRequest req = new RestRequest(); 
        RestResponse res = new RestResponse();
        String reqJson = json.serialize(jsonData);
        req.requestBody = Blob.valueof(reqJson);
        req.requestURI = 'https://cac-pa--partial.cs97.my.salesforce.com/services/apexrest/ACC_QuoteSyncToRIS/';  
        req.httpMethod = 'POST';
        RestContext.request = req;
        RestContext.response = res;*/
        
        ACC_QuoteSyncToRIS.doPost(quote.id,'test');
    }

}