Skip to main content
Solved

Cannot find where line of code is present


Forum|alt.badge.img

changing standard Shopify integration:

I am stuck trying to find out where cbapi.Put<SalesOrder>(obj.Local, obj.LocalID)
from SPSalesOrderProcessor class SaveBucketImport is defined in standard code.
I can find only interface
I need to override logic behind last line of code

Class SPSalesOrderProcessor

        public override async Task SaveBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, string operation, CancellationToken cancellationToken = default)
        {
            MappedOrder obj = bucket.Order;
            SalesOrder local = obj.Local;
            SalesOrder presented = existing?.Local as SalesOrder;
            BCBindingExt bindingExt = GetBindingExt<BCBindingExt>();

            // If custom mapped orderType, this will prevent attempt to modify existing SO type and following error
            if (existing != null)
                obj.Local.OrderType = ((MappedOrder)existing).Local.OrderType;

            SalesOrder impl;

            DetailInfo[] oldDetails = obj.Details.ToArray();
            obj.ClearDetails();

            //If we need to cancel the order in Acumatica
            //sort solines by deleted =true first because of api bug  in case if lines are deleted
            obj.Local.Details = obj.Local.Details.OrderByDescending(o => o.Delete).ToList();
            obj.Local.DiscountDetails = obj.Local.DiscountDetails.OrderByDescending(o => o.Delete).ToList();

            if (bindingExt.TaxSynchronization == true)
            {
                obj.AddDetail(BCEntitiesAttribute.TaxSynchronization, null, BCObjectsConstants.BCSyncDetailTaxSynced, true);
            }

            #region Taxes
            GetHelper<SPHelper>().LogTaxDetails(obj.SyncID, obj.Local);
            #endregion

            impl = cbapi.Put<SalesOrder>(obj.Local, obj.LocalID);

 

Best answer by noorula77

I can suggest two approaches please try to implement: 
The RowPersisting event captures the original tax details before any modifications
The RowPersisted event restores and updates the taxes after the main processing is complete

First Solution:
  // Adjust taxes right before the Sales Order is persisted
    protected void _(Events.RowPersisting<SOOrder> e)
    {
        if (e.Row == null) return;

        SOOrder order = e.Row;

        // Access the Taxes cache and adjust details
        PXCache taxCache = Base.Caches[typeof(SOTaxTran)];
        foreach (SOTaxTran tax in taxCache.Cached)
        {
            if (tax.TaxID == "SHOPIFY-TAX")
            {
                tax.CuryTaxAmt += 5; // Example adjustment
                taxCache.Update(tax);
            }
        }
    }

    // Optionally handle actions after persist
    protected void _(Events.RowPersisted<SOOrder> e, PXRowPersistedEventArgs args)
    {
        if (e.Row == null || args.TranStatus != PXTranStatus.Completed) return;

        SOOrder order = e.Row;

        // Log or perform additional adjustments post-persist
        PXTrace.WriteInformation($"Order {order.OrderNbr} persisted successfully with adjusted taxes.");
    }
}


OR try with below mentioned scenario:
Second Solution:
 

