Changing product price in Shopware 6 dynamically - shopware

I would like to change the price of a product based on the customer's selection. For example, I'm trying to build a small PDP widget to make customers able to choose the number of candles on a cake or write text on cakes and update the price accordingly. The docs only cover how to change the price by overwriting the cart's collector/processor but I don't want to use this method because of other plugins potentially overwriting the same service. So, is there are any other methods of changing the price of the products by subscribing to an event?

There are a few things you will need to consider in this one.
Firstly, you will need to save the user input data somewhere (amount of candles, text).
Possibly a separate database table that has a OneToMany relationship on cart line items. See this article. Und ya, this is also the part where you will hook into the onLineItemAdd event & save your user input to that table. You may as well also subscribe to the onLineItemUpdate for the same saving logic. You could do the same when removing the item from the cart, although that may not be necessary if you use database's "CASCADE on delete" when implementing your DB table. Meaning once the line item gets removed by the customer (deleted in the DB), your database entry gets deleted as well.
Afterwards, you can then use the extensions or otherwise called associations to pull this data on the cart page & the order pages. You can be a little more fancy here, if you look at all the frontend router calls, you will notice that Shopware sometimes passes "Criteria" class you can hook into.
public static function getSubscribedEvents(): array
{
return [
OrderRouteRequestEvent::class => 'alterCriteria',
DocumentOrderCriteriaEvent::class => 'alterCriteria',
];
}
public function alterCriteria(Event $event): void
{
if (method_exists($event, 'getCriteria')) {
$event->getCriteria()->addAssociation('lineItems.myExtension'); // check syntax, could be lineItem.
}
}
Now you can extend the twig templates to show your candles or text in the order page, cart page, document (invoice) pages.
Secondly, you will have to handle the price. This part will be easier now that you have data saved & being automatically pulled via criteria subscribers. If it's not possible to hook into those events all the time, you will still have an option to manually load your data.
I do not recommend modifying the price itself, but maybe you could look into adding a surcharge instead. Maybe this article will be helpful to understand the price flow. You could also see if there are any other plugins out there that implement surcharge logic to see it in action.

Related

Change Default Sort on Contact Activities Tab

On the Activities tab of the Contacts screen, I am pulling activity data from an outside source and creating corresponding Acumatica activities. This activity creation is a periodically schedule process, so the activities are not created real-time. Therefore the CreatedDateTime field for the external activities does not reflect the actual time that the activities happened.
However, I need these external activities to be sorted according to when they actually occurred (not according to when they were created in Acuamtica) and to be displayed in proper sequence relative to existing Acuamtica activities. But the Activities view for the Contacts graph is sorted by CreatedDateTime, so these activities are being displayed according to Acuamtica creation time not their actual time.
I can think of two possible solutions, but I cannot see a way to accomplish either one.
1. Set the CRActivity.CreatedDateTime field to be the actual activity time.
If we could set the Created Date Time to be the actual time of the activity, the default sort order for the Activities tab would correctly display the external activities in sequence of when they actually occurred. However, this field is a default system field, and I do not see a good way to force it to be anything other than the time the Acumatica object is persisted. Is there any way to specify a value for the CreatedDateTime field?
2. Modify the default sort order of the Activities tab.
We can set the StartDate of the new activities to be the actual time that they occurred. So if we could order the Activities grid by Start Date rather than by Created Date Time, that would also achieve the desired affect. If the Activities view were a straightforward PXSelect, we could simply create a new PXSelect statement with the order by we want in our graph extension and use that as the data member for the grid. However, the Activities view is not simple and the OrderBy is buried inside the GenerateOriginalCommand method of the CRActivityListBase class. Is there anyway to override this OrderBy in way that takes advantage of all the functionality of CRActivityListBase?
Is there an acceptable way to use either of these approaches, or is there an alternative approach that would display the activities sorted by actual time?
You can replace Sorting utilizing OrderByNew as below.
public class ContactMaintPXExt : PXGraphExtension<ContactMaint>
{
public override void Initialize()
{
Base.Activities.OrderByNew<OrderBy<Desc<CRActivity.startDate>>>();
}
}

Event Sourcing - where does Domain Logic fit in?

