Sometimes simple tasks like “On base product price change also change price for all its sub products” can lead to long nights of debugging and seeking of workarounds. Sounds like this task should be easy for implementation. Create PrepareInterceptor
, check if price was changed with InterceptorContext.isModified()
method, iterate all sub products and put the new price.
But real life doesn’t meet our expectations. Firstly isModified
method will surprised you, because it will always return false
, even when price was changed. The root of such behaviour is that prices are collectiontype
attribute and to deal with that can be used workaround with DefaultModelServiceInterceptorContext.getInitialElements() method, which return list of changed related objects, so you will be able to calculate if price was changed. The same workaround can be used to receive on which exact price it was changed.
1
2
3
4
5
6
7
8
9
10
|
boolean changedPrice = false;
if (ctx instanceof DefaultModelServiceInterceptorContext) {
Set<Object> changedTypes = ((DefaultModelServiceInterceptorContext) ctx).getInitialElements().unmodifiableSet();
for (Object changedObject : changedTypes) {
if (changedObject instanceof PriceRowModel) {
changedPrice = true;
break;
}
}
}
|
Second surprise us that you can’t use the same PriceRowModel for all sub products, because prices are also a partOf
attribute of a product, so you need to clone each price for each variant product.
Another interesting thing is related to ProductPriceGroups. All prices added by ProductPriceGroup will be visible as a regular prices via getEurope1Prices
method and you need to skip them while cloning prices to sub products.
So the final implementation can look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
package com.blog.core.interceptors.impl;
import com.blog.core.model.ApparelProductModel;
import com.blog.core.product.BlogProductService;
import de.hybris.platform.core.model.product.ProductModel;
import de.hybris.platform.europe1.enums.ProductPriceGroup;
import de.hybris.platform.europe1.enums.ProductTaxGroup;
import de.hybris.platform.europe1.model.PriceRowModel;
import de.hybris.platform.servicelayer.interceptor.InterceptorContext;
import de.hybris.platform.servicelayer.interceptor.PrepareInterceptor;
import de.hybris.platform.servicelayer.internal.model.impl.DefaultModelServiceInterceptorContext;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.variants.model.VariantProductModel;
import org.apache.commons.collections.CollectionUtils;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class BlogProductChangePriceInterceptor implements PrepareInterceptor {
@Resource
private BlogProductService productService;
@Resource
private ModelService modelService;
@Override
public void onPrepare(Object model, InterceptorContext ctx) {
if (model instanceof ApparelProductModel) {
ApparelProductModel apparelProductModel = (ApparelProductModel) model;
// If productModel is updating and price was modified
if (!ctx.isNew(apparelProductModel) && isPriceModified(ctx, apparelProductModel)) {
ProductPriceGroup priceGroup = apparelProductModel.getEurope1PriceFactory_PPG();
ProductTaxGroup taxGroup = apparelProductModel.getEurope1PriceFactory_PTG();
// Hash set is used to avoid price duplication
final Collection<PriceRowModel> prices = new HashSet<>(apparelProductModel.getEurope1Prices());
prices.addAll(getModifiedPrices(ctx));
List<VariantProductModel> productVariants = productService.getAllProductVariants(apparelProductModel.getCode());
assignPricesToVariantProduct(productVariants, priceGroup, taxGroup, prices, ctx);
}
}
}
private void assignPricesToVariantProduct(List<? extends VariantProductModel> products,
ProductPriceGroup priceGroup,
ProductTaxGroup taxGroup,
final Collection<PriceRowModel> prices,
InterceptorContext context) {
if (CollectionUtils.isNotEmpty(products)) {
products.forEach(product -> {
product.setEurope1PriceFactory_PPG(priceGroup);
product.setEurope1Prices(copyNonePGPrices(prices, product));
product.setEurope1PriceFactory_PTG(taxGroup);
context.registerElement(product);
});
}
}
// Due to price is partOf for product, we need to create new DB instances
private Collection<PriceRowModel> copyNonePGPrices(final Collection<PriceRowModel> prices, ProductModel product) {
Collection<PriceRowModel> copiedPrices = new HashSet<>();
for (PriceRowModel price : prices) {
// Clone only prices, which don`t belong to price group, beccause they will be added dynamically on
// product price group assigning
if (price.getPg() == null) {
PriceRowModel clonedPrice = modelService.clone(price);
clonedPrice.setProduct(product);
copiedPrices.add(clonedPrice);
}
}
return copiedPrices;
}
private boolean isPriceModified(InterceptorContext ctx, ApparelProductModel apparelProductModel) {
boolean changedPrice = ctx.isModified(apparelProductModel, ApparelProductModel.EUROPE1PRICES) ||
ctx.isModified(apparelProductModel, ApparelProductModel.OWNEUROPE1PRICES) ||
ctx.isModified(apparelProductModel, ApparelProductModel.EUROPE1PRICEFACTORY_PPG);
// Due to europe1price is collectiontype interceptor context can not identify that field was changed
// Method below is a workaround to identify change of europe1price field
if (!changedPrice && ctx instanceof DefaultModelServiceInterceptorContext) {
Set<Object> changedTypes = ((DefaultModelServiceInterceptorContext) ctx).getInitialElements().unmodifiableSet();
for (Object changedObject : changedTypes) {
if (changedObject instanceof PriceRowModel) {
changedPrice = true;
break;
}
}
}
// Check that all variant has prices to set them prices of base product
// We need this check when new product variant was added to product via hotfolder/impex
if (!changedPrice) {
// check that all variant has prices
List<ProductModel> productWithVariants = productService.getAllProductVariants(apparelProductModel);
long numberOfProductsWithoutPrices = productWithVariants.stream()
.filter(product -> CollectionUtils.isEmpty(product.getEurope1Prices()))
.count();
// if some variant do not have price, than we should remap all prices
if (numberOfProductsWithoutPrices > 0) {
changedPrice = true;
}
}
return changedPrice;
}
private Collection<PriceRowModel> getModifiedPrices(InterceptorContext ctx) {
Collection<PriceRowModel> changedPrices = new HashSet<>();
// Due to europe1price is collectiontype interceptor context can not identify that field was changed
// Method below is a workaround to identify change of europe1price field
if (ctx instanceof DefaultModelServiceInterceptorContext) {
Set<Object> changedTypes = ((DefaultModelServiceInterceptorContext) ctx).getInitialElements().unmodifiableSet();
for (Object changedObject : changedTypes) {
if (changedObject instanceof PriceRowModel) {
changedPrices.add((PriceRowModel) changedObject);
}
}
}
return changedPrices;
}
}
|
P.S. Keep in mind that to reuse code above you need to implement method getAllProductVariants
, which returns list of VariantProducts for price changing.