Skip to main content
Has anybody heard of a way segment (or desegment) all line items on a quote? 
12 Antworten
  1. 9. Sept., 18:57

    @Rohit paruchuri

    - 

    I'm open to meet with you if it helps to understand your use case.  

     

    You'll need to add a Screen before the action to warn the rep to save their quote before clicking the button, OR you can add an apex action to save the Quote.  

     

    To refresh the Quote after the action, use this invocable CalculateQuote action I built: 

     

    public with sharing class CPQCalculateQuoteAction {

    public class Input {

    @InvocableVariable(required=true label='CPQ Quote ID')

    public Id quoteId;

    }

    public class Result {

    @InvocableVariable(label='Calculation Request ID')

    public Id requestId;

    @InvocableVariable(label='CPQ Quote ID')

    public Id quoteId;

    }

    public class Status {

    @AuraEnabled

    public String state;

    @AuraEnabled

    public String message;

    @AuraEnabled

    public Id quoteId;

    }

    public class CalculationException extends Exception {

    }

    // Injectable boundary: unit tests never run the managed calculator or create quotes.

    public virtual class Gateway {

    public virtual void checkAccess(Set<Id> quoteIds) {

    if (

    !SBQQ__Quote__c.SObjectType.getDescribe().isAccessible() ||

    !SBQQ__Quote__c.SObjectType.getDescribe().isUpdateable()

    ) {

    throw new CalculationException(

    'Read and edit access to CPQ Quotes is required.'

    );

    }

    Set<Id> editable = new Set<Id>();

    for (UserRecordAccess access : [

    SELECT RecordId, HasEditAccess

    FROM UserRecordAccess

    WHERE UserId = :UserInfo.getUserId() AND RecordId IN :quoteIds

    ]) {

    if (access.HasEditAccess)

    editable.add(access.RecordId);

    }

    if (!editable.containsAll(quoteIds)) {

    throw new CalculationException(

    'A quote does not exist or you cannot edit it.'

    );

    }

    }

    public virtual String readQuote(Id quoteId) {

    return SBQQ.ServiceRouter.read(

    'SBQQ.QuoteAPI.QuoteReader',

    String.valueOf(quoteId)

    );

    }

    public virtual void calculate(String quoteJSON) {

    Map<String, Object> context = new Map<String, Object>{

    'quote' => JSON.deserializeUntyped(quoteJSON),

    'callbackClass' => 'CPQCalculateQuoteCallback'

    };

    SBQQ.ServiceRouter.load(

    'SBQQ.QuoteAPI.QuoteCalculator',

    null,

    JSON.serialize(context)

    );

    }

    public virtual void saveQuote(String quoteJSON) {

    SBQQ.ServiceRouter.save('SBQQ.QuoteAPI.QuoteSaver', quoteJSON);

    }

    public virtual Id launch(Set<Id> requestIds) {

    return Database.executeBatch(new CPQCalculateQuoteBatch(requestIds), 1);

    }

    }

    @TestVisible

    private static Gateway gateway = new Gateway();

    @InvocableMethod(

    label='Calculate CPQ Quote'

    description='Queues CPQ calculation and save. Pass the request ID to the Wait for CPQ Calculation Flow screen component. Completion is asynchronous.'

    )

    public static List<Result> calculateQuotes(List<Input> inputs) {

    if (inputs == null || inputs.isEmpty() || inputs.size() > 200) {

    throw new CalculationException('Supply between 1 and 200 CPQ Quote IDs.');

    }

    Set<Id> quoteIds = new Set<Id>();

    for (Input input : inputs) {

    if (

    input == null ||

    input.quoteId == null ||

    input.quoteId.getSObjectType() != SBQQ__Quote__c.SObjectType

    ) {

    throw new CalculationException(

    'Each input must be an SBQQ__Quote__c record ID.'

    );

    }

    quoteIds.add(input.quoteId);

    }

    gateway.checkAccess(quoteIds);

    List<CPQ_Calculation_Request__c> requests = new List<CPQ_Calculation_Request__c>();

    for (Id quoteId : quoteIds) {

    requests.add(

    new CPQ_Calculation_Request__c(

    Quote_Id__c = String.valueOf(quoteId),

    Active_Quote_Key__c = String.valueOf(quoteId),

    Status__c = 'Queued'

    )

    );

    }

    // The unique active key prevents races across users, not just duplicates in this list.

    Savepoint sp = Database.setSavepoint();

    try {

    insert requests;

    Set<Id> requestIds = new Map<Id, CPQ_Calculation_Request__c>(requests)

    .keySet();

    Id batchId = gateway.launch(requestIds);

    Map<Id, Id> requestByQuote = new Map<Id, Id>();

    for (CPQ_Calculation_Request__c request : requests) {

    request.Batch_Job_Id__c = String.valueOf(batchId);

    requestByQuote.put(Id.valueOf(request.Quote_Id__c), request.Id);

    }

    update requests;

    List<Result> results = new List<Result>();

    for (Input input : inputs) {

    Result result = new Result();

    result.quoteId = input.quoteId;

    result.requestId = requestByQuote.get(input.quoteId);

    results.add(result);

    }

    return results;

    } catch (Exception ex) {

    Database.rollback(sp);

    throw new CalculationException(

    'Could not queue calculation. A quote may already have an active or unresolved request. ' +

    ex.getMessage()

    );

    }

    }

    @AuraEnabled

    public static Status getStatus(Id requestId) {

    if (requestId == null)

    throw new AuraHandledException('Calculation request ID is required.');

    List<CPQ_Calculation_Request__c> requests = [

    SELECT Quote_Id__c, Status__c, Error__c, Batch_Job_Id__c

    FROM CPQ_Calculation_Request__c

    WHERE Id = :requestId

    LIMIT 1

    ];

    if (requests.isEmpty())

    throw new AuraHandledException(

    'Calculation request not found or not accessible.'

    );

    CPQ_Calculation_Request__c request = requests[0];

    Id quoteId = Id.valueOf(request.Quote_Id__c);

    gateway.checkAccess(new Set<Id>{ quoteId });

    Status result = new Status();

    result.quoteId = quoteId;

    result.state = request.Status__c;

    result.message = request.Error__c;

    if (

    result.state != 'Completed' &&

    result.state != 'Failed' &&

    String.isNotBlank(request.Batch_Job_Id__c)

    ) {

    Id batchId = Id.valueOf(request.Batch_Job_Id__c);

    for (AsyncApexJob job : [

    SELECT Status, NumberOfErrors

    FROM AsyncApexJob

    WHERE Id = :batchId

    ]) {

    if (

    job.Status == 'Failed' ||

    job.Status == 'Aborted' ||

    (job.Status == 'Completed' &&

    job.NumberOfErrors > 0)

    ) {

    result.state = 'Failed';

    result.message = 'The calculation batch stopped or reported an unhandled error. An administrator must inspect Apex Jobs before retrying.';

    }

    }

    }

    return result;

    }

    public static void processRequest(Id requestId) {

    CPQ_Calculation_Request__c request = [

    SELECT Quote_Id__c, Status__c

    FROM CPQ_Calculation_Request__c

    WHERE Id = :requestId

    ];

    if (request.Status__c != 'Queued')

    return;

    try {

    Id quoteId = Id.valueOf(request.Quote_Id__c);

    gateway.checkAccess(new Set<Id>{ quoteId });

    String quoteJSON = gateway.readQuote(quoteId);

    // No DML before CPQ: calculation can make callouts or schedule a callback.

    gateway.calculate(quoteJSON);

    request = [

    SELECT Status__c

    FROM CPQ_Calculation_Request__c

    WHERE Id = :requestId

    FOR UPDATE

    ];

    // A synchronous callback may already have saved the quote and completed the request.

    if (request.Status__c == 'Queued') {

    request.Status__c = 'Calculating';

    update request;

    }

    } catch (Exception ex) {

    failRequest(requestId, ex.getMessage());

    }

    }

    public static void saveCalculatedQuote(String quoteJSON) {

    Map<String, Object> model = (Map<String, Object>) JSON.deserializeUntyped(

    quoteJSON

    );

    Map<String, Object> record = (Map<String, Object>) model.get('record');

    Id quoteId = Id.valueOf((String) record.get('Id'));

    String activeKey = String.valueOf(quoteId);

    List<CPQ_Calculation_Request__c> requests = [

    SELECT Status__c

    FROM CPQ_Calculation_Request__c

    WHERE Active_Quote_Key__c = :activeKey

    FOR UPDATE

    ];

    if (requests.isEmpty() || requests[0].Status__c == 'Failed') {

    throw new CalculationException(

    'No active calculation request; the callback was not saved.'

    );

    }

    CPQ_Calculation_Request__c request = requests[0];

    Savepoint sp = Database.setSavepoint();

    try {

    gateway.checkAccess(new Set<Id>{ quoteId });

    gateway.saveQuote(quoteJSON);

    request.Status__c = 'Completed';

    request.Active_Quote_Key__c = null;

    request.Error__c = null;

    update request;

    } catch (Exception ex) {

    Database.rollback(sp);

    failRequest(request.Id, ex.getMessage());

    }

    }

    private static void failRequest(Id requestId, String message) {

    // Keep the active key on errors: an unobserved CPQ callback may still be in flight.

    update new CPQ_Calculation_Request__c(

    Id = requestId,

    Status__c = 'Failed',

    Error__c = message.left(32000)

    );

    }

    }

    ```

0/9000