Skip to main content
Solved

Sales Quote Endpoint Action Error with Manual Numbering

  • January 30, 2025
  • 2 replies
  • 81 views

Forum|alt.badge.img

Setup:

  1. Turn on manual numbering for Sales Quotes
  2. Make a web endpoint for Sales Quotes with OpportunityID and QuoteNbr mapped along with either of two mapped actions: PrimaryQuote or MarkAsConverted (QuoteEndpoint customization attached)
  3. Create an Opportunity
  4. Create two Quotes on that Opportunity

When I attempt to mark the first quote as primary via POST request to /entity/Quote/0.1/SalesQuote/PrimaryQuote:

{
"entity": {
"OpportunityID": {
"value": "261044"
},
"QuoteNbr": {
"value": "261044R00"
}
}
}

I get this error response:

{
"message": "An error has occurred.",
"exceptionMessage": "Object reference not set to an instance of an object.",
"exceptionType": "System.NullReferenceException",
"stackTrace": " at PX.Api.ContractBased.EntityService.Invoke(ISystemContract systemContract, String version, String name, EntityImpl entity, ActionImpl action, CbOperationContext operationContext, Boolean throwOnError)\r\n at PX.Api.ContractBased.AspNetCore.CbEndpointFeatureServiceExtensions.Invoke(IEntityService entityService, ICbEndpointFeature feature, EntityImpl entity, ActionImpl action, CbOperationContext operationContext)\r\n at PX.Api.ContractBased.WebApi.Controllers.ActionController.InvokeAction(String objectName, String actionName, ActionInvocation invocation)\r\n at lambda_method(Closure , Object , Object[] )\r\n at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeActionMethodAsync>d__12.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeNextActionFilterAsync>d__10.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\r\n at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.<InvokeInnerFilterAsync>d__13.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.<InvokeNextExceptionFilterAsync>d__24.MoveNext()"
}

Marking the second or third quote as primary works, only the first throws this error. 

Same request but referencing the second quote:

{
"entity": {
"OpportunityID": {
"value": "261044"
},
"QuoteNbr": {
"value": "261044R01"
}
}
}

Gets a 204 response and marks the 261044R01 quote as primary:

Related to my previous topic: https://community.acumatica.com/topic/show?tid=26964&fid=289

Marking quotes as primary works fine in the normal web interface, this only affects API requests. If the request is sent for a quote that is already primary, I instead get the expected "PX.Data.PXInvalidOperationException" for all quotes. 

Is there anything I can try to make this work?

Best answer by PBSA

@ankitaj21 thank you for the very detailed response. Unfortunately, I don’t believe the issue is related to quotes already being marked as primary. If the quote is already Primary, I get a different error: "PX.Data.PXInvalidOperationException" – which is correct and expected and does not vary between the first and second quote. 

I sent this topic to AcuPower and they were able to quickly produce a patch that fixed the QuoteMaint.PrimaryQuote function by loading Quote.Current during API requests if it was not loaded already. I expanded that solution to also happen in QuoteMaint.MarkAsConverted:

namespace PX.Objects.CR
{

public class PBSAQuoteMaintExtension : PXGraphExtension<PX.Objects.CR.QuoteMaint>
{

// fix flaw in SalesQuote API action requests
// see https://community.acumatica.com/develop-integrations-with-web-services-apis-289/sales-quote-endpoint-action-error-with-manual-numbering-28527
public delegate IEnumerable PrimaryQuoteDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable PrimaryQuote(PXAdapter adapter, PrimaryQuoteDelegate baseMethod)
{
this.LoadQuoteForApi(adapter);
return baseMethod(adapter);
}

public delegate IEnumerable MarkAsConvertedDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable MarkAsConverted(PXAdapter adapter, MarkAsConvertedDelegate baseMethod)
{
this.LoadQuoteForApi(adapter);
return baseMethod(adapter);
}

protected virtual void LoadQuoteForApi(PXAdapter adapter)
{
if (Base.IsContractBasedAPI && Base.Quote.Current == null)
{
IEnumerable<CRQuote> quotes = adapter.Get<CRQuote>().ToArray();
Base.Quote.Current = CRQuote.PK.Find(Base, (string)adapter.Searches[0], (string)adapter.Searches[1]);
quotes = quotes.Append(Base.Quote.Current);
}
}
}

}

This allows me to use the API to set quotes as primary and convert them as expected. 

2 replies

Forum|alt.badge.img+5
  • Jr Varsity I
  • February 3, 2025

HI ​@PBSA 

Document Overview

This document addresses an issue encountered in Acumatica ERP when using the API to mark the first Sales Quote as primary. The error occurs only for the first quote, while subsequent quotes work as expected. This document outlines the problem, root cause analysis, and step-by-step resolution.

 

Problem Description

Setup

1. Manual Numbering : Manual numbering for Sales Quotes is enabled.

2. Custom Web Endpoint : A custom endpoint (QuoteEndpoint) has been created with mappings for:

· OpportunityID

· QuoteNbr

· Actions: PrimaryQuote and MarkAsConverted

3. Scenario :

· An opportunity is created with two quotes.

· A POST request is sent to /entity/Quote/0.1/SalesQuote/PrimaryQuote to mark the first quote as primary.

Error Observed

When marking the first quote as primary via the API, the following error occurs:

{

"message": "An error has occurred.",

"exceptionMessage": "Object reference not set to an instance of an object.",

"exceptionType": "System.NullReferenceException",

"stackTrace": "..."

}

· Marking the second or third quote as primary works without issue.

· The functionality works correctly in the Acumatica web interface but fails via the API.

 

Root Cause Analysis

