Question

How to create a generic method that Accepts Graph, Viewes, Actions (generally speaking objects) from the Base and allows to access to their internal methods?


Userlevel 7
Badge +8

I have a couple of methods that I will need to use them in multiple graphs. These methods work if I pass them specific Graph, View … but the problem is I have to copy these methods into any screen I need them (currently in 6 places). For example assume I’m doing the exact same thing on APInvoice (APInvoiceEntry) and POOrder (POOrderEntry).

If I copy my methods into these graph extensions, I can declare APInvoiceEntry and APInvoice as my method input parameters and pass to my methods then from inside my methods I will have access to the internal objects of them. For example I can access to the Base Graph Viewes or Actions. The same goes to POOrderEntry and other graphs. The problem is I have repeated my codes six times and any improvement and bug fix should happen in six different places.

Ideally I would like to convert my methods to generic methods that I can pass any desired Graph and DAC and access to their dependent objects like as views and actions. It could be done through Reflection but I’m hoping Acumatica has some built in classes or methods that can be utlized.


10 replies

Userlevel 7
Badge +5

I think you are looking for “generic Graph Extensions” aka “reusable business logic” 

https://help-2022r1.acumatica.com/(W(3))/Help?ScreenId=ShowWiki&pageid=bbc04253-7686-41dd-898b-aca47c1c6789

Userlevel 7
Badge +8

Thank you for the hint @Dmitrii Naumov 

Using the provided guidline, I managed to have some progress but I have stuck in passing Actions and the Base Graph to the “Generic Graph Extension”. Here I have copied portion of the code I need help with. Can you please provide an example how I can pass for example APInvoiceEntry “Base” and APInvoice “Approve” action from “APInvoiceApprovalMaint” to “DocumentApprovalMaint”? One example for each Base and Action will be great.

 

    //**************************************** Generic Graph Extension ****************************************//
public abstract class DocumentApprovalMaint<TGraph, TPrimary> : PXGraphExtension<TGraph>
where TGraph : PXGraph
where TPrimary : class, IBqlTable, new()
{
//**************************************** Mapping Class - Document Fields ****************************************//
protected class DocumentMapping : IBqlMapping
{
public Type Extension => typeof(Document);
protected Type _table;
public Type Table => _table;

public DocumentMapping(Type table)
{
_table = table;
}

public Type Status = typeof(Document.status);
public Type Hold = typeof(Document.hold);
public Type Approved = typeof(Document.approved);
public Type Rejected = typeof(Document.rejected);
}


protected abstract DocumentMapping GetDocumentMapping();

//**************************************** Mapping Class - Document Status ****************************************//
protected class StatusMapping : IBqlMapping
{
public Type Extension => typeof(ApprovalStatus);
protected Type _table;
public Type Table => _table;

public StatusMapping(Type table)
{
_table = table;
}

public Type OnHold = typeof(ApprovalStatus.onHold);
public Type Pending = typeof(ApprovalStatus.pending);
public Type Approved = typeof(ApprovalStatus.approved);
public Type Rejected = typeof(ApprovalStatus.rejected);
}

protected abstract StatusMapping GetStatusMapping();

public PXSelectExtension<Document> Documents;
}


//**************************************** Implementation Class ****************************************//
public class APInvoiceApprovalMaint : DocumentApprovalMaint<APInvoiceEntry, APInvoice>
{
protected override DocumentMapping GetDocumentMapping()
{
return new DocumentMapping(typeof(APInvoice))
{
Status = typeof(APInvoice.status),
Hold = typeof(APInvoice.hold),
Approved = typeof(APInvoice.approved),
Rejected = typeof(APInvoice.rejected)
};
}

protected override StatusMapping GetStatusMapping()
{
return new StatusMapping(typeof(APDocStatus))
{
OnHold = typeof(APDocStatus.hold),
Pending = typeof(APDocStatus.pendingApproval),
Approved = typeof(APDocStatus.balanced),
Rejected = typeof(APDocStatus.rejected)
};
}
}

 

Userlevel 7
Badge +5

@aaghaei  I’m not completely sure on what you mean by ‘pass Base graph’ and ‘pass Action’

 

As for pass the base graph, I think you can just use Base in the extension the same way you do in regular graph extensions. Would it be sufficient for you?

 

As for the ‘passing Action’, I think the concept is a little bit reversed here. If you have a business logic, that implement an action to be in a Generic Graph Extension, you should have the action itself in the same Generic Graph Extension as well. You don’t need to declare the action in the base graph and then pass it.

Again, I’m not completely sure that I understood the requirement properly, so please let me know if I missed something.

Userlevel 7
Badge +8

​​​@Dmitrii Naumov

Here are examples that hopefully will help on clarifications:

1- In all my graphs that I want to add Generic Graph Extension (GGE) I have something like this:

public EPApprovalAutomation_Extension<APInvoice, APInvoice.approved, APInvoice.rejected, APInvoice.hold, APSetupApproval> Approval;


The logic is the same but Primary DAC and SetupApproval changes per graph. I am not sure how can pass them to my GGE.

 

2- In all my graphs I “Press” some buttons programatically. Some of the names are standard like as “Save” but some are not. For example “ReleaseFromHold” in APInvoice for my purpose functions as same as “Activate” in PMProject. Considering the names are not the same, I can not use static names in my GGE. So I need to pass these names to the GGE.

If you can help me on these two I have figured out the rest pretty much myself.

Userlevel 7
Badge +5
  1. Well, it looks like you are referring to another generic graph extension that actually implements Approval logic. If that’s the case, you may want to have a ‘second level graph extension over a graph extension’ if that makes sense.
  2. Well, you can have a little bit of an override in each specific implementation of your generic graph extension.