#region Override Events
    protected virtual void _(Events.RowPersisting<SOOrder> e)
    {
        if (e.Row == null) return;
        
        // Store original tax details before they get modified
        StoreTaxDetails(e.Row);
    }

    protected virtual void _(Events.RowPersisted<SOOrder> e, PXRowPersisted baseHandler)
    {
        if (e.Row == null || e.TranStatus != PXTranStatus.Open) return;

        if (e.Operation == PXDBOperation.Insert || e.Operation == PXDBOperation.Update)
        {
            RestoreAndUpdateTaxes(e.Row);
        }
    }
    #endregion

    #region Methods
    private const string ORIGINAL_TAXES_KEY = "OriginalTaxDetails";

    private void StoreTaxDetails(SOOrder order)
    {
        if (order?.TaxDetails == null || !order.TaxDetails.Any()) return;

        // Deep clone the tax details to preserve them
        var originalTaxes = order.TaxDetails.Select(tax => new SOTaxDetail
        {
            TaxID = tax.TaxID,
            TaxRate = tax.TaxRate,
            TaxAmount = tax.TaxAmount,
            TaxableAmount = tax.TaxableAmount
            // Add other relevant fields
        }).ToList();

        // Store in graph state
        Base.ProviderState[ORIGINAL_TAXES_KEY] = originalTaxes;
    }

    private void RestoreAndUpdateTaxes(SOOrder order)
    {
        var originalTaxes = Base.ProviderState[ORIGINAL_TAXES_KEY] as List<SOTaxDetail>;
        if (originalTaxes == null || !originalTaxes.Any()) return;

        // Clear existing taxes
        var taxesCache = Base.Taxes.Cache;
        var existingTaxes = Base.Taxes.Select().RowCast<SOTaxTran>().ToList();
        foreach (var existingTax in existingTaxes)
        {
            taxesCache.Delete(existingTax);
        }

        // Insert original taxes
        foreach (var originalTax in originalTaxes)
        {
            var newTaxTran = new SOTaxTran
            {
                TaxID = originalTax.TaxID,
                TaxRate = originalTax.TaxRate,
                CuryTaxAmt = originalTax.TaxAmount,
                CuryTaxableAmt = originalTax.TaxableAmount
                // Set other necessary fields
            };

            taxesCache.Insert(newTaxTran);
        }

        // Force cache persistence
        taxesCache.Persist(PXDBOperation.Insert);
        
        // Recalculate document totals if needed
        Base.Document.Cache.MarkUpdated(order);
    }
    #endregion
}

// Usage in your bucket import:
public override async Task SaveBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, 
    string operation, CancellationToken cancellationToken = default)
{
    MappedOrder obj = bucket.Order;
    
    // Store original tax details before any processing
    var originalTaxDetails = obj.Local.TaxDetails?.ToList();

    // Proceed with standard processing
    await base.SaveBucketImport(bucket, existing, operation, cancellationToken);

    // After base processing, ensure taxes are correct
    if (originalTaxDetails?.Any() == true)
    {
        var graph = PXGraph.CreateInstance<SOOrderEntry>();
        var orderExt = graph.GetExtension<SOOrderEntryExt>();
        
        var order = graph.Document.Search<SOOrder.orderNbr>(
            obj.Local.OrderNbr, 
            obj.Local.OrderType
        ).FirstOrDefault();

        if (order != null)
        {
            // Update the order with original tax details
            graph.ProviderState[SOOrderEntryExt.ORIGINAL_TAXES_KEY] = originalTaxDetails;
            graph.Document.Update(order);
            graph.Actions.PressSave();
        }
    }

 

View original
Did this topic help you find an answer to your question?

10 replies

Forum|alt.badge.img+1
  • Jr Varsity I
  • 62 replies
  • November 18, 2024

public override async Task SaveBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, string operation, CancellationToken cancellationToken = default)
{
await base.SaveBucketImport(bucket, existing, operation, cancellationToken);
}
OR
protected virtual SalesOrder ExecuteSalesOrderPut(SalesOrder order, string localId) {
// Your custom logic here before the Put
var result = cbapi.Put<SalesOrder>(order, localId);
// Your custom logic here after the Put
return result;
}


Forum|alt.badge.img
  • Author
  • Freshman II
  • 13 replies
  • November 18, 2024

what if i want to override 
var result = cbapi.Put<SalesOrder>(order, localId);
itself
i want to find where it’s defined

 


Forum|alt.badge.img+1
  • Jr Varsity I
  • 62 replies
  • November 18, 2024

The cbapi.Put<SalesOrder> is typically defined in Acumatica's API client libraries. If you want to override the actual API call behavior, you'll need to create a custom implementation of the API client interface.