The error (System.NullReferenceException) suggests that the system is attempting to access an uninitialized object or property during the API request. Key observations include:

1. Default Behavior for the First Quote :

· The first quote may already be marked as primary by default when created.

· Attempting to mark it as primary again could trigger the error.

2. API Logic :

· The API does not account for cases where the quote is already primary, leading to a null reference.

3. Edge Case Handling :

· The system logic for the PrimaryQuote action may not handle edge cases (e.g., redundant requests) gracefully.

 

Solution Approach

To resolve the issue, we will address the following areas:

1. Verify Default Behavior :

· Confirm if the first quote is automatically marked as primary when created.

2. Update Custom Endpoint Logic :

· Modify the custom endpoint to include additional checks before invoking the PrimaryQuote action.

3. Enable Detailed Logging :

· Capture detailed logs to identify the exact cause of the error.

4. Handle Edge Cases in API Requests :

· Ensure the API request includes all required fields and handles edge cases.

 

Implementation Steps

Step 1: Verify Default Behavior

1. Log into the Acumatica web interface.

2. Create a new opportunity with two quotes.

3. Check if the first quote is automatically marked as primary.

· If this is the case, update the API logic to skip the PrimaryQuote action for quotes that are already primary.

 

Step 2: Update the Custom Endpoint

Modify the custom endpoint XML to include a field for checking if the quote is primary:

xml

<Customization level="" description="" product-version="24.109">

<EntityEndpoint>

<Endpoint xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.acumatica.com/entity/maintenance/5.31" name="Quote" version="0.1" systemContractVersion="4">

<TopLevelEntity name="SalesQuote" screen="CR304500">

<Fields>

<Field name="OpportunityID" type="StringValue"/>

<Field name="QuoteNbr" type="StringValue"/>

<Field name="IsPrimary" type="BooleanValue"/> <!-- Add a field to check if the quote is primary -->

</Fields>

<Mappings>

<Mapping field="OpportunityID">

<To object="Quote" field="OpportunityID"/>

</Mapping>

<Mapping field="QuoteNbr">

<To object="Quote" field="QuoteNbr"/>

</Mapping>

<Mapping field="IsPrimary">

<To object="Quote" field="IsPrimary"/>

</Mapping>

</Mappings>

<Actions>

<Action name="MarkAsConverted" mappedTo="MarkAsConverted"/>

<Action name="PrimaryQuote" mappedTo="PrimaryQuote"/>

</Actions>

</TopLevelEntity>

</Endpoint>

</EntityEndpoint>

</Customization>

---

Step 3: Update the API Request Logic

Before sending the PrimaryQuote action, check if the quote is already primary. For example:

// Pseudocode for API request logic

if (!quote.IsPrimary)

{

// Send POST request to mark the quote as primary

var response = SendPostRequest("/entity/Quote/0.1/SalesQuote/PrimaryQuote", quote);

}

else

{

// Handle case where the quote is already primary

Console.WriteLine("Quote is already primary.");

}

 

Step 4: Enable Detailed Logging

1. Go to System > Management > Trace Settings in Acumatica.

2. Enable tracing for the CR304500 screen and the PrimaryQuote action.

3. Reproduce the issue and review the logs to identify the root cause.

 

Expected Outcome

After implementing the above steps:

1. The API will correctly handle cases where the first quote is already primary.

2. The NullReferenceException error will no longer occur.

3. The system will behave consistently between the web interface and the API.

 

Additional Notes

If the issue persists after applying these changes, consider reaching out to Acumatica support with the following information:

· Detailed logs from the trace settings.

· Customization files (e.g., the updated endpoint XML).

· Steps to reproduce the issue.

Acumatica support can provide further assistance in debugging the issue at the platform level.

 


Forum|alt.badge.img
  • Author
  • Jr Varsity II
  • Answer
  • February 3, 2025

@ankitaj21 thank you for the very detailed response. Unfortunately, I don’t believe the issue is related to quotes already being marked as primary. If the quote is already Primary, I get a different error: "PX.Data.PXInvalidOperationException" – which is correct and expected and does not vary between the first and second quote. 

I sent this topic to AcuPower and they were able to quickly produce a patch that fixed the QuoteMaint.PrimaryQuote function by loading Quote.Current during API requests if it was not loaded already. I expanded that solution to also happen in QuoteMaint.MarkAsConverted:

namespace PX.Objects.CR
{

public class PBSAQuoteMaintExtension : PXGraphExtension<PX.Objects.CR.QuoteMaint>
{

// fix flaw in SalesQuote API action requests
// see https://community.acumatica.com/develop-integrations-with-web-services-apis-289/sales-quote-endpoint-action-error-with-manual-numbering-28527
public delegate IEnumerable PrimaryQuoteDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable PrimaryQuote(PXAdapter adapter, PrimaryQuoteDelegate baseMethod)
{
this.LoadQuoteForApi(adapter);
return baseMethod(adapter);
}

public delegate IEnumerable MarkAsConvertedDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable MarkAsConverted(PXAdapter adapter, MarkAsConvertedDelegate baseMethod)
{
this.LoadQuoteForApi(adapter);
return baseMethod(adapter);
}

protected virtual void LoadQuoteForApi(PXAdapter adapter)
{
if (Base.IsContractBasedAPI && Base.Quote.Current == null)
{
IEnumerable<CRQuote> quotes = adapter.Get<CRQuote>().ToArray();
Base.Quote.Current = CRQuote.PK.Find(Base, (string)adapter.Searches[0], (string)adapter.Searches[1]);
quotes = quotes.Append(Base.Quote.Current);
}
}
}

}

This allows me to use the API to set quotes as primary and convert them as expected.