1
0
forked from erp-dev/erp

feat: salesitem delete guard service

This commit is contained in:
2026-04-06 15:04:22 +08:00
parent 9afbb73799
commit ebd687ccdd
5 changed files with 91 additions and 19 deletions

View File

@@ -413,9 +413,9 @@ class PrintingJobListSerializer(serializers.ModelSerializer):
def get_saleitems(self, obj):
items = getattr(obj, '_saleitems_cache', None)
if items is None:
from shipment.models import SalesItem
from shipment.services import get_active_sales_items_queryset
items = list(
SalesItem.objects.select_related('shipment', 'created_by')
get_active_sales_items_queryset().select_related('shipment', 'created_by')
.filter(printing_job_id=obj.id)
.order_by('id')
)
@@ -475,9 +475,9 @@ class PrintingJobDetailSerializer(serializers.ModelSerializer):
def get_saleitems(self, obj):
items = getattr(obj, '_saleitems_cache', None)
if items is None:
from shipment.models import SalesItem
from shipment.services import get_active_sales_items_queryset
items = list(
SalesItem.objects.select_related('shipment', 'created_by')
get_active_sales_items_queryset().select_related('shipment', 'created_by')
.filter(printing_job_id=obj.id)
.order_by('id')
)

View File

@@ -501,11 +501,11 @@ class PrintingJobViewSet(CustomerVisibilityFilterMixin, LimitedModelViewSet):
if not jobs:
return
from shipment.models import SalesItem
from shipment.services import get_active_sales_items_queryset
job_ids = [job.id for job in jobs]
saleitems = list(
SalesItem.objects.select_related("shipment", "created_by")
get_active_sales_items_queryset().select_related("shipment", "created_by")
.filter(printing_job_id__in=job_ids)
.order_by("id")
)

View File

@@ -7,6 +7,7 @@ from decimal import Decimal
from django.test import TestCase
from django.conf import settings
from django.contrib.auth.models import Permission
from django.utils import timezone
from rest_framework.test import APIClient, APITestCase
from rest_framework import status
from django.contrib.auth import get_user_model
@@ -321,6 +322,21 @@ class SalesItemByPrintingOrderAPITestCase(TestCase):
self.assertEqual(data["count"], 0)
self.assertEqual(data["results"], [])
def test_get_sales_items_by_printing_order_excludes_soft_deleted(self):
self.sales_item2.delete_at = timezone.now()
self.sales_item2.delete_by = self.user
self.sales_item2.save(update_fields=["delete_at", "delete_by", "updated_at"])
url = (
f"/api/v1/shipment/sales-items/by-printing-order/{self.printing_order.id}/"
)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
item_ids = [item["id"] for item in response.json()["results"]]
self.assertIn(self.sales_item1.id, item_ids)
self.assertNotIn(self.sales_item2.id, item_ids)
def test_get_sales_items_unauthenticated(self):
"""测试未认证用户"""
self.client.logout()
@@ -671,6 +687,20 @@ class SalesItemByCustomerAPITestCase(TestCase):
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
def test_get_sales_items_by_customer_excludes_soft_deleted(self):
self.sales_item2.delete_at = timezone.now()
self.sales_item2.delete_by = self.user
self.sales_item2.save(update_fields=["delete_at", "delete_by", "updated_at"])
resp = self.client.get(
f"/api/v1/shipment/sales-items/by-customer/{self.customer.id}/"
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
result_ids = [item["id"] for item in resp.json()["results"]]
self.assertIn(self.sales_item1.id, result_ids)
self.assertNotIn(self.sales_item2.id, result_ids)
def test_get_sales_items_by_customer_other_merchant_404(self):
resp = self.client.get(
f"/api/v1/shipment/sales-items/by-customer/{self.foreign_customer.id}/"
@@ -893,6 +923,26 @@ class SalesItemDetailAPITestCase(APITestCase):
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
def test_delete_sales_item_rejects_when_already_bound_to_shipment(self):
permission = Permission.objects.get(codename="soft_delete_salesitem")
self.user.user_permissions.add(permission)
shipment = shipment_models.Shipment.objects.create(
merchant=self.merchant,
customer=self.customer,
shipment_date="2026-04-03",
created_by=self.user,
)
self.sales_item.shipment = shipment
self.sales_item.save(update_fields=["shipment", "updated_at"])
resp = self.client.delete(f"/api/v1/shipment/sales-items/{self.sales_item.id}/")
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(resp.json()["detail"], "已关联出货单的销售品不允许删除")
self.sales_item.refresh_from_db()
self.assertIsNone(self.sales_item.delete_at)
class ShipmentCreateAPITestCase(TestCase):
"""测试创建出货单 API"""

View File

@@ -984,11 +984,11 @@ class SalesItemDetailView(GenericAPIView):
serializer_class = SalesItemDetailSerializer
def get_queryset(self):
from shipment.models import SalesItem
from shipment.services import get_active_sales_items_queryset
qs = (
SalesItem.objects.select_related("shipment", "created_by", "delete_by")
.filter(delete_at__isnull=True)
get_active_sales_items_queryset()
.select_related("shipment", "created_by", "delete_by")
.order_by("id")
)
@@ -1074,5 +1074,8 @@ class SalesItemDetailView(GenericAPIView):
from shipment.services import delete_sales_item
delete_sales_item(sales_item, deleted_by=request.user)
try:
delete_sales_item(sales_item, deleted_by=request.user)
except ValueError as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
return Response({"detail": "销售品已标记为删除"}, status=status.HTTP_200_OK)