Skip to content

Commit

Permalink
Merge pull request #488 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

feat(#486): allow fetching all prices for sale for entities in GQL and REST (only a count so far here), fix incorrectly requesting price data from evita core
  • Loading branch information
lukashornych authored Mar 7, 2024
2 parents 65ae56b + ff941b4 commit f4d983f
Show file tree
Hide file tree
Showing 26 changed files with 1,057 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
queryProduct(
filterBy: {
entityPrimaryKeyInSet: [
103911
],
priceInCurrency: EUR,
priceInPriceLists: [
"employee-basic-price",
"basic"
]
}
) {
recordPage {
data {
primaryKey
attributes {
code
}
priceForSale {
priceWithoutTax
priceWithTax
taxRate
}
allPricesForSale {
priceWithoutTax
priceWithTax
taxRate
}
multiplePricesForSaleAvailable
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +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
}
]
}
}
}
}
```
44 changes: 41 additions & 3 deletions documentation/user/en/query/requirements/fetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,14 @@ specifying the price list names as string arguments to the `priceContent` requir
fetch non-indexed prices of the entity that cannot (and are not intended to) be used to filter the entities, but you
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.

</LS>

To get an entity with prices that you filter by, use the following query:

<SourceCodeTabs requires="evita_functional_tests/src/test/resources/META-INF/documentation/evitaql-init.java" langSpecificTabOnly>
Expand Down Expand Up @@ -1080,8 +1088,9 @@ As you can see, all prices of the entity are returned in all available currencie
</LS>
<LS to="g">

To fetch price information for an entity, there are several different fields available within an entity: `priceForSale`, `price` and `prices`.
Each has a different purpose and returns different prices.
To fetch price information for an entity, there are several different fields available within an entity: `priceForSale`,
`allPricesForSale`, `multiplePricesForSaleAvailable`, `price` and `prices`.
Each has a different purpose and returns different information.

The price object returns various data that can be formatted by the server for you to display to the user. Specifically,
the actual price numbers within the price object can be retrieved formatted according to the specified locale and can even
Expand All @@ -1090,7 +1099,7 @@ The locale is either resolved from the context of the query
(either from a localized unique attribute in the filter or from the `entityLocaleEquals` constraint in the filter) or can be specified
directly on the parent price field by the `locale` argument.

### Price for sale
### Prices for sale

The `priceForSale` field returns a single price object representing the price for sale of the entity.
By default, this price is [computed based on input filter constraints](../filtering/price.md), more specifically:
Expand Down Expand Up @@ -1143,6 +1152,35 @@ 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.

<SourceCodeTabs langSpecificTabOnly>

[Getting entity with all prices for sale](/documentation/user/en/query/requirements/examples/fetching/allPricesForSaleField.graphql)
</SourceCodeTabs>

<Note type="info">

<NoteTitle toggles="true">

##### The result of an entity fetched with all of its prices for sale based on filter constraints
</NoteTitle>

The query returns the following prices for sale of the `Product` entity:

<MDInclude sourceVariable="data.queryProduct.recordPage">[The result of an entity fetched with all of its prices for sale](/documentation/user/en/query/requirements/examples/fetching/allPricesForSaleField.graphql.json.md)</MDInclude>

As you can see, the price for sale matching the filter constraints is returned, as well as all other prices for sale and
flag indicating that there are multiple prices for sale available.

</Note>

### Price

The `price` field returns a single specific price (even non-sellable one) based on the arguments passed: `priceList` and `currency`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
Expand All @@ -48,6 +49,8 @@
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -324,10 +327,22 @@ default List<PriceContract> getAllPricesForSale(@Nullable Currency currency, @Nu
.filter(it -> pLists.isEmpty() || pLists.containsKey(it.priceList()));
if (pLists.isEmpty()) {
return priceStream.toList();
} else {
} else if (getPriceInnerRecordHandling() == PriceInnerRecordHandling.NONE) {
return priceStream
.sorted(Comparator.comparingInt(o -> pLists.get(o.priceList())))
.toList();
.min(Comparator.comparing(o -> pLists.get(o.priceList())))
.map(List::of)
.orElse(Collections.emptyList());
} else {
return new ArrayList<>(
priceStream
.collect(
Collectors.toMap(
price -> ofNullable(price.innerRecordId()).orElse(Integer.MIN_VALUE),
Function.identity(),
BinaryOperator.minBy(Comparator.comparingInt(o -> pLists.get(o.priceList())))
)
).values()
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
* Copyright (c) 2023
* Copyright (c) 2023-2024
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -33,6 +33,7 @@
import static io.evitadb.externalApi.api.model.ObjectPropertyDataTypeDescriptor.nonNullListRef;
import static io.evitadb.externalApi.api.model.ObjectPropertyDataTypeDescriptor.nullableRef;
import static io.evitadb.externalApi.api.model.PrimitivePropertyDataTypeDescriptor.nonNull;
import static io.evitadb.externalApi.api.model.PrimitivePropertyDataTypeDescriptor.nullable;

/**
* Descriptor of {@link EntityContract} for schema-based external APIs. It describes what entity data are supported in API
Expand Down Expand Up @@ -91,11 +92,23 @@ binary arrays representing entire files (i.e. pictures, documents).
.name("priceForSale")
.description("""
Price for which the entity should be sold. This method can be used only when appropriate
price related constraints are present so that `currency` and `priceList` priority can be extracted from the query.
price related constraints are present or appropriate arguments are passed so that `currency` and `priceList`
priority can be extracted.
The moment is either extracted from the query as well (if present) or current date and time is used.
""")
.type(nullableRef(PriceDescriptor.THIS))
.build();
PropertyDescriptor MULTIPLE_PRICES_FOR_SALE_AVAILABLE = PropertyDescriptor.builder()
.name("multiplePricesForSaleAvailable")
.description("""
Whether the entity could be sold for multiple prices or not. This method can be used only when appropriate
price related constraints are present in query so that `currency` and `priceList`
priority can be extracted.
For actual prices, the `allPricesForSale` field can be used.
""")
.type(nullable(Boolean.class))
.build();
PropertyDescriptor PRICE = PropertyDescriptor.builder()
.name("price")
.description("""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,16 @@ public GraphQLObjectType build(@Nonnull CollectionGraphQLSchemaBuildingContext c
entityObjectBuilder,
buildEntityPriceForSaleField()
);
buildingContext.registerFieldToObject(
objectName,
entityObjectBuilder,
buildEntityMultiplePricesForSaleAvailableField()
);
buildingContext.registerFieldToObject(
objectName,
entityObjectBuilder,
buildEntityAllPricesForSaleField()
);

buildingContext.registerFieldToObject(
objectName,
Expand Down Expand Up @@ -331,6 +341,37 @@ private BuiltFieldDescriptor buildEntityPriceForSaleField() {
return new BuiltFieldDescriptor(field, new PriceForSaleDataFetcher());
}

@Nonnull
private BuiltFieldDescriptor buildEntityMultiplePricesForSaleAvailableField() {
return new BuiltFieldDescriptor(
GraphQLEntityDescriptor.MULTIPLE_PRICES_FOR_SALE_AVAILABLE
.to(fieldBuilderTransformer)
.build(),
new MultiplePricesForSaleAvailableDataFetcher()
);
}

@Nonnull
private BuiltFieldDescriptor buildEntityAllPricesForSaleField() {
final GraphQLFieldDefinition field = GraphQLEntityDescriptor.ALL_PRICES_FOR_SALE
.to(fieldBuilderTransformer)
.argument(PriceForSaleFieldHeaderDescriptor.PRICE_LIST
.to(argumentBuilderTransformer))
.argument(PriceForSaleFieldHeaderDescriptor.CURRENCY
.to(argumentBuilderTransformer)
.type(typeRef(CURRENCY_ENUM.name())))
.argument(PriceForSaleFieldHeaderDescriptor.VALID_IN
.to(argumentBuilderTransformer))
.argument(PriceForSaleFieldHeaderDescriptor.VALID_NOW
.to(argumentBuilderTransformer))
.argument(PriceForSaleFieldHeaderDescriptor.LOCALE
.to(argumentBuilderTransformer)
.type(typeRef(LOCALE_ENUM.name())))
.build();

return new BuiltFieldDescriptor(field, new AllPricesForSaleDataFetcher());
}

@Nonnull
private BuiltFieldDescriptor buildEntityPriceField() {
final GraphQLFieldDefinition field = GraphQLEntityDescriptor.PRICE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
* Copyright (c) 2023
* Copyright (c) 2023-2024
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -24,9 +24,11 @@
package io.evitadb.externalApi.graphql.api.catalog.dataApi.model;

import io.evitadb.externalApi.api.catalog.dataApi.model.EntityDescriptor;
import io.evitadb.externalApi.api.catalog.dataApi.model.PriceDescriptor;
import io.evitadb.externalApi.api.model.ObjectDescriptor;
import io.evitadb.externalApi.api.model.PropertyDescriptor;

import static io.evitadb.externalApi.api.model.ObjectPropertyDataTypeDescriptor.nonNullListRef;
import static io.evitadb.externalApi.api.model.PrimitivePropertyDataTypeDescriptor.nullable;

/**
Expand Down Expand Up @@ -58,6 +60,16 @@ Each entity must be part of at most single hierarchy (tree).
""")
// type is expected to be a list of non-hierarchical version of this entity
.build();
PropertyDescriptor ALL_PRICES_FOR_SALE = PropertyDescriptor.builder()
.name("allPricesForSale")
.description("""
All prices for which the entity could be sold. This method can be used only when appropriate
price related constraints are present or appropriate arguments are passed so that `currency` and `priceList`
priority can be extracted.
The moment is either extracted from the query/arguments as well (if present) or current date and time is used.
""")
.type(nonNullListRef(PriceDescriptor.THIS))
.build();

ObjectDescriptor THIS_NON_HIERARCHICAL = ObjectDescriptor.extend(THIS_CLASSIFIER)
.name("NonHierarchical*")
Expand Down
Loading

0 comments on commit f4d983f

Please sign in to comment.