diff --git a/schema.graphql b/schema.graphql index 0a4ab2fe4..4edc7a797 100644 --- a/schema.graphql +++ b/schema.graphql @@ -493,6 +493,13 @@ type BulkProductError { index: Int } +type BulkStockError { + field: String + message: String + code: ProductErrorCode! + index: Int +} + input CatalogueInput { products: [ID] categories: [ID] @@ -2158,10 +2165,6 @@ type Mutation { deleteWarehouse(id: ID!): WarehouseDelete assignWarehouseShippingZone(id: ID!, shippingZoneIds: [ID!]!): WarehouseShippingZoneAssign unassignWarehouseShippingZone(id: ID!, shippingZoneIds: [ID!]!): WarehouseShippingZoneUnassign - createStock(input: StockInput!): StockCreate - updateStock(id: ID!, input: StockInput!): StockUpdate - deleteStock(id: ID!): StockDelete - bulkDeleteStock(ids: [ID]!): StockBulkDelete authorizationKeyAdd(input: AuthorizationKeyInput!, keyType: AuthorizationKeyType!): AuthorizationKeyAdd authorizationKeyDelete(keyType: AuthorizationKeyType!): AuthorizationKeyDelete staffNotificationRecipientCreate(input: StaffNotificationRecipientInput!): StaffNotificationRecipientCreate @@ -2253,6 +2256,9 @@ type Mutation { productVariantDelete(id: ID!): ProductVariantDelete productVariantBulkCreate(product: ID!, variants: [ProductVariantBulkCreateInput]!): ProductVariantBulkCreate productVariantBulkDelete(ids: [ID]!): ProductVariantBulkDelete + productVariantStocksCreate(stocks: [StockInput!]!, variantId: ID!): ProductVariantStocksCreate + productVariantStocksDelete(variantId: ID!, warehouseIds: [ID!]): ProductVariantStocksDelete + productVariantStocksUpdate(stocks: [StockInput!]!, variantId: ID!): ProductVariantStocksUpdate productVariantUpdate(id: ID!, input: ProductVariantInput!): ProductVariantUpdate productVariantTranslate(id: ID!, input: NameTranslationInput!, languageCode: LanguageCodeEnum!): ProductVariantTranslate productVariantUpdateMetadata(id: ID!, input: MetaInput!): ProductVariantUpdateMeta @deprecated(reason: "Will be removed in Saleor 2.11. Use the `UpdateMetadata` mutation instead.") @@ -2444,8 +2450,8 @@ type Order implements Node & ObjectWithMetadata { voucher: Voucher giftCards: [GiftCard] discount: Money - discountName: String! - translatedDiscountName: String! + discountName: String + translatedDiscountName: String displayGrossPrices: Boolean! customerNote: String! weight: Weight @@ -2489,7 +2495,7 @@ type OrderAddNote { } input OrderAddNoteInput { - message: String + message: String! } type OrderBulkCancel { @@ -3218,6 +3224,7 @@ input ProductCreateInput { quantity: Int trackInventory: Boolean productType: ID! + stocks: [StockInput!] } type ProductDelete { @@ -3255,6 +3262,7 @@ input ProductFilterInput { attributes: [AttributeInput] stockAvailability: StockAvailability productType: ID + stocks: ProductStockFilterInput search: String minimalPrice: PriceRangeInput productTypes: [ID] @@ -3355,6 +3363,11 @@ type ProductPricingInfo { priceRangeLocalCurrency: TaxedMoneyRange } +input ProductStockFilterInput { + warehouseIds: [ID!] + quantity: IntRangeInput +} + type ProductTranslatableContent implements Node { id: ID! seoTitle: String @@ -3549,7 +3562,7 @@ type ProductVariant implements Node & ObjectWithMetadata { images: [ProductImage] translation(languageCode: LanguageCodeEnum!): ProductVariantTranslation digitalContent: DigitalContent - stock(country: String): [Stock] + stocks(countryCode: CountryCode): [Stock] } type ProductVariantBulkCreate { @@ -3613,6 +3626,7 @@ input ProductVariantCreateInput { trackInventory: Boolean weight: WeightScalar product: ID! + stocks: [StockInput!] } type ProductVariantDelete { @@ -3631,6 +3645,24 @@ input ProductVariantInput { weight: WeightScalar } +type ProductVariantStocksCreate { + errors: [Error!]! + productVariant: ProductVariant + bulkStockErrors: [BulkStockError!]! +} + +type ProductVariantStocksDelete { + errors: [Error!]! + productVariant: ProductVariant + stockErrors: [StockError!]! +} + +type ProductVariantStocksUpdate { + errors: [Error!]! + productVariant: ProductVariant + bulkStockErrors: [BulkStockError!]! +} + type ProductVariantTranslatableContent implements Node { id: ID! name: String! @@ -4341,12 +4373,6 @@ enum StockAvailability { OUT_OF_STOCK } -type StockBulkDelete { - errors: [Error!]! - count: Int! - stockError: [StockError!]! -} - type StockCountableConnection { pageInfo: PageInfo! edges: [StockCountableEdge!]! @@ -4358,18 +4384,13 @@ type StockCountableEdge { cursor: String! } -type StockCreate { - errors: [Error!]! - stockErrors: [StockError!]! - stock: Stock +type StockError { + field: String + message: String + code: StockErrorCode! } -type StockDelete { - errors: [Error!]! - stock: Stock -} - -enum StockErorrCode { +enum StockErrorCode { ALREADY_EXISTS GRAPHQL_ERROR INVALID @@ -4378,12 +4399,6 @@ enum StockErorrCode { UNIQUE } -type StockError { - field: String - message: String - code: StockErorrCode! -} - input StockFilterInput { quantity: Float quantityAllocated: Float @@ -4391,17 +4406,10 @@ input StockFilterInput { } input StockInput { - productVariant: ID! warehouse: ID! quantity: Int } -type StockUpdate { - errors: [Error!]! - stockError: [StockError!]! - stock: Stock -} - enum TaxRateType { ACCOMMODATION ADMISSION_TO_CULTURAL_EVENTS diff --git a/src/products/components/ProductStock/ProductStock.tsx b/src/products/components/ProductStock/ProductStock.tsx index c76609af4..6fce4b609 100644 --- a/src/products/components/ProductStock/ProductStock.tsx +++ b/src/products/components/ProductStock/ProductStock.tsx @@ -8,7 +8,6 @@ import { useIntl } from "react-intl"; import CardTitle from "@saleor/components/CardTitle"; import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; import { getFormErrors, getProductErrorMessage } from "@saleor/utils/errors"; -import { maybe } from "../../../misc"; import { ProductDetails_product } from "../../types/ProductDetails"; const useStyles = makeStyles( diff --git a/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx b/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx index d80aece07..b32a7ef2d 100644 --- a/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx +++ b/src/products/components/ProductVariantAttributes/ProductVariantAttributes.tsx @@ -13,7 +13,7 @@ import SingleAutocompleteSelectField, { import Skeleton from "@saleor/components/Skeleton"; import { FormsetAtomicData, FormsetChange } from "@saleor/hooks/useFormset"; import { commonMessages } from "@saleor/intl"; -import { VariantCreate_productVariantCreate_productErrors } from "@saleor/products/types/VariantCreate"; +import { VariantCreate_productVariantCreate_errors } from "@saleor/products/types/VariantCreate"; import { ProductErrorCode } from "@saleor/types/globalTypes"; import { ProductVariant_attributes_attribute_values } from "../../types/ProductVariant"; @@ -28,7 +28,7 @@ export type VariantAttributeInput = FormsetAtomicData< interface ProductVariantAttributesProps { attributes: VariantAttributeInput[]; disabled: boolean; - errors: VariantCreate_productVariantCreate_productErrors[]; + errors: VariantCreate_productVariantCreate_errors[]; onChange: FormsetChange; } diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx index 2caf5fba2..b735d3f0f 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreate.stories.tsx @@ -4,7 +4,7 @@ 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 { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate"; import { ProductErrorCode } from "@saleor/types/globalTypes"; import Decorator from "../../../storybook/Decorator"; import { createVariants } from "./createVariants"; @@ -43,13 +43,12 @@ const dataAttributes = selectedAttributes.map(attribute => ({ .filter((_, valueIndex) => valueIndex % 2 !== 1) })); -const errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[] = [ +const errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[] = [ { __typename: "BulkProductError", code: ProductErrorCode.UNIQUE, field: "sku", - index: 3, - message: "Duplicated SKU." + index: 3 } ]; diff --git a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx index 398b8cf3e..02bc0ae66 100644 --- a/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx +++ b/src/products/components/ProductVariantCreateDialog/ProductVariantCreateContent.tsx @@ -2,7 +2,7 @@ import { makeStyles } from "@material-ui/core/styles"; import React from "react"; import { ProductDetails_product_productType_variantAttributes } from "@saleor/products/types/ProductDetails"; -import { ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors } from "@saleor/products/types/ProductVariantBulkCreate"; +import { ProductVariantBulkCreate_productVariantBulkCreate_errors } from "@saleor/products/types/ProductVariantBulkCreate"; import { isSelected } from "@saleor/utils/lists"; import { ProductVariantCreateFormData } from "./form"; import ProductVariantCreatePrices from "./ProductVariantCreatePrices"; @@ -33,14 +33,12 @@ export interface ProductVariantCreateContentProps { currencySymbol: string; data: ProductVariantCreateFormData; dispatchFormDataAction: React.Dispatch; - errors: ProductVariantBulkCreate_productVariantBulkCreate_bulkProductErrors[]; + errors: ProductVariantBulkCreate_productVariantBulkCreate_errors[]; step: ProductVariantCreateStep; onStepClick: (step: ProductVariantCreateStep) => void; } -const ProductVariantCreateContent: React.FC< - ProductVariantCreateContentProps -> = props => { +const ProductVariantCreateContent: React.FC = props => { const { attributes, currencySymbol, diff --git a/src/products/components/ProductVariantPage/ProductVariantPage.tsx b/src/products/components/ProductVariantPage/ProductVariantPage.tsx index 8f7ca76ed..25b91284b 100644 --- a/src/products/components/ProductVariantPage/ProductVariantPage.tsx +++ b/src/products/components/ProductVariantPage/ProductVariantPage.tsx @@ -12,7 +12,7 @@ import useFormset, { FormsetChange, FormsetData } from "@saleor/hooks/useFormset"; -import { VariantUpdate_productVariantUpdate_productErrors } from "@saleor/products/types/VariantUpdate"; +import { VariantUpdate_productVariantUpdate_errors } from "@saleor/products/types/VariantUpdate"; import { getAttributeInputFromVariant } from "@saleor/products/utils/data"; import { maybe } from "../../../misc"; import { ProductVariant } from "../../types/ProductVariant"; @@ -39,7 +39,7 @@ export interface ProductVariantPageSubmitData interface ProductVariantPageProps { variant?: ProductVariant; - errors: VariantUpdate_productVariantUpdate_productErrors[]; + errors: VariantUpdate_productVariantUpdate_errors[]; saveButtonBarState: ConfirmButtonTransitionState; loading?: boolean; placeholderImage?: string; diff --git a/src/products/views/ProductVariant.tsx b/src/products/views/ProductVariant.tsx index 4163cb982..6a68c2d49 100644 --- a/src/products/views/ProductVariant.tsx +++ b/src/products/views/ProductVariant.tsx @@ -16,7 +16,7 @@ import ProductVariantOperations from "../containers/ProductVariantOperations"; import { TypedProductVariantQuery } from "../queries"; import { VariantUpdate, - VariantUpdate_productVariantUpdate_productErrors + VariantUpdate_productVariantUpdate_errors } from "../types/VariantUpdate"; import { productUrl, @@ -40,7 +40,7 @@ export const ProductVariant: React.FC = ({ const notify = useNotifier(); const intl = useIntl(); const [errors, setErrors] = useState< - VariantUpdate_productVariantUpdate_productErrors[] + VariantUpdate_productVariantUpdate_errors[] >([]); useEffect(() => { setErrors([]); @@ -66,10 +66,10 @@ export const ProductVariant: React.FC = ({ navigate(productUrl(productId)); }; const handleUpdate = (data: VariantUpdate) => { - if (!data.productVariantUpdate.productErrors.length) { + if (data.productVariantUpdate.errors.length === 0) { notify({ text: intl.formatMessage(commonMessages.savedChanges) }); } else { - setErrors(data.productVariantUpdate.productErrors); + setErrors(data.productVariantUpdate.errors); } }; diff --git a/src/storybook/misc.ts b/src/storybook/misc.ts index b1f96fe36..6a28d4117 100644 --- a/src/storybook/misc.ts +++ b/src/storybook/misc.ts @@ -1,5 +1,3 @@ -import { ProductErrorFragment } from "@saleor/attributes/types/ProductErrorFragment"; - export function formError( field: string, opts?: Partial> diff --git a/src/storybook/stories/attributes/AttributePage.tsx b/src/storybook/stories/attributes/AttributePage.tsx index e2d8f5b68..68ddf3930 100644 --- a/src/storybook/stories/attributes/AttributePage.tsx +++ b/src/storybook/stories/attributes/AttributePage.tsx @@ -5,8 +5,10 @@ import AttributePage, { AttributePageProps } from "@saleor/attributes/components/AttributePage"; import { attribute } from "@saleor/attributes/fixtures"; -import { formError } from "@saleor/storybook/misc"; -import { AttributeInputTypeEnum } from "@saleor/types/globalTypes"; +import { + AttributeInputTypeEnum, + ProductErrorCode +} from "@saleor/types/globalTypes"; import Decorator from "../../Decorator"; const props: AttributePageProps = { @@ -39,7 +41,11 @@ storiesOf("Views / Attributes / Attribute details", module) .add("form errors", () => ( ({ + __typename: "ProductError", + code: ProductErrorCode.INVALID, + field + }))} /> )) .add("multiple select input", () => ( diff --git a/src/storybook/stories/products/ProductCreatePage.tsx b/src/storybook/stories/products/ProductCreatePage.tsx index 1c968c390..306c5eca9 100644 --- a/src/storybook/stories/products/ProductCreatePage.tsx +++ b/src/storybook/stories/products/ProductCreatePage.tsx @@ -2,13 +2,13 @@ import { storiesOf } from "@storybook/react"; import React from "react"; import { fetchMoreProps } from "@saleor/fixtures"; +import { ProductErrorCode } from "@saleor/types/globalTypes"; import ProductCreatePage, { ProductCreatePageSubmitData } from "../../../products/components/ProductCreatePage"; import { product as productFixture } from "../../../products/fixtures"; import { productTypes } from "../../../productTypes/fixtures"; import Decorator from "../../Decorator"; -import { formError } from "../../misc"; const product = productFixture(""); @@ -60,7 +60,11 @@ storiesOf("Views / Products / Create product", module) disabled={false} errors={(["name", "productType", "category", "sku"] as Array< keyof ProductCreatePageSubmitData - >).map(formError)} + >).map(field => ({ + __typename: "ProductError", + code: ProductErrorCode.INVALID, + field + }))} header="Add product" collections={product.collections} fetchCategories={() => undefined} diff --git a/src/types/globalTypes.ts b/src/types/globalTypes.ts index cdee80d75..67e140b83 100644 --- a/src/types/globalTypes.ts +++ b/src/types/globalTypes.ts @@ -929,7 +929,7 @@ export interface NameTranslationInput { } export interface OrderAddNoteInput { - message?: string | null; + message: string; } export interface OrderDraftFilterInput { @@ -1026,6 +1026,7 @@ export interface ProductFilterInput { attributes?: (AttributeInput | null)[] | null; stockAvailability?: StockAvailability | null; productType?: string | null; + stocks?: ProductStockFilterInput | null; search?: string | null; minimalPrice?: PriceRangeInput | null; productTypes?: (string | null)[] | null; @@ -1037,6 +1038,11 @@ export interface ProductOrder { field?: ProductOrderField | null; } +export interface ProductStockFilterInput { + warehouseIds?: string[] | null; + quantity?: IntRangeInput | null; +} + export interface ProductTypeFilterInput { search?: string | null; configurable?: ProductTypeConfigurable | null; @@ -1080,6 +1086,7 @@ export interface ProductVariantCreateInput { trackInventory?: boolean | null; weight?: any | null; product: string; + stocks?: StockInput[] | null; } export interface ProductVariantInput { @@ -1208,6 +1215,11 @@ export interface StaffUserInput { search?: string | null; } +export interface StockInput { + warehouse: string; + quantity?: number | null; +} + export interface TranslationInput { seoTitle?: string | null; seoDescription?: string | null;