Skip to main content
Solved

Execute Credit Verification from Service Order

  • 19 July 2024
  • 5 replies
  • 48 views

Basically, I am trying to accomplish something similar to this feature request: Add Credit Hold Warning to Service Order

I need to access the VerifyByCreditRules method that is located in a protected class in the PX.Objects.Extensions.CustomerCreditHold namespace.

The signature for it is:

        protected virtual CreditVerificationResult VerifyByCreditRules(PXCache sender, TDoc Row, PX.Objects.AR.Customer customer, CustomerClass customerclass)

I need access it from a ServiceOrderEntry graph.

I read this interesting blog post: Overriding Credit Verification by @Keith Richardson that shows accessing a protected class for sales orders, but I sort of get lost with how to translate that into the ServiceOrder graph.

I have some source code that compiles, but does not run, since the creditExtensionProtected and creditExtension values are coming back null… 

But I am not sure I am even on the right track.

Basically, I want to call  VerifyByCreditRules method whenever the ServiceOrder ID is changed to determine if the customer should be placed on credit hold. 

How would I do that?

This is where I am right now, but I’m not sure it is even worth considering… It is attached in case I am close, and it might help:

using System;
using System.Collections;
using System.Collections.Generic;
using PX.SM;
using PX.Data;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using PX.Objects.FS;
using PX.Objects.CS;
using PX.Objects.IN;
using PX.Objects.AR;
using PX.Objects.Extensions.CustomerCreditHold;



namespace MyCode
{
public class ServiceOrderEntry_Ext : PXGraphExtension<ServiceOrderEntry>
{
public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.serviceManagementModule>();

protected virtual void _(Events.RowSelected<FSServiceOrder> e)
{
var row = (FSServiceOrder)e.Row;
var cache = e.Cache;

if (row == null) return;

string displayStatus = string.Empty;
}

protected virtual void _(Events.RowInserted<FSServiceOrder> e)
{
FSServiceOrder row = e.Row as FSServiceOrder;
var cache = e.Cache;

if (row == null) return;


var currentCustom = row.CustomerID;
}
// Access the CustomerCreditExtension
private CustomerCreditExtension<ServiceOrderProcess, FSServiceOrder, FSServiceOrder.customerID,
FSServiceOrder.hold, FSServiceOrder.completed, FSServiceOrder.status> creditExtension;

private CustomerCreditExtensionProtected creditExtensionProtected;

public override void Initialize()
{
base.Initialize();
creditExtension = Base.GetExtension<CustomerCreditExtension<ServiceOrderProcess, FSServiceOrder, FSServiceOrder.customerID,
FSServiceOrder.hold, FSServiceOrder.completed, FSServiceOrder.status>>();
creditExtensionProtected = Base.GetExtension<CustomerCreditExtensionProtected>();
}


// Method to check customer credit status
public CreditVerificationResult CheckCustomerCreditStatus(FSServiceOrder serviceOrder)
{
PXCache cache = Base.CachesCtypeof(FSServiceOrder)];
PX.Objects.AR.Customer customer = GetCustomer(cache, serviceOrder);
if (customer == null) return null;
CustomerClass customerClass = GetCustomerClass(cache, customer);

if (customer != null && customerClass != null)
{
return creditExtensionProtected.VerifyByCreditRules(cache, serviceOrder, customer, customerClass);
}

return null;
}

private PX.Objects.AR.Customer GetCustomer(PXCache cache, FSServiceOrder serviceOrder)
{
return SelectFrom<PX.Objects.AR.Customer>
.Where<PX.Objects.AR.Customer.bAccountID.IsEqual<@P.AsInt>>
.View.Select(Base, serviceOrder.CustomerID).TopFirst;
}

private CustomerClass GetCustomerClass(PXCache cache, PX.Objects.AR.Customer customer)
{
return SelectFrom<CustomerClass>
.Where<CustomerClass.customerClassID.IsEqual<@P.AsString>>
.View.Select(Base, customer.CustomerClassID).TopFirst;
}

private void ApplyCreditVerificationResult(CreditVerificationResult result)
{
if (result != null && result.Failed)
{
// Handle the credit verification failure (e.g., place the order on hold, show a warning, etc.)
// throw new PXException(result.ErrorMessage);
}
}

protected virtual void _(Events.FieldUpdated<FSServiceOrder, FSServiceOrder.customerID> e)
{
{
FSServiceOrder serviceOrder = (FSServiceOrder)e.Row;
CreditVerificationResult result = CheckCustomerCreditStatus(serviceOrder);

if (result != null && result.Failed)
{
// Handle the credit verification failure
ApplyCreditVerificationResult(result);
}
}
}

// Class to access the protected CreditExtension
iPXProtectedAccess(typeof(CustomerCreditExtension<ServiceOrderProcess, FSServiceOrder, FSServiceOrder.customerID, FSServiceOrder.hold, FSServiceOrder.completed, FSServiceOrder.status>))]
public abstract class CustomerCreditExtensionProtected : PXGraphExtension<CustomerCreditExtension<ServiceOrderProcess, FSServiceOrder, FSServiceOrder.customerID, FSServiceOrder.hold, FSServiceOrder.completed, FSServiceOrder.status>, ServiceOrderProcess>
{
PXProtectedAccess]
internal abstract CreditVerificationResult VerifyByCreditRules(PXCache cache, FSServiceOrder serviceOrder, Customer customer, CustomerClass customerClass);
}

}
}

 

 

 