I've just been watching Greg Youngs talk on Event Sourcing, but i'm confused as to where business logic fits in. A simple example:
1) Shopping Cart Created
2) Item Added
3) Item Added
4) Promotional Code - 20% Off
Is the Promotional Code calculated against the shopping carts items and the result of that stored as an event. I understand "PromotionalCodeAddedEvent" would maybe make sense, but where does the math happen? I'm thinking:
public void AddPromotionalCode(PromotionalCode code)
{
//perform calculation against shopping cart items.
//if valid
ApplyChanges(cmd);
}
Then the result doesn't end up anywhere and the Read Model would have to perform the calculations.
I'm not fully understanding the concept, any help would be great.
Something as seemingly simple as a promotional code can actually be a fairly complex use case. Primarily, this is due to the fact that a promotional code is logic that (usually) is maintained by one or more business users, whereas it also belongs inside of the domain, itself. It is non-traditional, in that sense. There are multiple ways to handle this, and what I will outline would just be my personal approach.
For the sake of argument, assume that you have a simple series of known promotional codes, such as:
X% Off Purchase, with or without a Minimum Purchase
$X Off Purchase, with or without a Minimum Purchase
We can make some assumptions, as well:
A Promotional Code has a Start Date
A Promotional Code has an End Date
The application of a promotional code can be tricky. Consider the two scenarios that we have defined. "$X Off Purchase" is comparatively simple, because it is a fixed amount. "X% Off Purchase", however, is more complex. If we only had the fixed amount, we could apply the discount to the cart as soon as any thresholds are met. With a percentage-based discount, if the user were to add two items, add a promotional code, and then add another item, the promotion would have already been "applied".
Because of this, I would personally only "attach" a promotional code to a cart. Why? The reason is that at the point of checkout, I can likely assume that a cart is going to be used to generate an order. Until that time, the cart's contents are fluid. A user's action against the cart will change the total value of the cart, as well as the total discount, assuming a non-fixed discount amount. It can also render either discount as being invalid, should a user remove one or more items from a cart and the total value of the cart fall below the threshold to apply the discount.
So, I would actually have multiple commands which would would be involved. Essentially any commands that impact the value of the cart could change the discount amount. To that end, I would be looking for the discount amount to be recalculated for the commands which:
Add an item to the cart
Remove an item from the cart
Change the quantity of items in the cart
Add a promotional code to the cart
Change the promotional code attached to the cart
Since these all are operations against a cart, I would be calculating the discount within the promotional code, itself, with the participation of the data contained in the cart. It feels like a promotional code is going to be an aggregate, going down this path. So, I would have the command handlers invoke a domain service that can provide my cart with the information that it requires. That domain service is going to load the promotional code, and I am going to be able to pass in the line items within that cart, so that the promotional code will tell me what the calculated discount would be. Then, I am going to generate the event, which contains the new value of the cart, along with the adjusted value (the discount). Going down this path, the logic for calculating a discount based upon line items within a cart is the responsibility of the promotional code.
You could put this responsibility in the cart, instead. Personally, though, I feel as if encapsulation of domain logic within the promotional code, itself, makes more sense. I had mentioned that it is likely that you will generate an order from a cart. By having the promotional code as an aggregate, and it containing the logic to apply the discount based upon the line items, we have a single truth, in how we calculate a discount for line items - whether that is in terms of a cart or in terms of an order.
You can, for example, raise a second event such as PromotionalCodeApplied which contains the computation results.
The Read Model then just have to use the pre-computed result.
I understand "PromotionalCodeAddedEvent" would maybe make sense, but where does the math happen?
It should happen in commands that perform modifications to the shopping cart. Every such command will call some method like RecalculateTotals() where all business logic will be hosted.
Consider the following pseudocode:
public void AddPromotionalCode(PromotionalCode code)
{
var #event = new PromotionalCodeAdded(code);
var amount = RecalculateTotalAmount(extraEvent: #event);
#event.TotalAmount = amount;
_eventStore.Publish(#event);
}
decimal RecalculateTotalAmount(IEvent extraEvent)
{
var relatedEventTypes = new[] { typeof(PromotionalCodeAdded), typeof(ShoppingCartCreated), typeof(ItemAdded) };
var events = _eventStore.ReadEventsOfTypes(relatedEventTypes);
var events = events.Concat(new[] { extraEvent });
//calculation logic goes here based on all related events
}
This is where I like to return events from command methods. As Alexander Langer mentioned, you'd apply the "math" and return the relevant event(s):
public PromotionalCodeApplied AddPromotionalCode(PromotionalCode code)
{
//perform calculation against shopping cart items.
var promotionalCodeApplied = new PromotionalCodeApplied(code.VoucherNumber, discountAmount, DateTime.Now);
On(promotionalCodeApplied);
return promotionalCodeApplied;
}
public void On(PromotionalCodeApplied promotionalCodeApplied)
{
_voucherNumber = promotionalCodeApplied.VoucherNumber;
_discountAmount = promotionalCodeApplied.DiscountAmount;
_discountDate = promotionalCodeApplied.DateAppllied;
}
Now your read model has access to the relevant values.

How can I trigger a NetSuite workflow on a Form Event (Client side event)

I want to trigger a NetSuite workflow when the user sets the value of a field, but I don't want to have them submit first. The Workflow state builder looks like it has useful options but I can't get it to work.
There's some useful looking blog posts around but a lot of them seem out of date.
Update - more info
My primary issue is this one: Restrict what customers an employee can see (NetSuite)
The hack I'm currently looking at is populating a custom Transaction Column Field that I've added to a custom Time Recording form. The idea is to load this field on the UI with only valid projects (not customers as well), and this I have been able to do.
The problem is I still (as far as I can tell) still need to populate the "Customer" field, which is mandatory; I'm also assuming that if I don't do that then any time that is recorded won't go against the project. I had thought that if the user selects the project they want then I can populate the customer field with that value. I hate this as an approach but I can't see how else to do it. I do have coding experience (including JavaScript) but haven't made the leap into SuiteScript yet.
You won't be able to do this in a Workflow, as they are currently limited to only work with body level fields and cannot modify Transaction Column Fields (aka sublists).
You should be able to achieve this with a Client Side Script though.
Source (Netsuite login required).
Sublist changes will be available for transactions in the 2018.1 release sometime in Feb/Mar.

NetSuite, prevent Quote to Sales Order (via Sales Order button) if prospect assigned vs customer

We allow sales to attach Prospects to an Opportunity and Quote, and once credit has qualified the prospect they promote the prospect to a customer. What we need to do is hide the "Sales Order" button on the quote, or disallow advancing the quote to a sales order.
I was hesitant to ask this- seems like it should be intuitive to figure out. I looked at the standard NetSuite button id's in NetSuite help but there wasn't one for "Sales Order". I've looked at validation logic but this isn't validation as the sales order button is shown when record is not in edit mode. If possible I'd like the solution to be form independent.
I'd be happy to hide the button or letting the user click the button and preventing them from creating the sales order. It might be more user friendly doing the latter because if the button is hidden sales will be calling asking why the button is not there.
For clarity here is an image:
I am assuming that when you are talking about "the Sales Order button", you mean this one:
I'm not sure if this is the best user experience, or if you have NetSuite development resources available to you, but here is one option:
Create a new User Event script that is deployed to the Sales Order (and any other Transaction record you may want this prevention on). Using the BeforeLoad event, you can check if the Entity on the Transaction is in the Prospect stage. If they are, then the script will throw an error, preventing the creation of the Transaction. Code to accomplish this:
function onBeforeLoad(type) {
var entityId = nlapiGetFieldValue('entity');
if ((type != 'create') || !entityId) { return; }
if(nlapiLookupField('customer', nlapiGetFieldValue('entity'), 'stage') === 'PROSPECT') {
throw nlapiCreateError('INVALID_REQUEST', 'You cannot create a Sales Order from a Quote placed for a Prospect');
}
}
I tested this code in a TSTDRV account, and it works as expected. You might alternatively be able to build a workflow that does the same thing without requiring you to write code, but I did not attempt this.
By using a User Event script, this code will be form independent as well as entry point independent, meaning that this code will execute if the Sales Order is being created through the UI, through some other script, through a web services integration (depending on your web services configuration), or through a CSV import (depending on your CSV import configuration).
To hide the option:
If you're referring to the dropdown list, you can create a script for context view/edit to do the following:
setFieldAndLabelVisibility("nl13", false);
Otherwise, replace nl13 with the value of the table or td element shown when you inspect element on the desired Sales Order link/icon.
--The ID in the example above is the table, button or label ID shown when you inspect element

How can I map fields from Product to Order Product and Product to Quote Product in CRM 2011

can anybody please let me know how can I map field from Product to Order Product and Product to Quote Product in CRM 2011.
CRM doesnt allow us to direct map.
You can't map these using attribute mappings.
One way around this is to write plugins on the PreCreate so that at least when you save the record it populates the data on save.
For plugin reference, the Quote Product entity is called quotedetail and the Order Product entity is called salesorderdetail.
Another other way around this, if you only need it firing on the UI, is create some javascript that triggers during onload of the form and populates this data for you.
If you want a no code method you could fire a workflow on create of the Order/Quote product and populate the fields from the product using an update. Only downside to this is the workflow runs asynchronously so you cannot predict when this mapping data will hit the record.
Finally, if you want another no code way to achieve this, and you don't mind investing a little money in a Formula Rules Engine tool I'd recommend looking into Formula Manager by xRM Consultancy and North 52 (Link to Formula Manager). This allows you to set up formulas on both the UI and Server Side which would do all of this mapping for you.
If you want a no code method you could fire a workflow on create of the Order/Quote product and populate the fields from the product using an update. The only downside to this is the workflow runs asynchronously so you cannot predict when this mapping data will hit the record.

Resources