diff --git a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx index ac57ec9fc..f341f5875 100644 --- a/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx +++ b/src/products/components/ProductUpdatePage/ProductUpdatePage.tsx @@ -60,6 +60,7 @@ export interface ProductUpdatePageProps extends ListActions { saveButtonBarState: ConfirmButtonTransitionState; fetchCategories: (query: string) => void; fetchCollections: (query: string) => void; + onVariantsAdd: () => void; onVariantShow: (id: string) => () => void; onImageDelete: (id: string) => () => void; onBack?(); @@ -100,6 +101,7 @@ export const ProductUpdatePage: React.FC = ({ onSeoClick, onSubmit, onVariantAdd, + onVariantsAdd, onVariantShow, isChecked, selected, @@ -236,6 +238,7 @@ export const ProductUpdatePage: React.FC = ({ fallbackPrice={product ? product.basePrice : undefined} onRowClick={onVariantShow} onVariantAdd={onVariantAdd} + onVariantsAdd={onVariantsAdd} toolbar={toolbar} isChecked={isChecked} selected={selected} diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx index f181027ed..a0738af4c 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx @@ -4,6 +4,8 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { attributes } from "@saleor/attributes/fixtures"; +import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; import Decorator from "../../../storybook/Decorator"; import { createVariants } from "./createVariants"; import { AllOrAttribute } from "./form"; @@ -19,7 +21,7 @@ const price: AllOrAttribute = { attribute: selectedAttributes[1].id, value: "2.79", values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({ - id: attribute.id, + slug: attribute.slug, value: (attributeIndex + 4).toFixed(2) })) }; @@ -29,7 +31,7 @@ const stock: AllOrAttribute = { attribute: selectedAttributes[1].id, value: "8", values: selectedAttributes[1].values.map((attribute, attributeIndex) => ({ - id: attribute.id, + slug: attribute.slug, value: (selectedAttributes.length * 10 - attributeIndex).toString() })) }; @@ -37,10 +39,20 @@ const stock: AllOrAttribute = { const dataAttributes = selectedAttributes.map(attribute => ({ id: attribute.id, values: attribute.values - .map(value => value.id) + .map(value => value.slug) .filter((_, valueIndex) => valueIndex % 2 !== 1) })); +const errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[] = [ + { + __typename: "BulkProductError", + code: ProductErrorCode.UNIQUE, + field: "sku", + index: 3, + message: "Duplicated SKU." + } +]; + const props: ProductVariantCreateContentProps = { attributes, currencySymbol: "USD", @@ -56,6 +68,7 @@ const props: ProductVariantCreateContentProps = { }) }, dispatchFormDataAction: () => undefined, + errors: [], step: "attributes" }; @@ -78,9 +91,26 @@ storiesOf("Views / Products / Create multiple variants", module) )) .add("prices and SKU", () => ( + )); + +storiesOf("Views / Products / Create multiple variants / summary", module) + .addDecorator(storyFn => ( + + {storyFn()} + )) - .add("summary", () => ( + .addDecorator(Decorator) + .add("default", () => ( + )) + .add("errors", () => ( + )); storiesOf("Views / Products / Create multiple variants", module) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx index 5de61f721..48b658d9f 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateAttributes.tsx @@ -1,4 +1,3 @@ -import { Theme } from "@material-ui/core/styles"; import Table from "@material-ui/core/Table"; import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; @@ -18,14 +17,14 @@ export interface ProductVariantCreateAttributesProps { onAttributeClick: (id: string) => void; } -const useStyles = makeStyles((theme: Theme) => ({ +const useStyles = makeStyles({ checkboxCell: { paddingLeft: 0 }, wideCell: { width: "100%" } -})); +}); const ProductVariantCreateAttributes: React.FC< ProductVariantCreateAttributesProps diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 9f66a7220..777e36632 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -2,6 +2,7 @@ import React from "react"; import { makeStyles } from "@material-ui/styles"; import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; +import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; import { isSelected } from "@saleor/utils/lists"; import { ProductVariantCreateFormData } from "./form"; import ProductVariantCreateAttributes from "./ProductVariantCreateAttributes"; @@ -24,6 +25,7 @@ export interface ProductVariantCreateContentProps { currencySymbol: string; data: ProductVariantCreateFormData; dispatchFormDataAction: React.Dispatch; + errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[]; step: ProductVariantCreateStep; } @@ -35,6 +37,7 @@ const ProductVariantCreateContent: React.FC< currencySymbol, data, dispatchFormDataAction, + errors, step } = props; const classes = useStyles(props); @@ -121,6 +124,7 @@ const ProductVariantCreateContent: React.FC< attributes={selectedAttributes} currencySymbol={currencySymbol} data={data} + errors={errors} onVariantDataChange={(variantIndex, field, value) => dispatchFormDataAction({ field, diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx index a83903d54..a86601cc8 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog.tsx @@ -7,6 +7,8 @@ import { Theme } from "@material-ui/core/styles"; import { makeStyles } from "@material-ui/styles"; import React from "react"; import { FormattedMessage } from "react-intl"; + +import { ProductVariantBulkCreateInput } from "../../../types/globalTypes"; import { initialForm, ProductVariantCreateFormData } from "./form"; import ProductVariantCreateContent, { ProductVariantCreateContentProps @@ -73,17 +75,17 @@ function canHitNext( export interface ProductVariantCreateDialogProps extends Omit< ProductVariantCreateContentProps, - "dispatchFormDataAction" | "step" + "data" | "dispatchFormDataAction" | "step" > { open: boolean; - onClose: () => undefined; - onSubmit: (data: ProductVariantCreateFormData) => void; + onClose: () => void; + onSubmit: (data: ProductVariantBulkCreateInput[]) => void; } const ProductVariantCreateDialog: React.FC< ProductVariantCreateDialogProps > = props => { - const { open, onClose, ...contentProps } = props; + const { open, onClose, onSubmit, ...contentProps } = props; const classes = useStyles(props); const [step, setStep] = React.useState( "attributes" @@ -167,6 +169,7 @@ const ProductVariantCreateDialog: React.FC< color="primary" disabled={!canHitNext(step, data)} variant="contained" + onClick={() => onSubmit(data.variants)} > attribute.id === data.price.attribute).values + ? attributes + .find(attribute => attribute.id === data.price.attribute) + .values.filter(value => + data.attributes + .find(attribute => attribute.id === data.price.attribute) + .values.includes(value.slug) + ) : []; const stockAttributeValues = data.stock.all ? null : data.stock.attribute - ? attributes.find(attribute => attribute.id === data.stock.attribute).values + ? attributes + .find(attribute => attribute.id === data.stock.attribute) + .values.filter(value => + data.attributes + .find(attribute => attribute.id === data.stock.attribute) + .values.includes(value.slug) + ) : []; return ( @@ -158,7 +170,7 @@ const ProductVariantCreatePrices: React.FC< value={data.price.values[attributeValueIndex].value} onChange={event => onAttributeValueChange( - attributeValue.id, + attributeValue.slug, event.target.value, "price" ) @@ -256,7 +268,7 @@ const ProductVariantCreatePrices: React.FC< value={data.stock.values[attributeValueIndex].value} onChange={event => onAttributeValueChange( - attributeValue.id, + attributeValue.slug, event.target.value, "stock" ) diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx index 53c98d44a..1d74e48f1 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateSummary.tsx @@ -4,11 +4,6 @@ import green from "@material-ui/core/colors/green"; import purple from "@material-ui/core/colors/purple"; import yellow from "@material-ui/core/colors/yellow"; import { Theme } from "@material-ui/core/styles"; -import Table from "@material-ui/core/Table"; -import TableBody from "@material-ui/core/TableBody"; -import TableCell from "@material-ui/core/TableCell"; -import TableHead from "@material-ui/core/TableHead"; -import TableRow from "@material-ui/core/TableRow"; import TextField from "@material-ui/core/TextField"; import Typography from "@material-ui/core/Typography"; import { makeStyles } from "@material-ui/styles"; @@ -17,7 +12,9 @@ import React from "react"; import { FormattedMessage } from "react-intl"; import Hr from "@saleor/components/Hr"; -import { ProductVariantCreateInput } from "@saleor/types/globalTypes"; +import { maybe } from "@saleor/misc"; +import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; +import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes"; import { ProductDetails_product_productType_variantAttributes } from "../../types/ProductDetails"; import { ProductVariantCreateFormData } from "./form"; import { VariantField } from "./reducer"; @@ -26,6 +23,7 @@ export interface ProductVariantCreateSummaryProps { attributes: ProductDetails_product_productType_variantAttributes[]; currencySymbol: string; data: ProductVariantCreateFormData; + errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[]; onVariantDataChange: ( variantIndex: number, field: VariantField, @@ -35,42 +33,58 @@ export interface ProductVariantCreateSummaryProps { const colors = [blue, cyan, green, purple, yellow].map(color => color[800]); -const useStyles = makeStyles((theme: Theme) => ({ - attributeValue: { - display: "inline-block", - marginRight: theme.spacing.unit - }, - col: { - paddingLeft: theme.spacing.unit, - paddingRight: theme.spacing.unit - }, - colName: { - paddingLeft: "0 !important", - width: "auto" - }, - colPrice: { - width: 200 - }, - colSku: { - width: 210 - }, - colStock: { - width: 120 - }, - hr: { - marginBottom: theme.spacing.unit, - marginTop: theme.spacing.unit / 2 - }, - input: { - "& input": { - padding: "16px 12px 17px" +const useStyles = makeStyles( + (theme: Theme) => ({ + attributeValue: { + display: "inline-block", + marginRight: theme.spacing.unit }, - marginTop: theme.spacing.unit / 2 + col: { + ...theme.typography.body2, + fontSize: 14, + paddingLeft: theme.spacing.unit, + paddingRight: theme.spacing.unit + }, + colHeader: { + ...theme.typography.body2, + fontSize: 14 + }, + colName: { + "&&": { + paddingLeft: "0 !important" + }, + "&:not($colHeader)": { + paddingTop: theme.spacing.unit * 2 + } + }, + colPrice: {}, + colSku: {}, + colStock: {}, + errorRow: {}, + hr: { + marginBottom: theme.spacing.unit, + marginTop: theme.spacing.unit / 2 + }, + input: { + "& input": { + padding: "16px 12px 17px" + }, + marginTop: theme.spacing.unit / 2 + }, + row: { + borderBottom: `1px solid ${theme.palette.divider}`, + display: "grid", + gridTemplateColumns: "1fr 200px 120px 210px", + padding: `${theme.spacing.unit}px 0` + } + }), + { + name: "ProductVariantCreateSummary" } -})); +); function getVariantName( - variant: ProductVariantCreateInput, + variant: ProductVariantBulkCreateInput, attributes: ProductDetails_product_productType_variantAttributes[] ): string[] { return attributes.reduce( @@ -78,7 +92,7 @@ function getVariantName( ...acc, attribute.values.find( value => - value.id === + value.slug === variant.attributes.find( variantAttribute => variantAttribute.id === attribute.id ).values[0] @@ -91,7 +105,13 @@ function getVariantName( const ProductVariantCreateSummary: React.FC< ProductVariantCreateSummaryProps > = props => { - const { attributes, currencySymbol, data, onVariantDataChange } = props; + const { + attributes, + currencySymbol, + data, + errors, + onVariantDataChange + } = props; const classes = useStyles(props); return ( @@ -103,40 +123,69 @@ const ProductVariantCreateSummary: React.FC< />
- - - - - - - - - - - - - - - - - - - {data.variants.map((variant, variantIndex) => ( - +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {data.variants.map((variant, variantIndex) => { + const variantErrors = errors.filter( + error => error.index === variantIndex + ); + + return ( +
0 + })} key={variant.attributes .map(attribute => attribute.values[0]) .join(":")} > - +
{getVariantName(variant, attributes).map( (value, valueIndex) => ( ) )} - - - - onVariantDataChange( - variantIndex, - "stock", - event.target.value - ) - } - /> - - +
+
error.field === "priceOverride" + ) + } + helperText={maybe( + () => + variantErrors.find( + error => error.field === "priceOverride" + ).message + )} inputProps={{ min: 0, type: "number" @@ -188,21 +230,52 @@ const ProductVariantCreateSummary: React.FC< ) } /> - - +
+
error.field === "quantity") + } + helperText={maybe( + () => + variantErrors.find(error => error.field === "quantity") + .message + )} + inputProps={{ + min: 0, + type: "number" + }} + fullWidth + value={variant.quantity} + onChange={event => + onVariantDataChange( + variantIndex, + "stock", + event.target.value + ) + } + /> +
+
+ error.field === "sku")} + helperText={maybe( + () => + variantErrors.find(error => error.field === "sku").message + )} fullWidth value={variant.sku} onChange={event => onVariantDataChange(variantIndex, "sku", event.target.value) } /> - - - ))} - -
+ + + ); + })} + ); }; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx index 801dc222f..8d73cabe3 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateValues.tsx @@ -48,15 +48,15 @@ const ProductVariantCreateValues: React.FC< {attribute.values.map(value => ( attribute.id === dataAttribute.id ).values, (a, b) => a === b )} - name={`value:${value.id}`} + name={`value:${value.slug}`} label={value.name} - onChange={() => onValueClick(attribute.id, value.id)} + onChange={() => onValueClick(attribute.id, value.slug)} /> ))} diff --git a/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap index b40089ef1..f9b455627 100644 --- a/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap +++ b/src/products/components/ProductVariantCreateDialog/__snapshots__/reducer.test.ts.snap @@ -133,7 +133,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -159,7 +158,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -185,7 +183,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -211,7 +208,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -237,7 +233,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -263,7 +258,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -289,7 +283,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -315,7 +308,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -354,11 +346,11 @@ Object { "value": "", "values": Array [ Object { - "id": "val-1-1", + "slug": "val-1-1", "value": "45.99", }, Object { - "id": "val-1-7", + "slug": "val-1-7", "value": "51.99", }, ], @@ -392,7 +384,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -418,7 +409,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -444,7 +434,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -470,7 +459,6 @@ Object { }, ], "priceOverride": "45.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -496,7 +484,6 @@ Object { }, ], "priceOverride": "51.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -522,7 +509,6 @@ Object { }, ], "priceOverride": "51.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -548,7 +534,6 @@ Object { }, ], "priceOverride": "51.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -574,7 +559,6 @@ Object { }, ], "priceOverride": "51.99", - "product": "", "quantity": NaN, "sku": "", }, @@ -642,7 +626,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -668,7 +651,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -694,7 +676,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -720,7 +701,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -746,7 +726,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -772,7 +751,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -798,7 +776,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -824,7 +801,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 45, "sku": "", }, @@ -869,11 +845,11 @@ Object { "value": "", "values": Array [ Object { - "id": "val-1-1", + "slug": "val-1-1", "value": "13", }, Object { - "id": "val-1-7", + "slug": "val-1-7", "value": "19", }, ], @@ -901,7 +877,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 13, "sku": "", }, @@ -927,7 +902,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 13, "sku": "", }, @@ -953,7 +927,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 13, "sku": "", }, @@ -979,7 +952,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 13, "sku": "", }, @@ -1005,7 +977,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 19, "sku": "", }, @@ -1031,7 +1002,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 19, "sku": "", }, @@ -1057,7 +1027,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 19, "sku": "", }, @@ -1083,7 +1052,6 @@ Object { }, ], "priceOverride": "", - "product": "", "quantity": 19, "sku": "", }, diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.test.ts b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts index 1ecc83b45..e41c65229 100644 --- a/src/products/components/ProductVariantCreateDialog/createVariants.test.ts +++ b/src/products/components/ProductVariantCreateDialog/createVariants.test.ts @@ -62,7 +62,7 @@ describe("Creates variant matrix", () => { all: false, attribute: attribute.id, values: attribute.values.map((attributeValue, attributeValueIndex) => ({ - id: attributeValue, + slug: attributeValue, value: (price * (attributeValueIndex + 1)).toString() })) }, @@ -120,7 +120,7 @@ describe("Creates variant matrix", () => { all: false, attribute: attribute.id, values: attribute.values.map((attributeValue, attributeValueIndex) => ({ - id: attributeValue, + slug: attributeValue, value: (stock * (attributeValueIndex + 1)).toString() })) } @@ -166,7 +166,7 @@ describe("Creates variant matrix", () => { all: false, attribute: attribute.id, values: attribute.values.map((attributeValue, attributeValueIndex) => ({ - id: attributeValue, + slug: attributeValue, value: (price * (attributeValueIndex + 1)).toString() })) }, @@ -175,7 +175,7 @@ describe("Creates variant matrix", () => { all: false, attribute: attribute.id, values: attribute.values.map((attributeValue, attributeValueIndex) => ({ - id: attributeValue, + slug: attributeValue, value: (stock * (attributeValueIndex + 1)).toString() })) } diff --git a/src/products/components/ProductVariantCreateDialog/createVariants.ts b/src/products/components/ProductVariantCreateDialog/createVariants.ts index 4433aa5ef..6239832e2 100644 --- a/src/products/components/ProductVariantCreateDialog/createVariants.ts +++ b/src/products/components/ProductVariantCreateDialog/createVariants.ts @@ -1,4 +1,4 @@ -import { ProductVariantCreateInput } from "@saleor/types/globalTypes"; +import { ProductVariantBulkCreateInput } from "@saleor/types/globalTypes"; import { AllOrAttribute, Attribute, @@ -7,7 +7,7 @@ import { interface CreateVariantAttributeValueInput { attributeId: string; - attributeValueId: string; + attributeValueSlug: string; } type CreateVariantInput = CreateVariantAttributeValueInput[]; @@ -20,7 +20,7 @@ function getAttributeValuePriceOrStock( ); const attributeValue = priceOrStock.values.find( - attributeValue => attribute.attributeValueId === attributeValue.id + attributeValue => attribute.attributeValueSlug === attributeValue.slug ); return attributeValue.value; @@ -29,7 +29,7 @@ function getAttributeValuePriceOrStock( function createVariant( data: ProductVariantCreateFormData, attributes: CreateVariantInput -): ProductVariantCreateInput { +): ProductVariantBulkCreateInput { const priceOverride = data.price.all ? data.price.value : getAttributeValuePriceOrStock(attributes, data.price); @@ -43,10 +43,9 @@ function createVariant( return { attributes: attributes.map(attribute => ({ id: attribute.attributeId, - values: [attribute.attributeValueId] + values: [attribute.attributeValueSlug] })), priceOverride, - product: "", quantity, sku: "" }; @@ -56,11 +55,11 @@ function addAttributeToVariant( attribute: Attribute, variant: CreateVariantInput ): CreateVariantInput[] { - return attribute.values.map(attributeValueId => [ + return attribute.values.map(attributeValueSlug => [ ...variant, { attributeId: attribute.id, - attributeValueId + attributeValueSlug } ]); } @@ -91,7 +90,7 @@ export function createVariantFlatMatrixDimension( export function createVariants( data: ProductVariantCreateFormData -): ProductVariantCreateInput[] { +): ProductVariantBulkCreateInput[] { if ( (!data.price.all && !data.price.attribute) || (!data.stock.all && !data.stock.attribute) diff --git a/src/products/components/ProductVariantCreateDialog/fixtures.ts b/src/products/components/ProductVariantCreateDialog/fixtures.ts index 9601839f0..e53aa0aca 100644 --- a/src/products/components/ProductVariantCreateDialog/fixtures.ts +++ b/src/products/components/ProductVariantCreateDialog/fixtures.ts @@ -74,11 +74,11 @@ const price: AllOrAttribute = { value: "", values: [ { - id: thirdStep.attributes[1].values[0], + slug: thirdStep.attributes[1].values[0], value: "24.99" }, { - id: thirdStep.attributes[1].values[1], + slug: thirdStep.attributes[1].values[1], value: "26.99" } ] @@ -89,11 +89,11 @@ const stock: AllOrAttribute = { value: "", values: [ { - id: thirdStep.attributes[2].values[0], + slug: thirdStep.attributes[2].values[0], value: "50" }, { - id: thirdStep.attributes[2].values[1], + slug: thirdStep.attributes[2].values[1], value: "35" } ] diff --git a/src/products/components/ProductVariantCreateDialog/form.ts b/src/products/components/ProductVariantCreateDialog/form.ts index 157ad2c55..b6331224e 100644 --- a/src/products/components/ProductVariantCreateDialog/form.ts +++ b/src/products/components/ProductVariantCreateDialog/form.ts @@ -1,7 +1,7 @@ -import { ProductVariantCreateInput } from "../../../types/globalTypes"; +import { ProductVariantBulkCreateInput } from "../../../types/globalTypes"; export interface AttributeValue { - id: string; + slug: string; value: string; } export interface AllOrAttribute { @@ -18,7 +18,7 @@ export interface ProductVariantCreateFormData { attributes: Attribute[]; price: AllOrAttribute; stock: AllOrAttribute; - variants: ProductVariantCreateInput[]; + variants: ProductVariantBulkCreateInput[]; } export const initialForm: ProductVariantCreateFormData = { diff --git a/src/products/components/ProductVariantCreateDialog/reducer.ts b/src/products/components/ProductVariantCreateDialog/reducer.ts index 045685921..f1492ed22 100644 --- a/src/products/components/ProductVariantCreateDialog/reducer.ts +++ b/src/products/components/ProductVariantCreateDialog/reducer.ts @@ -108,20 +108,20 @@ function applyStockToAll( function changeAttributeValuePrice( state: ProductVariantCreateFormData, - attributeValueId: string, + attributeValueSlug: string, price: string ): ProductVariantCreateFormData { const index = state.price.values.findIndex( - value => value.id === attributeValueId + value => value.slug === attributeValueSlug ); if (index === -1) { - throw new Error(`Value with id ${attributeValueId} not found`); + throw new Error(`Value with id ${attributeValueSlug} not found`); } const values = updateAtIndex( { - id: attributeValueId, + slug: attributeValueSlug, value: price }, state.price.values, @@ -144,20 +144,20 @@ function changeAttributeValuePrice( function changeAttributeValueStock( state: ProductVariantCreateFormData, - attributeValueId: string, + attributeValueSlug: string, stock: string ): ProductVariantCreateFormData { const index = state.stock.values.findIndex( - value => value.id === attributeValueId + value => value.slug === attributeValueSlug ); if (index === -1) { - throw new Error(`Value with id ${attributeValueId} not found`); + throw new Error(`Value with id ${attributeValueSlug} not found`); } const values = updateAtIndex( { - id: attributeValueId, + slug: attributeValueSlug, value: stock }, state.stock.values, @@ -185,8 +185,8 @@ function changeApplyPriceToAttributeId( const attribute = state.attributes.find( attribute => attribute.id === attributeId ); - const values = attribute.values.map(id => ({ - id, + const values = attribute.values.map(slug => ({ + slug, value: "" })); const data = { @@ -211,8 +211,8 @@ function changeApplyStockToAttributeId( const attribute = state.attributes.find( attribute => attribute.id === attributeId ); - const values = attribute.values.map(id => ({ - id, + const values = attribute.values.map(slug => ({ + slug, value: "" })); diff --git a/src/products/components/ProductVariants/ProductVariants.tsx b/src/products/components/ProductVariants/ProductVariants.tsx index bf6332396..0de9f974d 100644 --- a/src/products/components/ProductVariants/ProductVariants.tsx +++ b/src/products/components/ProductVariants/ProductVariants.tsx @@ -69,6 +69,7 @@ interface ProductVariantsProps extends ListActions, WithStyles { fallbackPrice?: ProductVariant_costPrice; onRowClick: (id: string) => () => void; onVariantAdd?(); + onVariantsAdd?(); } const numberOfColumns = 5; @@ -81,6 +82,7 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })( fallbackPrice, onRowClick, onVariantAdd, + onVariantsAdd, isChecked, selected, toggle, @@ -98,7 +100,7 @@ export const ProductVariants = withStyles(styles, { name: "ProductVariants" })( description: "section header" })} toolbar={ - <> + hasVariants ? ( - + ) : ( + + ) } /> {!variants.length && ( diff --git a/src/products/containers/ProductUpdateOperations.tsx b/src/products/containers/ProductUpdateOperations.tsx index 3ed040bbc..73940d8e5 100644 --- a/src/products/containers/ProductUpdateOperations.tsx +++ b/src/products/containers/ProductUpdateOperations.tsx @@ -7,6 +7,7 @@ import { TypedProductImageCreateMutation, TypedProductImageDeleteMutation, TypedProductUpdateMutation, + TypedProductVariantBulkCreateMutation, TypedProductVariantBulkDeleteMutation, TypedSimpleProductUpdateMutation } from "../mutations"; @@ -25,6 +26,10 @@ import { ProductImageReorderVariables } from "../types/ProductImageReorder"; import { ProductUpdate, ProductUpdateVariables } from "../types/ProductUpdate"; +import { + ProductVariantBulkCreate, + ProductVariantBulkCreateVariables +} from "../types/ProductVariantBulkCreate"; import { ProductVariantBulkDelete, ProductVariantBulkDeleteVariables @@ -38,6 +43,10 @@ import ProductImagesReorderProvider from "./ProductImagesReorder"; interface ProductUpdateOperationsProps { product: ProductDetails_product; children: (props: { + bulkProductVariantCreate: PartialMutationProviderOutput< + ProductVariantBulkCreate, + ProductVariantBulkCreateVariables + >; bulkProductVariantDelete: PartialMutationProviderOutput< ProductVariantBulkDelete, ProductVariantBulkDeleteVariables @@ -67,6 +76,7 @@ interface ProductUpdateOperationsProps { SimpleProductUpdateVariables >; }) => React.ReactNode; + onBulkProductVariantCreate?: (data: ProductVariantBulkCreate) => void; onBulkProductVariantDelete?: (data: ProductVariantBulkDelete) => void; onDelete?: (data: ProductDelete) => void; onImageCreate?: (data: ProductImageCreate) => void; @@ -80,6 +90,7 @@ const ProductUpdateOperations: React.StatelessComponent< > = ({ product, children, + onBulkProductVariantCreate, onBulkProductVariantDelete, onDelete, onImageDelete, @@ -112,31 +123,40 @@ const ProductUpdateOperations: React.StatelessComponent< - {(...bulkProductVariantDelete) => - children({ - bulkProductVariantDelete: getMutationProviderData( - ...bulkProductVariantDelete - ), - createProductImage: getMutationProviderData( - ...createProductImage - ), - deleteProduct: getMutationProviderData( - ...deleteProduct - ), - deleteProductImage: getMutationProviderData( - ...deleteProductImage - ), - reorderProductImages: getMutationProviderData( - ...reorderProductImages - ), - updateProduct: getMutationProviderData( - ...updateProduct - ), - updateSimpleProduct: getMutationProviderData( - ...updateSimpleProduct - ) - }) - } + {(...bulkProductVariantDelete) => ( + + {(...bulkProductVariantCreate) => + children({ + bulkProductVariantCreate: getMutationProviderData( + ...bulkProductVariantCreate + ), + bulkProductVariantDelete: getMutationProviderData( + ...bulkProductVariantDelete + ), + createProductImage: getMutationProviderData( + ...createProductImage + ), + deleteProduct: getMutationProviderData( + ...deleteProduct + ), + deleteProductImage: getMutationProviderData( + ...deleteProductImage + ), + reorderProductImages: getMutationProviderData( + ...reorderProductImages + ), + updateProduct: getMutationProviderData( + ...updateProduct + ), + updateSimpleProduct: getMutationProviderData( + ...updateSimpleProduct + ) + }) + } + + )} )} diff --git a/src/products/mutations.ts b/src/products/mutations.ts index 563c09f84..c612d576d 100644 --- a/src/products/mutations.ts +++ b/src/products/mutations.ts @@ -45,6 +45,10 @@ import { productBulkPublish, productBulkPublishVariables } from "./types/productBulkPublish"; +import { + ProductVariantBulkCreate, + ProductVariantBulkCreateVariables +} from "./types/ProductVariantBulkCreate"; import { ProductVariantBulkDelete, ProductVariantBulkDeleteVariables @@ -440,6 +444,30 @@ export const TypedProductBulkPublishMutation = TypedMutation< productBulkPublishVariables >(productBulkPublishMutation); +export const ProductVariantBulkCreateMutation = gql` + mutation ProductVariantBulkCreate( + $id: ID! + $inputs: [ProductVariantBulkCreateInput]! + ) { + productVariantBulkCreate(product: $id, variants: $inputs) { + bulkProductErrors { + field + message + code + index + } + errors { + field + message + } + } + } +`; +export const TypedProductVariantBulkCreateMutation = TypedMutation< + ProductVariantBulkCreate, + ProductVariantBulkCreateVariables +>(ProductVariantBulkCreateMutation); + export const ProductVariantBulkDeleteMutation = gql` mutation ProductVariantBulkDelete($ids: [ID!]!) { productVariantBulkDelete(ids: $ids) { diff --git a/src/products/queries.ts b/src/products/queries.ts index efbdb9489..626b6ff3a 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -267,6 +267,7 @@ const productDetailsQuery = gql` values { id name + slug } } } diff --git a/src/products/types/ProductDetails.ts b/src/products/types/ProductDetails.ts index 803e66d8d..3ed43c47a 100644 --- a/src/products/types/ProductDetails.ts +++ b/src/products/types/ProductDetails.ts @@ -143,6 +143,7 @@ export interface ProductDetails_product_productType_variantAttributes_values { __typename: "AttributeValue"; id: string; name: string | null; + slug: string | null; } export interface ProductDetails_product_productType_variantAttributes { diff --git a/src/products/types/ProductVariantBulkCreate.ts b/src/products/types/ProductVariantBulkCreate.ts new file mode 100644 index 000000000..f0a0160ff --- /dev/null +++ b/src/products/types/ProductVariantBulkCreate.ts @@ -0,0 +1,38 @@ +/* tslint:disable */ +/* eslint-disable */ +// This file was automatically generated and should not be edited. + +import { ProductVariantBulkCreateInput, ProductErrorCode } from "./../../types/globalTypes"; + +// ==================================================== +// GraphQL mutation operation: ProductVariantBulkCreate +// ==================================================== + +export interface ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors { + __typename: "BulkProductError"; + field: string | null; + message: string | null; + code: ProductErrorCode | null; + index: number | null; +} + +export interface ProductVariantBulkCreate_productVariantBulkCreate_errors { + __typename: "Error"; + field: string | null; + message: string | null; +} + +export interface ProductVariantBulkCreate_productVariantBulkCreate { + __typename: "ProductVariantBulkCreate"; + bulkProductErrors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[] | null; + errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[] | null; +} + +export interface ProductVariantBulkCreate { + productVariantBulkCreate: ProductVariantBulkCreate_productVariantBulkCreate | null; +} + +export interface ProductVariantBulkCreateVariables { + id: string; + inputs: (ProductVariantBulkCreateInput | null)[]; +} diff --git a/src/products/urls.ts b/src/products/urls.ts index 5ebfe8d31..2f240496f 100644 --- a/src/products/urls.ts +++ b/src/products/urls.ts @@ -53,7 +53,7 @@ export const productListUrl = (params?: ProductListUrlQueryParams): string => export const productPath = (id: string) => urlJoin(productSection + id); export type ProductUrlDialog = "remove"; export type ProductUrlQueryParams = BulkAction & - Dialog<"remove" | "remove-variants">; + Dialog<"create-variants" | "remove" | "remove-variants">; export const productUrl = (id: string, params?: ProductUrlQueryParams) => productPath(encodeURIComponent(id)) + "?" + stringifyQs(params); diff --git a/src/products/views/ProductUpdate/ProductUpdate.tsx b/src/products/views/ProductUpdate/ProductUpdate.tsx index d86c58c25..c12d53bc3 100644 --- a/src/products/views/ProductUpdate/ProductUpdate.tsx +++ b/src/products/views/ProductUpdate/ProductUpdate.tsx @@ -10,7 +10,10 @@ import { WindowTitle } from "@saleor/components/WindowTitle"; import useBulkActions from "@saleor/hooks/useBulkActions"; import useNavigator from "@saleor/hooks/useNavigator"; import useNotifier from "@saleor/hooks/useNotifier"; +import useShop from "@saleor/hooks/useShop"; import { commonMessages } from "@saleor/intl"; +import ProductVariantCreateDialog from "@saleor/products/components/ProductVariantCreateDialog/ProductVariantCreateDialog"; +import { ProductVariantBulkCreate } from "@saleor/products/types/ProductVariantBulkCreate"; import { DEFAULT_INITIAL_SEARCH_DATA } from "../../../config"; import SearchCategories from "../../../containers/SearchCategories"; import SearchCollections from "../../../containers/SearchCollections"; @@ -54,6 +57,7 @@ export const ProductUpdate: React.StatelessComponent = ({ params.ids ); const intl = useIntl(); + const shop = useShop(); const openModal = (action: ProductUrlDialog) => navigate( @@ -115,6 +119,15 @@ export const ProductUpdate: React.StatelessComponent = ({ const handleVariantAdd = () => navigate(productVariantAddUrl(id)); + const handleBulkProductVariantCreate = ( + data: ProductVariantBulkCreate + ) => { + if (data.productVariantBulkCreate.errors.length === 0) { + navigate(productUrl(id), true); + refetch(); + } + }; + const handleBulkProductVariantDelete = ( data: ProductVariantBulkDelete ) => { @@ -125,10 +138,19 @@ export const ProductUpdate: React.StatelessComponent = ({ } }; + const handleVariantCreatorOpen = () => + navigate( + productUrl(id, { + ...params, + action: "create-variants" + }) + ); + const product = data ? data.product : undefined; return ( = ({ onUpdate={handleUpdate} > {({ + bulkProductVariantCreate, bulkProductVariantDelete, createProductImage, deleteProduct, @@ -245,6 +268,7 @@ export const ProductUpdate: React.StatelessComponent = ({ onImageReorder={handleImageReorder} onSubmit={handleSubmit} onVariantAdd={handleVariantAdd} + onVariantsAdd={handleVariantCreatorOpen} onVariantShow={variantId => () => navigate( productVariantEditUrl(product.id, variantId) @@ -328,6 +352,34 @@ export const ProductUpdate: React.StatelessComponent = ({ /> + + bulkProductVariantCreate.opts.data + .productVariantBulkCreate.bulkProductErrors, + [] + )} + open={params.action === "create-variants"} + attributes={maybe( + () => data.product.productType.variantAttributes, + [] + )} + currencySymbol={maybe(() => shop.defaultCurrency)} + onClose={() => + navigate( + productUrl(id, { + ...params, + action: undefined + }) + ) + } + onSubmit={inputs => + bulkProductVariantCreate.mutate({ + id, + inputs + }) + } + /> ); }} diff --git a/src/products/views/ProductVariantCreate.tsx b/src/products/views/ProductVariantCreate.tsx index b78ca66a9..a8ac30785 100644 --- a/src/products/views/ProductVariantCreate.tsx +++ b/src/products/views/ProductVariantCreate.tsx @@ -58,18 +58,20 @@ export const ProductVariant: React.StatelessComponent = ({ ) => variantCreate({ variables: { - attributes: formData.attributes - .filter(attribute => attribute.value !== "") - .map(attribute => ({ - id: attribute.id, - values: [attribute.value] - })), - costPrice: decimal(formData.costPrice), - priceOverride: decimal(formData.priceOverride), - product: productId, - quantity: formData.quantity || null, - sku: formData.sku, - trackInventory: true + input: { + attributes: formData.attributes + .filter(attribute => attribute.value !== "") + .map(attribute => ({ + id: attribute.id, + values: [attribute.value] + })), + costPrice: decimal(formData.costPrice), + priceOverride: decimal(formData.priceOverride), + product: productId, + quantity: formData.quantity || null, + sku: formData.sku, + trackInventory: true + } } }); const handleVariantClick = (id: string) => diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 8fbbd2cc5..3c6fd9f16 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -82051,6 +82051,1203 @@ exports[`Storyshots Views / Product types / Unassign multiple attributes default /> `; +exports[`Storyshots Views / Products / Create multiple variants / summary default 1`] = ` +
+
+
+
+
+
+ + Choose Attributes + +
+
+ + Select Values + +
+
+ + Prices and SKU + +
+
+ + Summary + +
+
+
+
+ You will create variants below +
+
+
+
+
+ Variant +
+
+ Price +
+
+ Inventory +
+
+ SKU +
+
+
+
+ + 100g + + + Arabica + + + Round + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 100g + + + Arabica + + + Polo + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 500g + + + Arabica + + + Round + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 500g + + + Arabica + + + Polo + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots Views / Products / Create multiple variants / summary errors 1`] = ` +
+
+
+
+
+
+ + Choose Attributes + +
+
+ + Select Values + +
+
+ + Prices and SKU + +
+
+ + Summary + +
+
+
+
+ You will create variants below +
+
+
+
+
+ Variant +
+
+ Price +
+
+ Inventory +
+
+ SKU +
+
+
+
+ + 100g + + + Arabica + + + Round + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 100g + + + Arabica + + + Polo + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 500g + + + Arabica + + + Round + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + 500g + + + Arabica + + + Polo + +
+
+
+
+ + + USD +
+
+
+
+
+
+ + +
+
+
+
+
+
+ + +
+

+ Duplicated SKU. +

+
+
+
+
+
+
+
+
+
+`; + exports[`Storyshots Views / Products / Create multiple variants choose attributes 1`] = `
-
-
-

- Robusta -

-
-
-
- -
- - -
-
-
-
-
@@ -83019,58 +84164,6 @@ exports[`Storyshots Views / Products / Create multiple variants prices and SKU 1
-
-
-
-

- Robusta -

-
-
-
- -
- - -
-
-
-
@@ -83154,7 +84247,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83175,7 +84268,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83196,7 +84289,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83217,7 +84310,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83250,7 +84343,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83271,7 +84364,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83304,7 +84397,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83325,7 +84418,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83346,7 +84439,7 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` > @@ -83365,616 +84458,6 @@ exports[`Storyshots Views / Products / Create multiple variants select values 1` `; -exports[`Storyshots Views / Products / Create multiple variants summary 1`] = ` -
-
-
-
-
-
- - Choose Attributes - -
-
- - Select Values - -
-
- - Prices and SKU - -
-
- - Summary - -
-
-
-
- You will create variants below -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Variant - - Inventory - - Price - - SKU -
- - 100g - - - Arabica - - - Round - - -
-
- - -
-
-
-
-
- - - USD -
-
-
-
-
- - -
-
-
- - 100g - - - Arabica - - - Polo - - -
-
- - -
-
-
-
-
- - - USD -
-
-
-
-
- - -
-
-
- - 500g - - - Arabica - - - Round - - -
-
- - -
-
-
-
-
- - - USD -
-
-
-
-
- - -
-
-
- - 500g - - - Arabica - - - Polo - - -
-
- - -
-
-
-
-
- - - USD -
-
-
-
-
- - -
-
-
-
-
-
-
-
-`; - exports[`Storyshots Views / Products / Create product When loading 1`] = `
undefined, onVariantAdd: () => undefined, onVariantShow: () => undefined, + onVariantsAdd: () => undefined, placeholderImage, product, saveButtonBarState: "default", diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index 5dff42bb4..0c23293c3 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -193,6 +193,20 @@ export enum PermissionEnum { MANAGE_WEBHOOKS = "MANAGE_WEBHOOKS", } +export enum ProductErrorCode { + ALREADY_EXISTS = "ALREADY_EXISTS", + ATTRIBUTE_ALREADY_ASSIGNED = "ATTRIBUTE_ALREADY_ASSIGNED", + ATTRIBUTE_CANNOT_BE_ASSIGNED = "ATTRIBUTE_CANNOT_BE_ASSIGNED", + ATTRIBUTE_VARIANTS_DISABLED = "ATTRIBUTE_VARIANTS_DISABLED", + GRAPHQL_ERROR = "GRAPHQL_ERROR", + INVALID = "INVALID", + NOT_FOUND = "NOT_FOUND", + NOT_PRODUCTS_IMAGE = "NOT_PRODUCTS_IMAGE", + REQUIRED = "REQUIRED", + UNIQUE = "UNIQUE", + VARIANT_NO_DIGITAL_CONTENT = "VARIANT_NO_DIGITAL_CONTENT", +} + export enum ProductOrderField { DATE = "DATE", MINIMAL_PRICE = "MINIMAL_PRICE", @@ -614,6 +628,16 @@ export interface ProductTypeInput { taxCode?: string | null; } +export interface ProductVariantBulkCreateInput { + attributes: (AttributeValueInput | null)[]; + costPrice?: any | null; + priceOverride?: any | null; + sku: string; + quantity?: number | null; + trackInventory?: boolean | null; + weight?: any | null; +} + export interface ProductVariantCreateInput { attributes: (AttributeValueInput | null)[]; costPrice?: any | null;