5 replies

Userlevel 6
Badge +2

Hi @mjgrice32 

Please take a look at other implementations of CustomerCreditExtension extension (examples provided by the Acumatica in the Site\App_Data\CodeRepository - ARInvoiceCustomerCreditExtension, ARPaymentCustomerCreditExtension, SOOrderCustomerCreditExtension, SOOrderCustomerCreditExtension).

This is an abstract class - which is a kind of template in C# syntax. You inherit this class and receive logic implemented in it inside your child class.

If you don’t need to inherit some parts of this code, you can override affected events to change the logic to the one you need.

Move your custom logic from ServiceOrderEntry_Ext into the child class and get rid of CustomerCreditExtensionProtected - the workaround with PXProtectedAccess attribute allows you to get access to the protected method in the base graph by exposing it in the separate extension. It’s neat, but does not match your use case (as I see it right now).

Userlevel 4
Badge

Well, unfortunately, I am still unable to access the VerifyByCreditRule method, due to protection level.

Here’s the extension class I created following the example I found in the code repository at:  

CodeRepository\PX.Objects\SO\GraphExtensions\SOOrderCustomerCreditExtension.cs

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using PX.Data;
using PX.Objects.AR;
using PX.Objects.CS;
using PX.Objects.Extensions.CustomerCreditHold;
using PX.Objects.FS;


namespace MyNamespace
{
//***********************************************************************
// ASG Custom Class:
// custom extension class that inherits from CustomerCreditExtension.
// This will allow access to the protected method: VerifyByCreditRules()
//************************************************************************

public class ServiceOrderEntry_CustomerCreditExtension : CustomerCreditExtension<
ServiceOrderEntry,
FSServiceOrder,
FSServiceOrder.customerID,
FSServiceOrder.hold,
FSServiceOrder.completed,
FSServiceOrder.status>
{
public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.serviceManagementModule>();

// For our purposes, we will never be updating the AR Balances, but the class definition
// is requiring that we implement this method. So we just create an abstract that does nothing
// but throws an error which -- since we never call it -- should never happen;
public override void UpdateARBalances(PXCache cache, FSServiceOrder newRow, FSServiceOrder oldRow)
{
throw new NotImplementedException();
}

// Again, we were forced to created this method... IDK why, since the example in the repository
// (found at: App_Data\CodeRepository\PX.Objects\SO\GraphExtensions\SOOrderCustomerCreditExtension.cs
// doesn't have it implemented. But VS2022 is requiring us to have it, so
// We simply call back to the Service order Base and get the ARSetup from the cache there.
protected override ARSetup GetARSetup()
{
return PXSelect<ARSetup>.Select(Base);
}


// Override other methods if needed



}

}

 

Userlevel 6
Badge +2

Hi @mjgrice32 

I took your latest extension code, and parts from the original message and adjusted them:

using PX.Data;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using PX.Objects.AR;
using PX.Objects.CS;
using PX.Objects.Extensions.CustomerCreditHold;
using PX.Objects.FS;


