Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove corresponding draft order lines when variant is removing #6119

Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -52,6 +52,7 @@ All notable, unreleased changes to this project will be documented in this file.
- Update default decimal places - #6098 by @IKarbowiak
- Avoid assigning the same pictures twice to a variant - #6112 by @IKarbowiak
- Fix crashing system when avalara is improperly configured - #6117 by @IKarbowiak
- Remove corresponding draft order lines when variant is removing - #6119 by @IKarbowiak

## 2.10.2

Expand Down
38 changes: 37 additions & 1 deletion saleor/graphql/product/bulk_mutations/products.py
Expand Up @@ -5,6 +5,7 @@
from django.db import transaction

from ....core.permissions import ProductPermissions
from ....order import OrderStatus, models as order_models
from ....product import models
from ....product.error_codes import ProductErrorCode
from ....product.tasks import update_product_minimal_variant_price_task
Expand Down Expand Up @@ -35,7 +36,7 @@
ProductVariantInput,
StockInput,
)
from ..types import ProductVariant
from ..types import Product, ProductVariant
from ..utils import create_stocks, get_used_variants_attribute_values


Expand Down Expand Up @@ -108,6 +109,24 @@ class Meta:
error_type_class = ProductError
error_type_field = "product_errors"

@classmethod
def perform_mutation(cls, _root, info, ids, **data):
_, pks = resolve_global_ids_to_primary_keys(ids, Product)
variants = models.ProductVariant.objects.filter(product__pk__in=pks)
# get draft order lines for products
order_line_pks = list(
order_models.OrderLine.objects.filter(
variant__in=variants, order__status=OrderStatus.DRAFT
).values_list("pk", flat=True)
)

response = super().perform_mutation(_root, info, ids, **data)

# delete order lines for deleted variants
order_models.OrderLine.objects.filter(pk__in=order_line_pks).delete()

return response