Forum|alt.badge.img
  • Author
  • Freshman II
  • 13 replies
  • November 18, 2024

Any idea in what library that can be?
I am working with Shopify change.


Forum|alt.badge.img+1
  • Jr Varsity I
  • 62 replies
  • November 18, 2024

Can you please elaborate on what your customization.


Forum|alt.badge.img
  • Author
  • Freshman II
  • 13 replies
  • November 18, 2024

fixing tax creation from Shopify


Forum|alt.badge.img+1
  • Jr Varsity I
  • 62 replies
  • November 19, 2024

If your customization is specifically for handling tax synchronization or creation, you might not need to override the entire cbapi.Put<T> call. Instead:

Adjust the logic before calling cbapi.Put<T>, such as modifying the local object (e.g., obj.Local.TaxDetails).
Below mentioned sample code you can adjust and try:
 

public override async Task SaveBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, string operation, CancellationToken cancellationToken = default)
        {
            MappedOrder obj = bucket.Order;
            SalesOrder local = obj.Local;
            SalesOrder presented = existing?.Local as SalesOrder;

            // If custom mapped orderType, prevent modification of existing SO type
            if (existing != null)
                obj.Local.OrderType = ((MappedOrder)existing).Local.OrderType;

            // Sort details to prevent API bug with deleted lines
            obj.Local.Details = obj.Local.Details.OrderByDescending(o => o.Delete).ToList();
            obj.Local.DiscountDetails = obj.Local.DiscountDetails.OrderByDescending(o => o.Delete).ToList();

            // Handle Tax Synchronization
            BCBindingExt bindingExt = GetBindingExt<BCBindingExt>();
            if (bindingExt.TaxSynchronization == true)
            {
                obj.AddDetail(BCEntitiesAttribute.TaxSynchronization, null, BCObjectsConstants.BCSyncDetailTaxSynced, true);
            }

            #region Taxes (Custom Handling)
            // Log and potentially modify tax details
            GetHelper<SPHelper>().LogTaxDetails(obj.SyncID, obj.Local);
            ModifyTaxDetails(obj.Local); // Custom logic to modify taxes
            #endregion

            // Custom CBAPI Put Logic
            SalesOrder impl = CustomPutSalesOrder(obj.Local, obj.LocalID);

            // Optionally, call base logic if required
            await base.SaveBucketImport(bucket, existing, operation, cancellationToken);
        }

     
        /// Custom implementation for CBAPI Put<SalesOrder>
     
        private SalesOrder CustomPutSalesOrder(SalesOrder order, Guid? localId)
        {
            // Example of modifying order before saving
            if (order.TaxDetails != null && order.TaxDetails.Any())
            {
                foreach (var taxDetail in order.TaxDetails)
                {
                    taxDetail.TaxAmount += 10; // Example adjustment to tax amount
                }
            }

            // Call the standard CBAPI Put method (or custom logic to save)
            var result = cbapi.Put<SalesOrder>(order, localId);

            // Log or handle the result if needed
            GetHelper<SPHelper>().WriteLog("CustomPutSalesOrder", $"Saved SalesOrder with ID: {result.OrderNbr}");

            return result;
        }

             /// Custom logic to modify tax details

        private void ModifyTaxDetails(SalesOrder order)
        {
            if (order.TaxDetails == null || !order.TaxDetails.Any())
                return;

            foreach (var taxDetail in order.TaxDetails)
            {
                if (taxDetail.TaxID == "SHOPIFY-TAX") // Example condition
                {
                    taxDetail.TaxAmount += 5; // Adjust tax amount
                }
            }
        }
    
 


saifalisabri
Jr Varsity II
Forum|alt.badge.img
  • Jr Varsity II
  • 44 replies
  • November 19, 2024

Added by moderator: This reply has been created with the help of AI

 

The method call cbapi.Put<SalesOrder>(obj.Local, obj.LocalID) is part of the Acumatica ERP REST-based integration framework and interacts with the Contract-Based API (CBAPI)