e.g. 

    public class APInvoiceApprovalMaint : DocumentApprovalMaint<APInvoiceEntry, APInvoice>
{
protected override DocumentMapping GetDocumentMapping()
{
return new DocumentMapping(typeof(APInvoice))
{
Status = typeof(APInvoice.status),
Hold = typeof(APInvoice.hold),
Approved = typeof(APInvoice.approved),
Rejected = typeof(APInvoice.rejected)
};
}

protected override StatusMapping GetStatusMapping()
{
return new StatusMapping(typeof(APDocStatus))
{
OnHold = typeof(APDocStatus.hold),
Pending = typeof(APDocStatus.pendingApproval),
Approved = typeof(APDocStatus.balanced),
Rejected = typeof(APDocStatus.rejected)
};
}
protected override PressHold()
{
Base.Hold.Press();
}
}

 

Userlevel 7
Badge +8

@Dmitrii Naumov I guess I couldn’t explain what I mean in the second part. Out of multiple custom actions that I have, I have copied “CustomUnhold” that works on APInvoice. In my AP Graph I call actions like

                    Base.releaseFromHold.Press();
                    Base.Save.Press();
 

but when I rewrite “Press” Actions in my GGE, they beome like this:

                    Base.Actions["ReleaseFromHold"].Press();
                    Base.Actions["Save"].Press();
 

So I’m not sure how I can make it work. Here I copied my custom actions that works in AP graph directly. How can I make this work in GGE I do not know.

 

        protected virtual IEnumerable CustomUnhold(PXAdapter adapter)
{
// Save possible pending changes.
Base.Save.Press();

if (RetainApprovalHistoryOnReject(Base) == true)
{
// Get Current Graph and Cache
APInvoiceEntry_Workflow BaseWF = Base.GetExtension<APInvoiceEntry_Workflow>();
APInvoiceEntry_ApprovalWorkflow BaseAM = Base.GetExtension<APInvoiceEntry_ApprovalWorkflow>();
APInvoice document = Base.Document.Current;
EPApproval approvalBase = null;
EPApproval approvalNext = null;

// Open Approval Transaction Scope to Get the Next Approver for Approval Dialog
using (PXTransactionScope unholdTransaction = new PXTransactionScope())
{
// Detach the Approval records from the document
var RefNoteID = new Guid();
List<EPApproval> approvalHolder = new List<EPApproval>();

foreach (EPApproval approval in Base.Approval.Select())
{
if (approval.Status != EPStatuses.CustomPending)
{
approvalHolder.Add(approval);
approval.RefNoteID = RefNoteID;
Base.Approval.Update(approval);
Base.Save.Press();
}
}

// Remove the document from hold and save.
Base.releaseFromHold.Press();
Base.Save.Press();

// Re-attach the Approval records to the document
foreach (EPApproval approval in approvalHolder)
{
approval.RefNoteID = Base.Document.Current.NoteID;
Base.Approval.Update(approval);
Base.Save.Press();
}

unholdTransaction.Complete();
}

// If Approval workflow is not enabled or document has not been through approval before, return. Otherwise get the last Approved & the first Pending Approval.
approvalBase = Base.Approval.Current;
if (approvalBase == null) return adapter.Get();

approvalBase = GetApproval(Base, (Guid)document.NoteID, null, EPStatuses.CustomApproved, EPLookupRange.Last, EPAssignee.NotPrompted, null, null);
approvalNext = GetApproval(Base, (Guid)document.NoteID, null, EPStatuses.CustomPending, EPLookupRange.First, EPAssignee.NotPrompted, null, null);

// Get Next Approval Info and update the current Pending Approval to the first Approver from the Assignment and Approval Map
if (approvalNext == null) return adapter.Get();

approvalNext = GetApprovalInfo(Base, document, approvalNext, EPActions.Pending);
approvalNext = Base.Approval.Update(approvalNext);
Base.Save.Press();

// Send assignment notification
SendAssignmentNotification(Base, document, approvalNext);
}
else
{
// Remove the document from hold and save as per Acumatica's standard methods.
Base.releaseFromHold.Press();
Base.Save.Press();
}

return adapter.Get();
}

 

Userlevel 7
Badge +8

 

@Dmitrii Naumov

 

Considering I need to press buttons from GGE but the names are not the same, I thought the best way to manage it is to add some hidden action items to the implementation graphs with unified names that I will call them in my GGE. 

here is the action with unified name that I will add to the implementation graphs. the ReleaseFromHold buttom is in APInvoiceEntry graph and is valid.

        #region Remove Hold
[PXHidden]
public PXAction<APInvoice> unhold;
[PXButton(CommitChanges = true, Category = ActionsMessages.Processing, DisplayOnMainToolbar = true, IsLockedOnToolbar = true)]
[PXUIField(DisplayName = EPMessages.CustomUnhold, MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]

protected virtual IEnumerable Unhold(PXAdapter adapter)
{
Base.releaseFromHold.Press();
return adapter.Get();
}
#endregion

and here is how I’m calling the Action in my GGE

Base.Actions["Unhold"].Press();

and here is the error I receive.


Any help is appreciated.  Thank you.

Userlevel 7
Badge

Hi @aaghaei - were you able to find a solution for your issue? Thank you!

Userlevel 7
Badge +8

Hi @Chris Hackett no I was not. Thanks for the follow up 

Userlevel 6
Badge +3

Hi @aaghaei 

EDIT:  Please ignore this comment.  I think I know what I am missing.

Reply


About Acumatica ERP system
Acumatica Cloud ERP provides the best business management solution for transforming your company to thrive in the new digital economy. Built on a future-proof platform with open architecture for rapid integrations, scalability, and ease of use, Acumatica delivers unparalleled value to small and midmarket organizations. Connected Business. Delivered.
© 2008 — 2024  Acumatica, Inc. All rights reserved