class ProductVariantBulkCreateInput(ProductVariantInput):
attributes = graphene.List(
Expand Down Expand Up @@ -342,6 +361,23 @@ class Meta:
error_type_class = ProductError
error_type_field = "product_errors"

@classmethod
def perform_mutation(cls, _root, info, ids, **data):
_, pks = resolve_global_ids_to_primary_keys(ids, ProductVariant)
# get draft order lines for variants
order_line_pks = list(
order_models.OrderLine.objects.filter(
variant__pk__in=pks, order__status=OrderStatus.DRAFT
).values_list("pk", flat=True)
)

response = super().perform_mutation(_root, info, ids, **data)

# delete order lines for deleted variants
order_models.OrderLine.objects.filter(pk__in=order_line_pks).delete()

return response


class ProductVariantStocksCreate(BaseMutation):
product_variant = graphene.Field(
Expand Down
69 changes: 68 additions & 1 deletion saleor/graphql/product/mutations/products.py
Expand Up @@ -12,6 +12,7 @@

from ....core.exceptions import PermissionDenied
from ....core.permissions import ProductPermissions
from ....order import OrderStatus, models as order_models
from ....product import models
from ....product.error_codes import ProductErrorCode
from ....product.tasks import (
Expand Down Expand Up @@ -44,7 +45,14 @@
from ...core.validators import validate_price_precision
from ...meta.deprecated.mutations import ClearMetaBaseMutation, UpdateMetaBaseMutation
from ...warehouse.types import Warehouse
from ..types import Category, Collection, Product, ProductImage, ProductVariant
from ..types import (
Category,
Collection,
Product,
ProductImage,
ProductType,
ProductVariant,
)
from ..utils import (
create_stocks,
get_used_attribute_values_for_variant,
Expand Down Expand Up @@ -1066,6 +1074,25 @@ class Meta:
error_type_class = ProductError
error_type_field = "product_errors"

@classmethod
def perform_mutation(cls, _root, info, **data):
node_id = data.get("id")
instance = cls.get_node_or_error(info, node_id, only_type=Product)

# get draft order lines for variant
line_pks = list(
order_models.OrderLine.objects.filter(
variant__in=instance.variants.all(), order__status=OrderStatus.DRAFT
).values_list("pk", flat=True)
)

response = super().perform_mutation(_root, info, **data)

# delete order lines for deleted variant
order_models.OrderLine.objects.filter(pk__in=line_pks).delete()

return response


class ProductUpdateMeta(UpdateMetaBaseMutation):
class Meta:
Expand Down Expand Up @@ -1372,6 +1399,25 @@ def success_response(cls, instance):
update_product_minimal_variant_price_task.delay(instance.product_id)
return super().success_response(instance)

@classmethod
def perform_mutation(cls, _root, info, **data):
node_id = data.get("id")
variant_pk = from_global_id_strict_type(node_id, ProductVariant, field="pk")

# get draft order lines for variant
line_pks = list(
order_models.OrderLine.objects.filter(
variant__pk=variant_pk, order__status=OrderStatus.DRAFT
).values_list("pk", flat=True)
)

response = super().perform_mutation(_root, info, **data)

# delete order lines for deleted variant
order_models.OrderLine.objects.filter(pk__in=line_pks).delete()

return response


class ProductVariantUpdateMeta(UpdateMetaBaseMutation):
class Meta:
Expand Down Expand Up @@ -1542,6 +1588,27 @@ class Meta:
error_type_class = ProductError
error_type_field = "product_errors"

@classmethod
def perform_mutation(cls, _root, info, **data):
node_id = data.get("id")
product_type_pk = from_global_id_strict_type(node_id, ProductType, field="pk")
variants_pks = models.Product.objects.filter(
product_type__pk=product_type_pk
).values_list("variants__pk", flat=True)
# get draft order lines for products
order_line_pks = list(
order_models.OrderLine.objects.filter(
variant__pk__in=variants_pks, order__status=OrderStatus.DRAFT
).values_list("pk", flat=True)
)

response = super().perform_mutation(_root, info, **data)

# delete order lines for deleted variants
order_models.OrderLine.objects.filter(pk__in=order_line_pks).delete()

return response


class ProductTypeUpdateMeta(UpdateMetaBaseMutation):
class Meta:
Expand Down
181 changes: 167 additions & 14 deletions saleor/graphql/product/tests/test_bulk_delete.py
Expand Up @@ -2,7 +2,10 @@

import graphene
import pytest
from prices import Money, TaxedMoney

from ....order import OrderStatus
from ....order.models import OrderLine
from ....product.models import (
Attribute,
AttributeValue,
Expand Down Expand Up @@ -213,14 +216,85 @@ def test_delete_collections(
).exists()


def test_delete_products(staff_api_client, product_list, permission_manage_products):
query = """
mutation productBulkDelete($ids: [ID]!) {
productBulkDelete(ids: $ids) {
count
}
DELETE_PRODUCTS_MUTATION = """
mutation productBulkDelete($ids: [ID]!) {
productBulkDelete(ids: $ids) {
count
}
"""
}
"""


def test_delete_products(
staff_api_client, product_list, permission_manage_products, order_list
):
# given
query = DELETE_PRODUCTS_MUTATION

not_draft_order = order_list[0]
draft_order = order_list[1]
draft_order.status = OrderStatus.DRAFT
draft_order.save(update_fields=["status"])

draft_order_lines_pks = []
not_draft_order_lines_pks = []
for variant in [product_list[0].variants.first(), product_list[1].variants.first()]:
net = variant.get_price()
gross = Money(amount=net.amount, currency=net.currency)

order_line = OrderLine.objects.create(
variant=variant,
order=draft_order,
product_name=str(variant.product),
variant_name=str(variant),
product_sku=variant.sku,
is_shipping_required=variant.is_shipping_required(),
unit_price=TaxedMoney(net=net, gross=gross),
quantity=3,
)
draft_order_lines_pks.append(order_line.pk)

order_line_not_draft = OrderLine.objects.create(
variant=variant,
order=not_draft_order,
product_name=str(variant.product),
variant_name=str(variant),
product_sku=variant.sku,
is_shipping_required=variant.is_shipping_required(),
unit_price=TaxedMoney(net=net, gross=gross),
quantity=3,
)
not_draft_order_lines_pks.append(order_line_not_draft.pk)

variables = {
"ids": [
graphene.Node.to_global_id("Product", product.id)
for product in product_list
]
}

# when
response = staff_api_client.post_graphql(
query, variables, permissions=[permission_manage_products]
)

# then
content = get_graphql_content(response)

assert content["data"]["productBulkDelete"]["count"] == 3
assert not Product.objects.filter(
id__in=[product.id for product in product_list]
).exists()

assert not OrderLine.objects.filter(pk__in=draft_order_lines_pks).exists()

assert OrderLine.objects.filter(pk__in=not_draft_order_lines_pks).exists()


def test_delete_products_variants_in_draft_order(
staff_api_client, product_list, permission_manage_products
):
query = DELETE_PRODUCTS_MUTATION

variables = {
"ids": [
Expand Down Expand Up @@ -296,16 +370,19 @@ def test_delete_product_types(
).exists()


PRODUCT_VARIANT_BULK_DELETE_MUTATION = """
mutation productVariantBulkDelete($ids: [ID]!) {
productVariantBulkDelete(ids: $ids) {
count
}
}
"""


def test_delete_product_variants(
staff_api_client, product_variant_list, permission_manage_products
):
query = """
mutation productVariantBulkDelete($ids: [ID]!) {
productVariantBulkDelete(ids: $ids) {
count
}
}
"""
query = PRODUCT_VARIANT_BULK_DELETE_MUTATION

variables = {
"ids": [
Expand All @@ -322,3 +399,79 @@ def test_delete_product_variants(
assert not ProductVariant.objects.filter(
id__in=[variant.id for variant in product_variant_list]
).exists()


def test_delete_product_variants_in_draft_orders(
staff_api_client,
product_variant_list,
permission_manage_products,
order_line,
order_list,
):
# given
query = PRODUCT_VARIANT_BULK_DELETE_MUTATION
variants = product_variant_list

draft_order = order_line.order
draft_order.status = OrderStatus.DRAFT
draft_order.save(update_fields=["status"])

second_variant_in_draft = variants[1]
net = second_variant_in_draft.get_price()
gross = Money(amount=net.amount, currency=net.currency)
second_draft_order = OrderLine.objects.create(
variant=second_variant_in_draft,
order=draft_order,
product_name=str(second_variant_in_draft.product),
variant_name=str(second_variant_in_draft),
product_sku=second_variant_in_draft.sku,
is_shipping_required=second_variant_in_draft.is_shipping_required(),
unit_price=TaxedMoney(net=net, gross=gross),
quantity=3,
)

variant = variants[0]
net = variant.get_price()
gross = Money(amount=net.amount, currency=net.currency)
order_not_draft = order_list[-1]
order_line_not_in_draft = OrderLine.objects.create(
variant=variant,
order=order_not_draft,
product_name=str(variant.product),
variant_name=str(variant),
product_sku=variant.sku,
is_shipping_required=variant.is_shipping_required(),
unit_price=TaxedMoney(net=net, gross=gross),
quantity=3,
)
order_line_not_in_draft_pk = order_line_not_in_draft.pk

variant_count = ProductVariant.objects.count()

variables = {
"ids": [
graphene.Node.to_global_id("ProductVariant", variant.id)
for variant in ProductVariant.objects.all()
]
}

# when
response = staff_api_client.post_graphql(
query, variables, permissions=[permission_manage_products]
)

# then
content = get_graphql_content(response)

assert content["data"]["productVariantBulkDelete"]["count"] == variant_count
assert not ProductVariant.objects.filter(
id__in=[variant.id for variant in product_variant_list]
).exists()

with pytest.raises(order_line._meta.model.DoesNotExist):
order_line.refresh_from_db()

with pytest.raises(second_draft_order._meta.model.DoesNotExist):
second_draft_order.refresh_from_db()

assert OrderLine.objects.filter(pk=order_line_not_in_draft_pk).exists()