1. Understand cbapi

The cbapi object represents an instance of the Acumatica Contract-Based API Client. It is often implemented using the PX.Api.ContractBased namespace or a similar custom wrapper. The Put method typically handles the creation or updating of entities (in this case, a SalesOrder).

Put Method Behavior

  • Signature:

    public T Put<T>(T entity, string id = null) where T : class; 
    • T: The type of entity being processed, such as SalesOrder.
    • entity: The entity instance being sent to Acumatica for creation or update.
    • id: Optional, used to update an existing entity based on its identifier.
  • The method performs:

    1. A check if the entity exists (using id).
    2. Updates the entity if it exists or creates a new one if it does not.
    3. Returns the entity after processing.

2. Where cbapi.Put is Defined

To locate the logic behind cbapi.Put, follow these steps:

a. Check the cbapi Initialization

Search for where the cbapi variable is defined in the SPSalesOrderProcessor or its parent class. Commonly, it is instantiated in the constructor or inherited from a base class.

For example:

cbapi = new PX.Api.ContractBased.Client(); 

b. Trace the Put Method

Navigate to the PX.Api.ContractBased.Client or equivalent namespace. The Put method is likely part of this client library.

  • If it's part of an external library or package:
    • Check if the PX.Api.ContractBased.Client assembly is included in the project references.
    • Use a decompiler (like JetBrains Rider or ILSpy) to inspect the referenced library.

3. Override the Logic

The cbapi.Put method cannot be directly overridden because it is a library method. However, you can intercept its usage in the SPSalesOrderProcessor.SaveBucketImport method by overriding the SaveBucketImport method or modifying its behavior.

a. Subclass the Processor

Create a subclass of SPSalesOrderProcessor and override the SaveBucketImport method to implement your custom logic.

Example:

public class CustomSPSalesOrderProcessor : SPSalesOrderProcessor { public override async Task SaveBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, string operation, CancellationToken cancellationToken = default) { // Custom implementation MappedOrder obj = bucket.Order; // Replace or extend cbapi.Put logic CustomPutLogic(obj.Local, obj.LocalID); } private SalesOrder CustomPutLogic(SalesOrder local, string localId) { // Implement custom logic here return cbapi.Put<SalesOrder>(local, localId); // Call the original method if needed } } 

b. Replace the Standard Processor

In the configuration for the Shopify integration, replace the standard processor with your custom implementation.

4. Debugging Tips

  • Use debugging tools to step into the cbapi.Put call to verify the exact library or method invoked.
  • If you're unable to locate cbapi.Put, search for the cbapi initialization or its type definition in the codebase.

5. Alternatives if Overriding is Not Feasible

If overriding SaveBucketImport or replacing the cbapi.Put logic is not feasible, consider:

