I have been asked the question by one of my customers that they would like to automatically "Capture CC Payment" for Sales orders at the time that the shipper is confirmed.
I know that in Acumatica, there is the batch processing methods to handle doing this in bulk but they'd still like to have it automatically do it as mentioned above.
My initial thoughts were a Customization that will use an Automation Step when the shipper is confirmed to load the order and execute the "Capture CC Payment" step.
More or less a customization step that is composed of
1) A custom method in the shipper screen that loads the order(s) in question and then executes the "Capture CC Payment" button/function
2) An automation step assigned to the shipper at the time of confirmation that executes the above method.
Is this the best way of handling this request or is there another pre-existing method that I might have overlooked?
The customer is currently on 4.20.2115 with no real interest in going to 5.1 anytime soon.
Thank you
If you have a hummer in your hands, everything is a nail :). I can propose you solution from C# prospective. As a solution, I propose you to override method confirm in extension class with usage of delegate. I don't know about which form you are talking about, but lets assume, that form has graph SubmitterManager. Lets also assume, that you want to track method CreatePayment, which has paramethers string, IDictionary, IDictionary. Then you can create graph extension SubmitterManagerExt, and use PXOverride attribute with delegate in the following way:
public class SubmitterManagerExt : PXGraphExtension<SubmitterManager>
{
[PXOverride]
public bool CreatePayment(string viewName,
IDictionary keys,
IDictionary values,
Func<string, IDictionary,
IDictionary, bool> method)
{
// Call your method CreatePayment
bool result = method(viewName, keys, values);
if(result)
{
// here you can add some C# code, which will do something
// with created payments
}
}
return result;
}
}
Related
Assume scenario where the service requires some global configuration to handle some request.
For example when user wants to do something it requires some global configuration to check whether the user is permited todo so.
I realize that in axon i can have command handlers that could handle commands without specified target aggregate so the handling part isn't a problem.
Problem is where i would like to have persistent storage on top of that and some invariants when trying to change the configuration. The whole idea of the configuration is that it should be consistent like aggregate in axon.
ConfigService {
#Inject
configRepository;
#Inject
eventGateway;
#CommandHandler
handle(changeConfig){
let current = configRepository.loadCurrent;
//some checks
//persist here?
eventGateway.send(confgChanged)
}
#EventHandler
on(configChanged){
//or persist here?
configRepository.saveCurrent(configChanged.data)
}
}
If I do persistance on the command handler I think I shouldn't use event handler because it would save it twice. But then when i somehow lose the config repository data i can rebuild it based on the events.
Im not sure what im missing here in the understanding of the DDD concepts, to put it simply i would like to know where to put command handler for something that is neither an aggregate nor entity.
Maybe i should create command handler that calls the Config service instead making config service the command handler.
Are you using Axon without event sourcing here?
In Axon framework it is generally good practice only to change the state of an aggregate with events. If you are going to mix state or configuration loaded from a repository with state from the event store, how will you be able to guarantee that when you replay the same events, the resulting state will be the same? The next time the aggregate is loaded, there may be different state in your configRepository, resulting in a different state and different behavior of your aggregate.
Why is this bad? Well, those same events may have been handled by eventprocessors, they may have filled query tables, they may have sent messages to other systems or done other work based on the state the system had at the time. You will have a disagreement between your query database and your aggregate.
A concrete example: Imagine your aggregate processed a command to switch an email service on. The aggregate did this by applying an EmailServiceEnabledEvent and changing its own state to 'boolean emailEnabled = true'. After a while, the aggregate gets unloaded from memory. Now you change that configurationRepository to disable switching the email service on. When the aggregate is loaded again, events from the event store are applied, but this time it loads the configuration from your repository that says it shouldn't switch the email service on. The 'boolean emailEnabled' state is left false. You send a disable email service command to the aggregate, but the command handler in the aggregate thinks the email is already disabled, and doesn't apply an EmailServiceDisabledEvent. The email service is left on.
In short: I would recommend using commands to change the configuration of your aggregate.
It seems to me that you your global configuration is either a specification or a set of rules like in a rules engine.
Unlike the patterns described in GOF book, in DDD, some building blocks/patterns are more generic and can apply to different types of object that you have.
For example an Entity is something that has a life-cycle and has an identity. The stages in the life-cycle usually are: created, persisted, reconstructed from storage, modified and then it's life cycle ends by being deleted, archived, completed etc.
A Value Object is something that doesn't have identity, (most of the time) is immutable, two instances can be compared by the equality of their properties. Value Object represent important concepts in our domains like: Money in system that deal with accounting, banking etc., Vector3 and Matrix3 in systems that do mathematical calculations and simulations like modeling systems (3dsMax, Maya), video games etc. They contain important behavior.
So everything that you need to track and has identity can be an Entity.
You can have a Specification that is an entity, a Rule that is an entity, an Event can also be an entity if it has a unique ID assigned to it. In this case you can treat them just like any another entity. You can form aggregates, have repositories and services and use EventSourcing if necessary.
On the other hand a Specification, a Rule, an Event or a Command can also be Value Objects.
Specifications and Rules can also be Domain Services.
One important thing here is also the Bounded Context. The system that updates these rules is probably in a different Bounded context than the system that applies there rules. It's also possible that this isn't the case.
Here's an example.
Let's have a system, where a Customer can buy stuff. This sytem will also have Discounts on Orders that have specific Rules.
Let's say we have rule that says that: if a Customer has made an Order with more than 5 LineItems he get's a discount. If that Order has a total price of some amount (say 1000$) he gets discount.
The percentage of the discounts can be changed by the Sales team. The Sales system has OrderDicountPolicy aggregates that it can modify. On the other hand the Ordering system only reads OrderDicountPolicy aggregates and won't be able to modify them as this is the responsibility of the Sales team.
The Sales system and the Ordering system can be part of two separate Bounded Contexts: Sales and Orders. The Orders Bounded Context depends on Sales Bounded Context.
Note: I'll skip the most implementation details and add only the relevant things to shorten and simplify this example. If it's intent is not clear, I'll edit and add more details. UUID, DiscountPercentage and Money are value objects that I'll skip.
public interface OrderDiscountPolicy {
public UUID getID();
public DiscountPercentage getDiscountPercentage();
public void changeDiscountPercentage(DiscountPercentage percentage);
public bool canApplyDiscount(Order order);
}
public class LineItemsCountOrderDiscountPolicy implements OrderDiscountPolicy {
public int getLineItemsCount() { }
public void changeLineItemsCount(int count) { }
public bool canApplyDiscount(Order order) {
return order.getLineItemsCount() > this.getLineItemsCount();
}
// other stuff from interface implementation
}
public class PriceThresholdOrderDiscountPolicy implements OrderDiscountPolicy {
public Money getPriceThreshold() { }
public void changePriceThreshold(Money threshold) { }
public bool canApplyDiscount(Order order) {
return order.getTotalPriceWithoutDiscount() > this.getPriceThreshold();
}
// other stuff from interface implementation
}
public class LineItem {
public UUID getOrderID() { }
public UUID getProductID() { }
public Quantity getQuantity { }
public Money getProductPrice() { }
public Money getTotalPrice() {
return getProductPrice().multiply(getQuantity());
}
}
public enum OrderStatus { Pending, Placed, Approced, Rejected, Shipped, Finalized }
public class Order {
private UUID mID;
private OrderStatus mStatus;
private List<LineItem> mLineItems;
private DscountPercentage mDiscountPercentage;
public UUID getID() { }
public OrderStatus getStatus() { }
public DscountPercentage getDiscountPercentage() { };
public Money getTotalPriceWithoutDiscount() {
// return sum of all line items
}
public Money getTotalPrice() {
// return sum of all line items + discount percentage
}
public void changeStatus(OrderStatus newStatus) { }
public List<LineItem> getLineItems() {
return Collections.unmodifiableList(mLineItems);
}
public LineItem addLineItem(UUID productID, Quantity quantity, Money price) {
LineItem item = new LineItem(this.getID(), productID, quantity, price);
mLineItems.add(item);
return item;
}
public void applyDiscount(DiscountPercentage discountPercentage) {
mDiscountPercentage = discountPercentage;
}
}
public class PlaceOrderCommandHandler {
public void handle(PlaceOrderCommand cmd) {
Order order = mOrderRepository.getByID(cmd.getOrderID());
List<OrderDiscountPolicy> discountPolicies =
mOrderDiscountPolicyRepository.getAll();
for (OrderDiscountPolicy policy : discountPolicies) {
if (policy.canApplyDiscount(order)) {
order.applyDiscount(policy.getDiscountPercentage());
}
}
order.changeStatus(OrderStatus.Placed);
mOrderRepository.save(order);
}
}
public class ChangeOrderDiscountPolicyPercentageHandler {
public void handle(ChangeOrderDiscountPolicyPercentage cmd) {
OrderDiscountPolicy policy =
mOrderDiscountRepository.getByID(cmd.getPolicyID());
policy.changePercentage(cmd.getDiscountPercentage());
mOrderDiscountRepository.save(policy);
}
}
You can use EventSourcing if you think that it's appropriate for some aggregates. The DDD book has a chapter on global rules and specifications.
Let's take a look what whould we do in the case of a distributed application for example using microservices.
Let's say we have 2 services: OrdersService and OrdersDiscountService.
There are couple of ways to implement this operation. We can use:
Choreography with Events
Orchestration with explicit Saga or a Process Manager
Here's how we can do it if we use Choreography with Events.
CreateOrderCommand -> OrdersService -> OrderCreatedEvent
OrderCreatedEvent -> OrdersDiscountService -> OrderDiscountAvailableEvent or OrderDiscountNotAvailableEvent
OrderDiscountAvailableEvent or OrderDiscountNotAvailableEvent -> OrdersService -> OrderPlacedEvent
In this example to place the order OrdersService will wait for OrderDiscountNotAvailableEvent or OrderDiscountNotAvailableEvent so it can apply a discount before changing the status of the order to OrderPlaced.
We can also use an explicit Saga to do Orchestration between services.
This Saga will containt the sequence of steps for the process so it can execute it.
PlaceOrderCommand -> Saga
Saga asks OrdersDiscountService to see if a discount is available for that Order.
If discount is available, Saga calls OrdersService to apply a discount
Saga calls OrdersService to set the status of the Order to OrderPlaced
Note: Steps 3 and 4 can be combined
This raises the question: *"How OrdersDiscountService get's all the necessary information for the Order to calculate discounts?"*
This can either be achieved by adding all of the information of the order in the Event that this service will receive or by having OrdersDiscountService call OrdersService to get the information.
Here's a Great video from Martin Folwer on Event Driven Architectures that discusses these approaches.
The advantage of Orchestration with a Saga is that the exact process is explicitly defined in the Saga and can be found, understood and debugged.
Having implicit processes like in the case of the Choreography with Events can be harder to understand, debug and maintain.
The downside of having Sagas is that we do define more things.
Personally, I tend to go for the explicit Saga especially for complex processes, but most of the systems I work and see use both approaches.
Here are some additional resources:
https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part/
https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part-2/
https://microservices.io/patterns/data/saga.html
The LMAX Architecture is very interesting read. It's not distributed system, but is event driven and records both incomming events/commands and outgoint events. It's an interesting way to capture everything that happend in a system or a service.
I need to work with the reusable business objects for Sales tax, discounts, etc. and need to override some of the methods in these graph extensions. For example I am starting with the Opportunities graph. I have a set of order totals that need to calculate into the overall products amount and in the past we just overrode the tax attribute on (I think) tax category. Anyhow I don't see how its possible to use the PXOverrideAttribute on a method from a second level graph extension.
Here is my example:
public class OpportunityMaintExtOne : PXGraphExtension<PX.Objects.CR.OpportunityMaint.SalesTax, PX.Objects.CR.OpportunityMaint>
{
[PXOverride]
public virtual void CalcDocTotals(object row, decimal CuryTaxTotal, decimal CuryInclTaxTotal, decimal CuryWhTaxTotal,
Action<object, decimal, decimal, decimal> del)
{
del?.Invoke(row, CuryTaxTotal, CuryInclTaxTotal, CuryWhTaxTotal);
var someOtherTotal = Base1.Documents.Cache.GetValueAsDecimal<CROpportunityExtension.aMCurySomeOtherTotal>(row);
if (someOtherTotal == 0)
{
return;
}
var curyDocTotal = someOtherTotal + Base1.Documents.Cache.GetValueAsDecimal<CROpportunity.curyProductsAmount>(row);
Base1.Documents.Cache.SetValue<CROpportunity.curyProductsAmount>(row, curyDocTotal);
}
}
What is going on inside of CalcDocTotals in my graph extension is not the focus. It is the fact that I cannot override the OpportunityMaint.SalesTax CalcDocTotals method as I could if the method was in the first level (Base) graph. The SalesTax graph extension has the method as protected but protected methods (if it was in the base graph) are overrideable using the PXOverrideAttribute if you make your method call public which is what I have done. I also tried using a declared delegate in place of the Action but same results (as I expected but wanted to confirm).
My question: Is it possible to override a second, third, etc. level graph extension method using the PXOverrideAttribute?
When I compile the code above and the page loads I get this error:
Method Void CalcDocTotals(System.Object, System.Decimal,
System.Decimal, System.Decimal,
System.Action`4[System.Object,System.Decimal,System.Decimal,System.Decimal])
in graph extension is marked as [PXOverride], but the original method
with such name has not been found in PXGraph
The ability to override extension methods from a higher level extension has been added in 2018R1 Update 4 (18.104.0023). This resolves my question/issue and allows for the code posted in my question to function as is.
You cannot override methods from Extension1 in Extension2 etc as far as I've been able to see in my years with Acumatica. My solution to the problem was thus : Create a helper graph with your basic methodology, create a field or property for it in whatever graph you wish to use it in (Preferably a Lazy initialized one), then in whatever project you must override the logic on, just reference your original project, and create an extension of your helper graph.
I need to change how a Release procedure works. Specifically, I need to change the Release button on the Cash Sales site to use custom fields to populate the Cash Account and Subaccount fields. I found that the release button forwards the process to ARDocumentRelease.ReleaseDoc() which is located in PX.Objects.AR. This led me to believe that I need to customize the function that exists in 'PX.Objects.AR', but I'm not sure if that is doable.
Is it possible to customize functions that exist in the PX.Objects DLL? If not, is it possible for me to force the function in the DLL to receive custom fields for the Cash Account and Subaccount values instead of the base fields?
ARDocumentRelease is the process list page which calls ARReleaseProcess for each document release and is a graph. You can create a graph extension to override the calls it makes which is a virtual call to ReleaseDocProc meaning you can use PXOverride in your graph extension to override the call. Without testing, it could look something like this...
public class ARReleaseProcessExtension : PXGraphExtension<PX.Objects.AR.ARReleaseProcess>
{
[PXOverride]
public virtual List<PX.Objects.AR.ARRegister> ReleaseDocProc(PX.Objects.GL.JournalEntry je, PX.Objects.AR.ARRegister ardoc, List<PX.Objects.GL.Batch> pmBatchList,
PX.Objects.AR.ARDocumentRelease.ARMassProcessReleaseTransactionScopeDelegate onreleasecomplete,
Func<PX.Objects.GL.JournalEntry, PX.Objects.AR.ARRegister, List<PX.Objects.GL.Batch>, PX.Objects.AR.ARDocumentRelease.ARMassProcessReleaseTransactionScopeDelegate, List<PX.Objects.AR.ARRegister>> del)
{
//Code before...
//The base call...
var ret = del?.Invoke(je, ardoc, pmBatchList, onreleasecomplete);
//Code after...
return ret;
}
}
I need to perform more action after the existing action is performed. For example, After Distribution > Sales Orders > Shipment > Action > Confirm Shipment, I need to populate all tracking numbers into another text box.
Please suggest.
The best thing here in my opinion would be to override the logic of the stock method. That will allow you to add your needed code without touching the base method as well as you being able to validate information before and after the base method is called.
In the case of your example, "Confirm Shipment" action ultimately executes the method "ConfirmShipment" which is defined as below:
public virtual void ConfirmShipment(SOOrderEntry docgraph, SOShipment shiporder)
{
.....
}
In order to customize the logic in here you have a few options.
Create an override method (added to the method queue, base called first then all 'override' methods)
Create an method that calls the stock one first, then your code. - this will in essence "replace" the stock logic but allow you to still call the base method. in doing this, you can run some checks before calling the base.
To do the second you would do the following
Create first the delegate in your code:
public delegate void ConfirmShipmentDelegate(SOOrderEntry docgraph, SOShipment shiporder)
Then define your override method:
[PXOverride]
public virtual void ConfirmShipment(SOOrderEntry docgraph, SOShipment shiporder, ConfirmShipmentDelegate baseMethod = null)
{
// Call our base method first if it exists
if (baseMethod != null)
{
baseMethod(docgraph,shiporder);
}
// Do my stuff here
}
Couple items to note here.
The definition has a third param to our delegate, this allows us to call the stock method and then do further work. It also tells the Acumatica framework our method should take priority over the stock method.
The stock method is called from the baseMethod call in the actual code.
Creating the extension this way allows for upgrades to occur without you having to totally redefine your method every time.
The second method would be just be an override of the stockcode. That is done with the following syntax
[PXOverride]
public virtual void ConfirmShipment(SOOrderEntry docgraph, SOShipment shiporder)
{}
When taking this approach, the stock method is first called, then your override method is called.
both of these would be handled in a graph extension defined as:
public class SOShipmentEntryExt : PXGraphExtension<SOShipmentEntry>
Before attempting either, I would look at the articles in the Wiki on PXOverride as they give further examples/situations for these
I have read Evans, Nilsson and McCarthy, amongst others, and understand the concepts and reasoning behind a domain driven design; however, I'm finding it difficult to put all of these together in a real-world application. The lack of complete examples has left me scratching my head. I've found a lot of frameworks and simple examples but nothing so far that really demonstrates how to build a real business application following a DDD.
Using the typical order management system as an example, take the case of order cancellation. In my design I can see an OrderCancellationService with a CancelOrder method which accepts the order # and a reason as parameters. It then has to perform the following 'steps':
Verify that the current user has the necessary permission to cancel an Order
Retrieve the Order entity with the specified order # from the OrderRepository
Verify that the Order may be canceled (should the service interrogate the state of the Order to evaluate the rules or should the Order have a CanCancel property that encapsulates the rules?)
Update the state of the Order entity by calling Order.Cancel(reason)
Persist the updated Order to the data store
Contact the CreditCardService to revert any credit card charges that have already been processed
Add an audit entry for the operation
Of course, all of this should happen in a transaction and none of the operations should be allowed to occur independently. What I mean is, I must revert the credit card transaction if I cancel the order, I cannot cancel and not perform this step. This, imo, suggests better encapsulation but I don't want to have a dependency on the CreditCardService in my domain object (Order), so it seems like this is the responsibility of the domain service.
I am looking for someone to show me code examples how this could/should be "assembled". The thought-process behind the code would be helpful in getting me to connect all of the dots for myself. Thx!
Your domain service may look like this. Note that we want to keep as much logic as possible in the entities, keeping the domain service thin. Also note that there is no direct dependency on credit card or auditor implementation (DIP). We only depend on interfaces that are defined in our domain code. The implementation can later be injected in the application layer. Application layer would also be responsible for finding Order by number and, more importantly, for wrapping 'Cancel' call in a transaction (rolling back on exceptions).
class OrderCancellationService {
private readonly ICreditCardGateway _creditCardGateway;
private readonly IAuditor _auditor;
public OrderCancellationService(
ICreditCardGateway creditCardGateway,
IAuditor auditor) {
if (creditCardGateway == null) {
throw new ArgumentNullException("creditCardGateway");
}
if (auditor == null) {
throw new ArgumentNullException("auditor");
}
_creditCardGateway = creditCardGateway;
_auditor = auditor;
}
public void Cancel(Order order) {
if (order == null) {
throw new ArgumentNullException("order");
}
// get current user through Ambient Context:
// http://blogs.msdn.com/b/ploeh/archive/2007/07/23/ambientcontext.aspx
if (!CurrentUser.CanCancelOrders()) {
throw new InvalidOperationException(
"Not enough permissions to cancel order. Use 'CanCancelOrders' to check.");
}
// try to keep as much domain logic in entities as possible
if(!order.CanBeCancelled()) {
throw new ArgumentException(
"Order can not be cancelled. Use 'CanBeCancelled' to check.");
}
order.Cancel();
// this can throw GatewayException that would be caught by the
// 'Cancel' caller and rollback the transaction
_creditCardGateway.RevertChargesFor(order);
_auditor.AuditCancellationFor(order);
}
}
A slightly different take on it:
//UI
public class OrderController
{
private readonly IApplicationService _applicationService;
[HttpPost]
public ActionResult CancelOrder(CancelOrderViewModel viewModel)
{
_applicationService.CancelOrder(new CancelOrderCommand
{
OrderId = viewModel.OrderId,
UserChangedTheirMind = viewModel.UserChangedTheirMind,
UserFoundItemCheaperElsewhere = viewModel.UserFoundItemCheaperElsewhere
});
return RedirectToAction("CancelledSucessfully");
}
}
//App Service
public class ApplicationService : IApplicationService
{
private readonly IOrderRepository _orderRepository;
private readonly IPaymentGateway _paymentGateway;
//provided by DI
public ApplicationService(IOrderRepository orderRepository, IPaymentGateway paymentGateway)
{
_orderRepository = orderRepository;
_paymentGateway = paymentGateway;
}
[RequiredPermission(PermissionNames.CancelOrder)]
public void CancelOrder(CancelOrderCommand command)
{
using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
{
Order order = _orderRepository.GetById(command.OrderId);
if (!order.CanBeCancelled())
throw new InvalidOperationException("The order cannot be cancelled");
if (command.UserChangedTheirMind)
order.Cancel(CancellationReason.UserChangeTheirMind);
if (command.UserFoundItemCheaperElsewhere)
order.Cancel(CancellationReason.UserFoundItemCheaperElsewhere);
_orderRepository.Save(order);
_paymentGateway.RevertCharges(order.PaymentAuthorisationCode, order.Amount);
}
}
}
Notes:
In general I only see the need for a domain service when a command/use case involves the state change of more than one aggregate. For example, if I needed to invoke methods on the Customer aggregate as well as Order, then I'd create the domain service OrderCancellationService that invoked the methods on both aggregates.
The application layer orchestrates between infrastructure (payment gateways) and the domain. Like domain objects, domain services should only be concerned with domain logic, and ignorant of infrastructure such as payment gateways; even if you've abstracted it using your own adapter.
With regards to permissions, I would use aspect oriented programming to extract this away from the logic itself. As you see in my example, I've added an attribute to the CancelOrder method. You can use an intercepter on that method to see if the current user (which I would set on Thread.CurrentPrincipal) has that permission.
With regards to auditing, you simply said 'audit for the operation'. If you just mean auditing in general, (i.e. for all app service calls), again I would use interceptors on the method, logging the user, which method was called, and with what parameters. If however you meant auditing specifically for the cancellation of orders/payments then do something similar to Dmitry's example.