diff --git a/kmuhelper/migrations/0113_customer_woocommerce_deleted_and_more.py b/kmuhelper/migrations/0113_customer_woocommerce_deleted_and_more.py new file mode 100644 index 0000000..3fd19e2 --- /dev/null +++ b/kmuhelper/migrations/0113_customer_woocommerce_deleted_and_more.py @@ -0,0 +1,40 @@ +# Generated by Django 5.0.4 on 2024-04-09 14:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("kmuhelper", "0112_product_parent"), + ] + + operations = [ + migrations.AddField( + model_name="customer", + name="woocommerce_deleted", + field=models.BooleanField( + blank=True, default=False, verbose_name="Deleted in WooCommerce?" + ), + ), + migrations.AddField( + model_name="order", + name="woocommerce_deleted", + field=models.BooleanField( + blank=True, default=False, verbose_name="Deleted in WooCommerce?" + ), + ), + migrations.AddField( + model_name="product", + name="woocommerce_deleted", + field=models.BooleanField( + blank=True, default=False, verbose_name="Deleted in WooCommerce?" + ), + ), + migrations.AddField( + model_name="productcategory", + name="woocommerce_deleted", + field=models.BooleanField( + blank=True, default=False, verbose_name="Deleted in WooCommerce?" + ), + ), + ] diff --git a/kmuhelper/modules/integrations/woocommerce/api/_base.py b/kmuhelper/modules/integrations/woocommerce/api/_base.py index 6c233a1..5c2503b 100644 --- a/kmuhelper/modules/integrations/woocommerce/api/_base.py +++ b/kmuhelper/modules/integrations/woocommerce/api/_base.py @@ -72,6 +72,7 @@ def update_object_from_api(self, db_object) -> bool: str(db_object), ) db_object.woocommerceid = 0 + db_object.woocommerce_deleted = True db_object.save() else: self.log(wc_obj) diff --git a/kmuhelper/modules/integrations/woocommerce/filters.py b/kmuhelper/modules/integrations/woocommerce/filters.py new file mode 100644 index 0000000..19e569d --- /dev/null +++ b/kmuhelper/modules/integrations/woocommerce/filters.py @@ -0,0 +1,71 @@ +from django.contrib import admin +from django.utils.translation import gettext_lazy as _ + +from kmuhelper.modules.integrations.woocommerce.utils import is_connected + + +class WooCommerceStateFilter(admin.SimpleListFilter): + """Filter that filters views based on WooCommerce state.""" + + # Human-readable title which will be displayed in the + # right admin sidebar just above the filter options. + title = _("WooCommerce State") + + # Parameter for the filter that will be used in the URL query. + parameter_name = "wc_state" + + def __init__(self, request, params, model, model_admin): + super(WooCommerceStateFilter, self).__init__( + request, params, model, model_admin + ) + + def lookups(self, request, model_admin): + return [ + ("all", _("All")), + ("linked", _("Linked")), + ("not_linked", _("Not linked")), + ("deleted", _("Deleted")), + ("not_deleted", _("Not deleted")), + ] + + def queryset(self, request, queryset): + """ + Returns the filtered queryset based on the value + provided in the query string and retrievable via + `self.value()`. + """ + + if self.value() is None: + # default option + self.used_parameters[self.parameter_name] = "not_deleted" + + if self.value() == "linked": + return queryset.exclude(woocommerceid=0) + if self.value() == "not_linked": + return queryset.filter(woocommerceid=0) + if self.value() == "deleted": + return queryset.filter(woocommerce_deleted=True) + if self.value() == "not_deleted": + return queryset.exclude(woocommerce_deleted=True) + + return queryset + + def choices(self, changelist): + add_facets = changelist.add_facets + facet_counts = self.get_facet_queryset(changelist) if add_facets else None + for i, (lookup, title) in enumerate(self.lookup_choices): + if add_facets: + if (count := facet_counts.get(f"{i}__c", -1)) != -1: + title = f"{title} ({count})" + else: + title = f"{title} (-)" + yield { + "selected": self.value() == str(lookup), + "query_string": changelist.get_query_string( + {self.parameter_name: lookup} + ), + "display": title, + } + + def has_output(self): + return is_connected() and super().has_output() diff --git a/kmuhelper/modules/integrations/woocommerce/mixins.py b/kmuhelper/modules/integrations/woocommerce/mixins.py index dbc4c2b..d0b7655 100644 --- a/kmuhelper/modules/integrations/woocommerce/mixins.py +++ b/kmuhelper/modules/integrations/woocommerce/mixins.py @@ -4,9 +4,11 @@ from django.utils.translation import gettext_lazy as _ from kmuhelper import settings -WC_ID_PRESENT_DESCRIPTION = format_html( - '', - _("Mit WooCommerce-Objekt verknüpft?"), +WC_STATE_DESCRIPTION = format_html( + '', + _( + "WooCommerce-Status (grün = verknüpft, rot = verknüpft, aber auf WooCommerce gelöscht, grau = nicht verknüpft)" + ), ) @@ -18,6 +20,13 @@ class WooCommerceModelMixin(models.Model): default=0, ) + woocommerce_deleted = models.BooleanField( + verbose_name=_("Deleted in WooCommerce?"), + default=False, + blank=True, + null=False, + ) + def get_woocommerce_url(self): return self.WOOCOMMERCE_URL_FORMAT.format( settings.get_secret_db_setting("wc-url"), self.woocommerceid @@ -33,16 +42,21 @@ def display_woocommerce_id(self): link = self.get_woocommerce_url() return format_html( - '#{}', + '#{} {}', link, str(self.woocommerceid), + _("(Deleted)") if self.woocommerce_deleted else "", ) @admin.display( - description=WC_ID_PRESENT_DESCRIPTION, ordering="woocommerceid", boolean=True + description=WC_STATE_DESCRIPTION, + ordering="woocommerceid", + boolean=True, ) - def display_woocommerce_id_present(self): - return bool(self.woocommerceid) + def display_woocommerce_state(self): + if not self.woocommerceid: + return None + return not self.woocommerce_deleted class Meta: abstract = True diff --git a/kmuhelper/modules/integrations/woocommerce/views.py b/kmuhelper/modules/integrations/woocommerce/views.py index 8327ebd..0b4a535 100644 --- a/kmuhelper/modules/integrations/woocommerce/views.py +++ b/kmuhelper/modules/integrations/woocommerce/views.py @@ -350,53 +350,56 @@ def wc_webhooks(request): log("WooCommerce Webhook is being processed...") + delivery_id = request.headers["x-wc-webhook-delivery-id"] topic = request.headers["x-wc-webhook-topic"] wc_obj = json.loads(request.body) + log("Delivery ID: ", delivery_id) log("Topic: ", topic) - log("ID: ", wc_obj.get("id")) - - if topic in ("product.updated", "product.created"): - if Product.objects.filter(woocommerceid=wc_obj["id"]).exists(): - WCProductsAPI().update_object_from_data( - Product.objects.get(woocommerceid=wc_obj["id"]), wc_obj - ) - else: - WCProductsAPI().create_object_from_data(wc_obj) - elif topic == "product.deleted": - if Product.objects.filter(woocommerceid=wc_obj["id"]).exists(): - product = Product.objects.get(woocommerceid=wc_obj["id"]) - product.woocommerceid = 0 - product.save() - elif topic in ("customer.updated", "customer.created"): - if Customer.objects.filter(woocommerceid=wc_obj["id"]).exists(): - WCCustomersAPI().update_object_from_data( - Customer.objects.get(woocommerceid=wc_obj["id"]), wc_obj - ) - else: - WCCustomersAPI().create_object_from_data(wc_obj) - elif topic == "customer.deleted": - if Customer.objects.filter(woocommerceid=wc_obj["id"]).exists(): - customer = Customer.objects.get(woocommerceid=wc_obj["id"]) - customer.woocommerceid = 0 - customer.save() - elif topic in ("order.updated", "order.created"): - if Order.objects.filter(woocommerceid=wc_obj["id"]).exists(): - WCOrdersAPI().update_object_from_data( - Order.objects.get(woocommerceid=wc_obj["id"]), wc_obj + log("Object ID: ", wc_obj.get("id")) + + match topic: + case "product.updated" | "product.created" | "product.restored": + if Product.objects.filter(woocommerceid=wc_obj["id"]).exists(): + product = Product.objects.get(woocommerceid=wc_obj["id"]) + product.woocommerce_deleted = False + WCProductsAPI().update_object_from_data(product, wc_obj) + else: + WCProductsAPI().create_object_from_data(wc_obj) + case "product.deleted": + if Product.objects.filter(woocommerceid=wc_obj["id"]).exists(): + product = Product.objects.get(woocommerceid=wc_obj["id"]) + product.woocommerce_deleted = True + product.save() + case "customer.updated" | "customer.created" | "customer.restored": + if Customer.objects.filter(woocommerceid=wc_obj["id"]).exists(): + customer = Customer.objects.get(woocommerceid=wc_obj["id"]) + customer.woocommerce_deleted = False + WCCustomersAPI().update_object_from_data(customer, wc_obj) + else: + WCCustomersAPI().create_object_from_data(wc_obj) + case "customer.deleted": + if Customer.objects.filter(woocommerceid=wc_obj["id"]).exists(): + customer = Customer.objects.get(woocommerceid=wc_obj["id"]) + customer.woocommerce_deleted = True + customer.save() + case "order.updated" | "order.created" | "order.restored": + if Order.objects.filter(woocommerceid=wc_obj["id"]).exists(): + order = Order.objects.get(woocommerceid=wc_obj["id"]) + order.woocommerce_deleted = False + WCOrdersAPI().update_object_from_data(order, wc_obj) + else: + WCOrdersAPI().create_object_from_data(wc_obj) + case "order.deleted": + if Order.objects.filter(woocommerceid=wc_obj["id"]).exists(): + order = Order.objects.get(woocommerceid=wc_obj["id"]) + order.woocommerce_deleted = True + order.save() + case _: + log(f"[orange_red1]Unknown topic: '{topic}'") + return JsonResponse( + {"accepted": False, "message": "Topic not supported!"}, status=400 ) - else: - WCOrdersAPI().create_object_from_data(wc_obj) - elif topic == "order.deleted": - if Order.objects.filter(woocommerceid=wc_obj["id"]).exists(): - order = Order.objects.get(woocommerceid=wc_obj["id"]) - order.woocommerceid = 0 - order.save() - else: - log(f"[orange_red1]Unknown topic: '{topic}'") - return JsonResponse( - {"accepted": False, "message": "Topic not supported!"}, status=400 - ) return JsonResponse({"accepted": True}) diff --git a/kmuhelper/modules/main/admin.py b/kmuhelper/modules/main/admin.py index 0aed571..21cb439 100644 --- a/kmuhelper/modules/main/admin.py +++ b/kmuhelper/modules/main/admin.py @@ -11,6 +11,7 @@ WCProductsAPI, WCProductCategoriesAPI, ) +from kmuhelper.modules.integrations.woocommerce.filters import WooCommerceStateFilter from kmuhelper.modules.integrations.woocommerce.utils import is_connected from kmuhelper.modules.main import views from kmuhelper.modules.main.models import ( @@ -266,6 +267,7 @@ class OrderAdmin(CustomModelAdmin): "status", "is_paid", "is_shipped", + WooCommerceStateFilter, "payment_method", "payment_receiver", "contact_person", @@ -300,7 +302,7 @@ class OrderAdmin(CustomModelAdmin): def get_list_display(self, request): if is_connected(): ls = self.list_display.copy() - ls.insert(1, "display_woocommerce_id_present") + ls.insert(1, "display_woocommerce_state") return ls return self.list_display @@ -613,6 +615,8 @@ class CustomerAdmin(CustomModelAdmin): readonly_fields = ["linked_note_html", "display_woocommerce_id"] + list_filter = [WooCommerceStateFilter] + list_select_related = ["linked_note"] autocomplete_fields = ["combine_with"] @@ -624,7 +628,7 @@ class CustomerAdmin(CustomModelAdmin): def get_list_display(self, request): if is_connected(): ls = self.list_display.copy() - ls.insert(1, "display_woocommerce_id_present") + ls.insert(1, "display_woocommerce_state") return ls return self.list_display @@ -1081,7 +1085,7 @@ class ProductAdmin(CustomModelAdmin): ordering = ("article_number", "name") - list_filter = ("supplier", "categories", "stock_current") + list_filter = ("supplier", WooCommerceStateFilter, "categories") search_fields = [ "pk", "article_number", @@ -1106,7 +1110,7 @@ class ProductAdmin(CustomModelAdmin): def get_list_display(self, request): if is_connected(): ls = self.list_display.copy() - ls.insert(1, "display_woocommerce_id_present") + ls.insert(1, "display_woocommerce_state") return ls return self.list_display @@ -1296,7 +1300,7 @@ class ProductCategoryAdmin(CustomModelAdmin): def get_list_display(self, request): if is_connected(): ls = self.list_display.copy() - ls.insert(1, "display_woocommerce_id_present") + ls.insert(1, "display_woocommerce_state") return ls return self.list_display diff --git a/kmuhelper/static/admin/kmuhelper/css/style.css b/kmuhelper/static/admin/kmuhelper/css/style.css index ec304e0..a234679 100644 --- a/kmuhelper/static/admin/kmuhelper/css/style.css +++ b/kmuhelper/static/admin/kmuhelper/css/style.css @@ -79,7 +79,7 @@ form .wide textarea + div.help { /* Icon columns width fix for boolean columns */ .column-pkfill, -.column-display_woocommerce_id_present, +.column-display_woocommerce_state, .column-display_is_paid, .column-display_is_shipped { width: 1px;