  • Implementing a customization plugin that modifies the SalesOrder after it is created or updated.
  • Using Acumatica customization tools to add a hook or event handler to manipulate the entity post-update.

Forum|alt.badge.img
  • Author
  • Freshman II
  • 13 replies
  • November 20, 2024
noorula77 wrote:

If your customization is specifically for handling tax synchronization or creation, you might not need to override the entire cbapi.Put<T> call. Instead:

Adjust the logic before calling cbapi.Put<T>, such as modifying the local object (e.g., obj.Local.TaxDetails).
Below mentioned sample code you can adjust and try:
 

public override async Task SaveBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, string operation, CancellationToken cancellationToken = default)
        {
            MappedOrder obj = bucket.Order;
            SalesOrder local = obj.Local;
            SalesOrder presented = existing?.Local as SalesOrder;

            // If custom mapped orderType, prevent modification of existing SO type
            if (existing != null)
                obj.Local.OrderType = ((MappedOrder)existing).Local.OrderType;

            // Sort details to prevent API bug with deleted lines
            obj.Local.Details = obj.Local.Details.OrderByDescending(o => o.Delete).ToList();
            obj.Local.DiscountDetails = obj.Local.DiscountDetails.OrderByDescending(o => o.Delete).ToList();

            // Handle Tax Synchronization
            BCBindingExt bindingExt = GetBindingExt<BCBindingExt>();
            if (bindingExt.TaxSynchronization == true)
            {
                obj.AddDetail(BCEntitiesAttribute.TaxSynchronization, null, BCObjectsConstants.BCSyncDetailTaxSynced, true);
            }

            #region Taxes (Custom Handling)
            // Log and potentially modify tax details
            GetHelper<SPHelper>().LogTaxDetails(obj.SyncID, obj.Local);
            ModifyTaxDetails(obj.Local); // Custom logic to modify taxes
            #endregion

            // Custom CBAPI Put Logic
            SalesOrder impl = CustomPutSalesOrder(obj.Local, obj.LocalID);

            // Optionally, call base logic if required
            await base.SaveBucketImport(bucket, existing, operation, cancellationToken);
        }

     
        /// Custom implementation for CBAPI Put<SalesOrder>
     
        private SalesOrder CustomPutSalesOrder(SalesOrder order, Guid? localId)
        {
            // Example of modifying order before saving
            if (order.TaxDetails != null && order.TaxDetails.Any())
            {
                foreach (var taxDetail in order.TaxDetails)
                {
                    taxDetail.TaxAmount += 10; // Example adjustment to tax amount
                }
            }

            // Call the standard CBAPI Put method (or custom logic to save)
            var result = cbapi.Put<SalesOrder>(order, localId);

            // Log or handle the result if needed
            GetHelper<SPHelper>().WriteLog("CustomPutSalesOrder", $"Saved SalesOrder with ID: {result.OrderNbr}");

            return result;
        }

             /// Custom logic to modify tax details

        private void ModifyTaxDetails(SalesOrder order)
        {
            if (order.TaxDetails == null || !order.TaxDetails.Any())
                return;

            foreach (var taxDetail in order.TaxDetails)
            {
                if (taxDetail.TaxID == "SHOPIFY-TAX") // Example condition
                {
                    taxDetail.TaxAmount += 5; // Adjust tax amount
                }
            }
        }
    
 

Any chance i can modify code after Sale Order create, or before SOOrderEntry call?

My problem is that something is removing some taxes from list before or durin taxes insert to SOOrderEntry?

obj.Local.TaxDetails passed to “Put” during debug is showing correct values.

I tried to use SOOrderEntry , removing all taxes and adding correct taxes to SOTaxTran in Taxes Cache, but get error: Unable to cast object of type 'PX.Objects.SO.SOOrderEntry' to type 'DynamicInterface.PX.Objects.SO.GraphExtensions.SOOrderEntryExt.SOOrderLineSplittingExtension'.
 


Forum|alt.badge.img+1
  • Jr Varsity I
  • 62 replies
  • Answer
  • November 21, 2024

I can suggest two approaches please try to implement: 
The RowPersisting event captures the original tax details before any modifications
The RowPersisted event restores and updates the taxes after the main processing is complete

First Solution:
  // Adjust taxes right before the Sales Order is persisted
    protected void _(Events.RowPersisting<SOOrder> e)
    {
        if (e.Row == null) return;

        SOOrder order = e.Row;

        // Access the Taxes cache and adjust details
        PXCache taxCache = Base.Caches[typeof(SOTaxTran)];
        foreach (SOTaxTran tax in taxCache.Cached)
        {
            if (tax.TaxID == "SHOPIFY-TAX")
            {
                tax.CuryTaxAmt += 5; // Example adjustment
                taxCache.Update(tax);
            }
        }
    }

    // Optionally handle actions after persist
    protected void _(Events.RowPersisted<SOOrder> e, PXRowPersistedEventArgs args)
    {
        if (e.Row == null || args.TranStatus != PXTranStatus.Completed) return;

        SOOrder order = e.Row;

        // Log or perform additional adjustments post-persist
        PXTrace.WriteInformation($"Order {order.OrderNbr} persisted successfully with adjusted taxes.");
    }
}