namespace MyNamespace
{
//***********************************************************************
// ASG Custom Class:
// custom extension class that inherits from CustomerCreditExtension.
// This will allow access to the protected method: VerifyByCreditRules()
//************************************************************************

public class ServiceOrderEntry_CustomerCreditExtension : CustomerCreditExtension<
ServiceOrderEntry,
FSServiceOrder,
FSServiceOrder.customerID,
FSServiceOrder.hold,
FSServiceOrder.completed,
FSServiceOrder.status>
{
public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.serviceManagementModule>();

//***********************************************************************
// Andrii Tkachenko: This method WILL be called - Verify calls it at the end of its process.
// Leaving throw new NotImplementedException() will break your code.
// If you don't know what to do here, just leave it empty for now.
// And later on, maybe you will find use for it.
// You can also check other children of CustomerCreditExtension for ideas how UpdateARBalances can be implemented.
//************************************************************************
public override void UpdateARBalances(PXCache cache, FSServiceOrder newRow, FSServiceOrder oldRow)
{

}

// Again, we were forced to created this method... IDK why, since the example in the repository
// (found at: App_Data\CodeRepository\PX.Objects\SO\GraphExtensions\SOOrderCustomerCreditExtension.cs
// doesn't have it implemented. But VS2022 is requiring us to have it, so
// We simply call back to the Service order Base and get the ARSetup from the cache there.
//***********************************************************************
// Andrii Tkachenko: I agree, you have to make it.
// SOOrderCustomerCreditExtension is also marked as abstract class - meaning, it's another "template" class,
// that is inherited by some other class, which (assuming it's not another "template") should have this method implemented.
// For example, SOOrderCustomerCredit is a child class of SOOrderCustomerCreditExtension and it implements this method like so:
// return base.Base.arsetup.Current;
// Just keep it like you did. I made it the same as SOOrderCustomerCredit, but you can keep your previous version.
//***********************************************************************
protected override ARSetup GetARSetup()
{
return PXSetup<ARSetup>.Select(Base);
}

//***********************************************************************
// Andrii Tkachenko: I copied this event from your ServiceOrderEntry_Ext example at the 1st message of the topic, as-is
// This is an example that you can now use your
//************************************************************************
protected virtual void _(Events.FieldUpdated<FSServiceOrder, FSServiceOrder.customerID> e)
{
{
FSServiceOrder serviceOrder = (FSServiceOrder)e.Row;
CreditVerificationResult result = CheckCustomerCreditStatus(serviceOrder);

if (result != null && result.Failed)
{
// Handle the credit verification failure
ApplyCreditVerificationResult(result);
}
}
}

//***********************************************************************
// Andrii Tkachenko: I copied these from your ServiceOrderEntry_Ext example at the 1st message of the topic, as-is
// Update if needed
//************************************************************************

// Method to check customer credit status
public CreditVerificationResult CheckCustomerCreditStatus(FSServiceOrder serviceOrder)
{
PXCache cache = Base.Caches[typeof(FSServiceOrder)];
Customer customer = GetCustomer(cache, serviceOrder);
if (customer == null) return null;
CustomerClass customerClass = GetCustomerClass(cache, customer);

if (customer != null && customerClass != null)
{
return VerifyByCreditRules(cache, serviceOrder, customer, customerClass); // Andrii Tkachenko:
// note that this method is available for you
// without any additional code,
// because you inherited from CustomerCreditExtension
}

return null;
}

private Customer GetCustomer(PXCache cache, FSServiceOrder serviceOrder)
{
return SelectFrom<Customer>
.Where<Customer.bAccountID.IsEqual<@P.AsInt>>
.View.Select(Base, serviceOrder.CustomerID).TopFirst;
}

private CustomerClass GetCustomerClass(PXCache cache, Customer customer)
{
return SelectFrom<CustomerClass>
.Where<CustomerClass.customerClassID.IsEqual<@P.AsString>>
.View.Select(Base, customer.CustomerClassID).TopFirst;
}

private void ApplyCreditVerificationResult(CreditVerificationResult result)
{
if (result != null && result.Failed)
{
// Handle the credit verification failure (e.g., place the order on hold, show a warning, etc.)
// throw new PXException(result.ErrorMessage);
}
}

// Override other methods if needed
}

}

I’ve also left comments for you to explain what I did and why. I start all of them with my name (Andrii Tkachenko), so you can easily find and highlight them.

Userlevel 5
Badge +2

Did you complete this customization @jgrice32 ? I have previously, and can break it all apart into a customization project if needed. I have extended a lot of the workflows to our business requirements, so it wouldn’t be something that I could easily copy/paste out.

Userlevel 4
Badge

Did you complete this customization @jgrice32 ? I have previously, and can break it all apart into a customization project if needed. I have extended a lot of the workflows to our business requirements, so it wouldn’t be something that I could easily copy/paste out.

Keith, thanks for the response.

Yes, thanks to the sample code Andrii provided above, I was able to figure out how to do my customization.

Reply