Salesforce:在PHP中创建OpportunityLineItems作为Opportunity的一部分

Salesforce:在PHP中创建OpportunityLineItems作为Opportunity的一部分,php,soap,salesforce,Php,Soap,Salesforce,我正在尝试使用PHP Toolkit 20.0和企业SOAP API将“Opportunities”批量上传到Salesforce 我发现这样做的方法是创建一个Opportunity对象,然后通过SOAP API在Salesforce中创建它,然后在响应中,我获取Id,并将其用于该Opportunity存在的每个1..n OpportunityLineItems 这不是很有效,因为它使用2个SOAP API调用,批量执行时会占用大量资源,并且容易超时。(我不想减少一次性发送的数量,因为API调用

我正在尝试使用PHP Toolkit 20.0和企业SOAP API将“Opportunities”批量上传到Salesforce

我发现这样做的方法是创建一个Opportunity对象,然后通过SOAP API在Salesforce中创建它,然后在响应中,我获取
Id
,并将其用于该Opportunity存在的每个1..n OpportunityLineItems

这不是很有效,因为它使用2个SOAP API调用,批量执行时会占用大量资源,并且容易超时。(我不想减少一次性发送的数量,因为API调用有限)

因此,是否有一种方法可以在单个API调用中创建Opportunity及其OpportunityLineItems?

我尝试了以下方法:

$opp = new stdClass();
$opp->Name = 'Opp1';
$opp->StageName = 'Closed Won';
$opp->Account = new stdClass();
$opp->Account->Custom_ID__c = '1234';
$opp->Pricebook2Id = '...'; 
$opp->OpportunityLineItems = array();
$opp->OpportunityLineItems[0] = new stdClass();
$opp->OpportunityLineItems[0]->Description = 'Product name';
$opp->OpportunityLineItems[0]->Quantity = 1;
$opp->OpportunityLineItems[0]->UnitPrice = 10.00;
...
$opp->OpportunityLineItems[n] = new stdClass();
$opp->OpportunityLineItems[n]->Description = 'Product name';
$opp->OpportunityLineItems[n]->Quantity = 1;
$opp->OpportunityLineItems[n]->UnitPrice = 10.00;
但结果是:
无效\u字段:实体“Opportunity”上没有此类列“OpportunityLineItems”。如果试图使用自定义字段,请确保在自定义字段名称后附加“\uuu c”。请参考您的WSDL或描述调用以获得适当的名称。


这是意料之中的,因为WSDL文件声明OpportunityLineItems的类型为
tns:QueryResult
,而不是
ens:
编辑:完成大修,以显示如何同时添加多个Opp。如果您可以以某种方式错开它们的创建(将它们存储在您的本地数据库中,并仅在您有足够的时间/已经过了足够的时间/用户按下了“刷新队列”按钮时上载)应该很有用

警告,代码现在看起来更可怕了,您最好先签入编辑历史

创建一个Apex类来接受带有2个参数的传入请求并尝试插入它们并不难。进入设置->开发->类->新建并尝试以下操作:

global with sharing class OpportunityLinkedInsert{

    static webservice Opportunity insertSingle(Opportunity opp, OpportunityLineItem[] lines){
        if(opp == null || lines == null){
            throw new IntegrationException('Invalid data');
        }
        Opportunity[] result = insertMultiple(new List<Opportunity>{opp}, new List<List<OpportunityLineItem>>{lines});
        return result[0]; // I imagine you want the Id back :) 
    }

    /*  I think SOAP doesn't handle method overloading well so this method has different name.
        'lines' are list of lists (jagged array if you like) so opps[i] will be inserted and then lines[i] will be linked to it etc.

        You can insert up to 10,000 rows in one go with this function (remember to count items in both arrays).
    */
    static webservice List<Opportunity> insertMultiple(List<Opportunity> opps, List<List<OpportunityLineItem>> lines){
        if(opps == null || lines == null || opps.size() == 0 || opps.size() != lines.size()){
            throw new IntegrationException('Invalid data');
        }
        insert opps;

        // I need to flatten the structure before I insert it.
        List<OpportunityLineItem> linesToInsert = new List<OpportunityLineItem>();

        for(Integer i = 0; i < opps.size(); ++i){
            List<OpportunityLineItem> linesForOne = lines[i];
            if(linesForOne != null && !linesForOne.isEmpty()){
                for(Integer j = 0; j < linesForOne.size(); ++j){
                    linesForOne[j].OpportunityId = opps[i].Id;
                }
                linesToInsert.addAll(linesForOne);
            }
        }
        insert linesToInsert;
        return opps;
    }

    // helper class to throw custom errors
    public class IntegrationException extends Exception{}
}
global与共享类OpportunityLinkedInsert{
静态webservice Opportunity insertSingle(Opportunity opp,OpportunityLineItem[]行){
if(opp==null | | line==null){
抛出新的IntegrationException(“无效数据”);
}
Opportunity[]result=insertMultiple(新列表{opp},新列表{lines});
返回结果[0];//我想您想要回Id:)
}
/*我认为SOAP不能很好地处理方法重载,所以这个方法有不同的名称。
“行”是列表的列表(锯齿状数组,如果你喜欢),所以opp[i]将被插入,然后行[i]将链接到它等等。
使用此函数一次最多可以插入10000行(请记住在两个数组中都要计数项目)。
*/
静态webservice列表插入多个(列表OPP、列表行){
如果(opps==null | | line==null | | opps.size()==0 | | opps.size()!=lines.size()){
抛出新的IntegrationException(“无效数据”);
}
插入OPP;
//我需要在插入结构之前将其展平。
List linesToInsert=新列表();
对于(整数i=0;i
您还需要一个单元测试类,然后才能将其发送到您的生产组织。类似的东西应该可以(在100%可用之前,需要填充更多的东西,)

@isTest
公共类OpportunityLinkedInsertest{
私有静态列表OPP;
私有静态列表项;
@伊斯特
公共静态void checSingleOppkErrorFlow(){
试一试{
OpportunityLinkedInsert.insertSingle(null,null);
assert(false,“它应该在空值上失败”);
}捕获(例外e){
System.assertEquals('无效数据',例如getMessage());
}
}
@伊斯特
公共静态无效检查MultiopErrorFlow(){
prepareTestData();
拆除(1);
试一试{
OpportunityLinkedInsert.insertMultiple(Opp、items);
assert(false,“它应该在列表大小不匹配时失败”);
}捕获(例外e){
System.assertEquals('无效数据',例如getMessage());
}
}
@伊斯特
公共静态无效检查成功流(){
prepareTestData();
List insertResults=OpportunityLinkedInsert.insertMultiple(Opp,items);
列表检查=[选择Id、名称、,
(从OpportunityLineItems中选择Id)
来自机遇
Id所在位置:insertResults
按名称排序];
System.assertEquals(项[0].size(),检查[0].OpportunityLineItems.size(),“Opp 1应添加1个产品”);
System.assertEquals(项目[1].size(),检查[0].OpportunityLineItems.size(),“Opp 3应该有1个产品”);
}
//我们可以在多个测试中重用帮助器方法。使用不同数量的行项目创建2个机会。
私有静态void prepareTestData(){
opps=新列表{
新商机(名称='Opp 1',阶段名称='Prospecting',CloseDate=System.today()+10),
新商机(名称='opp2',StageName='Closed Won',CloseDate=System.today())
};
//您可能需要在此处填写更多字段!
//插入产品及其所有标准/自定义价格手册依赖项等都非常痛苦。。。
项目=新列表{
新名单{
新OpportunityLineItem(描述='Opp 1,产品1',数量=1,单价=10)
},
新名单{
新OpportunityLineItem(描述='Opp 2,产品1',数量=1,单价=10),
@isTest
public class OpportunityLinkedInsertTest{
    private static List<Opportunity> opps;
    private static List<List<OpportunityLineItem>> items;

    @isTest
    public static void checSingleOppkErrorFlow(){
        try{
            OpportunityLinkedInsert.insertSingle(null, null);
            System.assert(false, 'It should have failed on null values');
        } catch(Exception e){
            System.assertEquals('Invalid data',e.getMessage());
        }
    }

    @isTest
    public static void checkMultiOppErrorFlow(){
        prepareTestData();
        opps.remove(1);

        try{
            OpportunityLinkedInsert.insertMultiple(opps, items);
            System.assert(false, 'It should have failed on list size mismatch');
        } catch(Exception e){
            System.assertEquals('Invalid data',e.getMessage());
        }
    }

    @isTest
    public static void checkSuccessFlow(){
        prepareTestData();
        List<Opportunity> insertResults = OpportunityLinkedInsert.insertMultiple(opps, items);

        List<Opportunity> check = [SELECT Id, Name, 
            (SELECT Id FROM OpportunityLineItems) 
            FROM Opportunity 
            WHERE Id IN :insertResults 
            ORDER BY Name];
        System.assertEquals(items[0].size(), check[0].OpportunityLineItems.size(), 'Opp 1 should have 1 product added to it');
        System.assertEquals(items[1].size(), check[0].OpportunityLineItems.size(), 'Opp 3 should have 1 products');
    }

    // Helper method we can reuse in several tests. Creates 2 Opportunities with different number of line items.
    private static void prepareTestData(){
        opps = new List<Opportunity>{
            new Opportunity(Name = 'Opp 1', StageName = 'Prospecting', CloseDate = System.today() + 10),
            new Opportunity(Name = 'Opp 2', StageName = 'Closed Won', CloseDate = System.today())
        };

        // You might have to fill in more fields here!
        // Products are quite painful to insert with all their standard/custom pricebook dependencies etc...
        items = new List<List<OpportunityLineItem>>{
            new List<OpportunityLineItem>{
                new OpportunityLineItem(Description = 'Opp 1, Product 1', Quantity = 1, UnitPrice = 10)
            },
            new List<OpportunityLineItem>{
                new OpportunityLineItem(Description = 'Opp 2, Product 1', Quantity = 1, UnitPrice = 10),
                new OpportunityLineItem(Description = 'Opp 2, Product 2', Quantity = 1, UnitPrice = 10),
                new OpportunityLineItem(Description = 'Opp 2, Product 3', Quantity = 1, UnitPrice = 10)
            }
        };
    }
}