OR try with below mentioned scenario:
Second Solution:
 

#region Override Events
    protected virtual void _(Events.RowPersisting<SOOrder> e)
    {
        if (e.Row == null) return;
        
        // Store original tax details before they get modified
        StoreTaxDetails(e.Row);
    }

    protected virtual void _(Events.RowPersisted<SOOrder> e, PXRowPersisted baseHandler)
    {
        if (e.Row == null || e.TranStatus != PXTranStatus.Open) return;

        if (e.Operation == PXDBOperation.Insert || e.Operation == PXDBOperation.Update)
        {
            RestoreAndUpdateTaxes(e.Row);
        }
    }
    #endregion

    #region Methods
    private const string ORIGINAL_TAXES_KEY = "OriginalTaxDetails";

    private void StoreTaxDetails(SOOrder order)
    {
        if (order?.TaxDetails == null || !order.TaxDetails.Any()) return;

        // Deep clone the tax details to preserve them
        var originalTaxes = order.TaxDetails.Select(tax => new SOTaxDetail
        {
            TaxID = tax.TaxID,
            TaxRate = tax.TaxRate,
            TaxAmount = tax.TaxAmount,
            TaxableAmount = tax.TaxableAmount
            // Add other relevant fields
        }).ToList();

        // Store in graph state
        Base.ProviderState[ORIGINAL_TAXES_KEY] = originalTaxes;
    }

    private void RestoreAndUpdateTaxes(SOOrder order)
    {
        var originalTaxes = Base.ProviderState[ORIGINAL_TAXES_KEY] as List<SOTaxDetail>;
        if (originalTaxes == null || !originalTaxes.Any()) return;

        // Clear existing taxes
        var taxesCache = Base.Taxes.Cache;
        var existingTaxes = Base.Taxes.Select().RowCast<SOTaxTran>().ToList();
        foreach (var existingTax in existingTaxes)
        {
            taxesCache.Delete(existingTax);
        }

        // Insert original taxes
        foreach (var originalTax in originalTaxes)
        {
            var newTaxTran = new SOTaxTran
            {
                TaxID = originalTax.TaxID,
                TaxRate = originalTax.TaxRate,
                CuryTaxAmt = originalTax.TaxAmount,
                CuryTaxableAmt = originalTax.TaxableAmount
                // Set other necessary fields
            };

            taxesCache.Insert(newTaxTran);
        }

        // Force cache persistence
        taxesCache.Persist(PXDBOperation.Insert);
        
        // Recalculate document totals if needed
        Base.Document.Cache.MarkUpdated(order);
    }
    #endregion
}

// Usage in your bucket import:
public override async Task SaveBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, 
    string operation, CancellationToken cancellationToken = default)
{
    MappedOrder obj = bucket.Order;
    
    // Store original tax details before any processing
    var originalTaxDetails = obj.Local.TaxDetails?.ToList();

    // Proceed with standard processing
    await base.SaveBucketImport(bucket, existing, operation, cancellationToken);

    // After base processing, ensure taxes are correct
    if (originalTaxDetails?.Any() == true)
    {
        var graph = PXGraph.CreateInstance<SOOrderEntry>();
        var orderExt = graph.GetExtension<SOOrderEntryExt>();
        
        var order = graph.Document.Search<SOOrder.orderNbr>(
            obj.Local.OrderNbr, 
            obj.Local.OrderType
        ).FirstOrDefault();

        if (order != null)
        {
            // Update the order with original tax details
            graph.ProviderState[SOOrderEntryExt.ORIGINAL_TAXES_KEY] = originalTaxDetails;
            graph.Document.Update(order);
            graph.Actions.PressSave();
        }
    }

 


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings