Adding Custom Credit Max Check Type in D365 Finance & Operations
This post shows a complete framework for adding a new custom TypeOfCreditmaxCheck enum value and wiring it through the credit control pipeline in D365 F&O to extend the credit control framework to support a custom calculation based on released but not yet invoiced sales orders.
Base Implementation: Adding a Custom Credit Check Type
The following extensions define the new enum value and implement the core calculation logic within the CustCreditLimit framework.
Step 1: Extend the Credit Check Enum
[Enum Extension] TypeOfCreditmaxCheck::BalanceReleasedNotYetInvoiced[ExtensionOf(classStr(CustCreditLimit))]
Step 2: Implement the Core Calculation Logic
[ExtensionOf(classStr(CustCreditLimit))]
CustCreditLimit_SSI_Extension
{
AmountMST balanceReleasedNotYetInvoiced(AmountMST _balanceNotInvoiced = _balanceNotInvoiced)
{
if (!prmisDefault(_balanceNotInvoiced))
{
BalanceReleasedNotYetInvoiced = _balanceNotInvoiced;
BalanceReleasedNotYetInvoicedCalculated = true;
}
else
{
if (!BalanceReleasedNotYetInvoicedCalculated)
{
BalanceReleasedNotYetInvoiced = this.calcBalanceReleasedNotYetInvoiced();
BalanceReleasedNotYetInvoicedCalculated = true;
}
}
return BalanceReleasedNotYetInvoiced;
}
protected AmountMST calcBalanceReleasedNotYetInvoiced()
{
SalesTable salesTableLocal;
SalesLine salesLine;
CredManTrans credManTrans;
AmountMST ret;
CredManCreditLimitId credManGroup = this.credManCreditLimitCustGroup(custTable.AccountNum);
RollupView RollupView;
if(credManGroup == '')
{
select sum(Total) from RollupView
where RollupView.InvoiceAccount == custTable.InvoiceAccount;
ret += RollupView.Total;
}
else // we have a credit group and we need to account for the added accounts
{
CredManCreditLimitCustGroupView credManGroupView;
while select credManGroupView where credManGroupView.CreditLimitId == credManGroup
{
select sum(Total) from RollupView
where RollupView.InvoiceAccount == credManGroupView.AccountNum;
ret += RollupView.Total;
}
}
return ret;
}
internal CredManCreditLimitId credManCreditLimitCustGroup(CustAccount _custAccount)
{
CredManCreditLimitId credManCreditLimitId;
CredManCreditLimitCustGroupView credManCreditLimitCustGroupView;
if (CredManParameterManagementVisibilityManager::isEnabled())
{
select firstonly CreditLimitId from credManCreditLimitCustGroupView
where credManCreditLimitCustGroupView.AccountNum == _custAccount
&& credManCreditLimitCustGroupView.Company == curExt();
credManCreditLimitId = credManCreditLimitCustGroupView.CreditLimitId;
}
return credManCreditLimitId;
}
protected AmountMST amountAddedToBalanceBasedOnTypeOfCreditmaxCheck()
{
AmountMST amountToAdd = next amountAddedToBalanceBasedOnTypeOfCreditmaxCheck();
switch (this.typeOfCreditMaxCheck())
{
case TypeOfCreditmaxCheck::BalanceReleasedNotYetInvoiced:
amountToAdd = this.calcBalanceReleasedNotYetInvoiced();
break;
}
return amountToAdd;
}
}
Step 3: Apply the Calculation Across Companies
[ExtensionOf(classStr(CredManCustCreditLimitCrossCompany))]
CredManCustCreditLimitCrossCompany_SSI_Extension
{
protected AmountMST amountAddedToBalanceBasedOnTypeOfCreditmaxCheck(CustCreditLimit _custCreditLimit)
{
AmountMST balance = next amountAddedToBalanceBasedOnTypeOfCreditmaxCheck(_custCreditLimit);
switch (_custCreditLimit.typeOfCreditMaxCheck())
{
case TypeOfCreditmaxCheck::BalanceReleasedNotYetInvoiced:
balance = _custCreditLimit.balanceInvoiced() + _custCreditLimit.balanceDelivered() + _custCreditLimit.balanceReleasedNotYetInvoiced();
break;
}
return balance;
}
}
Step 4: Extend Vendor Credit Checks
[ExtensionOf(tableStr(VendTable))]
final class VendTable_SSI_Extension
{
protected static boolean checkCreditLimitForCreditMaxCheck(VendAccount _vendAccount,
TypeOfCreditmaxCheck _check,
AmountMST _openBalanceMST,
AmountMST _agreementCreditLimitMax,
boolean _checkAgreementLimit,
AmountMST _amountMST,
PurchId _excludePurchId,
AgreementHeaderExtRecId_RU _agreementHeaderExtRecId)
{
boolean ret = next checkCreditLimitForCreditMaxCheck(_vendAccount, _check,
_openBalanceMST, _agreementCreditLimitMax,_checkAgreementLimit,
_amountMST, _excludePurchId, _agreementHeaderExtRecId);
AmountMST purchBalanceMst;
switch (_check)
{
// Default to Balance option unless further mod is needed.
case TypeOfCreditmaxCheck::BalanceReleasedNotYetInvoiced:
purchBalanceMst = PurchTable::balanceAmountNotInvoiced(_vendAccount, _excludePurchId, _checkAgreementLimit, _agreementHeaderExtRecId) + _amountMST;
if (_openBalanceMST + purchBalanceMst > _agreementCreditLimitMax)
{
ret = checkFailed(strFmt("@SYS25668", _openBalanceMST, purchBalanceMst,
_openBalanceMST + purchBalanceMst, _agreementCreditLimitMax,
(_openBalanceMST + purchBalanceMst) - _agreementCreditLimitMax));
}
else
{
ret = true;
}
break;
}
return ret;
}
}
Step 5: Adjust Estimated Credit Behavior
[ExtensionOf(tableStr(CustTable))]
Final class SSICustTable_Extension
{
public boolean isEstimatedBasedOnCreditMaxCheck()
{
boolean ret = next isEstimatedBasedOnCreditMaxCheck();
CustParameters custParameters = CustParameters::find();
if(custParameters.CreditMaxCheck == TypeOfCreditmaxCheck::BalanceReleasedNotYetInvoiced)
{
return false;
}
else
{
return ret;
}
}
}
Handling Advanced Scenarios and Transaction Context
- CustCreditLimit_SalesTable
- CustCreditLimit_CustInvoiceTable
- CustCreditLimit_SalesParmTable
Extending for Transaction-Specific Adjustments
Cross-Company Routing Logic
[ExtensionOf(classStr(CredManCustCreditLimitCrossCompany))]
final class CredManCustCreditLimitCrossCompany_SSI_Extension
{
protected AmountMST amountAddedToBalanceBasedOnTypeOfCreditmaxCheck(CustCreditLimit _custCreditLimit)
{
AmountMST balance = next amountAddedToBalanceBasedOnTypeOfCreditmaxCheck(_custCreditLimit);
switch (_custCreditLimit.typeOfCreditMaxCheck())
{
case TypeOfCreditmaxCheck::BalanceReleasedNotYetInvoiced:
if (_custCreditLimit is CustCreditLimit_SalesParmTable)
{
CustCreditLimit_SalesParmTable custCreditLimitSalesParm = _custCreditLimit;
balance = _custCreditLimit.calcBalanceReleasedNotYetInvoiced();
}
else if (_custCreditLimit is CustCreditLimit_CustInvoiceTable)
{
CustCreditLimit_CustInvoiceTable custCreditLimitSales = _custCreditLimit;
balance = _custCreditLimit.calcBalanceReleasedNotYetInvoiced();
}
else if (_custCreditLimit is CustCreditLimit_SalesTable)
{
CustCreditLimit_SalesTable custCreditLimitSales = _custCreditLimit;
balance = _custCreditLimit.calcBalanceReleasedNotYetInvoiced();
}
else
{
balance = _custCreditLimit.calcBalanceReleasedNotYetInvoiced();
}
break;
}
return balance;
}
}
Sales Order-Specific Logic
[ExtensionOf(classStr(CustCreditLimit_SalesTable))]
final class CustCreditLimit_SalesTable_SSI_Extension
{
public AmountMST calcBalanceReleasedNotYetInvoiced()
{
SalesTable salesTableLocal;
SalesLine salesLine;
CredManTrans credManTrans;
AmountMST ret;
CredManCreditLimitId credManGroup = this.credManCreditLimitCustGroup(custTable.AccountNum);
RollupView RollupView;
if(credManGroup == '')
{
select sum(Total) from RollupView
where RollupView.InvoiceAccount == custTable.InvoiceAccount;
ret += RollupView.Total;
}
else
{
CredManCreditLimitCustGroupView credManGroupView;
while select credManGroupView where credManGroupView.CreditLimitId == credManGroup
{
select sum(Total) from RollupView
where RollupView.InvoiceAccount == credManGroupView.AccountNum;
ret += RollupView.Total;
}
}
SalesTable salesTable;
if(this.salesTable)
{
salesTable = SalesTable::find(this.salesTable.SalesId);
}
if(salesTable)
{
ret -= SSICustCreditLimitHelper::getSalesTotals(salesTable);
}
return ret;
}
}
Invoice-Specific Logic
[ExtensionOf(classStr(CustCreditLimit_SalesTable))]
final class CustCreditLimit_CustInvoiceTable_SSI_Extension
{
public AmountMST calcBalanceReleasedNotYetInvoiced()
{
SalesTable salesTableLocal;
SalesLine salesLine;
CredManTrans credManTrans;
AmountMST ret;
CredManCreditLimitId credManGroup = this.credManCreditLimitCustGroup(custTable.AccountNum);
RollupView RollupView;
if(credManGroup == '')
{
select sum(Total) from RollupView
where RollupView.InvoiceAccount == custTable.InvoiceAccount;
ret += RollupView.Total;
}
else
{
CredManCreditLimitCustGroupView credManGroupView;
while select credManGroupView where credManGroupView.CreditLimitId == credManGroup
{
select sum(Total) from RollupView
where RollupView.InvoiceAccount == credManGroupView.AccountNum;
ret += RollupView.Total;
}
}
SalesTable salesTable;
if(this.custInvoiceTable && this.custInvoiceTable.SalesId)
{
salesTable = SalesTable::find(this.custInvoiceTable.SalesId);
}
if(salesTable)
{
ret -= SSICustCreditLimitHelper::getSalesTotals(salesTable);
}
return ret;
}
}
Posting Parameter / Batch Logic
[ExtensionOf(classStr(CustCreditLimit_SalesParmTable))]
final class CustCreditLimit_SalesParmTable_SSI_Extension
{
public AmountMST calcBalanceReleasedNotYetInvoiced()
{
SalesTable salesTableLocal;
SalesLine salesLine;
CredManTrans credManTrans;
AmountMST ret;
CredManCreditLimitId credManGroup = this.credManCreditLimitCustGroup(custTable.AccountNum);
RollupView RollupView;
if(credManGroup == '')
{
select sum(Total) from RollupView
where RollupView.InvoiceAccount == custTable.InvoiceAccount;
ret += RollupView.Total;
}
else
{
CredManCreditLimitCustGroupView credManGroupView;
while select credManGroupView where credManGroupView.CreditLimitId == credManGroup
{
select sum(Total) from RollupView
where RollupView.InvoiceAccount == credManGroupView.AccountNum;
ret += RollupView.Total;
}
}
SalesTable salesTable;
if(this.salesTable)
{
salesTable = SalesTable::find(this.salesTable.SalesId);
if(salesTable)
{
ret -= SSICustCreditLimitHelper::getSalesTotals(salesTable);
}
}
else if(this.salesParmTable)
{
SalesParmTable salesParmTable;
while select salesParmTable where salesParmTable.ParmId == this.salesParmTable.ParmId
{
salesTable = SalesTable::find(salesParmTable.SalesId);
if(salesTable)
{
ret -= SSICustCreditLimitHelper::getSalesTotals(salesTable);
}
}
}
return ret;
}
}
Key Takeaways
Extending credit management in Dynamics 365 Finance & Operations allows you to tailor credit exposure calculations to better reflect how your business actually operates. By introducing a custom 'TypeOfCreditmaxCheck', you can account for release but not yet invoiced sales orders.
If you're evaluating a similar requirement or need help designing a custom credit control approach in D365 F&O, the Stoneridge team can help.
Under the terms of this license, you are authorized to share and redistribute the content across various mediums, subject to adherence to the specified conditions: you must provide proper attribution to Stoneridge as the original creator in a manner that does not imply their endorsement of your use, the material is to be utilized solely for non-commercial purposes, and alterations, modifications, or derivative works based on the original material are strictly prohibited.
Responsibility rests with the licensee to ensure that their use of the material does not violate any other rights.


