Add product variant reference attribute (#2268)

* Handle variant references

* Update schema & types

* Update changelog

* Extract messages

* Fix handlers

* Update schema

* Build types

* Update types

* Improve assign reference value modals
This commit is contained in:
Michał Droń 2022-09-13 12:07:41 +02:00 committed by GitHub
parent ff09d04660
commit 19d5e7e71d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 781 additions and 112 deletions

View file

@ -5,6 +5,7 @@ All notable, unreleased changes to this project will be documented in this file.
## [Unreleased] ## [Unreleased]
- Pass query params in `ORDER_DETAILS_MORE_ACTIONS` and `PRODUCT_DETAILS_MORE_ACTIONS` mounting points - #2100 by @witoszekdev - Pass query params in `ORDER_DETAILS_MORE_ACTIONS` and `PRODUCT_DETAILS_MORE_ACTIONS` mounting points - #2100 by @witoszekdev
- Add product variant reference attribute - #2268 by @droniu
## 3.4 ## 3.4

View file

@ -7065,6 +7065,12 @@
"description": null, "description": null,
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
},
{
"name": "PRODUCT_VARIANT",
"description": null,
"isDeprecated": false,
"deprecationReason": null
} }
], ],
"possibleTypes": null "possibleTypes": null
@ -9104,9 +9110,13 @@
"name": "id", "name": "id",
"description": "ID of the selected attribute.", "description": "ID of the selected attribute.",
"type": { "type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "ID", "name": "ID",
"ofType": null "ofType": null
}
}, },
"defaultValue": null, "defaultValue": null,
"isDeprecated": false, "isDeprecated": false,
@ -10106,6 +10116,87 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "CalculateTaxes",
"description": "Synchronous webhook for calculating checkout/order taxes.\n\nAdded in Saleor 3.7.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
"fields": [
{
"name": "issuedAt",
"description": "Time of the event.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "DateTime",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "version",
"description": "Saleor version that triggered the event.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "issuingPrincipal",
"description": "The user or application that triggered the event.",
"args": [],
"type": {
"kind": "UNION",
"name": "IssuingPrincipal",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "recipient",
"description": "The application receiving the webhook.",
"args": [],
"type": {
"kind": "OBJECT",
"name": "App",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "taxBase",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "TaxableObject",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
{
"kind": "INTERFACE",
"name": "Event",
"ofType": null
}
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "CardInput", "name": "CardInput",
@ -25088,6 +25179,11 @@
"name": "AttributeValueUpdated", "name": "AttributeValueUpdated",
"ofType": null "ofType": null
}, },
{
"kind": "OBJECT",
"name": "CalculateTaxes",
"ofType": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "CategoryCreated", "name": "CategoryCreated",
@ -56121,7 +56217,7 @@
}, },
{ {
"name": "deliveryMethod", "name": "deliveryMethod",
"description": "The delivery method selected for this checkout.\n\nAdded in Saleor 3.1.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.", "description": "The delivery method selected for this order.\n\nAdded in Saleor 3.1.\n\nNote: this API is currently in Feature Preview and can be subject to changes at later point.",
"args": [], "args": [],
"type": { "type": {
"kind": "UNION", "kind": "UNION",
@ -94029,6 +94125,48 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "UNION",
"name": "TaxSourceLine",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": [
{
"kind": "OBJECT",
"name": "CheckoutLine",
"ofType": null
},
{
"kind": "OBJECT",
"name": "OrderLine",
"ofType": null
}
]
},
{
"kind": "UNION",
"name": "TaxSourceObject",
"description": null,
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": [
{
"kind": "OBJECT",
"name": "Checkout",
"ofType": null
},
{
"kind": "OBJECT",
"name": "Order",
"ofType": null
}
]
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "TaxType", "name": "TaxType",
@ -94064,6 +94202,331 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "OBJECT",
"name": "TaxableObject",
"description": "Taxable object.",
"fields": [
{
"name": "sourceObject",
"description": "The source object related to this tax object.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "UNION",
"name": "TaxSourceObject",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pricesEnteredWithTax",
"description": "Determines if prices contain entered tax..",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "currency",
"description": "The currency of the object.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "shippingPrice",
"description": "The price of shipping method.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Money",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "address",
"description": "The address data.",
"args": [],
"type": {
"kind": "OBJECT",
"name": "Address",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "discounts",
"description": "List of discounts.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "TaxableObjectDiscount",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "lines",
"description": "List of lines assigned to the object.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "TaxableObjectLine",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "channel",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Channel",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TaxableObjectDiscount",
"description": "Taxable object discount.",
"fields": [
{
"name": "name",
"description": "The name of the discount.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "amount",
"description": "The amount of the discount.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Money",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TaxableObjectLine",
"description": null,
"fields": [
{
"name": "sourceLine",
"description": "The source line related to this tax line.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "UNION",
"name": "TaxSourceLine",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "quantity",
"description": "Number of items.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "chargeTaxes",
"description": "Determines if taxes are being charged for the product.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "productName",
"description": "The product name.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "variantName",
"description": "The variant name.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "productSku",
"description": "The product sku.",
"args": [],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "unitPrice",
"description": "Price of the single item in the order line.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Money",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalPrice",
"description": "Price of the order line.",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "Money",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "TaxedMoney", "name": "TaxedMoney",

View file

@ -889,6 +889,10 @@
"context": "alert", "context": "alert",
"string": "Warehouse limit reached" "string": "Warehouse limit reached"
}, },
"5I7Lc2": {
"context": "dialog header",
"string": "Assign page"
},
"5JT4v2": { "5JT4v2": {
"context": "dialog header", "context": "dialog header",
"string": "Send Invoice" "string": "Send Invoice"
@ -2265,10 +2269,6 @@
"GOdq5V": { "GOdq5V": {
"string": "Catalog" "string": "Catalog"
}, },
"GUlwXU": {
"context": "dialog header",
"string": "Assign Attribute Value"
},
"GVM/fi": { "GVM/fi": {
"context": "order history message", "context": "order history message",
"string": "Payment was authorized" "string": "Payment was authorized"
@ -3235,10 +3235,6 @@
"context": "navigator placeholder", "context": "navigator placeholder",
"string": "Type Command" "string": "Type Command"
}, },
"NsgWhZ": {
"context": "placeholder",
"string": "Search by value name, etc..."
},
"NtFVFS": { "NtFVFS": {
"context": "weight", "context": "weight",
"string": "{value} {unit}" "string": "{value} {unit}"
@ -3293,6 +3289,10 @@
"context": "WarehouseSettings disabled warehouse label", "context": "WarehouseSettings disabled warehouse label",
"string": "Disabled" "string": "Disabled"
}, },
"OFW7nq": {
"context": "placeholder",
"string": "Search by page name, etc..."
},
"OGemtu": { "OGemtu": {
"context": "payment status", "context": "payment status",
"string": "Partially refunded" "string": "Partially refunded"
@ -3757,10 +3757,6 @@
"RlfqSV": { "RlfqSV": {
"string": "No orders found" "string": "No orders found"
}, },
"RoKOQJ": {
"context": "label",
"string": "Search Attribute Value"
},
"Rp/Okl": { "Rp/Okl": {
"string": "Shipping Weight Unit" "string": "Shipping Weight Unit"
}, },
@ -5284,10 +5280,6 @@
"context": "product", "context": "product",
"string": "Digital" "string": "Digital"
}, },
"dTCDMn": {
"context": "dialog header",
"string": "Assign Product"
},
"dTCWqt": { "dTCWqt": {
"string": "You are about to end your products preorder. You have sold {variantGlobalSoldUnits} units of this variant. Sold units will be allocated at appropriate warehouses. Remember to add remaining threshold stock to warehouses." "string": "You are about to end your products preorder. You have sold {variantGlobalSoldUnits} units of this variant. Sold units will be allocated at appropriate warehouses. Remember to add remaining threshold stock to warehouses."
}, },
@ -5790,6 +5782,10 @@
"context": "customer", "context": "customer",
"string": "Join Date" "string": "Join Date"
}, },
"idr+JK": {
"context": "assign reference to a page, button",
"string": "Assign and save"
},
"ij7olm": { "ij7olm": {
"context": "error message", "context": "error message",
"string": "This fulfillment cannot be cancelled" "string": "This fulfillment cannot be cancelled"
@ -5820,6 +5816,10 @@
"ixjvkM": { "ixjvkM": {
"string": "Weve created your default token. Make sure to copy your new personal access token now. You wont be able to see it again." "string": "Weve created your default token. Make sure to copy your new personal access token now. You wont be able to see it again."
}, },
"izJvcM": {
"context": "label",
"string": "Search pages"
},
"j/vV0n": { "j/vV0n": {
"context": "channel name", "context": "channel name",
"string": "Channel Name" "string": "Channel Name"
@ -5918,6 +5918,10 @@
"juBV+h": { "juBV+h": {
"string": "End Hour" "string": "End Hour"
}, },
"juxCV3": {
"context": "dialog header",
"string": "Assign product"
},
"jvKNMP": { "jvKNMP": {
"string": "Discount Code" "string": "Discount Code"
}, },
@ -7262,6 +7266,9 @@
"umsU70": { "umsU70": {
"string": "Search Page Type" "string": "Search Page Type"
}, },
"un+VWt": {
"string": "Search products"
},
"usSkzP": { "usSkzP": {
"context": "navigator order mode description", "context": "navigator order mode description",
"string": "Search Orders" "string": "Search Orders"
@ -7531,6 +7538,10 @@
"context": "WarehouseSettings private stock label", "context": "WarehouseSettings private stock label",
"string": "Private Stock" "string": "Private Stock"
}, },
"wsDF7X": {
"context": "product variant attribute entity type",
"string": "Product variants"
},
"wyvzh9": { "wyvzh9": {
"context": "dialog header", "context": "dialog header",
"string": "Publish Pages" "string": "Publish Pages"
@ -7744,10 +7755,6 @@
"context": "voucher requirements, header", "context": "voucher requirements, header",
"string": "Minimum Requirements" "string": "Minimum Requirements"
}, },
"ylobu9": {
"context": "assign reference to product, button",
"string": "Assign"
},
"ypW4Mi": { "ypW4Mi": {
"context": "button refreshing page", "context": "button refreshing page",
"string": "Refresh page" "string": "Refresh page"

View file

@ -1384,6 +1384,7 @@ type AttributeDeleted implements Event {
enum AttributeEntityTypeEnum { enum AttributeEntityTypeEnum {
PAGE PAGE
PRODUCT PRODUCT
PRODUCT_VARIANT
} }
type AttributeError { type AttributeError {
@ -1823,7 +1824,7 @@ input AttributeValueFilterInput {
input AttributeValueInput { input AttributeValueInput {
"""ID of the selected attribute.""" """ID of the selected attribute."""
id: ID id: ID!
""" """
The value or slug of an attribute to resolve. If the passed value is non-existent, it will be created. The value or slug of an attribute to resolve. If the passed value is non-existent, it will be created.
@ -2052,6 +2053,28 @@ type BulkStockError {
index: Int index: Int
} }
"""
Synchronous webhook for calculating checkout/order taxes.
Added in Saleor 3.7.
Note: this API is currently in Feature Preview and can be subject to changes at later point.
"""
type CalculateTaxes implements Event {
"""Time of the event."""
issuedAt: DateTime
"""Saleor version that triggered the event."""
version: String
"""The user or application that triggered the event."""
issuingPrincipal: IssuingPrincipal
"""The application receiving the webhook."""
recipient: App
taxBase: TaxableObject!
}
input CardInput { input CardInput {
""" """
Payment method nonce, a token returned by the appropriate provider's SDK. Payment method nonce, a token returned by the appropriate provider's SDK.
@ -12540,7 +12563,7 @@ type Order implements Node & ObjectWithMetadata {
isShippingRequired: Boolean! isShippingRequired: Boolean!
""" """
The delivery method selected for this checkout. The delivery method selected for this order.
Added in Saleor 3.1. Added in Saleor 3.1.
@ -21189,6 +21212,10 @@ type Subscription {
event: Event event: Event
} }
union TaxSourceLine = CheckoutLine | OrderLine
union TaxSourceObject = Checkout | Order
"""Representation of tax types fetched from tax gateway.""" """Representation of tax types fetched from tax gateway."""
type TaxType { type TaxType {
"""Description of the tax type.""" """Description of the tax type."""
@ -21198,6 +21225,66 @@ type TaxType {
taxCode: String taxCode: String
} }
"""Taxable object."""
type TaxableObject {
"""The source object related to this tax object."""
sourceObject: TaxSourceObject!
"""Determines if prices contain entered tax.."""
pricesEnteredWithTax: Boolean!
"""The currency of the object."""
currency: String!
"""The price of shipping method."""
shippingPrice: Money!
"""The address data."""
address: Address
"""List of discounts."""
discounts: [TaxableObjectDiscount!]!
"""List of lines assigned to the object."""
lines: [TaxableObjectLine!]!
channel: Channel!
}
"""Taxable object discount."""
type TaxableObjectDiscount {
"""The name of the discount."""
name: String
"""The amount of the discount."""
amount: Money!
}
type TaxableObjectLine {
"""The source line related to this tax line."""
sourceLine: TaxSourceLine!
"""Number of items."""
quantity: Int!
"""Determines if taxes are being charged for the product."""
chargeTaxes: Boolean!
"""The product name."""
productName: String!
"""The variant name."""
variantName: String!
"""The product sku."""
productSku: String
"""Price of the single item in the order line."""
unitPrice: Money!
"""Price of the order line."""
totalPrice: Money!
}
""" """
Represents a monetary value with taxes. In cases where taxes were not applied, net and gross values will be equal. Represents a monetary value with taxes. In cases where taxes were not applied, net and gross values will be equal.
""" """

View file

@ -33,6 +33,11 @@ const entityTypeMessages = defineMessages({
defaultMessage: "Products", defaultMessage: "Products",
description: "product attribute entity type", description: "product attribute entity type",
}, },
productVariant: {
id: "wsDF7X",
defaultMessage: "Product variants",
description: "product variant attribute entity type",
},
}); });
const useStyles = makeStyles( const useStyles = makeStyles(
@ -129,6 +134,10 @@ const AttributeDetails: React.FC<AttributeDetailsProps> = props => {
label: intl.formatMessage(entityTypeMessages.product), label: intl.formatMessage(entityTypeMessages.product),
value: AttributeEntityTypeEnum.PRODUCT, value: AttributeEntityTypeEnum.PRODUCT,
}, },
{
label: intl.formatMessage(entityTypeMessages.productVariant),
value: AttributeEntityTypeEnum.PRODUCT_VARIANT,
},
]; ];
const formApiErrors = getFormErrors( const formApiErrors = getFormErrors(

View file

@ -420,6 +420,27 @@ export const getProductReferenceAttributeDisplayData = (
}, },
}); });
export const getProductVariantReferenceAttributeDisplayData = (
attribute: AttributeInput,
referenceProducts: RelayToFlat<SearchProductsQuery["search"]>,
) => ({
...attribute,
data: {
...attribute.data,
references:
referenceProducts?.length > 0 && attribute.value?.length > 0
? mapNodeToChoice(
attribute.value.map(value => {
const reference = mapReferenceProductsToVariants(
referenceProducts,
).find(reference => reference.id === value);
return { ...reference };
}),
)
: [],
},
});
export const getReferenceAttributeDisplayData = ( export const getReferenceAttributeDisplayData = (
attribute: AttributeInput, attribute: AttributeInput,
referencePages: RelayToFlat<SearchPagesQuery["search"]>, referencePages: RelayToFlat<SearchPagesQuery["search"]>,
@ -432,6 +453,13 @@ export const getReferenceAttributeDisplayData = (
attribute, attribute,
referenceProducts, referenceProducts,
); );
} else if (
attribute.data.entityType === AttributeEntityTypeEnum.PRODUCT_VARIANT
) {
return getProductVariantReferenceAttributeDisplayData(
attribute,
referenceProducts,
);
} }
}; };
@ -464,22 +492,18 @@ export const getSelectedReferencesFromAttribute = <T extends Node>(
!attribute?.value?.some(selectedValue => selectedValue === value.id), !attribute?.value?.some(selectedValue => selectedValue === value.id),
) || []; ) || [];
export const getAttributeValuesFromReferences = ( export const getReferenceAttributeEntityTypeFromAttribute = (
attributeId: string, attributeId: string,
attributes?: AttributeInput[], attributes?: AttributeInput[],
referencePages?: RelayToFlat<SearchPagesQuery["search"]>, ): AttributeEntityTypeEnum | undefined =>
referenceProducts?: RelayToFlat<SearchProductsQuery["search"]>, attributes?.find(attribute => attribute.id === attributeId)?.data?.entityType;
) => {
const attribute = attributes?.find(attribute => attribute.id === attributeId);
if (attribute?.data?.entityType === AttributeEntityTypeEnum.PAGE) { export const mapReferenceProductsToVariants = (
return mapPagesToChoices( referenceProducts: RelayToFlat<SearchProductsQuery["search"]>,
getSelectedReferencesFromAttribute(attribute, referencePages), ) =>
referenceProducts.flatMap(product =>
product.variants.map(variant => ({
...variant,
name: product.name + " " + variant.name,
})),
); );
} else if (attribute?.data?.entityType === AttributeEntityTypeEnum.PRODUCT) {
return mapNodeToChoice(
getSelectedReferencesFromAttribute(attribute, referenceProducts),
);
}
return [];
};

View file

@ -88,7 +88,10 @@ export function createFetchReferencesHandler(
) { ) {
fetchReferencePages(value); fetchReferencePages(value);
} else if ( } else if (
attribute.data.entityType === AttributeEntityTypeEnum.PRODUCT && [
AttributeEntityTypeEnum.PRODUCT,
AttributeEntityTypeEnum.PRODUCT_VARIANT,
].includes(attribute.data.entityType) &&
fetchReferenceProducts fetchReferenceProducts
) { ) {
fetchReferenceProducts(value); fetchReferenceProducts(value);
@ -112,7 +115,12 @@ export function createFetchMoreReferencesHandler(
if (attribute.data.entityType === AttributeEntityTypeEnum.PAGE) { if (attribute.data.entityType === AttributeEntityTypeEnum.PAGE) {
return fetchMoreReferencePages; return fetchMoreReferencePages;
} else if (attribute.data.entityType === AttributeEntityTypeEnum.PRODUCT) { } else if (
[
AttributeEntityTypeEnum.PRODUCT,
AttributeEntityTypeEnum.PRODUCT_VARIANT,
].includes(attribute.data.entityType)
) {
return fetchMoreReferenceProducts; return fetchMoreReferenceProducts;
} }
} }

View file

@ -1,65 +1,69 @@
import { AttributeReference } from "@saleor/attributes/utils/data"; import { AttributeEntityTypeEnum, SearchPagesQuery } from "@saleor/graphql";
import { RelayToFlat } from "@saleor/types";
import React from "react"; import React from "react";
import { defineMessages, useIntl } from "react-intl"; import { defineMessages, useIntl } from "react-intl";
import AssignContainerDialog, { import AssignContainerDialog from "../AssignContainerDialog";
AssignContainerDialogProps, import AssignProductDialog, {
} from "../AssignContainerDialog"; AssignProductDialogProps,
} from "../AssignProductDialog";
import AssignVariantDialog from "../AssignVariantDialog";
const messages = defineMessages({ const pagesMessages = defineMessages({
confirmBtn: { confirmBtn: {
id: "ylobu9", id: "idr+JK",
defaultMessage: "Assign", defaultMessage: "Assign and save",
description: "assign reference to product, button", description: "assign reference to a page, button",
}, },
header: { header: {
id: "GUlwXU", id: "5I7Lc2",
defaultMessage: "Assign Attribute Value", defaultMessage: "Assign page",
description: "dialog header", description: "dialog header",
}, },
searchLabel: { searchLabel: {
id: "RoKOQJ", id: "izJvcM",
defaultMessage: "Search Attribute Value", defaultMessage: "Search pages",
description: "label", description: "label",
}, },
searchPlaceholder: { searchPlaceholder: {
id: "NsgWhZ", id: "OFW7nq",
defaultMessage: "Search by value name, etc...", defaultMessage: "Search by page name, etc...",
description: "placeholder", description: "placeholder",
}, },
}); });
interface AssignAttributeValueDialogProps type AssignAttributeValueDialogProps = AssignProductDialogProps & {
extends Omit< entityType: AttributeEntityTypeEnum;
AssignContainerDialogProps, pages: RelayToFlat<SearchPagesQuery["search"]>;
"containers" | "title" | "search" | "confirmButtonState" | "labels" };
> {
attributeValues: AttributeReference[];
}
const AssignAttributeValueDialog: React.FC<AssignAttributeValueDialogProps> = ({ const AssignAttributeValueDialog: React.FC<AssignAttributeValueDialogProps> = ({
attributeValues, entityType,
pages,
products,
...rest ...rest
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
switch (entityType) {
case AttributeEntityTypeEnum.PAGE:
return ( return (
<AssignContainerDialog <AssignContainerDialog
containers={attributeValues.map(value => ({ containers={pages.map(page => ({ id: page.id, name: page.title }))}
id: value.value,
name: value.label,
}))}
labels={{ labels={{
confirmBtn: intl.formatMessage(messages.confirmBtn), confirmBtn: intl.formatMessage(pagesMessages.confirmBtn),
label: intl.formatMessage(messages.searchLabel), label: intl.formatMessage(pagesMessages.searchLabel),
placeholder: intl.formatMessage(messages.searchPlaceholder), placeholder: intl.formatMessage(pagesMessages.searchPlaceholder),
title: intl.formatMessage(messages.header), title: intl.formatMessage(pagesMessages.header),
}} }}
confirmButtonState="default"
{...rest} {...rest}
/> />
); );
case AttributeEntityTypeEnum.PRODUCT:
return <AssignProductDialog products={products} {...rest} />;
case AttributeEntityTypeEnum.PRODUCT_VARIANT:
return <AssignVariantDialog products={products} {...rest} />;
}
}; };
AssignAttributeValueDialog.displayName = "AssignAttributeValueDialog"; AssignAttributeValueDialog.displayName = "AssignAttributeValueDialog";
export default AssignAttributeValueDialog; export default AssignAttributeValueDialog;

View file

@ -2,8 +2,8 @@ import { defineMessages } from "react-intl";
export const messages = defineMessages({ export const messages = defineMessages({
assignVariantDialogHeader: { assignVariantDialogHeader: {
id: "dTCDMn", id: "juxCV3",
defaultMessage: "Assign Product", defaultMessage: "Assign product",
description: "dialog header", description: "dialog header",
}, },
assignProductDialogButton: { assignProductDialogButton: {
@ -12,8 +12,8 @@ export const messages = defineMessages({
description: "button", description: "button",
}, },
assignProductDialogContent: { assignProductDialogContent: {
id: "/TF6BZ", id: "un+VWt",
defaultMessage: "Search Products", defaultMessage: "Search products",
}, },
assignProductDialogSearch: { assignProductDialogSearch: {
id: "SHm7ee", id: "SHm7ee",

View file

@ -45,7 +45,7 @@ export interface AssignVariantDialogProps extends FetchMoreProps, DialogProps {
products: RelayToFlat<SearchProductsQuery["search"]>; products: RelayToFlat<SearchProductsQuery["search"]>;
loading: boolean; loading: boolean;
onFetch: (value: string) => void; onFetch: (value: string) => void;
onSubmit: (data: SearchVariant[]) => void; onSubmit: (data: string[]) => void;
} }
const scrollableTargetId = "assignVariantScrollableDialog"; const scrollableTargetId = "assignVariantScrollableDialog";
@ -84,7 +84,7 @@ const AssignVariantDialog: React.FC<AssignVariantDialogProps> = props => {
) )
: []; : [];
const handleSubmit = () => onSubmit(variants); const handleSubmit = () => onSubmit(variants.map(variant => variant.id));
return ( return (
<Dialog <Dialog

View file

@ -390,7 +390,7 @@ export const SaleDetails: React.FC<SaleDetailsProps> = ({ id, params }) => {
...paginationState, ...paginationState,
id, id,
input: { input: {
variants: variants.map(variant => variant.id), variants,
}, },
}, },
}) })

View file

@ -25,6 +25,7 @@
"AttributeValueCreated", "AttributeValueCreated",
"AttributeValueDeleted", "AttributeValueDeleted",
"AttributeValueUpdated", "AttributeValueUpdated",
"CalculateTaxes",
"CategoryCreated", "CategoryCreated",
"CategoryDeleted", "CategoryDeleted",
"CategoryUpdated", "CategoryUpdated",
@ -237,6 +238,14 @@
"Voucher", "Voucher",
"Warehouse" "Warehouse"
], ],
"TaxSourceLine": [
"CheckoutLine",
"OrderLine"
],
"TaxSourceObject": [
"Checkout",
"Order"
],
"TranslatableItem": [ "TranslatableItem": [
"ProductTranslatableContent", "ProductTranslatableContent",
"CollectionTranslatableContent", "CollectionTranslatableContent",

View file

@ -607,6 +607,14 @@ export type BulkStockErrorFieldPolicy = {
values?: FieldPolicy<any> | FieldReadFunction<any>, values?: FieldPolicy<any> | FieldReadFunction<any>,
index?: FieldPolicy<any> | FieldReadFunction<any> index?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type CalculateTaxesKeySpecifier = ('issuedAt' | 'version' | 'issuingPrincipal' | 'recipient' | 'taxBase' | CalculateTaxesKeySpecifier)[];
export type CalculateTaxesFieldPolicy = {
issuedAt?: FieldPolicy<any> | FieldReadFunction<any>,
version?: FieldPolicy<any> | FieldReadFunction<any>,
issuingPrincipal?: FieldPolicy<any> | FieldReadFunction<any>,
recipient?: FieldPolicy<any> | FieldReadFunction<any>,
taxBase?: FieldPolicy<any> | FieldReadFunction<any>
};
export type CategoryKeySpecifier = ('id' | 'privateMetadata' | 'privateMetafield' | 'privateMetafields' | 'metadata' | 'metafield' | 'metafields' | 'seoTitle' | 'seoDescription' | 'name' | 'description' | 'slug' | 'parent' | 'level' | 'descriptionJson' | 'ancestors' | 'products' | 'children' | 'backgroundImage' | 'translation' | CategoryKeySpecifier)[]; export type CategoryKeySpecifier = ('id' | 'privateMetadata' | 'privateMetafield' | 'privateMetafields' | 'metadata' | 'metafield' | 'metafields' | 'seoTitle' | 'seoDescription' | 'name' | 'description' | 'slug' | 'parent' | 'level' | 'descriptionJson' | 'ancestors' | 'products' | 'children' | 'backgroundImage' | 'translation' | CategoryKeySpecifier)[];
export type CategoryFieldPolicy = { export type CategoryFieldPolicy = {
id?: FieldPolicy<any> | FieldReadFunction<any>, id?: FieldPolicy<any> | FieldReadFunction<any>,
@ -4634,6 +4642,33 @@ export type TaxTypeFieldPolicy = {
description?: FieldPolicy<any> | FieldReadFunction<any>, description?: FieldPolicy<any> | FieldReadFunction<any>,
taxCode?: FieldPolicy<any> | FieldReadFunction<any> taxCode?: FieldPolicy<any> | FieldReadFunction<any>
}; };
export type TaxableObjectKeySpecifier = ('sourceObject' | 'pricesEnteredWithTax' | 'currency' | 'shippingPrice' | 'address' | 'discounts' | 'lines' | 'channel' | TaxableObjectKeySpecifier)[];
export type TaxableObjectFieldPolicy = {
sourceObject?: FieldPolicy<any> | FieldReadFunction<any>,
pricesEnteredWithTax?: FieldPolicy<any> | FieldReadFunction<any>,
currency?: FieldPolicy<any> | FieldReadFunction<any>,
shippingPrice?: FieldPolicy<any> | FieldReadFunction<any>,
address?: FieldPolicy<any> | FieldReadFunction<any>,
discounts?: FieldPolicy<any> | FieldReadFunction<any>,
lines?: FieldPolicy<any> | FieldReadFunction<any>,
channel?: FieldPolicy<any> | FieldReadFunction<any>
};
export type TaxableObjectDiscountKeySpecifier = ('name' | 'amount' | TaxableObjectDiscountKeySpecifier)[];
export type TaxableObjectDiscountFieldPolicy = {
name?: FieldPolicy<any> | FieldReadFunction<any>,
amount?: FieldPolicy<any> | FieldReadFunction<any>
};
export type TaxableObjectLineKeySpecifier = ('sourceLine' | 'quantity' | 'chargeTaxes' | 'productName' | 'variantName' | 'productSku' | 'unitPrice' | 'totalPrice' | TaxableObjectLineKeySpecifier)[];
export type TaxableObjectLineFieldPolicy = {
sourceLine?: FieldPolicy<any> | FieldReadFunction<any>,
quantity?: FieldPolicy<any> | FieldReadFunction<any>,
chargeTaxes?: FieldPolicy<any> | FieldReadFunction<any>,
productName?: FieldPolicy<any> | FieldReadFunction<any>,
variantName?: FieldPolicy<any> | FieldReadFunction<any>,
productSku?: FieldPolicy<any> | FieldReadFunction<any>,
unitPrice?: FieldPolicy<any> | FieldReadFunction<any>,
totalPrice?: FieldPolicy<any> | FieldReadFunction<any>
};
export type TaxedMoneyKeySpecifier = ('currency' | 'gross' | 'net' | 'tax' | TaxedMoneyKeySpecifier)[]; export type TaxedMoneyKeySpecifier = ('currency' | 'gross' | 'net' | 'tax' | TaxedMoneyKeySpecifier)[];
export type TaxedMoneyFieldPolicy = { export type TaxedMoneyFieldPolicy = {
currency?: FieldPolicy<any> | FieldReadFunction<any>, currency?: FieldPolicy<any> | FieldReadFunction<any>,
@ -5498,6 +5533,10 @@ export type StrictTypedTypePolicies = {
keyFields?: false | BulkStockErrorKeySpecifier | (() => undefined | BulkStockErrorKeySpecifier), keyFields?: false | BulkStockErrorKeySpecifier | (() => undefined | BulkStockErrorKeySpecifier),
fields?: BulkStockErrorFieldPolicy, fields?: BulkStockErrorFieldPolicy,
}, },
CalculateTaxes?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | CalculateTaxesKeySpecifier | (() => undefined | CalculateTaxesKeySpecifier),
fields?: CalculateTaxesFieldPolicy,
},
Category?: Omit<TypePolicy, "fields" | "keyFields"> & { Category?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | CategoryKeySpecifier | (() => undefined | CategoryKeySpecifier), keyFields?: false | CategoryKeySpecifier | (() => undefined | CategoryKeySpecifier),
fields?: CategoryFieldPolicy, fields?: CategoryFieldPolicy,
@ -7398,6 +7437,18 @@ export type StrictTypedTypePolicies = {
keyFields?: false | TaxTypeKeySpecifier | (() => undefined | TaxTypeKeySpecifier), keyFields?: false | TaxTypeKeySpecifier | (() => undefined | TaxTypeKeySpecifier),
fields?: TaxTypeFieldPolicy, fields?: TaxTypeFieldPolicy,
}, },
TaxableObject?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | TaxableObjectKeySpecifier | (() => undefined | TaxableObjectKeySpecifier),
fields?: TaxableObjectFieldPolicy,
},
TaxableObjectDiscount?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | TaxableObjectDiscountKeySpecifier | (() => undefined | TaxableObjectDiscountKeySpecifier),
fields?: TaxableObjectDiscountFieldPolicy,
},
TaxableObjectLine?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | TaxableObjectLineKeySpecifier | (() => undefined | TaxableObjectLineKeySpecifier),
fields?: TaxableObjectLineFieldPolicy,
},
TaxedMoney?: Omit<TypePolicy, "fields" | "keyFields"> & { TaxedMoney?: Omit<TypePolicy, "fields" | "keyFields"> & {
keyFields?: false | TaxedMoneyKeySpecifier | (() => undefined | TaxedMoneyKeySpecifier), keyFields?: false | TaxedMoneyKeySpecifier | (() => undefined | TaxedMoneyKeySpecifier),
fields?: TaxedMoneyFieldPolicy, fields?: TaxedMoneyFieldPolicy,

View file

@ -334,7 +334,8 @@ export type AttributeCreateInput = {
/** An enumeration. */ /** An enumeration. */
export enum AttributeEntityTypeEnum { export enum AttributeEntityTypeEnum {
PAGE = 'PAGE', PAGE = 'PAGE',
PRODUCT = 'PRODUCT' PRODUCT = 'PRODUCT',
PRODUCT_VARIANT = 'PRODUCT_VARIANT'
} }
/** An enumeration. */ /** An enumeration. */
@ -485,7 +486,7 @@ export type AttributeValueFilterInput = {
export type AttributeValueInput = { export type AttributeValueInput = {
/** ID of the selected attribute. */ /** ID of the selected attribute. */
id?: InputMaybe<Scalars['ID']>; id: Scalars['ID'];
/** The value or slug of an attribute to resolve. If the passed value is non-existent, it will be created. */ /** The value or slug of an attribute to resolve. If the passed value is non-existent, it will be created. */
values?: InputMaybe<Array<Scalars['String']>>; values?: InputMaybe<Array<Scalars['String']>>;
/** URL of the file attribute. Every time, a new value is created. */ /** URL of the file attribute. Every time, a new value is created. */

View file

@ -1,5 +1,5 @@
import { import {
getAttributeValuesFromReferences, getReferenceAttributeEntityTypeFromAttribute,
mergeAttributeValues, mergeAttributeValues,
} from "@saleor/attributes/utils/data"; } from "@saleor/attributes/utils/data";
import AssignAttributeValueDialog from "@saleor/components/AssignAttributeValueDialog"; import AssignAttributeValueDialog from "@saleor/components/AssignAttributeValueDialog";
@ -264,12 +264,13 @@ const PageDetailsPage: React.FC<PageDetailsPageProps> = ({
/> />
{canOpenAssignReferencesAttributeDialog && ( {canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog <AssignAttributeValueDialog
attributeValues={getAttributeValuesFromReferences( entityType={getReferenceAttributeEntityTypeFromAttribute(
assignReferencesAttributeId, assignReferencesAttributeId,
data.attributes, data.attributes,
referencePages,
referenceProducts,
)} )}
confirmButtonState={"default"}
products={referenceProducts}
pages={referencePages}
hasMore={handlers.fetchMoreReferences?.hasMore} hasMore={handlers.fetchMoreReferences?.hasMore}
open={canOpenAssignReferencesAttributeDialog} open={canOpenAssignReferencesAttributeDialog}
onFetch={handlers.fetchReferences} onFetch={handlers.fetchReferences}

View file

@ -1,5 +1,5 @@
import { import {
getAttributeValuesFromReferences, getReferenceAttributeEntityTypeFromAttribute,
mergeAttributeValues, mergeAttributeValues,
} from "@saleor/attributes/utils/data"; } from "@saleor/attributes/utils/data";
import CannotDefineChannelsAvailabilityCard from "@saleor/channels/components/CannotDefineChannelsAvailabilityCard/CannotDefineChannelsAvailabilityCard"; import CannotDefineChannelsAvailabilityCard from "@saleor/channels/components/CannotDefineChannelsAvailabilityCard/CannotDefineChannelsAvailabilityCard";
@ -376,12 +376,13 @@ export const ProductCreatePage: React.FC<ProductCreatePageProps> = ({
/> />
{canOpenAssignReferencesAttributeDialog && ( {canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog <AssignAttributeValueDialog
attributeValues={getAttributeValuesFromReferences( entityType={getReferenceAttributeEntityTypeFromAttribute(
assignReferencesAttributeId, assignReferencesAttributeId,
data.attributes, data.attributes,
referencePages,
referenceProducts,
)} )}
confirmButtonState={"default"}
products={referenceProducts}
pages={referencePages}
hasMore={handlers.fetchMoreReferences?.hasMore} hasMore={handlers.fetchMoreReferences?.hasMore}
open={canOpenAssignReferencesAttributeDialog} open={canOpenAssignReferencesAttributeDialog}
onFetch={handlers.fetchReferences} onFetch={handlers.fetchReferences}

View file

@ -5,7 +5,7 @@ import {
useExtensions, useExtensions,
} from "@saleor/apps/useExtensions"; } from "@saleor/apps/useExtensions";
import { import {
getAttributeValuesFromReferences, getReferenceAttributeEntityTypeFromAttribute,
mergeAttributeValues, mergeAttributeValues,
} from "@saleor/attributes/utils/data"; } from "@saleor/attributes/utils/data";
import { ChannelData } from "@saleor/channels/utils"; import { ChannelData } from "@saleor/channels/utils";
@ -525,12 +525,13 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
/> />
{canOpenAssignReferencesAttributeDialog && ( {canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog <AssignAttributeValueDialog
attributeValues={getAttributeValuesFromReferences( entityType={getReferenceAttributeEntityTypeFromAttribute(
assignReferencesAttributeId, assignReferencesAttributeId,
data.attributes, data.attributes,
referencePages,
referenceProducts,
)} )}
confirmButtonState={"default"}
products={referenceProducts}
pages={referencePages}
hasMore={handlers.fetchMoreReferences?.hasMore} hasMore={handlers.fetchMoreReferences?.hasMore}
open={canOpenAssignReferencesAttributeDialog} open={canOpenAssignReferencesAttributeDialog}
onFetch={handlers.fetchReferences} onFetch={handlers.fetchReferences}

View file

@ -1,5 +1,5 @@
import { import {
getAttributeValuesFromReferences, getReferenceAttributeEntityTypeFromAttribute,
mergeAttributeValues, mergeAttributeValues,
} from "@saleor/attributes/utils/data"; } from "@saleor/attributes/utils/data";
import AssignAttributeValueDialog from "@saleor/components/AssignAttributeValueDialog"; import AssignAttributeValueDialog from "@saleor/components/AssignAttributeValueDialog";
@ -281,12 +281,13 @@ const ProductVariantCreatePage: React.FC<ProductVariantCreatePageProps> = ({
/> />
{canOpenAssignReferencesAttributeDialog && ( {canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog <AssignAttributeValueDialog
attributeValues={getAttributeValuesFromReferences( entityType={getReferenceAttributeEntityTypeFromAttribute(
assignReferencesAttributeId, assignReferencesAttributeId,
data.attributes, data.attributes,
referencePages,
referenceProducts,
)} )}
confirmButtonState={"default"}
products={referenceProducts}
pages={referencePages}
hasMore={handlers.fetchMoreReferences?.hasMore} hasMore={handlers.fetchMoreReferences?.hasMore}
open={canOpenAssignReferencesAttributeDialog} open={canOpenAssignReferencesAttributeDialog}
onFetch={handlers.fetchReferences} onFetch={handlers.fetchReferences}

View file

@ -1,5 +1,5 @@
import { import {
getAttributeValuesFromReferences, getReferenceAttributeEntityTypeFromAttribute,
mergeAttributeValues, mergeAttributeValues,
} from "@saleor/attributes/utils/data"; } from "@saleor/attributes/utils/data";
import { ChannelPriceData } from "@saleor/channels/utils"; import { ChannelPriceData } from "@saleor/channels/utils";
@ -381,12 +381,13 @@ const ProductVariantPage: React.FC<ProductVariantPageProps> = ({
/> />
{canOpenAssignReferencesAttributeDialog && ( {canOpenAssignReferencesAttributeDialog && (
<AssignAttributeValueDialog <AssignAttributeValueDialog
attributeValues={getAttributeValuesFromReferences( entityType={getReferenceAttributeEntityTypeFromAttribute(
assignReferencesAttributeId, assignReferencesAttributeId,
data.attributes, data.attributes,
referencePages,
referenceProducts,
)} )}
confirmButtonState={"default"}
products={referenceProducts}
pages={referencePages}
hasMore={handlers.fetchMoreReferences?.hasMore} hasMore={handlers.fetchMoreReferences?.hasMore}
open={canOpenAssignReferencesAttributeDialog} open={canOpenAssignReferencesAttributeDialog}
onFetch={handlers.fetchReferences} onFetch={handlers.fetchReferences}