Skip to content

Commit

Permalink
Merge pull request #490 from FgForrest/486-access-to-all-prices-for-s…
Browse files Browse the repository at this point in the history
…ale-in-case-of-price-inner-record-handling-first_occurence-sum

fix(#486): fix behaviour of `multiplePricesForSaleAvailable`, leaving out duplicate prices
  • Loading branch information
lukashornych authored Mar 11, 2024
2 parents 15aed28 + 0c87465 commit 7aa8185
Show file tree
Hide file tree
Showing 22 changed files with 396 additions and 195 deletions.
Original file line number Diff line number Diff line change
@@ -1,106 +1,106 @@
```json
{
"data": {
"queryProduct": {
"recordPage": {
"data": [
{
"primaryKey": 103911,
"attributes": {
"code": "apple-ipad-10-2-10th-generation-2022"
},
"priceForSale": {
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
"allPricesForSale": [
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "436.36",
"priceWithTax": "528.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "436.36",
"priceWithTax": "528.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "436.36",
"priceWithTax": "528.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "436.36",
"priceWithTax": "528.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
}
],
"multiplePricesForSaleAvailable": true
}
]
}
}
"queryProduct": {
"recordPage": {
"data": [
{
"primaryKey": 103911,
"attributes": {
"code": "apple-ipad-10-2-10th-generation-2022"
},
"priceForSale": {
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
"allPricesForSale": [
{
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "395.87",
"priceWithTax": "479.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "412.4",
"priceWithTax": "499.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "436.36",
"priceWithTax": "528.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "436.36",
"priceWithTax": "528.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "436.36",
"priceWithTax": "528.0",
"taxRate": "21.0"
},
{
"priceWithoutTax": "436.36",
"priceWithTax": "528.0",
"taxRate": "21.0"
}
],
"multiplePricesForSaleAvailable": true
}
]
}
}
}
}
```
33 changes: 23 additions & 10 deletions documentation/user/en/query/requirements/fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -944,9 +944,13 @@ still want to fetch them to display in the UI for the user.

<LS to="r">

For entities that have either `FIRST_OCCURENCE` or `SUM` inner record handling with prices
for multiple referenced entities grouped by the `innerRecordId`, the `multiplePricesForSaleAvailable` field will be returned
indicating whether there are multiple prices for sale available or just one.
For entities that have either `FIRST_OCCURENCE` or `SUM` inner record handling, the `multiplePricesForSaleAvailable`
property is returned, indicating whether there are multiple _unique_ prices for sale (grouped by the `innerRecordId`).
It is important to note that it doesn't simply return the count of all prices for sale.
Instead, it uses the [`priceType`](../requirements/price.md#price-type) constraint to determine the uniqueness of each
price value. This means that even if there are, say, 3 prices for sale, but they all have the same value, this property
will return `false`. This is especially useful for the
UI to determine whether to display a price range or just a single price without having to fetch all prices for sale.

</LS>

Expand Down Expand Up @@ -1152,13 +1156,22 @@ As you can see, the price for sale matching the custom arguments is returned.

</Note>

Similarly, you can use `allPricesForSale`, which is almost the same as `priceForSale`, but returns all prices for the
sale of the entity. This makes sense if an entity has either `FIRST_OCCURENCE` or `SUM` inner record handling with prices
for multiple referenced entities grouped by the `innerRecordId`. In the case of the `priceForSale` field, only the lowest
price for sale would be returned, but this may not be sufficient if you want to display more complex pricing for the entity.
You can use the `allPricesForSale` field to get all these prices and display more complex information. You can also
use the simpler `multiplePricesForSaleAvailable` field to simply check if there are multiple prices for sale available or
just one without fetching the actual price objects.
Similarly, you can use `allPricesForSale`, which is almost the same as `priceForSale`, but returns all prices for sale
of the entity grouped by the `innerRecordId`. This usually only makes sense for master products with variants
(i.e. `FIRST_OCCURENCE` inner record handling) where the master product has prices for all of its variants, where you may
want to know (and display) prices for sale for each variant (or some kind of range). For the `NONE` inner record handling,
this will always return at most the actual single price for sale. For the `SUM` inner record handling, this will return prices for sale
for each `innerRecordId` in the same way as for the `FIRST_OCCURNCE`, but the use cases are limited.

The returned list of prices is sorted by the price value from the lowest to the highest depending on the [`priceType`](../requirements/price.md#price-type)
constraint used.

There is also the simpler `multiplePricesForSaleAvailable` field, which returns a boolean indicating whether there are
multiple _unique_ prices for sale. It is important to note that it doesn't simply return the count of `allPricesForSale`.
Instead, it uses the [`priceType`](../requirements/price.md#price-type) constraint to determine the uniqueness of each
price value. This means that even if there are, say, 3 prices for sale, but they all have the same value, this field
will return `false` (as opposed to `allPricesForSale`, which would return all prices). This is especially useful for the
UI to determine whether to display a price range or just a single price without having to fetch all prices for sale.

<SourceCodeTabs langSpecificTabOnly>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ default List<PriceContract> getAllPricesForSale(@Nullable Currency currency, @Nu
.filter(it -> ofNullable(atTheMoment).map(mmt -> it.validity() == null || it.validity().isValidFor(mmt)).orElse(true))
.filter(it -> pLists.isEmpty() || pLists.containsKey(it.priceList()));
if (pLists.isEmpty()) {
return priceStream.toList();
return priceStream.collect(Collectors.toCollection(ArrayList::new));
} else if (getPriceInnerRecordHandling() == PriceInnerRecordHandling.NONE) {
return priceStream
.min(Comparator.comparing(o -> pLists.get(o.priceList())))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,16 @@ public Optional<PriceContract> getPriceForSaleIfAvailable() {
@Override
public List<PriceContract> getAllPricesForSale(@Nullable Currency currency, @Nullable OffsetDateTime atTheMoment, @Nullable String... priceListPriority) throws ContextMissingException {
pricePredicate.checkFetched(currency, priceListPriority);
return SealedEntity.super.getAllPricesForSale(currency, atTheMoment, priceListPriority);
final List<PriceContract> allPricesForSale = SealedEntity.super.getAllPricesForSale(currency, atTheMoment, priceListPriority);
if (allPricesForSale.size() > 1) {
allPricesForSale.sort(
Comparator.comparing(
pricePredicate.getQueryPriceMode() == QueryPriceMode.WITH_TAX ?
PriceContract::priceWithTax : PriceContract::priceWithoutTax
)
);
}
return allPricesForSale;
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
import io.evitadb.api.EvitaSessionContract;
import io.evitadb.api.exception.ContextMissingException;
import io.evitadb.api.query.require.PriceContentMode;
import io.evitadb.api.query.require.QueryPriceMode;
import io.evitadb.api.requestResponse.EvitaRequest;
import io.evitadb.api.requestResponse.data.EntityContract;
import io.evitadb.api.requestResponse.data.PriceContract;
import io.evitadb.api.requestResponse.data.SealedEntity;
import io.evitadb.api.requestResponse.data.structure.Entity;
import io.evitadb.api.requestResponse.data.structure.EntityDecorator;
import io.evitadb.api.requestResponse.data.structure.SerializablePredicate;
Expand Down Expand Up @@ -83,10 +84,14 @@ public class PriceContractSerializablePredicate implements SerializablePredicate
* Contains the same information as {@link #priceLists} but in the form of the set for faster lookups.
*/
@Getter @Nonnull private final Set<String> priceListsAsSet;
/**
* Contains information about the type of price that should be used for sorting and filtering.
*/
@Getter @Nonnull private final QueryPriceMode queryPriceMode;
/**
* Contains information about underlying predicate that is bound to the {@link EntityDecorator}. This underlying
* predicate represents the scope of the fetched (enriched) entity in its true form (i.e. {@link Entity}) and needs
* to be carried around even if {@link io.evitadb.api.EntityCollectionContract#limitEntity(SealedEntity, EvitaRequest, EvitaSessionContract)}
* to be carried around even if {@link io.evitadb.api.EntityCollectionContract#limitEntity(EntityContract, EvitaRequest, EvitaSessionContract)}
* is invoked on the entity.
*/
@Nullable @Getter private final PriceContractSerializablePredicate underlyingPredicate;
Expand All @@ -101,6 +106,7 @@ public PriceContractSerializablePredicate() {
this.priceListsAsSet = Collections.emptySet();
this.underlyingPredicate = null;
this.contextAvailable = false;
this.queryPriceMode = QueryPriceMode.WITH_TAX;
}

public PriceContractSerializablePredicate(@Nonnull EvitaRequest evitaRequest, @Nullable Boolean contextAvailable) {
Expand All @@ -115,6 +121,7 @@ public PriceContractSerializablePredicate(@Nonnull EvitaRequest evitaRequest, @N
this.underlyingPredicate = null;
this.contextAvailable = contextAvailable != null ?
contextAvailable : this.currency != null && !ArrayUtils.isEmpty(this.priceLists);
this.queryPriceMode = evitaRequest.getQueryPriceMode();
}

public PriceContractSerializablePredicate(@Nonnull EvitaRequest evitaRequest, @Nonnull PriceContractSerializablePredicate underlyingPredicate) {
Expand All @@ -132,6 +139,7 @@ public PriceContractSerializablePredicate(@Nonnull EvitaRequest evitaRequest, @N
this.priceListsAsSet = underlyingPredicate.priceListsAsSet;
this.underlyingPredicate = underlyingPredicate;
this.contextAvailable = this.currency != null && !ArrayUtils.isEmpty(this.priceLists);
this.queryPriceMode = evitaRequest.getQueryPriceMode();
}

PriceContractSerializablePredicate(
Expand All @@ -141,6 +149,7 @@ public PriceContractSerializablePredicate(@Nonnull EvitaRequest evitaRequest, @N
@Nullable String[] priceLists,
@Nullable String[] additionalPriceLists,
@Nonnull Set<String> priceListsAsSet,
@Nonnull QueryPriceMode queryPriceMode,
boolean contextAvailable
) {
this.priceContentMode = priceContentMode;
Expand All @@ -150,6 +159,7 @@ public PriceContractSerializablePredicate(@Nonnull EvitaRequest evitaRequest, @N
this.additionalPriceLists = additionalPriceLists;
this.priceListsAsSet = priceListsAsSet;
this.underlyingPredicate = null;
this.queryPriceMode = queryPriceMode;
this.contextAvailable = contextAvailable;
}

Expand Down Expand Up @@ -225,6 +235,7 @@ public PriceContractSerializablePredicate createRicherCopyWith(@Nonnull EvitaReq
this.currency, this.validIn, this.priceLists,
this.additionalPriceLists == null ? fetchesAdditionalPriceLists : ArrayUtils.mergeArrays(this.additionalPriceLists, fetchesAdditionalPriceLists),
this.priceListsAsSet,
this.queryPriceMode,
this.contextAvailable
);
}
Expand All @@ -234,7 +245,9 @@ public PriceContractSerializablePredicate createRicherCopyWith(@Nonnull EvitaReq
requiresEntityPrices,
this.currency, this.validIn, this.priceLists,
this.additionalPriceLists,
this.priceListsAsSet, this.contextAvailable
this.priceListsAsSet,
this.queryPriceMode,
this.contextAvailable
);
} else {
return new PriceContractSerializablePredicate(
Expand All @@ -243,6 +256,7 @@ public PriceContractSerializablePredicate createRicherCopyWith(@Nonnull EvitaReq
this.priceLists,
this.additionalPriceLists == null ? fetchesAdditionalPriceLists : ArrayUtils.mergeArrays(this.additionalPriceLists, fetchesAdditionalPriceLists),
this.additionalPriceLists == null ? this.priceListsAsSet : Stream.concat(this.priceListsAsSet.stream(), Arrays.stream(fetchesAdditionalPriceLists)).collect(Collectors.toSet()),
this.queryPriceMode,
this.contextAvailable
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,8 @@ private BuiltFieldDescriptor buildGetEntityField(@Nonnull CollectionGraphQLSchem
.argument(GetEntityHeaderDescriptor.PRICE_VALID_IN
.to(argumentBuilderTransformer))
.argument(GetEntityHeaderDescriptor.PRICE_VALID_NOW
.to(argumentBuilderTransformer))
.argument(GetEntityHeaderDescriptor.PRICE_TYPE
.to(argumentBuilderTransformer));
}

Expand Down
Loading

0 comments on commit 7aa8185

Please sign in to comment.