Product variants bulk update (#3032)
This commit is contained in:
parent
63cf983ee8
commit
b040a0deb8
32 changed files with 5634 additions and 820 deletions
|
@ -163,7 +163,7 @@ describe("As an admin I should be able to create variant", () => {
|
||||||
});
|
});
|
||||||
getProductVariants(createdProduct.id, defaultChannel.slug);
|
getProductVariants(createdProduct.id, defaultChannel.slug);
|
||||||
})
|
})
|
||||||
.then(([firstVariant, secondVariant]) => {
|
.then(([secondVariant, firstVariant]) => {
|
||||||
expect(firstVariant).to.have.property("price", variants[0].price);
|
expect(firstVariant).to.have.property("price", variants[0].price);
|
||||||
expect(firstVariant).to.have.property("name", "value");
|
expect(firstVariant).to.have.property("name", "value");
|
||||||
expect(firstVariant).to.have.property("currency", "USD");
|
expect(firstVariant).to.have.property("currency", "USD");
|
||||||
|
|
2749
introspection.json
2749
introspection.json
File diff suppressed because it is too large
Load diff
968
schema.graphql
968
schema.graphql
File diff suppressed because it is too large
Load diff
|
@ -117,6 +117,17 @@ export const bulkProductErrorFragment = gql`
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const productVariantBulkError = gql`
|
||||||
|
fragment ProductVariantBulkError on ProductVariantBulkError {
|
||||||
|
field
|
||||||
|
code
|
||||||
|
message
|
||||||
|
attributes
|
||||||
|
values
|
||||||
|
warehouses
|
||||||
|
channels
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const bulkStockErrorFragment = gql`
|
export const bulkStockErrorFragment = gql`
|
||||||
fragment BulkStockError on BulkStockError {
|
fragment BulkStockError on BulkStockError {
|
||||||
code
|
code
|
||||||
|
|
|
@ -79,6 +79,7 @@ export const channelListingProductFragment = gql`
|
||||||
|
|
||||||
export const channelListingProductVariantFragment = gql`
|
export const channelListingProductVariantFragment = gql`
|
||||||
fragment ChannelListingProductVariant on ProductVariantChannelListing {
|
fragment ChannelListingProductVariant on ProductVariantChannelListing {
|
||||||
|
id
|
||||||
channel {
|
channel {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
|
|
@ -98,6 +98,7 @@
|
||||||
"ProductVariantDeleted",
|
"ProductVariantDeleted",
|
||||||
"ProductVariantMetadataUpdated",
|
"ProductVariantMetadataUpdated",
|
||||||
"ProductVariantOutOfStock",
|
"ProductVariantOutOfStock",
|
||||||
|
"ProductVariantStockUpdated",
|
||||||
"ProductVariantUpdated",
|
"ProductVariantUpdated",
|
||||||
"SaleCreated",
|
"SaleCreated",
|
||||||
"SaleDeleted",
|
"SaleDeleted",
|
||||||
|
@ -224,6 +225,7 @@
|
||||||
"Webhook"
|
"Webhook"
|
||||||
],
|
],
|
||||||
"ObjectWithMetadata": [
|
"ObjectWithMetadata": [
|
||||||
|
"Address",
|
||||||
"App",
|
"App",
|
||||||
"Attribute",
|
"Attribute",
|
||||||
"Category",
|
"Category",
|
||||||
|
|
|
@ -682,6 +682,17 @@ export const BulkProductErrorFragmentDoc = gql`
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const ProductVariantBulkErrorFragmentDoc = gql`
|
||||||
|
fragment ProductVariantBulkError on ProductVariantBulkError {
|
||||||
|
field
|
||||||
|
code
|
||||||
|
message
|
||||||
|
attributes
|
||||||
|
values
|
||||||
|
warehouses
|
||||||
|
channels
|
||||||
|
}
|
||||||
|
`;
|
||||||
export const BulkStockErrorFragmentDoc = gql`
|
export const BulkStockErrorFragmentDoc = gql`
|
||||||
fragment BulkStockError on BulkStockError {
|
fragment BulkStockError on BulkStockError {
|
||||||
code
|
code
|
||||||
|
@ -2031,6 +2042,7 @@ export const PreorderFragmentDoc = gql`
|
||||||
`;
|
`;
|
||||||
export const ChannelListingProductVariantFragmentDoc = gql`
|
export const ChannelListingProductVariantFragmentDoc = gql`
|
||||||
fragment ChannelListingProductVariant on ProductVariantChannelListing {
|
fragment ChannelListingProductVariant on ProductVariantChannelListing {
|
||||||
|
id
|
||||||
channel {
|
channel {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
@ -11765,121 +11777,6 @@ export function useVariantDeleteMutation(baseOptions?: ApolloReactHooks.Mutation
|
||||||
export type VariantDeleteMutationHookResult = ReturnType<typeof useVariantDeleteMutation>;
|
export type VariantDeleteMutationHookResult = ReturnType<typeof useVariantDeleteMutation>;
|
||||||
export type VariantDeleteMutationResult = Apollo.MutationResult<Types.VariantDeleteMutation>;
|
export type VariantDeleteMutationResult = Apollo.MutationResult<Types.VariantDeleteMutation>;
|
||||||
export type VariantDeleteMutationOptions = Apollo.BaseMutationOptions<Types.VariantDeleteMutation, Types.VariantDeleteMutationVariables>;
|
export type VariantDeleteMutationOptions = Apollo.BaseMutationOptions<Types.VariantDeleteMutation, Types.VariantDeleteMutationVariables>;
|
||||||
export const VariantDatagridUpdateDocument = gql`
|
|
||||||
mutation VariantDatagridUpdate($id: ID!, $input: ProductVariantInput!) {
|
|
||||||
productVariantUpdate(id: $id, input: $input) {
|
|
||||||
errors {
|
|
||||||
...ProductErrorWithAttributes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${ProductErrorWithAttributesFragmentDoc}`;
|
|
||||||
export type VariantDatagridUpdateMutationFn = Apollo.MutationFunction<Types.VariantDatagridUpdateMutation, Types.VariantDatagridUpdateMutationVariables>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useVariantDatagridUpdateMutation__
|
|
||||||
*
|
|
||||||
* To run a mutation, you first call `useVariantDatagridUpdateMutation` within a React component and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useVariantDatagridUpdateMutation` returns a tuple that includes:
|
|
||||||
* - A mutate function that you can call at any time to execute the mutation
|
|
||||||
* - An object with fields that represent the current status of the mutation's execution
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const [variantDatagridUpdateMutation, { data, loading, error }] = useVariantDatagridUpdateMutation({
|
|
||||||
* variables: {
|
|
||||||
* id: // value for 'id'
|
|
||||||
* input: // value for 'input'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useVariantDatagridUpdateMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<Types.VariantDatagridUpdateMutation, Types.VariantDatagridUpdateMutationVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return ApolloReactHooks.useMutation<Types.VariantDatagridUpdateMutation, Types.VariantDatagridUpdateMutationVariables>(VariantDatagridUpdateDocument, options);
|
|
||||||
}
|
|
||||||
export type VariantDatagridUpdateMutationHookResult = ReturnType<typeof useVariantDatagridUpdateMutation>;
|
|
||||||
export type VariantDatagridUpdateMutationResult = Apollo.MutationResult<Types.VariantDatagridUpdateMutation>;
|
|
||||||
export type VariantDatagridUpdateMutationOptions = Apollo.BaseMutationOptions<Types.VariantDatagridUpdateMutation, Types.VariantDatagridUpdateMutationVariables>;
|
|
||||||
export const VariantDatagridStockUpdateDocument = gql`
|
|
||||||
mutation VariantDatagridStockUpdate($stocks: [StockInput!]!, $removeStocks: [ID!]!, $id: ID!) {
|
|
||||||
productVariantStocksDelete(warehouseIds: $removeStocks, variantId: $id) {
|
|
||||||
errors {
|
|
||||||
...ProductVariantStocksDeleteError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
productVariantStocksUpdate(stocks: $stocks, variantId: $id) {
|
|
||||||
errors {
|
|
||||||
...BulkStockError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${ProductVariantStocksDeleteErrorFragmentDoc}
|
|
||||||
${BulkStockErrorFragmentDoc}`;
|
|
||||||
export type VariantDatagridStockUpdateMutationFn = Apollo.MutationFunction<Types.VariantDatagridStockUpdateMutation, Types.VariantDatagridStockUpdateMutationVariables>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useVariantDatagridStockUpdateMutation__
|
|
||||||
*
|
|
||||||
* To run a mutation, you first call `useVariantDatagridStockUpdateMutation` within a React component and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useVariantDatagridStockUpdateMutation` returns a tuple that includes:
|
|
||||||
* - A mutate function that you can call at any time to execute the mutation
|
|
||||||
* - An object with fields that represent the current status of the mutation's execution
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const [variantDatagridStockUpdateMutation, { data, loading, error }] = useVariantDatagridStockUpdateMutation({
|
|
||||||
* variables: {
|
|
||||||
* stocks: // value for 'stocks'
|
|
||||||
* removeStocks: // value for 'removeStocks'
|
|
||||||
* id: // value for 'id'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useVariantDatagridStockUpdateMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<Types.VariantDatagridStockUpdateMutation, Types.VariantDatagridStockUpdateMutationVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return ApolloReactHooks.useMutation<Types.VariantDatagridStockUpdateMutation, Types.VariantDatagridStockUpdateMutationVariables>(VariantDatagridStockUpdateDocument, options);
|
|
||||||
}
|
|
||||||
export type VariantDatagridStockUpdateMutationHookResult = ReturnType<typeof useVariantDatagridStockUpdateMutation>;
|
|
||||||
export type VariantDatagridStockUpdateMutationResult = Apollo.MutationResult<Types.VariantDatagridStockUpdateMutation>;
|
|
||||||
export type VariantDatagridStockUpdateMutationOptions = Apollo.BaseMutationOptions<Types.VariantDatagridStockUpdateMutation, Types.VariantDatagridStockUpdateMutationVariables>;
|
|
||||||
export const VariantDatagridChannelListingUpdateDocument = gql`
|
|
||||||
mutation VariantDatagridChannelListingUpdate($id: ID!, $input: [ProductVariantChannelListingAddInput!]!) {
|
|
||||||
productVariantChannelListingUpdate(id: $id, input: $input) {
|
|
||||||
errors {
|
|
||||||
...ProductChannelListingError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${ProductChannelListingErrorFragmentDoc}`;
|
|
||||||
export type VariantDatagridChannelListingUpdateMutationFn = Apollo.MutationFunction<Types.VariantDatagridChannelListingUpdateMutation, Types.VariantDatagridChannelListingUpdateMutationVariables>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useVariantDatagridChannelListingUpdateMutation__
|
|
||||||
*
|
|
||||||
* To run a mutation, you first call `useVariantDatagridChannelListingUpdateMutation` within a React component and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useVariantDatagridChannelListingUpdateMutation` returns a tuple that includes:
|
|
||||||
* - A mutate function that you can call at any time to execute the mutation
|
|
||||||
* - An object with fields that represent the current status of the mutation's execution
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const [variantDatagridChannelListingUpdateMutation, { data, loading, error }] = useVariantDatagridChannelListingUpdateMutation({
|
|
||||||
* variables: {
|
|
||||||
* id: // value for 'id'
|
|
||||||
* input: // value for 'input'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useVariantDatagridChannelListingUpdateMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<Types.VariantDatagridChannelListingUpdateMutation, Types.VariantDatagridChannelListingUpdateMutationVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return ApolloReactHooks.useMutation<Types.VariantDatagridChannelListingUpdateMutation, Types.VariantDatagridChannelListingUpdateMutationVariables>(VariantDatagridChannelListingUpdateDocument, options);
|
|
||||||
}
|
|
||||||
export type VariantDatagridChannelListingUpdateMutationHookResult = ReturnType<typeof useVariantDatagridChannelListingUpdateMutation>;
|
|
||||||
export type VariantDatagridChannelListingUpdateMutationResult = Apollo.MutationResult<Types.VariantDatagridChannelListingUpdateMutation>;
|
|
||||||
export type VariantDatagridChannelListingUpdateMutationOptions = Apollo.BaseMutationOptions<Types.VariantDatagridChannelListingUpdateMutation, Types.VariantDatagridChannelListingUpdateMutationVariables>;
|
|
||||||
export const VariantUpdateDocument = gql`
|
export const VariantUpdateDocument = gql`
|
||||||
mutation VariantUpdate($addStocks: [StockInput!]!, $removeStocks: [ID!]!, $id: ID!, $attributes: [AttributeValueInput!], $sku: String, $quantityLimitPerCustomer: Int, $trackInventory: Boolean!, $stocks: [StockInput!]!, $preorder: PreorderSettingsInput, $weight: WeightScalar, $firstValues: Int, $afterValues: String, $lastValues: Int, $beforeValues: String, $name: String!) {
|
mutation VariantUpdate($addStocks: [StockInput!]!, $removeStocks: [ID!]!, $id: ID!, $attributes: [AttributeValueInput!], $sku: String, $quantityLimitPerCustomer: Int, $trackInventory: Boolean!, $stocks: [StockInput!]!, $preorder: PreorderSettingsInput, $weight: WeightScalar, $firstValues: Int, $afterValues: String, $lastValues: Int, $beforeValues: String, $name: String!) {
|
||||||
productVariantStocksDelete(warehouseIds: $removeStocks, variantId: $id) {
|
productVariantStocksDelete(warehouseIds: $removeStocks, variantId: $id) {
|
||||||
|
@ -12528,6 +12425,52 @@ export function useProductVariantPreorderDeactivateMutation(baseOptions?: Apollo
|
||||||
export type ProductVariantPreorderDeactivateMutationHookResult = ReturnType<typeof useProductVariantPreorderDeactivateMutation>;
|
export type ProductVariantPreorderDeactivateMutationHookResult = ReturnType<typeof useProductVariantPreorderDeactivateMutation>;
|
||||||
export type ProductVariantPreorderDeactivateMutationResult = Apollo.MutationResult<Types.ProductVariantPreorderDeactivateMutation>;
|
export type ProductVariantPreorderDeactivateMutationResult = Apollo.MutationResult<Types.ProductVariantPreorderDeactivateMutation>;
|
||||||
export type ProductVariantPreorderDeactivateMutationOptions = Apollo.BaseMutationOptions<Types.ProductVariantPreorderDeactivateMutation, Types.ProductVariantPreorderDeactivateMutationVariables>;
|
export type ProductVariantPreorderDeactivateMutationOptions = Apollo.BaseMutationOptions<Types.ProductVariantPreorderDeactivateMutation, Types.ProductVariantPreorderDeactivateMutationVariables>;
|
||||||
|
export const ProductVariantBulkUpdateDocument = gql`
|
||||||
|
mutation ProductVariantBulkUpdate($product: ID!, $input: [ProductVariantBulkUpdateInput!]!, $errorPolicy: ErrorPolicyEnum) {
|
||||||
|
productVariantBulkUpdate(
|
||||||
|
errorPolicy: $errorPolicy
|
||||||
|
product: $product
|
||||||
|
variants: $input
|
||||||
|
) {
|
||||||
|
errors {
|
||||||
|
...ProductVariantBulkError
|
||||||
|
}
|
||||||
|
results {
|
||||||
|
errors {
|
||||||
|
...ProductVariantBulkError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${ProductVariantBulkErrorFragmentDoc}`;
|
||||||
|
export type ProductVariantBulkUpdateMutationFn = Apollo.MutationFunction<Types.ProductVariantBulkUpdateMutation, Types.ProductVariantBulkUpdateMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useProductVariantBulkUpdateMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useProductVariantBulkUpdateMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useProductVariantBulkUpdateMutation` returns a tuple that includes:
|
||||||
|
* - A mutate function that you can call at any time to execute the mutation
|
||||||
|
* - An object with fields that represent the current status of the mutation's execution
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [productVariantBulkUpdateMutation, { data, loading, error }] = useProductVariantBulkUpdateMutation({
|
||||||
|
* variables: {
|
||||||
|
* product: // value for 'product'
|
||||||
|
* input: // value for 'input'
|
||||||
|
* errorPolicy: // value for 'errorPolicy'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useProductVariantBulkUpdateMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<Types.ProductVariantBulkUpdateMutation, Types.ProductVariantBulkUpdateMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return ApolloReactHooks.useMutation<Types.ProductVariantBulkUpdateMutation, Types.ProductVariantBulkUpdateMutationVariables>(ProductVariantBulkUpdateDocument, options);
|
||||||
|
}
|
||||||
|
export type ProductVariantBulkUpdateMutationHookResult = ReturnType<typeof useProductVariantBulkUpdateMutation>;
|
||||||
|
export type ProductVariantBulkUpdateMutationResult = Apollo.MutationResult<Types.ProductVariantBulkUpdateMutation>;
|
||||||
|
export type ProductVariantBulkUpdateMutationOptions = Apollo.BaseMutationOptions<Types.ProductVariantBulkUpdateMutation, Types.ProductVariantBulkUpdateMutationVariables>;
|
||||||
export const InitialProductFilterAttributesDocument = gql`
|
export const InitialProductFilterAttributesDocument = gql`
|
||||||
query InitialProductFilterAttributes {
|
query InitialProductFilterAttributes {
|
||||||
attributes(first: 100, filter: {type: PRODUCT_TYPE}) {
|
attributes(first: 100, filter: {type: PRODUCT_TYPE}) {
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -277,7 +277,11 @@ export const getMutationErrors = <
|
||||||
return [] as TErrors;
|
return [] as TErrors;
|
||||||
}
|
}
|
||||||
return Object.values(result.data).reduce(
|
return Object.values(result.data).reduce(
|
||||||
(acc: TErrors[], mut: TData) => [...acc, ...(mut.errors || [])],
|
(acc: TErrors[], mut: TData) => [
|
||||||
|
...acc,
|
||||||
|
...(mut.errors || []),
|
||||||
|
...(mut?.results?.flatMap(res => res.errors) || []),
|
||||||
|
],
|
||||||
[] as TErrors[],
|
[] as TErrors[],
|
||||||
) as TErrors;
|
) as TErrors;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,7 +2,10 @@ import placeholderImage from "@assets/images/placeholder255x255.png";
|
||||||
import { channelsList } from "@dashboard/channels/fixtures";
|
import { channelsList } from "@dashboard/channels/fixtures";
|
||||||
import { collections } from "@dashboard/collections/fixtures";
|
import { collections } from "@dashboard/collections/fixtures";
|
||||||
import { fetchMoreProps, limits, limitsReached } from "@dashboard/fixtures";
|
import { fetchMoreProps, limits, limitsReached } from "@dashboard/fixtures";
|
||||||
import { ProductErrorCode } from "@dashboard/graphql";
|
import {
|
||||||
|
ProductErrorCode,
|
||||||
|
ProductVariantBulkErrorCode,
|
||||||
|
} from "@dashboard/graphql";
|
||||||
import Decorator from "@dashboard/storybook/Decorator";
|
import Decorator from "@dashboard/storybook/Decorator";
|
||||||
import { taxClasses } from "@dashboard/taxes/fixtures";
|
import { taxClasses } from "@dashboard/taxes/fixtures";
|
||||||
import { warehouseList } from "@dashboard/warehouses/fixtures";
|
import { warehouseList } from "@dashboard/warehouses/fixtures";
|
||||||
|
@ -23,7 +26,7 @@ const props: ProductUpdatePageProps = {
|
||||||
variantId: product.variants[0].id,
|
variantId: product.variants[0].id,
|
||||||
type: "channel",
|
type: "channel",
|
||||||
channelIds: [channelsList[1].id],
|
channelIds: [channelsList[1].id],
|
||||||
error: ProductErrorCode.ALREADY_EXISTS,
|
error: ProductVariantBulkErrorCode.PRODUCT_NOT_ASSIGNED_TO_CHANNEL,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
productId: "123",
|
productId: "123",
|
||||||
|
@ -154,18 +157,20 @@ storiesOf("Products / Product edit", module)
|
||||||
.add("form errors", () => (
|
.add("form errors", () => (
|
||||||
<ProductUpdatePage
|
<ProductUpdatePage
|
||||||
{...props}
|
{...props}
|
||||||
errors={([
|
errors={(
|
||||||
"attributes",
|
[
|
||||||
"category",
|
"attributes",
|
||||||
"chargeTaxes",
|
"category",
|
||||||
"collections",
|
"chargeTaxes",
|
||||||
"name",
|
"collections",
|
||||||
"publicationDate",
|
"name",
|
||||||
"seoDescription",
|
"publicationDate",
|
||||||
"seoTitle",
|
"seoDescription",
|
||||||
"sku",
|
"seoTitle",
|
||||||
"stockQuantity",
|
"sku",
|
||||||
] as Array<keyof ProductUpdateFormData | "attributes">).map(field => ({
|
"stockQuantity",
|
||||||
|
] as Array<keyof ProductUpdateFormData | "attributes">
|
||||||
|
).map(field => ({
|
||||||
__typename: "ProductError",
|
__typename: "ProductError",
|
||||||
attributes:
|
attributes:
|
||||||
field === "attributes" ? [product.attributes[0].attribute.id] : null,
|
field === "attributes" ? [product.attributes[0].attribute.id] : null,
|
||||||
|
|
|
@ -309,10 +309,11 @@ export const ProductUpdatePage: React.FC<ProductUpdatePageProps> = ({
|
||||||
const listings = data.channels.updateChannels.map<ChannelData>(
|
const listings = data.channels.updateChannels.map<ChannelData>(
|
||||||
listing => {
|
listing => {
|
||||||
const channel = channels?.find(ac => ac.id === listing.channelId);
|
const channel = channels?.find(ac => ac.id === listing.channelId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: listing.channelId,
|
|
||||||
...channel,
|
...channel,
|
||||||
...listing,
|
...listing,
|
||||||
|
id: listing.channelId,
|
||||||
availableForPurchase: listing.availableForPurchaseDate,
|
availableForPurchase: listing.availableForPurchaseDate,
|
||||||
currency: channel.currencyCode,
|
currency: channel.currencyCode,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
import {
|
|
||||||
DatagridChange,
|
|
||||||
DatagridChangeOpts,
|
|
||||||
} from "@dashboard/components/Datagrid/useDatagridChange";
|
|
||||||
|
|
||||||
import { getColumnChannel, getColumnChannelAvailability } from "./columnData";
|
|
||||||
|
|
||||||
const byCurrentRowByIndex = (index: number, data: DatagridChangeOpts) => (
|
|
||||||
change: DatagridChange,
|
|
||||||
) => {
|
|
||||||
const totalRemoved = data.removed.filter(r => r <= index).length;
|
|
||||||
return change.row === index + totalRemoved;
|
|
||||||
};
|
|
||||||
|
|
||||||
const byChannelColumn = (change: DatagridChange) =>
|
|
||||||
getColumnChannel(change.column);
|
|
||||||
|
|
||||||
const availabilityToChannelColumn = (change: DatagridChange) => {
|
|
||||||
const availabilityChannelId = getColumnChannelAvailability(change.column);
|
|
||||||
|
|
||||||
if (availabilityChannelId) {
|
|
||||||
return {
|
|
||||||
data: {
|
|
||||||
value: change.data ? 0 : null,
|
|
||||||
},
|
|
||||||
column: `channel:${availabilityChannelId}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return change;
|
|
||||||
};
|
|
||||||
|
|
||||||
const byColumn = (prev: DatagridChange[], change: DatagridChange) => {
|
|
||||||
const index = prev.findIndex(p => p.column === change.column);
|
|
||||||
if (index > -1) {
|
|
||||||
prev[index] = change;
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
return prev.concat(change);
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataGridChangeToFlatChannel = (change: DatagridChange) => ({
|
|
||||||
channelId: getColumnChannel(change.column),
|
|
||||||
price: change.data.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
const byNotNullPrice = (
|
|
||||||
change: ReturnType<typeof dataGridChangeToFlatChannel>,
|
|
||||||
) => change.price !== null;
|
|
||||||
|
|
||||||
export function getVariantChannelsInputs(
|
|
||||||
data: DatagridChangeOpts,
|
|
||||||
index: number,
|
|
||||||
) {
|
|
||||||
return data.updates
|
|
||||||
.filter(byCurrentRowByIndex(index, data))
|
|
||||||
.map(availabilityToChannelColumn)
|
|
||||||
.filter(byChannelColumn)
|
|
||||||
.reduce(byColumn, [])
|
|
||||||
.map(dataGridChangeToFlatChannel)
|
|
||||||
.filter(byNotNullPrice);
|
|
||||||
}
|
|
|
@ -9,17 +9,11 @@ import {
|
||||||
import { emptyDropdownCellValue } from "@dashboard/components/Datagrid/DropdownCell";
|
import { emptyDropdownCellValue } from "@dashboard/components/Datagrid/DropdownCell";
|
||||||
import { numberCellEmptyValue } from "@dashboard/components/Datagrid/NumberCell";
|
import { numberCellEmptyValue } from "@dashboard/components/Datagrid/NumberCell";
|
||||||
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
|
import { AvailableColumn } from "@dashboard/components/Datagrid/types";
|
||||||
import {
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
DatagridChange,
|
|
||||||
DatagridChangeOpts,
|
|
||||||
} from "@dashboard/components/Datagrid/useDatagridChange";
|
|
||||||
import { Choice } from "@dashboard/components/SingleSelectField";
|
import { Choice } from "@dashboard/components/SingleSelectField";
|
||||||
import {
|
import {
|
||||||
ProductDetailsVariantFragment,
|
ProductDetailsVariantFragment,
|
||||||
ProductFragment,
|
ProductFragment,
|
||||||
VariantDatagridChannelListingUpdateMutationVariables,
|
|
||||||
VariantDatagridStockUpdateMutationVariables,
|
|
||||||
VariantDatagridUpdateMutationVariables,
|
|
||||||
WarehouseFragment,
|
WarehouseFragment,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { ProductVariantListError } from "@dashboard/products/views/ProductUpdate/handlers/errors";
|
import { ProductVariantListError } from "@dashboard/products/views/ProductUpdate/handlers/errors";
|
||||||
|
@ -32,117 +26,11 @@ import {
|
||||||
getColumnAttribute,
|
getColumnAttribute,
|
||||||
getColumnChannel,
|
getColumnChannel,
|
||||||
getColumnChannelAvailability,
|
getColumnChannelAvailability,
|
||||||
|
getColumnName,
|
||||||
getColumnStock,
|
getColumnStock,
|
||||||
} from "./datagrid/columnData";
|
} from "../../utils/datagrid";
|
||||||
import { getVariantChannelsInputs } from "./datagrid/getVariantChannelsInputs";
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
export function getVariantInput(data: DatagridChangeOpts, index: number) {
|
|
||||||
const attributes = data.updates
|
|
||||||
.filter(
|
|
||||||
change =>
|
|
||||||
getColumnAttribute(change.column) &&
|
|
||||||
change.row === index + data.removed.filter(r => r <= index).length,
|
|
||||||
)
|
|
||||||
.map(change => {
|
|
||||||
const attributeId = getColumnAttribute(change.column);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: attributeId,
|
|
||||||
values: [change.data.value.value],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const sku = data.updates.find(
|
|
||||||
change =>
|
|
||||||
change.column === "sku" &&
|
|
||||||
change.row === index + data.removed.filter(r => r <= index).length,
|
|
||||||
)?.data;
|
|
||||||
|
|
||||||
const name = data.updates.find(
|
|
||||||
change =>
|
|
||||||
change.column === "name" &&
|
|
||||||
change.row === index + data.removed.filter(r => r <= index).length,
|
|
||||||
)?.data;
|
|
||||||
|
|
||||||
return {
|
|
||||||
attributes,
|
|
||||||
sku,
|
|
||||||
name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getVariantInputs(
|
|
||||||
variants: ProductFragment["variants"],
|
|
||||||
data: DatagridChangeOpts,
|
|
||||||
): VariantDatagridUpdateMutationVariables[] {
|
|
||||||
return variants
|
|
||||||
.map(
|
|
||||||
(variant, variantIndex): VariantDatagridUpdateMutationVariables => ({
|
|
||||||
id: variant.id,
|
|
||||||
input: getVariantInput(data, variantIndex),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.filter(
|
|
||||||
variables =>
|
|
||||||
variables.input.sku ||
|
|
||||||
variables.input.name ||
|
|
||||||
variables.input.attributes.length > 0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStockInputs(data: DatagridChangeOpts, index: number) {
|
|
||||||
const stockChanges = data.updates.filter(change =>
|
|
||||||
getColumnStock(change.column),
|
|
||||||
);
|
|
||||||
|
|
||||||
const variantChanges = stockChanges
|
|
||||||
.filter(
|
|
||||||
change =>
|
|
||||||
change.row === index + data.removed.filter(r => r <= index).length,
|
|
||||||
)
|
|
||||||
.map(change => ({
|
|
||||||
warehouse: getColumnStock(change.column),
|
|
||||||
quantity: change.data.value,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
|
||||||
stocks: variantChanges.filter(
|
|
||||||
change => change.quantity !== numberCellEmptyValue,
|
|
||||||
),
|
|
||||||
removeStocks: variantChanges
|
|
||||||
.filter(change => change.quantity === numberCellEmptyValue)
|
|
||||||
.map(({ warehouse }) => warehouse),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getStocks(
|
|
||||||
variants: ProductFragment["variants"],
|
|
||||||
data: DatagridChangeOpts,
|
|
||||||
): VariantDatagridStockUpdateMutationVariables[] {
|
|
||||||
return variants
|
|
||||||
.map((variant, variantIndex) => ({
|
|
||||||
id: variant.id,
|
|
||||||
...getStockInputs(data, variantIndex),
|
|
||||||
}))
|
|
||||||
.filter(
|
|
||||||
variables =>
|
|
||||||
variables.removeStocks.length > 0 || variables.stocks.length > 0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getVariantChannels(
|
|
||||||
variants: ProductFragment["variants"],
|
|
||||||
data: DatagridChangeOpts,
|
|
||||||
): VariantDatagridChannelListingUpdateMutationVariables[] {
|
|
||||||
return variants
|
|
||||||
.map((variant, variantIndex) => ({
|
|
||||||
id: variant.id,
|
|
||||||
input: getVariantChannelsInputs(data, variantIndex),
|
|
||||||
}))
|
|
||||||
.filter(({ input }) => input.length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorMatchesColumn(
|
function errorMatchesColumn(
|
||||||
error: ProductVariantListError,
|
error: ProductVariantListError,
|
||||||
columnId: string,
|
columnId: string,
|
||||||
|
@ -162,7 +50,8 @@ function errorMatchesColumn(
|
||||||
if (error.attributes?.length > 0) {
|
if (error.attributes?.length > 0) {
|
||||||
return error.attributes.includes(getColumnAttribute(columnId));
|
return error.attributes.includes(getColumnAttribute(columnId));
|
||||||
}
|
}
|
||||||
return columnId === "sku";
|
|
||||||
|
return error?.field?.includes(getColumnName(columnId)) ?? false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,8 +154,9 @@ export function getData({
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const currency = channels.find(channel => channelId === channel.id)
|
const currency = channels.find(
|
||||||
?.currency;
|
channel => channelId === channel.id,
|
||||||
|
)?.currency;
|
||||||
const value = change?.value ?? listing?.price?.amount ?? 0;
|
const value = change?.value ?? listing?.price?.amount ?? 0;
|
||||||
|
|
||||||
return moneyCell(value, currency);
|
return moneyCell(value, currency);
|
||||||
|
|
|
@ -635,8 +635,7 @@ export const product: (
|
||||||
quantity: 272,
|
quantity: 272,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOjI2NDNiNmIwLWExMTQtNGRiMC1hM2U4LTFkZGY3ZGM3NDVkMg==",
|
||||||
"V2FyZWhvdXNlOjI2NDNiNmIwLWExMTQtNGRiMC1hM2U4LTFkZGY3ZGM3NDVkMg==",
|
|
||||||
name: "Europe for click and collect",
|
name: "Europe for click and collect",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -647,8 +646,7 @@ export const product: (
|
||||||
quantity: 272,
|
quantity: 272,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOmFmZDA4YjY4LWQwYmMtNGQ1My1iNjJkLTg1YWMxOWI3MjliYg==",
|
||||||
"V2FyZWhvdXNlOmFmZDA4YjY4LWQwYmMtNGQ1My1iNjJkLTg1YWMxOWI3MjliYg==",
|
|
||||||
name: "Europe",
|
name: "Europe",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -659,8 +657,7 @@ export const product: (
|
||||||
quantity: 274,
|
quantity: 274,
|
||||||
quantityAllocated: 2,
|
quantityAllocated: 2,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOjMxOTRjNjY5LTY1YjItNDBjYy04ZDI5LWI3M2Q0YTUwODBmMQ==",
|
||||||
"V2FyZWhvdXNlOjMxOTRjNjY5LTY1YjItNDBjYy04ZDI5LWI3M2Q0YTUwODBmMQ==",
|
|
||||||
name: "Asia",
|
name: "Asia",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -671,8 +668,7 @@ export const product: (
|
||||||
quantity: 272,
|
quantity: 272,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOjI5YzBlYmYwLWVkNzktNDlmOS1hYmQ0LWQwNDBlOGNlZmI3Mg==",
|
||||||
"V2FyZWhvdXNlOjI5YzBlYmYwLWVkNzktNDlmOS1hYmQ0LWQwNDBlOGNlZmI3Mg==",
|
|
||||||
name: "Oceania",
|
name: "Oceania",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -683,8 +679,7 @@ export const product: (
|
||||||
quantity: 272,
|
quantity: 272,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOjRiNjc1ZmVlLTE3OWYtNGMwNS04YmJlLWE0ZDJjOTc0OWQzMA==",
|
||||||
"V2FyZWhvdXNlOjRiNjc1ZmVlLTE3OWYtNGMwNS04YmJlLWE0ZDJjOTc0OWQzMA==",
|
|
||||||
name: "Africa",
|
name: "Africa",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -695,8 +690,7 @@ export const product: (
|
||||||
quantity: 274,
|
quantity: 274,
|
||||||
quantityAllocated: 2,
|
quantityAllocated: 2,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOmQwODA2MzM5LTVhNjAtNDAxNi1hNGUwLTRjNDYxNTZlY2IzMQ==",
|
||||||
"V2FyZWhvdXNlOmQwODA2MzM5LTVhNjAtNDAxNi1hNGUwLTRjNDYxNTZlY2IzMQ==",
|
|
||||||
name: "Americas",
|
name: "Americas",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -707,6 +701,7 @@ export const product: (
|
||||||
preorder: null,
|
preorder: null,
|
||||||
channelListings: [
|
channelListings: [
|
||||||
{
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNTM=",
|
||||||
channel: {
|
channel: {
|
||||||
id: "Q2hhbm5lbDox",
|
id: "Q2hhbm5lbDox",
|
||||||
name: "Channel-USD",
|
name: "Channel-USD",
|
||||||
|
@ -731,6 +726,7 @@ export const product: (
|
||||||
__typename: "ProductVariantChannelListing",
|
__typename: "ProductVariantChannelListing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNDM=",
|
||||||
channel: {
|
channel: {
|
||||||
id: "Q2hhbm5lbDoy",
|
id: "Q2hhbm5lbDoy",
|
||||||
name: "Channel-PLN",
|
name: "Channel-PLN",
|
||||||
|
@ -793,6 +789,7 @@ export const product: (
|
||||||
preorder: null,
|
preorder: null,
|
||||||
channelListings: [
|
channelListings: [
|
||||||
{
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNEM=",
|
||||||
channel: {
|
channel: {
|
||||||
id: "Q2hhbm5lbDox",
|
id: "Q2hhbm5lbDox",
|
||||||
name: "Channel-USD",
|
name: "Channel-USD",
|
||||||
|
@ -817,6 +814,7 @@ export const product: (
|
||||||
__typename: "ProductVariantChannelListing",
|
__typename: "ProductVariantChannelListing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoreNTM=",
|
||||||
channel: {
|
channel: {
|
||||||
id: "Q2hhbm5lbDoy",
|
id: "Q2hhbm5lbDoy",
|
||||||
name: "Channel-PLN",
|
name: "Channel-PLN",
|
||||||
|
@ -880,8 +878,7 @@ export const product: (
|
||||||
quantity: 418,
|
quantity: 418,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOmFmZDA4YjY4LWQwYmMtNGQ1My1iNjJkLTg1YWMxOWI3MjliYg==",
|
||||||
"V2FyZWhvdXNlOmFmZDA4YjY4LWQwYmMtNGQ1My1iNjJkLTg1YWMxOWI3MjliYg==",
|
|
||||||
name: "Europe",
|
name: "Europe",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -892,8 +889,7 @@ export const product: (
|
||||||
quantity: 418,
|
quantity: 418,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOjMxOTRjNjY5LTY1YjItNDBjYy04ZDI5LWI3M2Q0YTUwODBmMQ==",
|
||||||
"V2FyZWhvdXNlOjMxOTRjNjY5LTY1YjItNDBjYy04ZDI5LWI3M2Q0YTUwODBmMQ==",
|
|
||||||
name: "Asia",
|
name: "Asia",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -904,8 +900,7 @@ export const product: (
|
||||||
quantity: 418,
|
quantity: 418,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOmQwODA2MzM5LTVhNjAtNDAxNi1hNGUwLTRjNDYxNTZlY2IzMQ==",
|
||||||
"V2FyZWhvdXNlOmQwODA2MzM5LTVhNjAtNDAxNi1hNGUwLTRjNDYxNTZlY2IzMQ==",
|
|
||||||
name: "Americas",
|
name: "Americas",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -916,8 +911,7 @@ export const product: (
|
||||||
quantity: 418,
|
quantity: 418,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOjI5YzBlYmYwLWVkNzktNDlmOS1hYmQ0LWQwNDBlOGNlZmI3Mg==",
|
||||||
"V2FyZWhvdXNlOjI5YzBlYmYwLWVkNzktNDlmOS1hYmQ0LWQwNDBlOGNlZmI3Mg==",
|
|
||||||
name: "Oceania",
|
name: "Oceania",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -928,8 +922,7 @@ export const product: (
|
||||||
quantity: 418,
|
quantity: 418,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOjI2NDNiNmIwLWExMTQtNGRiMC1hM2U4LTFkZGY3ZGM3NDVkMg==",
|
||||||
"V2FyZWhvdXNlOjI2NDNiNmIwLWExMTQtNGRiMC1hM2U4LTFkZGY3ZGM3NDVkMg==",
|
|
||||||
name: "Europe for click and collect",
|
name: "Europe for click and collect",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -940,8 +933,7 @@ export const product: (
|
||||||
quantity: 418,
|
quantity: 418,
|
||||||
quantityAllocated: 0,
|
quantityAllocated: 0,
|
||||||
warehouse: {
|
warehouse: {
|
||||||
id:
|
id: "V2FyZWhvdXNlOjRiNjc1ZmVlLTE3OWYtNGMwNS04YmJlLWE0ZDJjOTc0OWQzMA==",
|
||||||
"V2FyZWhvdXNlOjRiNjc1ZmVlLTE3OWYtNGMwNS04YmJlLWE0ZDJjOTc0OWQzMA==",
|
|
||||||
name: "Africa",
|
name: "Africa",
|
||||||
__typename: "Warehouse",
|
__typename: "Warehouse",
|
||||||
},
|
},
|
||||||
|
@ -952,6 +944,7 @@ export const product: (
|
||||||
preorder: null,
|
preorder: null,
|
||||||
channelListings: [
|
channelListings: [
|
||||||
{
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaSAD3w2FubmVsTGlzdGluZzoyNTM=",
|
||||||
channel: {
|
channel: {
|
||||||
id: "Q2hhbm5lbDox",
|
id: "Q2hhbm5lbDox",
|
||||||
name: "Channel-USD",
|
name: "Channel-USD",
|
||||||
|
@ -976,6 +969,7 @@ export const product: (
|
||||||
__typename: "ProductVariantChannelListing",
|
__typename: "ProductVariantChannelListing",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhdwxDaGFubmVsTGlzdGluZzoyNTM=",
|
||||||
channel: {
|
channel: {
|
||||||
id: "Q2hhbm5lbDoy",
|
id: "Q2hhbm5lbDoy",
|
||||||
name: "Channel-PLN",
|
name: "Channel-PLN",
|
||||||
|
@ -3215,6 +3209,7 @@ export const variant = (placeholderImage: string): ProductVariantFragment => ({
|
||||||
__typename: "ProductVariant",
|
__typename: "ProductVariant",
|
||||||
channelListings: [
|
channelListings: [
|
||||||
{
|
{
|
||||||
|
id: "UHJvZHVjdFZhcasdasdASDDaGFubmVsTGlzdGluZzoyNTM=",
|
||||||
__typename: "ProductVariantChannelListing",
|
__typename: "ProductVariantChannelListing",
|
||||||
channel: {
|
channel: {
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
|
@ -3239,6 +3234,7 @@ export const variant = (placeholderImage: string): ProductVariantFragment => ({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGGSAEdsWsTGlzdGluZzoyNTM=",
|
||||||
__typename: "ProductVariantChannelListing",
|
__typename: "ProductVariantChannelListing",
|
||||||
channel: {
|
channel: {
|
||||||
__typename: "Channel",
|
__typename: "Channel",
|
||||||
|
@ -3377,8 +3373,8 @@ export const variant = (placeholderImage: string): ProductVariantFragment => ({
|
||||||
__typename: "Product" as "Product",
|
__typename: "Product" as "Product",
|
||||||
channelListings: [
|
channelListings: [
|
||||||
{
|
{
|
||||||
|
id: "2",
|
||||||
__typename: "ProductChannelListing",
|
__typename: "ProductChannelListing",
|
||||||
id: "11",
|
|
||||||
isPublished: false,
|
isPublished: false,
|
||||||
publicationDate: null,
|
publicationDate: null,
|
||||||
channel: {
|
channel: {
|
||||||
|
@ -3389,8 +3385,8 @@ export const variant = (placeholderImage: string): ProductVariantFragment => ({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: "2",
|
||||||
__typename: "ProductChannelListing",
|
__typename: "ProductChannelListing",
|
||||||
id: "12",
|
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
publicationDate: "2022-01-21",
|
publicationDate: "2022-01-21",
|
||||||
channel: {
|
channel: {
|
||||||
|
@ -3791,10 +3787,9 @@ export const variantProductImages = (placeholderImage: string) =>
|
||||||
export const variantSiblings = (placeholderImage: string) =>
|
export const variantSiblings = (placeholderImage: string) =>
|
||||||
variant(placeholderImage).product.variants;
|
variant(placeholderImage).product.variants;
|
||||||
|
|
||||||
export const productTypesList: Array<Pick<
|
export const productTypesList: Array<
|
||||||
ProductType,
|
Pick<ProductType, "id" | "name" | "hasVariants">
|
||||||
"id" | "name" | "hasVariants"
|
> = [
|
||||||
>> = [
|
|
||||||
{
|
{
|
||||||
hasVariants: true,
|
hasVariants: true,
|
||||||
id: "UHJvZHVjdFR5cGU6Nw==",
|
id: "UHJvZHVjdFR5cGU6Nw==",
|
||||||
|
|
|
@ -117,48 +117,6 @@ export const variantDeleteMutation = gql`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const variantDatagridUpdateMutation = gql`
|
|
||||||
mutation VariantDatagridUpdate($id: ID!, $input: ProductVariantInput!) {
|
|
||||||
productVariantUpdate(id: $id, input: $input) {
|
|
||||||
errors {
|
|
||||||
...ProductErrorWithAttributes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const variantDatagridStockUpdateMutation = gql`
|
|
||||||
mutation VariantDatagridStockUpdate(
|
|
||||||
$stocks: [StockInput!]!
|
|
||||||
$removeStocks: [ID!]!
|
|
||||||
$id: ID!
|
|
||||||
) {
|
|
||||||
productVariantStocksDelete(warehouseIds: $removeStocks, variantId: $id) {
|
|
||||||
errors {
|
|
||||||
...ProductVariantStocksDeleteError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
productVariantStocksUpdate(stocks: $stocks, variantId: $id) {
|
|
||||||
errors {
|
|
||||||
...BulkStockError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const variantDatagridChannelListingUpdateMutation = gql`
|
|
||||||
mutation VariantDatagridChannelListingUpdate(
|
|
||||||
$id: ID!
|
|
||||||
$input: [ProductVariantChannelListingAddInput!]!
|
|
||||||
) {
|
|
||||||
productVariantChannelListingUpdate(id: $id, input: $input) {
|
|
||||||
errors {
|
|
||||||
...ProductChannelListingError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const variantUpdateMutation = gql`
|
export const variantUpdateMutation = gql`
|
||||||
mutation VariantUpdate(
|
mutation VariantUpdate(
|
||||||
$addStocks: [StockInput!]!
|
$addStocks: [StockInput!]!
|
||||||
|
@ -458,3 +416,26 @@ export const ProductVariantPreorderDeactivateMutation = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const ProductVariantBulkUpdateMutation = gql`
|
||||||
|
mutation ProductVariantBulkUpdate(
|
||||||
|
$product: ID!
|
||||||
|
$input: [ProductVariantBulkUpdateInput!]!
|
||||||
|
$errorPolicy: ErrorPolicyEnum
|
||||||
|
) {
|
||||||
|
productVariantBulkUpdate(
|
||||||
|
errorPolicy: $errorPolicy
|
||||||
|
product: $product
|
||||||
|
variants: $input
|
||||||
|
) {
|
||||||
|
errors {
|
||||||
|
...ProductVariantBulkError
|
||||||
|
}
|
||||||
|
results {
|
||||||
|
errors {
|
||||||
|
...ProductVariantBulkError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
63
src/products/utils/datagrid.test.ts
Normal file
63
src/products/utils/datagrid.test.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { isCurrentRow } from "./datagrid";
|
||||||
|
|
||||||
|
describe("isCurrentRow", () => {
|
||||||
|
test("should return true when variant index is equal to datagrid row index and no removed rows", () => {
|
||||||
|
// Arrange & Act
|
||||||
|
const datagridChangeRowIndex = 1;
|
||||||
|
const variantIndex = 1;
|
||||||
|
const datagridRemoveRowsIds = [];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(
|
||||||
|
isCurrentRow(datagridChangeRowIndex, variantIndex, datagridRemoveRowsIds),
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return true when variant index is equal to datagrid row index and removed rows contain higher rows ids", () => {
|
||||||
|
// Arrange & Act
|
||||||
|
const datagridChangeRowIndex = 1;
|
||||||
|
const variantIndex = 1;
|
||||||
|
const datagridRemoveRowsIds = [4, 5, 6];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(
|
||||||
|
isCurrentRow(datagridChangeRowIndex, variantIndex, datagridRemoveRowsIds),
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return false when variant index is not equal to datagrid row index and removed rows contains prev row id", () => {
|
||||||
|
// Arrange & Act
|
||||||
|
const datagridChangeRowIndex = 2;
|
||||||
|
const variantIndex = 1;
|
||||||
|
const datagridRemoveRowsIds = [1];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(
|
||||||
|
isCurrentRow(datagridChangeRowIndex, variantIndex, datagridRemoveRowsIds),
|
||||||
|
).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return false when variant index is not equal to datagrid row index ", () => {
|
||||||
|
// Arrange & Act
|
||||||
|
const datagridChangeRowIndex = 1;
|
||||||
|
const variantIndex = 2;
|
||||||
|
const datagridRemoveRowsIds = [];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(
|
||||||
|
isCurrentRow(datagridChangeRowIndex, variantIndex, datagridRemoveRowsIds),
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return false when variant index is equal to datagrid row index and removed rows contains prev row id", () => {
|
||||||
|
// Arrange & Act
|
||||||
|
const datagridChangeRowIndex = 2;
|
||||||
|
const variantIndex = 2;
|
||||||
|
const datagridRemoveRowsIds = [1];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(
|
||||||
|
isCurrentRow(datagridChangeRowIndex, variantIndex, datagridRemoveRowsIds),
|
||||||
|
).toEqual(false);
|
||||||
|
});
|
||||||
|
});
|
|
@ -16,3 +16,17 @@ export const getColumnChannelAvailability = makeGetColumnData(
|
||||||
/^availableInChannel:(.*)/,
|
/^availableInChannel:(.*)/,
|
||||||
);
|
);
|
||||||
export const getColumnStock = makeGetColumnData(/^stock:(.*)/);
|
export const getColumnStock = makeGetColumnData(/^stock:(.*)/);
|
||||||
|
|
||||||
|
export const getColumnName = (column: string) => {
|
||||||
|
const splited = column.split(":");
|
||||||
|
return splited[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isCurrentRow = (
|
||||||
|
datagridChangeIndex: number,
|
||||||
|
variantIndex: number,
|
||||||
|
datagridRemovedRowsIds: number[],
|
||||||
|
) =>
|
||||||
|
datagridChangeIndex ===
|
||||||
|
variantIndex +
|
||||||
|
datagridRemovedRowsIds.filter(index => index <= variantIndex).length;
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
|
||||||
|
import { getAttributeData } from "./attributes";
|
||||||
|
|
||||||
|
describe("getAttributeData", () => {
|
||||||
|
test("should filter and map data to attribute format", () => {
|
||||||
|
// Arrage
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "attribute:1", row: 1, data: { value: { value: "test" } } },
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const attributes = getAttributeData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(attributes).toEqual([
|
||||||
|
{ id: "1", values: ["test"] },
|
||||||
|
{ id: "2", values: ["test2"] },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty array when no changes for given row", () => {
|
||||||
|
// Arrage
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "attribute:1", row: 1, data: { value: { value: "test" } } },
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const attributes = getAttributeData(changeData, 2, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(attributes).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty array when no changes for attributes column", () => {
|
||||||
|
// Arrage
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "channel:1", row: 1, data: { value: { value: "test" } } },
|
||||||
|
{ column: "channel:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const attributes = getAttributeData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(attributes).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
29
src/products/views/ProductUpdate/handlers/data/attributes.ts
Normal file
29
src/products/views/ProductUpdate/handlers/data/attributes.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
import {
|
||||||
|
getColumnAttribute,
|
||||||
|
isCurrentRow,
|
||||||
|
} from "@dashboard/products/utils/datagrid";
|
||||||
|
|
||||||
|
export function getAttributeData(
|
||||||
|
data: DatagridChange[],
|
||||||
|
currentIndex: number,
|
||||||
|
removedIds: number[],
|
||||||
|
) {
|
||||||
|
return data
|
||||||
|
.filter(change => isCurrentRow(change.row, currentIndex, removedIds))
|
||||||
|
.filter(byHavingAnyAttribute)
|
||||||
|
.map(toAttributeData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function byHavingAnyAttribute(change: DatagridChange): boolean {
|
||||||
|
return !!getColumnAttribute(change.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toAttributeData(change: DatagridChange) {
|
||||||
|
const attributeId = getColumnAttribute(change.column);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: attributeId,
|
||||||
|
values: change.data.value.value ? [change.data.value.value] : [],
|
||||||
|
};
|
||||||
|
}
|
301
src/products/views/ProductUpdate/handlers/data/channel.test.ts
Normal file
301
src/products/views/ProductUpdate/handlers/data/channel.test.ts
Normal file
|
@ -0,0 +1,301 @@
|
||||||
|
import { DatagridChangeOpts } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
import { ProductFragment } from "@dashboard/graphql";
|
||||||
|
|
||||||
|
import {
|
||||||
|
getUpdateVariantChannelInputs,
|
||||||
|
getVariantChannelsInputs,
|
||||||
|
} from "./channel";
|
||||||
|
|
||||||
|
const channelListings = [
|
||||||
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjA=",
|
||||||
|
channel: {
|
||||||
|
id: "Q2hhbm5lbDox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjD=",
|
||||||
|
channel: {
|
||||||
|
id: "Q2hhbm5lbDot",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("getUpdateVariantChannelInputs", () => {
|
||||||
|
test("should handle updated channels", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
column: "channel:Q2hhbm5lbDox",
|
||||||
|
row: 1,
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 43343,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
column: "channel:Q2hhbm5lbDot",
|
||||||
|
row: 1,
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 123,
|
||||||
|
currency: "PLN",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getUpdateVariantChannelInputs(changeData, 1, {
|
||||||
|
channelListings,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(channels).toEqual({
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
update: [
|
||||||
|
{
|
||||||
|
channelListing: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjA=",
|
||||||
|
price: 43343,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
channelListing: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjD=",
|
||||||
|
price: 123,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle removed channels", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
data: false,
|
||||||
|
column: "availableInChannel:Q2hhbm5lbDox",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: false,
|
||||||
|
column: "availableInChannel:Q2hhbm5lbDot",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getUpdateVariantChannelInputs(changeData, 1, {
|
||||||
|
channelListings,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(channels).toEqual({
|
||||||
|
create: [],
|
||||||
|
remove: [
|
||||||
|
"UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjA=",
|
||||||
|
"UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjD=",
|
||||||
|
],
|
||||||
|
update: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle created channels", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
data: true,
|
||||||
|
column: "availableInChannel:Q2hhbm5lbDod",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 3434,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
column: "channel:Q2hhbm5lbDod",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getUpdateVariantChannelInputs(changeData, 1, {
|
||||||
|
channelListings,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(channels).toEqual({
|
||||||
|
create: [
|
||||||
|
{
|
||||||
|
channelId: "Q2hhbm5lbDod",
|
||||||
|
price: 3434,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
remove: [],
|
||||||
|
update: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty arrays when no changes for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
column: "channel:Q2hhbm5lbDox",
|
||||||
|
row: 11,
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 43343,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getUpdateVariantChannelInputs(changeData, 1, {
|
||||||
|
channelListings,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(channels).toEqual({
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
update: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty arrays when no changes for given column", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{ column: "attribute:1", row: 1, data: { value: { value: "test" } } },
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getUpdateVariantChannelInputs(changeData, 1, {
|
||||||
|
channelListings,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(channels).toEqual({
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
update: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getVariantChannelsInputs", () => {
|
||||||
|
test("should filter and map change data to channel format", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
column: "channel:Q2hhbm5lbDox",
|
||||||
|
row: 1,
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 43343,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getVariantChannelsInputs(changeData, 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
expect(channels).toEqual([
|
||||||
|
{
|
||||||
|
channelId: "Q2hhbm5lbDox",
|
||||||
|
price: 43343,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should filter out changes with null prices", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
data: false,
|
||||||
|
column: "availableInChannel:Q2hhbm5lbDox",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getVariantChannelsInputs(changeData, 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(channels).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty arrays when no changes for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
column: "channel:Q2hhbm5lbDox",
|
||||||
|
row: 11,
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 43343,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getVariantChannelsInputs(changeData, 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(channels).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty arrays when no changes for given column", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChangeOpts = {
|
||||||
|
added: [],
|
||||||
|
removed: [],
|
||||||
|
updates: [
|
||||||
|
{ column: "attribute:1", row: 1, data: { value: { value: "test" } } },
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const channels = getVariantChannelsInputs(changeData, 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(channels).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
129
src/products/views/ProductUpdate/handlers/data/channel.ts
Normal file
129
src/products/views/ProductUpdate/handlers/data/channel.ts
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
import {
|
||||||
|
DatagridChange,
|
||||||
|
DatagridChangeOpts,
|
||||||
|
} from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
import {
|
||||||
|
ProductFragment,
|
||||||
|
ProductVariantChannelListingAddInput,
|
||||||
|
ProductVariantChannelListingUpdateInput,
|
||||||
|
} from "@dashboard/graphql";
|
||||||
|
import {
|
||||||
|
getColumnChannel,
|
||||||
|
getColumnChannelAvailability,
|
||||||
|
} from "@dashboard/products/utils/datagrid";
|
||||||
|
|
||||||
|
export function getUpdateVariantChannelInputs(
|
||||||
|
data: DatagridChangeOpts,
|
||||||
|
index: number,
|
||||||
|
variant: ProductFragment["variants"][number],
|
||||||
|
): ProductVariantChannelListingUpdateInput {
|
||||||
|
return data.updates
|
||||||
|
.filter(byCurrentRowByIndex(index, data))
|
||||||
|
.map(availabilityToChannelColumn)
|
||||||
|
.filter(byChannelColumn)
|
||||||
|
.reduce(byColumn, [])
|
||||||
|
.map(dataGridChangeToFlatChannel)
|
||||||
|
.reduce<ProductVariantChannelListingUpdateInput>(
|
||||||
|
toUpdateChannelData(variant),
|
||||||
|
{
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
update: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVariantChannelsInputs(
|
||||||
|
data: DatagridChangeOpts,
|
||||||
|
index: number,
|
||||||
|
): ProductVariantChannelListingAddInput[] {
|
||||||
|
return data.updates
|
||||||
|
.filter(byCurrentRowByIndex(index, data))
|
||||||
|
.map(availabilityToChannelColumn)
|
||||||
|
.filter(byChannelColumn)
|
||||||
|
.reduce(byColumn, [])
|
||||||
|
.map(dataGridChangeToFlatChannel)
|
||||||
|
.filter(byNotNullPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
function byCurrentRowByIndex(index: number, data: DatagridChangeOpts) {
|
||||||
|
return (change: DatagridChange) => {
|
||||||
|
const totalRemoved = data.removed.filter(r => r <= index).length;
|
||||||
|
return change.row === index + totalRemoved;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function byChannelColumn(change: DatagridChange) {
|
||||||
|
return getColumnChannel(change.column);
|
||||||
|
}
|
||||||
|
|
||||||
|
function availabilityToChannelColumn(change: DatagridChange) {
|
||||||
|
const availabilityChannelId = getColumnChannelAvailability(change.column);
|
||||||
|
|
||||||
|
if (availabilityChannelId) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
value: change.data ? 0 : null,
|
||||||
|
},
|
||||||
|
column: `channel:${availabilityChannelId}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return change;
|
||||||
|
}
|
||||||
|
|
||||||
|
function byColumn(prev: DatagridChange[], change: DatagridChange) {
|
||||||
|
const index = prev.findIndex(p => p.column === change.column);
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
prev[index] = change;
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prev.concat(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dataGridChangeToFlatChannel(change: DatagridChange) {
|
||||||
|
return {
|
||||||
|
channelId: getColumnChannel(change.column),
|
||||||
|
price: change.data.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function byNotNullPrice(
|
||||||
|
change: ReturnType<typeof dataGridChangeToFlatChannel>,
|
||||||
|
) {
|
||||||
|
return change.price !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toUpdateChannelData(variant: ProductFragment["variants"][number]) {
|
||||||
|
return (
|
||||||
|
acc: ProductVariantChannelListingUpdateInput,
|
||||||
|
channel: ReturnType<typeof dataGridChangeToFlatChannel>,
|
||||||
|
) => {
|
||||||
|
const variantChannel = variant.channelListings.find(
|
||||||
|
c => c.channel.id === channel.channelId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (channel.price === null) {
|
||||||
|
if (variantChannel) {
|
||||||
|
acc.remove.push(variantChannel.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variantChannel) {
|
||||||
|
acc.update.push({
|
||||||
|
channelListing: variantChannel.id,
|
||||||
|
price: channel.price,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
acc.create.push({
|
||||||
|
channelId: channel.channelId,
|
||||||
|
price: channel.price,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
};
|
||||||
|
}
|
45
src/products/views/ProductUpdate/handlers/data/name.test.ts
Normal file
45
src/products/views/ProductUpdate/handlers/data/name.test.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
|
||||||
|
import { getNameData } from "./name";
|
||||||
|
|
||||||
|
describe("getNameData", () => {
|
||||||
|
test("should return name data", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "name", row: 1, data: "Joe" },
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const name = getNameData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(name).toEqual("Joe");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return undefined when no changes for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const name = getNameData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(name).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return undefined when no name column for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "name", row: 2, data: "Joe" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const name = getNameData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(name).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
14
src/products/views/ProductUpdate/handlers/data/name.ts
Normal file
14
src/products/views/ProductUpdate/handlers/data/name.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
import { isCurrentRow } from "@dashboard/products/utils/datagrid";
|
||||||
|
|
||||||
|
export function getNameData(
|
||||||
|
data: DatagridChange[],
|
||||||
|
currentIndex: number,
|
||||||
|
removedIds: number[],
|
||||||
|
): string | undefined {
|
||||||
|
return data.find(
|
||||||
|
change =>
|
||||||
|
change.column === "name" &&
|
||||||
|
isCurrentRow(change.row, currentIndex, removedIds),
|
||||||
|
)?.data;
|
||||||
|
}
|
45
src/products/views/ProductUpdate/handlers/data/sku.test.ts
Normal file
45
src/products/views/ProductUpdate/handlers/data/sku.test.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
|
||||||
|
import { getSkuData } from "./sku";
|
||||||
|
|
||||||
|
describe("getSkuData", () => {
|
||||||
|
test("should return name data", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "sku", row: 1, data: "123" },
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const name = getSkuData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(name).toEqual("123");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return undefined when no changes for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const name = getSkuData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(name).toEqual(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return undefined when no name column for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "sku", row: 2, data: "Joe" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const name = getSkuData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(name).toEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
14
src/products/views/ProductUpdate/handlers/data/sku.ts
Normal file
14
src/products/views/ProductUpdate/handlers/data/sku.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
import { isCurrentRow } from "@dashboard/products/utils/datagrid";
|
||||||
|
|
||||||
|
export function getSkuData(
|
||||||
|
data: DatagridChange[],
|
||||||
|
currentIndex: number,
|
||||||
|
removedIds: number[],
|
||||||
|
): string | undefined {
|
||||||
|
return data.find(
|
||||||
|
change =>
|
||||||
|
change.column === "sku" &&
|
||||||
|
isCurrentRow(change.row, currentIndex, removedIds),
|
||||||
|
)?.data;
|
||||||
|
}
|
88
src/products/views/ProductUpdate/handlers/data/stock.ts
Normal file
88
src/products/views/ProductUpdate/handlers/data/stock.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
import { numberCellEmptyValue } from "@dashboard/components/Datagrid/NumberCell";
|
||||||
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
import {
|
||||||
|
ProductFragment,
|
||||||
|
ProductVariantStocksUpdateInput,
|
||||||
|
} from "@dashboard/graphql";
|
||||||
|
import {
|
||||||
|
getColumnStock,
|
||||||
|
isCurrentRow,
|
||||||
|
} from "@dashboard/products/utils/datagrid";
|
||||||
|
|
||||||
|
export function getStockData(
|
||||||
|
data: DatagridChange[],
|
||||||
|
currentIndex: number,
|
||||||
|
removedIds: number[],
|
||||||
|
) {
|
||||||
|
return data
|
||||||
|
.filter(change => byHavingStockColumn(change, currentIndex, removedIds))
|
||||||
|
.map(toStockData)
|
||||||
|
.filter(byStockWithQuantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVaraintUpdateStockData(
|
||||||
|
data: DatagridChange[],
|
||||||
|
currentIndex: number,
|
||||||
|
removedIds: number[],
|
||||||
|
variant: ProductFragment["variants"][number],
|
||||||
|
) {
|
||||||
|
return data
|
||||||
|
.filter(change => byHavingStockColumn(change, currentIndex, removedIds))
|
||||||
|
.map(toStockData)
|
||||||
|
.reduce<ProductVariantStocksUpdateInput>(toUpdateStockData(variant), {
|
||||||
|
create: [],
|
||||||
|
update: [],
|
||||||
|
remove: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toUpdateStockData(variant: ProductFragment["variants"][number]) {
|
||||||
|
return (
|
||||||
|
acc: ProductVariantStocksUpdateInput,
|
||||||
|
stock: ReturnType<typeof toStockData>,
|
||||||
|
) => {
|
||||||
|
const variantStock = variant.stocks.find(
|
||||||
|
variantStock => variantStock.warehouse.id === stock.warehouse,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (stock.quantity === numberCellEmptyValue) {
|
||||||
|
if (variantStock) {
|
||||||
|
acc.remove.push(variantStock.id);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variantStock) {
|
||||||
|
acc.update.push({
|
||||||
|
quantity: stock.quantity as number,
|
||||||
|
stock: variantStock.id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
acc.create.push(stock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function toStockData(change: DatagridChange) {
|
||||||
|
return {
|
||||||
|
warehouse: getColumnStock(change.column),
|
||||||
|
quantity: change.data.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function byStockWithQuantity(stock: { quantity: unknown }) {
|
||||||
|
return stock.quantity !== numberCellEmptyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function byHavingStockColumn(
|
||||||
|
change: DatagridChange,
|
||||||
|
currentIndex: number,
|
||||||
|
removedIds: number[],
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
getColumnStock(change.column) &&
|
||||||
|
isCurrentRow(change.row, currentIndex, removedIds)
|
||||||
|
);
|
||||||
|
}
|
198
src/products/views/ProductUpdate/handlers/data/stocks.test.ts
Normal file
198
src/products/views/ProductUpdate/handlers/data/stocks.test.ts
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
import { numberCellEmptyValue } from "@dashboard/components/Datagrid/NumberCell";
|
||||||
|
import { DatagridChange } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
|
import { ProductFragment } from "@dashboard/graphql";
|
||||||
|
|
||||||
|
import { getStockData, getVaraintUpdateStockData } from "./stock";
|
||||||
|
|
||||||
|
describe("getStockData", () => {
|
||||||
|
test("should filter and map to stock format", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{
|
||||||
|
data: false,
|
||||||
|
column: "availableInChannel:Q2hhbm5lbDox",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{ column: "attribute:2", row: 2, data: { value: { value: "test2" } } },
|
||||||
|
{ column: "stock:Q2hhbm5lbDox", row: 1, data: { value: "12345" } },
|
||||||
|
{ column: "stock:Q2hhbm5lbDot", row: 1, data: { value: "5666" } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const stocks = getStockData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(stocks).toEqual([
|
||||||
|
{
|
||||||
|
warehouse: "Q2hhbm5lbDox",
|
||||||
|
quantity: "12345",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
warehouse: "Q2hhbm5lbDot",
|
||||||
|
quantity: "5666",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty array when no changes for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const stocks = getStockData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(stocks).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty string when no name column for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "name", row: 2, data: "Joe" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const stocks = getStockData(changeData, 1, []);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(stocks).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getVaraintUpdateStockData", () => {
|
||||||
|
const stocks = [
|
||||||
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjA=",
|
||||||
|
warehouse: {
|
||||||
|
id: "Q2hhbm5lbDox",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjD=",
|
||||||
|
warehouse: {
|
||||||
|
id: "Q2hhbm5lbDot",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
test("should handle update stocks", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "stock:Q2hhbm5lbDox", row: 1, data: { value: "12345" } },
|
||||||
|
{ column: "stock:Q2hhbm5lbDot", row: 1, data: { value: "5666" } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const variantStocks = getVaraintUpdateStockData(changeData, 1, [], {
|
||||||
|
stocks,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(variantStocks).toEqual({
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
update: [
|
||||||
|
{
|
||||||
|
stock: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjA=",
|
||||||
|
quantity: "12345",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stock: "UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjD=",
|
||||||
|
quantity: "5666",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle remove stocks", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{
|
||||||
|
column: "stock:Q2hhbm5lbDox",
|
||||||
|
row: 1,
|
||||||
|
data: { value: numberCellEmptyValue },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
column: "stock:Q2hhbm5lbDot",
|
||||||
|
row: 1,
|
||||||
|
data: { value: numberCellEmptyValue },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const variantStocks = getVaraintUpdateStockData(changeData, 1, [], {
|
||||||
|
stocks,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(variantStocks).toEqual({
|
||||||
|
create: [],
|
||||||
|
remove: [
|
||||||
|
"UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjA=",
|
||||||
|
"UHJvZHVjdFZhcmlhbnRDaGFubmVsTGlzdGluZzoyNjD=",
|
||||||
|
],
|
||||||
|
update: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle create stocks", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "stock:Q2hhbm5lbDof", row: 1, data: { value: "12345" } },
|
||||||
|
{ column: "stock:Q2hhbm5lbDod", row: 1, data: { value: "5666" } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const variantStocks = getVaraintUpdateStockData(changeData, 1, [], {
|
||||||
|
stocks,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(variantStocks).toEqual({
|
||||||
|
create: [
|
||||||
|
{
|
||||||
|
warehouse: "Q2hhbm5lbDof",
|
||||||
|
quantity: "12345",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
warehouse: "Q2hhbm5lbDod",
|
||||||
|
quantity: "5666",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
remove: [],
|
||||||
|
update: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty array when no changes for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "attribute:2", row: 1, data: { value: { value: "test2" } } },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const variantStocks = getVaraintUpdateStockData(changeData, 1, [], {
|
||||||
|
stocks,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(variantStocks).toEqual({ create: [], remove: [], update: [] });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return empty string when no name column for given row", () => {
|
||||||
|
// Arrange
|
||||||
|
const changeData: DatagridChange[] = [
|
||||||
|
{ column: "name", row: 2, data: "Joe" },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const variantStocks = getVaraintUpdateStockData(changeData, 1, [], {
|
||||||
|
stocks,
|
||||||
|
} as ProductFragment["variants"][number]);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(variantStocks).toEqual({ create: [], remove: [], update: [] });
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,24 +1,19 @@
|
||||||
import { FetchResult } from "@apollo/client";
|
import { FetchResult } from "@apollo/client";
|
||||||
import {
|
import {
|
||||||
ProductChannelListingUpdateMutation,
|
|
||||||
ProductErrorCode,
|
ProductErrorCode,
|
||||||
ProductVariantBulkCreateMutation,
|
ProductVariantBulkCreateMutation,
|
||||||
ProductVariantChannelListingUpdateMutation,
|
ProductVariantBulkErrorCode,
|
||||||
ProductVariantChannelListingUpdateMutationVariables,
|
ProductVariantBulkErrorFragment,
|
||||||
StockInput,
|
ProductVariantBulkUpdateMutation,
|
||||||
VariantDatagridStockUpdateMutation,
|
|
||||||
VariantDatagridStockUpdateMutationVariables,
|
|
||||||
VariantDatagridUpdateMutation,
|
|
||||||
VariantDatagridUpdateMutationVariables,
|
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { hasMutationErrors } from "@dashboard/misc";
|
|
||||||
|
|
||||||
export type ProductVariantListError =
|
export type ProductVariantListError =
|
||||||
| {
|
| {
|
||||||
__typename: "DatagridError";
|
__typename: "DatagridError";
|
||||||
attributes: string[] | null;
|
attributes: string[] | null;
|
||||||
error: ProductErrorCode;
|
error: ProductVariantBulkErrorCode;
|
||||||
variantId: string;
|
variantId: string;
|
||||||
|
field?: string;
|
||||||
type: "variantData";
|
type: "variantData";
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
@ -29,7 +24,7 @@ export type ProductVariantListError =
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
__typename: "DatagridError";
|
__typename: "DatagridError";
|
||||||
error: ProductErrorCode;
|
error: ProductVariantBulkErrorCode;
|
||||||
variantId: string;
|
variantId: string;
|
||||||
channelIds: string[];
|
channelIds: string[];
|
||||||
type: "channel";
|
type: "channel";
|
||||||
|
@ -41,80 +36,101 @@ export type ProductVariantListError =
|
||||||
type: "create";
|
type: "create";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getProductVariantListErrors(
|
export function getCreateVariantMutationError(
|
||||||
productChannelsUpdateResult: FetchResult<ProductChannelListingUpdateMutation>,
|
result: FetchResult<ProductVariantBulkCreateMutation>,
|
||||||
variantMutationResults: FetchResult[],
|
|
||||||
): ProductVariantListError[] {
|
): ProductVariantListError[] {
|
||||||
return [productChannelsUpdateResult, ...variantMutationResults]
|
return result.data.productVariantBulkCreate.errors.map<ProductVariantListError>(
|
||||||
.filter(hasMutationErrors)
|
error => ({
|
||||||
.flatMap(result => {
|
__typename: "DatagridError",
|
||||||
if (result.data.productVariantChannelListingUpdate) {
|
type: "create",
|
||||||
const data = result.data as ProductVariantChannelListingUpdateMutation;
|
index: error.index,
|
||||||
return data.productVariantChannelListingUpdate.errors.map<
|
error: error.code,
|
||||||
ProductVariantListError
|
}),
|
||||||
>(error => ({
|
);
|
||||||
__typename: "DatagridError",
|
}
|
||||||
type: "channel",
|
|
||||||
error: error.code,
|
export function getVariantUpdateMutationErrors(
|
||||||
variantId: (result.extensions
|
mutationResult: FetchResult<ProductVariantBulkUpdateMutation>,
|
||||||
.variables as ProductVariantChannelListingUpdateMutationVariables)
|
varaintsIds: string[],
|
||||||
.id,
|
): ProductVariantListError[] {
|
||||||
channelIds: error.channels,
|
const { productVariantBulkUpdate } = mutationResult.data;
|
||||||
}));
|
const generalErrors = productVariantBulkUpdate.errors;
|
||||||
}
|
const variantsErrors = productVariantBulkUpdate.results.flatMap(
|
||||||
|
res => res.errors,
|
||||||
if (result.data.productVariantStocksUpdate) {
|
);
|
||||||
const data = result.data as VariantDatagridStockUpdateMutation;
|
const allErrors = [...generalErrors, ...variantsErrors];
|
||||||
const variables = result.extensions
|
|
||||||
.variables as VariantDatagridStockUpdateMutationVariables;
|
return [
|
||||||
return [
|
...getChannelErrors(allErrors, varaintsIds),
|
||||||
...data.productVariantStocksUpdate.errors.map<
|
...getStockErrors(allErrors, varaintsIds),
|
||||||
ProductVariantListError
|
...getRestOfErrors(allErrors, varaintsIds),
|
||||||
>(error => ({
|
];
|
||||||
__typename: "DatagridError",
|
}
|
||||||
type: "stock",
|
|
||||||
variantId: (variables as VariantDatagridStockUpdateMutationVariables)
|
function getChannelErrors(
|
||||||
.id,
|
errors: ProductVariantBulkErrorFragment[],
|
||||||
warehouseId: (variables.stocks as StockInput[])[error.index]
|
varaintsIds: string[],
|
||||||
.warehouse,
|
) {
|
||||||
})),
|
return errors.reduce<ProductVariantListError[]>((acc, error, index) => {
|
||||||
...data.productVariantStocksDelete.errors.map<
|
if (error.channels?.length) {
|
||||||
ProductVariantListError
|
const variantId = varaintsIds[index];
|
||||||
>(() => ({
|
|
||||||
__typename: "DatagridError",
|
acc.push({
|
||||||
type: "stock",
|
__typename: "DatagridError",
|
||||||
variantId: (variables as VariantDatagridStockUpdateMutationVariables)
|
type: "channel",
|
||||||
.id,
|
error: error.code,
|
||||||
warehouseId: null,
|
variantId,
|
||||||
})),
|
channelIds: error.channels,
|
||||||
];
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.data.productVariantUpdate) {
|
return acc;
|
||||||
const data = result.data as VariantDatagridUpdateMutation;
|
}, []);
|
||||||
const variables = result.extensions
|
}
|
||||||
.variables as VariantDatagridUpdateMutationVariables;
|
|
||||||
return data.productVariantUpdate.errors.map<ProductVariantListError>(
|
function getStockErrors(
|
||||||
error => ({
|
errors: ProductVariantBulkErrorFragment[],
|
||||||
__typename: "DatagridError",
|
varaintsIds: string[],
|
||||||
type: "variantData",
|
) {
|
||||||
variantId: (variables as VariantDatagridUpdateMutationVariables).id,
|
return errors.reduce<ProductVariantListError[]>((acc, error, index) => {
|
||||||
error: error.code,
|
if (error.warehouses?.length) {
|
||||||
attributes: error.attributes,
|
const variantId = varaintsIds[index];
|
||||||
}),
|
|
||||||
);
|
acc.push(
|
||||||
}
|
...error.warehouses.map(
|
||||||
|
warehouse =>
|
||||||
if (result.data.productVariantBulkCreate) {
|
({
|
||||||
const data = result.data as ProductVariantBulkCreateMutation;
|
__typename: "DatagridError",
|
||||||
return data.productVariantBulkCreate.errors.map<
|
variantId,
|
||||||
ProductVariantListError
|
warehouseId: warehouse,
|
||||||
>(error => ({
|
type: "stock",
|
||||||
__typename: "DatagridError",
|
} as const),
|
||||||
type: "create",
|
),
|
||||||
index: error.index,
|
);
|
||||||
error: error.code,
|
}
|
||||||
}));
|
|
||||||
}
|
return acc;
|
||||||
});
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRestOfErrors(
|
||||||
|
errors: ProductVariantBulkErrorFragment[],
|
||||||
|
varaintsIds: string[],
|
||||||
|
) {
|
||||||
|
return errors.reduce<ProductVariantListError[]>((acc, error, index) => {
|
||||||
|
if (!error.warehouses?.length && !error.channels?.length) {
|
||||||
|
const variantId = varaintsIds[index];
|
||||||
|
|
||||||
|
acc.push({
|
||||||
|
__typename: "DatagridError",
|
||||||
|
type: "variantData",
|
||||||
|
variantId,
|
||||||
|
error: error.code,
|
||||||
|
attributes: error.attributes,
|
||||||
|
field: error.field,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { FetchResult } from "@apollo/client";
|
|
||||||
import {
|
import {
|
||||||
mergeAttributeValueDeleteErrors,
|
mergeAttributeValueDeleteErrors,
|
||||||
mergeFileUploadErrors,
|
mergeFileUploadErrors,
|
||||||
|
@ -9,7 +8,7 @@ import {
|
||||||
} from "@dashboard/attributes/utils/handlers";
|
} from "@dashboard/attributes/utils/handlers";
|
||||||
import {
|
import {
|
||||||
AttributeErrorFragment,
|
AttributeErrorFragment,
|
||||||
BulkProductErrorFragment,
|
ErrorPolicyEnum,
|
||||||
MetadataErrorFragment,
|
MetadataErrorFragment,
|
||||||
ProductChannelListingErrorFragment,
|
ProductChannelListingErrorFragment,
|
||||||
ProductErrorFragment,
|
ProductErrorFragment,
|
||||||
|
@ -22,38 +21,34 @@ import {
|
||||||
useProductUpdateMutation,
|
useProductUpdateMutation,
|
||||||
useProductVariantBulkCreateMutation,
|
useProductVariantBulkCreateMutation,
|
||||||
useProductVariantBulkDeleteMutation,
|
useProductVariantBulkDeleteMutation,
|
||||||
|
useProductVariantBulkUpdateMutation,
|
||||||
useUpdateMetadataMutation,
|
useUpdateMetadataMutation,
|
||||||
useUpdatePrivateMetadataMutation,
|
useUpdatePrivateMetadataMutation,
|
||||||
useVariantDatagridChannelListingUpdateMutation,
|
|
||||||
useVariantDatagridStockUpdateMutation,
|
|
||||||
useVariantDatagridUpdateMutation,
|
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import useNotifier from "@dashboard/hooks/useNotifier";
|
import useNotifier from "@dashboard/hooks/useNotifier";
|
||||||
import { commonMessages } from "@dashboard/intl";
|
import { commonMessages } from "@dashboard/intl";
|
||||||
import { ProductUpdateSubmitData } from "@dashboard/products/components/ProductUpdatePage/types";
|
import { ProductUpdateSubmitData } from "@dashboard/products/components/ProductUpdatePage/types";
|
||||||
import { getVariantChannelsInputs } from "@dashboard/products/components/ProductVariants/datagrid/getVariantChannelsInputs";
|
|
||||||
import {
|
|
||||||
getStockInputs,
|
|
||||||
getStocks,
|
|
||||||
getVariantChannels,
|
|
||||||
getVariantInput,
|
|
||||||
getVariantInputs,
|
|
||||||
} from "@dashboard/products/components/ProductVariants/utils";
|
|
||||||
import { getProductErrorMessage } from "@dashboard/utils/errors";
|
import { getProductErrorMessage } from "@dashboard/utils/errors";
|
||||||
import createMetadataUpdateHandler from "@dashboard/utils/handlers/metadataUpdateHandler";
|
import createMetadataUpdateHandler from "@dashboard/utils/handlers/metadataUpdateHandler";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useIntl } from "react-intl";
|
import { useIntl } from "react-intl";
|
||||||
|
|
||||||
import { getProductVariantListErrors, ProductVariantListError } from "./errors";
|
|
||||||
import {
|
import {
|
||||||
|
getCreateVariantMutationError,
|
||||||
|
getVariantUpdateMutationErrors,
|
||||||
|
ProductVariantListError,
|
||||||
|
} from "./errors";
|
||||||
|
import {
|
||||||
|
getBulkVariantUpdateInputs,
|
||||||
|
getCreateVariantInput,
|
||||||
getProductChannelsUpdateVariables,
|
getProductChannelsUpdateVariables,
|
||||||
getProductUpdateVariables,
|
getProductUpdateVariables,
|
||||||
|
hasProductChannelsUpdate,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
|
|
||||||
export type UseProductUpdateHandlerError =
|
export type UseProductUpdateHandlerError =
|
||||||
| ProductErrorWithAttributesFragment
|
| ProductErrorWithAttributesFragment
|
||||||
| ProductErrorFragment
|
| ProductErrorFragment
|
||||||
| BulkProductErrorFragment
|
|
||||||
| AttributeErrorFragment
|
| AttributeErrorFragment
|
||||||
| UploadErrorFragment
|
| UploadErrorFragment
|
||||||
| ProductChannelListingErrorFragment
|
| ProductChannelListingErrorFragment
|
||||||
|
@ -62,6 +57,7 @@ export type UseProductUpdateHandlerError =
|
||||||
type UseProductUpdateHandler = (
|
type UseProductUpdateHandler = (
|
||||||
data: ProductUpdateSubmitData,
|
data: ProductUpdateSubmitData,
|
||||||
) => Promise<Array<UseProductUpdateHandlerError | MetadataErrorFragment>>;
|
) => Promise<Array<UseProductUpdateHandlerError | MetadataErrorFragment>>;
|
||||||
|
|
||||||
interface UseProductUpdateHandlerOpts {
|
interface UseProductUpdateHandlerOpts {
|
||||||
called: boolean;
|
called: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
@ -83,33 +79,26 @@ export function useProductUpdateHandler(
|
||||||
|
|
||||||
const [updateMetadata] = useUpdateMetadataMutation({});
|
const [updateMetadata] = useUpdateMetadataMutation({});
|
||||||
const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({});
|
const [updatePrivateMetadata] = useUpdatePrivateMetadataMutation({});
|
||||||
const [updateStocks] = useVariantDatagridStockUpdateMutation({});
|
const [updateVariants] = useProductVariantBulkUpdateMutation();
|
||||||
const [updateVariant] = useVariantDatagridUpdateMutation();
|
|
||||||
const [createVariants] = useProductVariantBulkCreateMutation();
|
const [createVariants] = useProductVariantBulkCreateMutation();
|
||||||
const [deleteVariants] = useProductVariantBulkDeleteMutation();
|
const [deleteVariants] = useProductVariantBulkDeleteMutation();
|
||||||
|
|
||||||
const [uploadFile] = useFileUploadMutation();
|
const [uploadFile] = useFileUploadMutation();
|
||||||
|
|
||||||
const [updateProduct, updateProductOpts] = useProductUpdateMutation();
|
const [updateProduct, updateProductOpts] = useProductUpdateMutation();
|
||||||
const [
|
const [updateChannels, updateChannelsOpts] =
|
||||||
updateChannels,
|
useProductChannelListingUpdateMutation({
|
||||||
updateChannelsOpts,
|
onCompleted: data => {
|
||||||
] = useProductChannelListingUpdateMutation({
|
if (!!data.productChannelListingUpdate.errors.length) {
|
||||||
onCompleted: data => {
|
data.productChannelListingUpdate.errors.forEach(error =>
|
||||||
if (!!data.productChannelListingUpdate.errors.length) {
|
notify({
|
||||||
data.productChannelListingUpdate.errors.forEach(error =>
|
status: "error",
|
||||||
notify({
|
text: getProductErrorMessage(error, intl),
|
||||||
status: "error",
|
}),
|
||||||
text: getProductErrorMessage(error, intl),
|
);
|
||||||
}),
|
}
|
||||||
);
|
},
|
||||||
}
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const [
|
|
||||||
updateVariantChannels,
|
|
||||||
] = useVariantDatagridChannelListingUpdateMutation();
|
|
||||||
|
|
||||||
const [deleteAttributeValue] = useAttributeValueDeleteMutation();
|
const [deleteAttributeValue] = useAttributeValueDeleteMutation();
|
||||||
|
|
||||||
|
@ -117,84 +106,103 @@ export function useProductUpdateHandler(
|
||||||
data: ProductUpdateSubmitData,
|
data: ProductUpdateSubmitData,
|
||||||
): Promise<UseProductUpdateHandlerError[]> => {
|
): Promise<UseProductUpdateHandlerError[]> => {
|
||||||
let errors: UseProductUpdateHandlerError[] = [];
|
let errors: UseProductUpdateHandlerError[] = [];
|
||||||
|
const variantErrors: ProductVariantListError[] = [];
|
||||||
|
|
||||||
const uploadFilesResult = await handleUploadMultipleFiles(
|
const uploadFilesResult = await handleUploadMultipleFiles(
|
||||||
data.attributesWithNewFileValue,
|
data.attributesWithNewFileValue,
|
||||||
variables => uploadFile({ variables }),
|
variables => uploadFile({ variables }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteAttributeValuesResult = await handleDeleteMultipleAttributeValues(
|
const deleteAttributeValuesResult =
|
||||||
data.attributesWithNewFileValue,
|
await handleDeleteMultipleAttributeValues(
|
||||||
product?.attributes,
|
data.attributesWithNewFileValue,
|
||||||
variables => deleteAttributeValue({ variables }),
|
product?.attributes,
|
||||||
|
variables => deleteAttributeValue({ variables }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateProductChannelsData = getProductChannelsUpdateVariables(
|
||||||
|
product,
|
||||||
|
data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (hasProductChannelsUpdate(updateProductChannelsData.input)) {
|
||||||
|
const updateChannelsResult = await updateChannels({
|
||||||
|
variables: updateProductChannelsData,
|
||||||
|
});
|
||||||
|
|
||||||
|
errors = [
|
||||||
|
...errors,
|
||||||
|
...updateChannelsResult.data.productChannelListingUpdate.errors,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.variants.removed.length > 0) {
|
||||||
|
const deleteVaraintsResult = await deleteVariants({
|
||||||
|
variables: {
|
||||||
|
ids: data.variants.removed.map(index => product.variants[index].id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
errors = [
|
||||||
|
...errors,
|
||||||
|
...deleteVaraintsResult.data.productVariantBulkDelete.errors,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateProductResult = await updateProduct({
|
||||||
|
variables: getProductUpdateVariables(product, data, uploadFilesResult),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.variants.added.length > 0) {
|
||||||
|
const createVariantsResults = await createVariants({
|
||||||
|
variables: {
|
||||||
|
id: product.id,
|
||||||
|
inputs: data.variants.added.map(index => ({
|
||||||
|
...getCreateVariantInput(data.variants, index),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const createVariantsErrors = getCreateVariantMutationError(
|
||||||
|
createVariantsResults,
|
||||||
|
);
|
||||||
|
|
||||||
|
errors.push(...createVariantsErrors);
|
||||||
|
variantErrors.push(...createVariantsErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.variants.updates.length > 0) {
|
||||||
|
const updateInputdData = getBulkVariantUpdateInputs(
|
||||||
|
product.variants,
|
||||||
|
data.variants,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updateInputdData.length) {
|
||||||
|
const updateVariantsResults = await updateVariants({
|
||||||
|
variables: {
|
||||||
|
product: product.id,
|
||||||
|
input: updateInputdData,
|
||||||
|
errorPolicy: ErrorPolicyEnum.REJECT_FAILED_ROWS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateVariantsErrors = getVariantUpdateMutationErrors(
|
||||||
|
updateVariantsResults,
|
||||||
|
updateInputdData.map(data => data.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
variantErrors.push(...updateVariantsErrors);
|
||||||
|
errors.push(...updateVariantsErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
errors = [
|
errors = [
|
||||||
...errors,
|
...errors,
|
||||||
...mergeFileUploadErrors(uploadFilesResult),
|
...mergeFileUploadErrors(uploadFilesResult),
|
||||||
...mergeAttributeValueDeleteErrors(deleteAttributeValuesResult),
|
...mergeAttributeValueDeleteErrors(deleteAttributeValuesResult),
|
||||||
|
...updateProductResult.data.productUpdate.errors,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (data.variants.removed.length > 0) {
|
|
||||||
errors.push(
|
|
||||||
...(
|
|
||||||
await deleteVariants({
|
|
||||||
variables: {
|
|
||||||
ids: data.variants.removed.map(
|
|
||||||
index => product.variants[index].id,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
).data.productVariantBulkDelete.errors,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await updateProduct({
|
|
||||||
variables: getProductUpdateVariables(product, data, uploadFilesResult),
|
|
||||||
});
|
|
||||||
errors = [...errors, ...result.data.productUpdate.errors];
|
|
||||||
|
|
||||||
const productChannelsUpdateResult = await updateChannels({
|
|
||||||
variables: getProductChannelsUpdateVariables(product, data),
|
|
||||||
});
|
|
||||||
|
|
||||||
const mutations: Array<Promise<FetchResult>> = [
|
|
||||||
...getStocks(product.variants, data.variants).map(variables =>
|
|
||||||
updateStocks({ variables }),
|
|
||||||
),
|
|
||||||
...getVariantInputs(product.variants, data.variants).map(variables =>
|
|
||||||
updateVariant({ variables }),
|
|
||||||
),
|
|
||||||
...getVariantChannels(product.variants, data.variants).map(variables =>
|
|
||||||
updateVariantChannels({
|
|
||||||
variables,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (data.variants.added.length > 0) {
|
|
||||||
mutations.push(
|
|
||||||
createVariants({
|
|
||||||
variables: {
|
|
||||||
id: product.id,
|
|
||||||
inputs: data.variants.added.map(index => ({
|
|
||||||
...getVariantInput(data.variants, index),
|
|
||||||
channelListings: getVariantChannelsInputs(data.variants, index),
|
|
||||||
stocks: getStockInputs(data.variants, index).stocks,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const variantMutationResults = await Promise.all<FetchResult>(mutations);
|
|
||||||
|
|
||||||
const variantErrors = getProductVariantListErrors(
|
|
||||||
productChannelsUpdateResult,
|
|
||||||
variantMutationResults,
|
|
||||||
);
|
|
||||||
|
|
||||||
errors = [...errors, ...variantErrors];
|
|
||||||
|
|
||||||
setVariantListErrors(variantErrors);
|
setVariantListErrors(variantErrors);
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
import { DatagridChangeOpts } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
import { ProductFragment } from "@dashboard/graphql";
|
import { ProductFragment } from "@dashboard/graphql";
|
||||||
import { ProductUpdateSubmitData } from "@dashboard/products/components/ProductUpdatePage/types";
|
import { ProductUpdateSubmitData } from "@dashboard/products/components/ProductUpdatePage/types";
|
||||||
|
|
||||||
import { inferProductChannelsAfterUpdate } from "./utils";
|
import { product } from "../../../fixtures";
|
||||||
|
import {
|
||||||
|
getBulkVariantUpdateInputs,
|
||||||
|
getCreateVariantInput,
|
||||||
|
inferProductChannelsAfterUpdate,
|
||||||
|
} from "./utils";
|
||||||
|
|
||||||
describe("Product update utils", () => {
|
describe("Product update utils", () => {
|
||||||
it("should infer product channels after update with data", () => {
|
it("should infer product channels after update with data", () => {
|
||||||
|
@ -74,3 +80,268 @@ describe("Product update utils", () => {
|
||||||
expect(result).toEqual(["1", "2", "3"]);
|
expect(result).toEqual(["1", "2", "3"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("getCreateVariantInput", () => {
|
||||||
|
test("should return input data base on datagrid change data", () => {
|
||||||
|
// Arrange
|
||||||
|
const inputData: DatagridChangeOpts = {
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
data: "new item",
|
||||||
|
column: "name",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "23423",
|
||||||
|
column: "sku",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: true,
|
||||||
|
column: "availableInChannel:Q2hhbm5lbDoyMjQz",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 33434,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
column: "channel:Q2hhbm5lbDoyMjQz",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: true,
|
||||||
|
column: "availableInChannel:Q2hhbm5lbDoyNTQy",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 434,
|
||||||
|
currency: "BHD",
|
||||||
|
},
|
||||||
|
column: "channel:Q2hhbm5lbDoyNTQy",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
kind: "number-cell",
|
||||||
|
value: 3223,
|
||||||
|
},
|
||||||
|
column:
|
||||||
|
"stock:V2FyZWhvdXNlOmQ0YzI0ODQxLTg2MDgtNGFiNC04MDkzLWUxNmQ4NWNlYjdkYQ==",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
allowCustomValues: true,
|
||||||
|
emptyOption: true,
|
||||||
|
kind: "dropdown-cell",
|
||||||
|
value: {
|
||||||
|
label: "1l",
|
||||||
|
value: "1l",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column: "attribute:QXR0cmlidXRlOjE1",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
removed: [],
|
||||||
|
added: [1],
|
||||||
|
};
|
||||||
|
// Act
|
||||||
|
const createDataInput = getCreateVariantInput(inputData, 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(createDataInput).toEqual({
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
id: "QXR0cmlidXRlOjE1",
|
||||||
|
values: ["1l"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sku: "23423",
|
||||||
|
name: "new item",
|
||||||
|
channelListings: [
|
||||||
|
{
|
||||||
|
channelId: "Q2hhbm5lbDoyMjQz",
|
||||||
|
price: 33434,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
channelId: "Q2hhbm5lbDoyNTQy",
|
||||||
|
price: 434,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stocks: [
|
||||||
|
{
|
||||||
|
warehouse:
|
||||||
|
"V2FyZWhvdXNlOmQ0YzI0ODQxLTg2MDgtNGFiNC04MDkzLWUxNmQ4NWNlYjdkYQ==",
|
||||||
|
quantity: 3223,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should return only sku and name", () => {
|
||||||
|
// Arrange
|
||||||
|
const inputData: DatagridChangeOpts = {
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
data: "new item",
|
||||||
|
column: "name",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "23423",
|
||||||
|
column: "sku",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
removed: [],
|
||||||
|
added: [1],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const createDataInput = getCreateVariantInput(inputData, 1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(createDataInput).toEqual({
|
||||||
|
attributes: [],
|
||||||
|
sku: "23423",
|
||||||
|
name: "new item",
|
||||||
|
channelListings: [],
|
||||||
|
stocks: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getBulkVariantUpdateInputs", () => {
|
||||||
|
test("should return input data base on datagrid change data for multiple variants", () => {
|
||||||
|
// Arrange
|
||||||
|
const variants: ProductFragment["variants"] =
|
||||||
|
product("http://google.com").variants;
|
||||||
|
|
||||||
|
const inputData: DatagridChangeOpts = {
|
||||||
|
updates: [
|
||||||
|
{
|
||||||
|
data: "item 1",
|
||||||
|
column: "name",
|
||||||
|
row: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "new item",
|
||||||
|
column: "name",
|
||||||
|
row: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "2345555",
|
||||||
|
column: "sku",
|
||||||
|
row: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 234,
|
||||||
|
currency: "USD",
|
||||||
|
},
|
||||||
|
column: `channel:${variants[2].channelListings[0].channel.id}`,
|
||||||
|
row: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
kind: "money-cell",
|
||||||
|
value: 565,
|
||||||
|
currency: "BHD",
|
||||||
|
},
|
||||||
|
column: `channel:${variants[2].channelListings[1].channel.id}`,
|
||||||
|
row: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
kind: "number-cell",
|
||||||
|
value: 2344,
|
||||||
|
},
|
||||||
|
column: `stock:${variants[2].stocks[0].warehouse.id}`,
|
||||||
|
row: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
allowCustomValues: true,
|
||||||
|
emptyOption: true,
|
||||||
|
kind: "dropdown-cell",
|
||||||
|
value: {
|
||||||
|
label: "2l",
|
||||||
|
value: "2l",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
column: `attribute:${variants[2].attributes[0].attribute.id}`,
|
||||||
|
row: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
removed: [],
|
||||||
|
added: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const bulkVariantUpdateInput = getBulkVariantUpdateInputs(
|
||||||
|
variants,
|
||||||
|
inputData,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
expect(bulkVariantUpdateInput).toEqual([
|
||||||
|
{
|
||||||
|
id: variants[1].id,
|
||||||
|
attributes: [],
|
||||||
|
name: "item 1",
|
||||||
|
stocks: {
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
update: [],
|
||||||
|
},
|
||||||
|
channelListings: {
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
update: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: variants[2].id,
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
id: variants[2].attributes[0].attribute.id,
|
||||||
|
values: ["2l"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sku: "2345555",
|
||||||
|
name: "new item",
|
||||||
|
stocks: {
|
||||||
|
update: [
|
||||||
|
{
|
||||||
|
stock: variants[2].stocks[0].id,
|
||||||
|
quantity: 2344,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
create: [],
|
||||||
|
remove: [],
|
||||||
|
},
|
||||||
|
channelListings: {
|
||||||
|
update: [
|
||||||
|
{
|
||||||
|
channelListing: variants[2].channelListings[0].id,
|
||||||
|
price: 234,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
channelListing: variants[2].channelListings[1].id,
|
||||||
|
price: 565,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
remove: [],
|
||||||
|
create: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
import { FetchResult } from "@apollo/client";
|
import { FetchResult } from "@apollo/client";
|
||||||
import { getAttributesAfterFileAttributesUpdate } from "@dashboard/attributes/utils/data";
|
import { getAttributesAfterFileAttributesUpdate } from "@dashboard/attributes/utils/data";
|
||||||
import { prepareAttributesInput } from "@dashboard/attributes/utils/handlers";
|
import { prepareAttributesInput } from "@dashboard/attributes/utils/handlers";
|
||||||
|
import { DatagridChangeOpts } from "@dashboard/components/Datagrid/useDatagridChange";
|
||||||
import { VALUES_PAGINATE_BY } from "@dashboard/config";
|
import { VALUES_PAGINATE_BY } from "@dashboard/config";
|
||||||
import {
|
import {
|
||||||
FileUploadMutation,
|
FileUploadMutation,
|
||||||
ProductChannelListingAddInput,
|
ProductChannelListingAddInput,
|
||||||
|
ProductChannelListingUpdateInput,
|
||||||
ProductChannelListingUpdateMutationVariables,
|
ProductChannelListingUpdateMutationVariables,
|
||||||
ProductFragment,
|
ProductFragment,
|
||||||
|
ProductVariantBulkUpdateInput,
|
||||||
} from "@dashboard/graphql";
|
} from "@dashboard/graphql";
|
||||||
import { ProductUpdateSubmitData } from "@dashboard/products/components/ProductUpdatePage/types";
|
import { ProductUpdateSubmitData } from "@dashboard/products/components/ProductUpdatePage/types";
|
||||||
import { getColumnChannelAvailability } from "@dashboard/products/components/ProductVariants/datagrid/columnData";
|
|
||||||
import { getAttributeInputFromProduct } from "@dashboard/products/utils/data";
|
import { getAttributeInputFromProduct } from "@dashboard/products/utils/data";
|
||||||
import { getParsedDataForJsonStringField } from "@dashboard/utils/richText/misc";
|
import { getParsedDataForJsonStringField } from "@dashboard/utils/richText/misc";
|
||||||
import pick from "lodash/pick";
|
import pick from "lodash/pick";
|
||||||
import uniq from "lodash/uniq";
|
import uniq from "lodash/uniq";
|
||||||
|
|
||||||
|
import { getAttributeData } from "./data/attributes";
|
||||||
|
import {
|
||||||
|
getUpdateVariantChannelInputs,
|
||||||
|
getVariantChannelsInputs,
|
||||||
|
} from "./data/channel";
|
||||||
|
import { getNameData } from "./data/name";
|
||||||
|
import { getSkuData } from "./data/sku";
|
||||||
|
import { getStockData, getVaraintUpdateStockData } from "./data/stock";
|
||||||
|
|
||||||
export function getProductUpdateVariables(
|
export function getProductUpdateVariables(
|
||||||
product: ProductFragment,
|
product: ProductFragment,
|
||||||
data: ProductUpdateSubmitData,
|
data: ProductUpdateSubmitData,
|
||||||
|
@ -49,34 +60,14 @@ export function getProductUpdateVariables(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasChannel = (
|
export function getCreateVariantInput(data: DatagridChangeOpts, index: number) {
|
||||||
channelId: string,
|
return {
|
||||||
variant?: ProductFragment["variants"][number],
|
attributes: getAttributeData(data.updates, index, data.removed),
|
||||||
) => {
|
sku: getSkuData(data.updates, index, data.removed),
|
||||||
if (!variant) {
|
name: getNameData(data.updates, index, data.removed),
|
||||||
return false;
|
channelListings: getVariantChannelsInputs(data, index),
|
||||||
}
|
stocks: getStockData(data.updates, index, data.removed),
|
||||||
|
};
|
||||||
return variant.channelListings.some(c => c.channel.id === channelId);
|
|
||||||
};
|
|
||||||
|
|
||||||
export function inferProductChannelsAfterUpdate(
|
|
||||||
product: ProductFragment,
|
|
||||||
data: ProductUpdateSubmitData,
|
|
||||||
) {
|
|
||||||
const productChannelsIds = product.channelListings.map(
|
|
||||||
listing => listing.channel.id,
|
|
||||||
);
|
|
||||||
const updatedChannelsIds =
|
|
||||||
data.channels.updateChannels?.map(listing => listing.channelId) || [];
|
|
||||||
const removedChannelsIds = data.channels.removeChannels || [];
|
|
||||||
|
|
||||||
return uniq([
|
|
||||||
...productChannelsIds.filter(
|
|
||||||
channelId => !removedChannelsIds.includes(channelId),
|
|
||||||
),
|
|
||||||
...updatedChannelsIds,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProductChannelsUpdateVariables(
|
export function getProductChannelsUpdateVariables(
|
||||||
|
@ -105,41 +96,10 @@ export function getProductChannelsUpdateVariables(
|
||||||
)
|
)
|
||||||
.forEach(listing => dataUpdated.set(listing.channelId, listing));
|
.forEach(listing => dataUpdated.set(listing.channelId, listing));
|
||||||
|
|
||||||
const variantsUpdates = new Map<string, ProductChannelListingAddInput>();
|
|
||||||
channels
|
|
||||||
.map(channelId => ({
|
|
||||||
channelId,
|
|
||||||
addVariants: data.variants.updates
|
|
||||||
.filter(
|
|
||||||
change =>
|
|
||||||
!data.variants.added.includes(change.row) &&
|
|
||||||
!hasChannel(channelId, product.variants[change.row]) &&
|
|
||||||
channelId === getColumnChannelAvailability(change.column) &&
|
|
||||||
change.data,
|
|
||||||
)
|
|
||||||
.map(change => product.variants[change.row].id),
|
|
||||||
removeVariants: data.variants.updates
|
|
||||||
.filter(
|
|
||||||
change =>
|
|
||||||
product.variants[change.row] &&
|
|
||||||
channelId === getColumnChannelAvailability(change.column) &&
|
|
||||||
!change.data,
|
|
||||||
)
|
|
||||||
.map(change => product.variants[change.row].id),
|
|
||||||
}))
|
|
||||||
.filter(
|
|
||||||
listing =>
|
|
||||||
listing.addVariants.length > 0 || listing.removeVariants.length > 0,
|
|
||||||
)
|
|
||||||
.forEach(listing => variantsUpdates.set(listing.channelId, listing));
|
|
||||||
|
|
||||||
const updateChannels = channels
|
const updateChannels = channels
|
||||||
.filter(
|
.filter(channelId => dataUpdated.has(channelId))
|
||||||
channelId => dataUpdated.has(channelId) || variantsUpdates.has(channelId),
|
|
||||||
)
|
|
||||||
.map(channelId => ({
|
.map(channelId => ({
|
||||||
...dataUpdated.get(channelId),
|
...dataUpdated.get(channelId),
|
||||||
...variantsUpdates.get(channelId),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -150,3 +110,63 @@ export function getProductChannelsUpdateVariables(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hasProductChannelsUpdate(
|
||||||
|
data: ProductChannelListingUpdateInput,
|
||||||
|
) {
|
||||||
|
return data?.removeChannels?.length || data?.updateChannels?.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBulkVariantUpdateInputs(
|
||||||
|
variants: ProductFragment["variants"],
|
||||||
|
data: DatagridChangeOpts,
|
||||||
|
): ProductVariantBulkUpdateInput[] {
|
||||||
|
const toUpdateInput = createToUpdateInput(data);
|
||||||
|
return variants.map(toUpdateInput).filter(byAvailability);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createToUpdateInput =
|
||||||
|
(data: DatagridChangeOpts) =>
|
||||||
|
(variant, variantIndex): ProductVariantBulkUpdateInput => ({
|
||||||
|
id: variant.id,
|
||||||
|
attributes: getAttributeData(data.updates, variantIndex, data.removed),
|
||||||
|
sku: getSkuData(data.updates, variantIndex, data.removed),
|
||||||
|
name: getNameData(data.updates, variantIndex, data.removed),
|
||||||
|
stocks: getVaraintUpdateStockData(
|
||||||
|
data.updates,
|
||||||
|
variantIndex,
|
||||||
|
data.removed,
|
||||||
|
variant,
|
||||||
|
),
|
||||||
|
channelListings: getUpdateVariantChannelInputs(data, variantIndex, variant),
|
||||||
|
});
|
||||||
|
|
||||||
|
const byAvailability = (variant: ProductVariantBulkUpdateInput): boolean =>
|
||||||
|
variant.name !== undefined ||
|
||||||
|
variant.sku !== undefined ||
|
||||||
|
variant.attributes.length > 0 ||
|
||||||
|
variant.stocks.create.length > 0 ||
|
||||||
|
variant.stocks.update.length > 0 ||
|
||||||
|
variant.stocks.remove.length > 0 ||
|
||||||
|
variant.channelListings.update.length > 0 ||
|
||||||
|
variant.channelListings.remove.length > 0 ||
|
||||||
|
variant.channelListings.create.length > 0;
|
||||||
|
|
||||||
|
export function inferProductChannelsAfterUpdate(
|
||||||
|
product: ProductFragment,
|
||||||
|
data: ProductUpdateSubmitData,
|
||||||
|
) {
|
||||||
|
const productChannelsIds = product.channelListings.map(
|
||||||
|
listing => listing.channel.id,
|
||||||
|
);
|
||||||
|
const updatedChannelsIds =
|
||||||
|
data.channels.updateChannels?.map(listing => listing.channelId) || [];
|
||||||
|
const removedChannelsIds = data.channels.removeChannels || [];
|
||||||
|
|
||||||
|
return uniq([
|
||||||
|
...productChannelsIds.filter(
|
||||||
|
channelId => !removedChannelsIds.includes(channelId),
|
||||||
|
),
|
||||||
|
...updatedChannelsIds,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue