1
0
forked from erp-dev/erp

feat: salesitem can change/soft delete

This commit is contained in:
2026-04-06 14:49:02 +08:00
parent d7bc43082e
commit 9afbb73799
16 changed files with 1075 additions and 10 deletions

View File

@@ -784,6 +784,115 @@ class SalesItemDetailAPITestCase(APITestCase):
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
def test_patch_sales_item_success_and_create_change_record(self):
resp = self.client.patch(
f"/api/v1/shipment/sales-items/{self.sales_item.id}/",
{
"quantity": "99.50",
"remark": "改备注",
"position": "C3-08",
},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.sales_item.refresh_from_db()
self.assertEqual(self.sales_item.quantity, Decimal("99.50"))
self.assertEqual(self.sales_item.remark, "改备注")
self.assertEqual(self.sales_item.position, "C3-08")
change_record = self.sales_item.change_records.get()
self.assertEqual(change_record.operator_id, self.user.id)
self.assertEqual(
change_record.before_values,
{
"quantity": "88.00",
"remark": "",
"position": "B2-03",
},
)
self.assertEqual(
change_record.after_values,
{
"quantity": "99.50",
"remark": "改备注",
"position": "C3-08",
},
)
def test_patch_sales_item_rejects_relation_or_other_disallowed_fields(self):
resp = self.client.patch(
f"/api/v1/shipment/sales-items/{self.sales_item.id}/",
{
"name": "不允许改名",
"customer_id": self.customer.id,
},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("仅允许修改以下字段", resp.json()["detail"])
self.assertFalse(self.sales_item.change_records.exists())
def test_patch_sales_item_invalid_quantity(self):
resp = self.client.patch(
f"/api/v1/shipment/sales-items/{self.sales_item.id}/",
{
"quantity": "abc",
},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("数量 abc 格式无效", resp.json()["detail"])
self.assertFalse(self.sales_item.change_records.exists())
def test_patch_sales_item_other_merchant_404(self):
self.client.force_authenticate(user=self.other_user)
resp = self.client.patch(
f"/api/v1/shipment/sales-items/{self.sales_item.id}/",
{
"remark": "无权限修改",
},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
def test_delete_sales_item_requires_permission(self):
resp = self.client.delete(f"/api/v1/shipment/sales-items/{self.sales_item.id}/")
self.assertEqual(resp.status_code, status.HTTP_403_FORBIDDEN)
self.sales_item.refresh_from_db()
self.assertIsNone(self.sales_item.delete_at)
self.assertIsNone(self.sales_item.delete_by)
def test_delete_sales_item_success_soft_deletes_and_hides_detail(self):
permission = Permission.objects.get(codename="soft_delete_salesitem")
self.user.user_permissions.add(permission)
resp = self.client.delete(f"/api/v1/shipment/sales-items/{self.sales_item.id}/")
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(resp.json()["detail"], "销售品已标记为删除")
self.sales_item.refresh_from_db()
self.assertIsNotNone(self.sales_item.delete_at)
self.assertEqual(self.sales_item.delete_by, self.user)
detail_resp = self.client.get(f"/api/v1/shipment/sales-items/{self.sales_item.id}/")
self.assertEqual(detail_resp.status_code, status.HTTP_404_NOT_FOUND)
def test_delete_sales_item_other_merchant_404(self):
permission = Permission.objects.get(codename="soft_delete_salesitem")
self.other_user.user_permissions.add(permission)
self.client.force_authenticate(user=self.other_user)
resp = self.client.delete(f"/api/v1/shipment/sales-items/{self.sales_item.id}/")
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
class ShipmentCreateAPITestCase(TestCase):
"""测试创建出货单 API"""
@@ -1683,6 +1792,166 @@ class ShipmentStatusServiceTestCase(TestCase):
)
class ShipmentStatusAPITestCase(APITestCase):
"""测试出货单状态流转 API"""
def setUp(self):
self.client = APIClient()
self.merchant = basic_models.Merchant.objects.create(
name="状态 API 商户", type=basic_models.MerchantTypeEnum.FACTORY
)
self.other_merchant = basic_models.Merchant.objects.create(
name="状态 API 其它商户", type=basic_models.MerchantTypeEnum.FACTORY
)
self.user = User.objects.create_user(
username="shipment_status_api_user",
password="testpass123",
email="shipment_status_api@example.com",
)
self.employee = basic_models.Employee.objects.create(
sys_user=self.user,
merchant=self.merchant,
name="状态 API 员工",
mobile="13800138101",
status=basic_models.EmployeeStatusEnum.ACTIVE,
)
self.other_user = User.objects.create_user(
username="shipment_status_api_other_user",
password="testpass123",
email="shipment_status_api_other@example.com",
)
self.other_employee = basic_models.Employee.objects.create(
sys_user=self.other_user,
merchant=self.other_merchant,
name="状态 API 其它员工",
mobile="13800138102",
status=basic_models.EmployeeStatusEnum.ACTIVE,
)
self.customer = basic_models.Customer.objects.create(
merchant=self.merchant,
name="状态 API 客户",
mobile="13900139101",
area="杭州",
)
self.shipment = shipment_models.Shipment.objects.create(
merchant=self.merchant,
customer=self.customer,
shipment_date="2026-04-02",
created_by=self.user,
)
self.client.force_authenticate(user=self.user)
def test_patch_status_draft_to_published_success(self):
resp = self.client.patch(
f"/api/v1/shipment/shipments/{self.shipment.id}/status/",
{"status": shipment_models.ShipmentStatus.PUBLISHED},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.shipment.refresh_from_db()
self.assertEqual(self.shipment.status, shipment_models.ShipmentStatus.PUBLISHED)
self.assertIsNotNone(self.shipment.status_modified_at)
self.assertIsNone(self.shipment.approved_by)
self.assertIsNone(self.shipment.cancelled_by)
def test_put_status_published_to_approved_sets_approved_by(self):
from shipment.services import modify_status
modify_status(
self.shipment,
target_status=shipment_models.ShipmentStatus.PUBLISHED,
operator=self.user,
)
resp = self.client.put(
f"/api/v1/shipment/shipments/{self.shipment.id}/status/",
{"status": shipment_models.ShipmentStatus.APPROVED},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.shipment.refresh_from_db()
self.assertEqual(self.shipment.status, shipment_models.ShipmentStatus.APPROVED)
self.assertEqual(self.shipment.approved_by, self.user)
def test_patch_status_published_to_rejected_success(self):
from shipment.services import modify_status
modify_status(
self.shipment,
target_status=shipment_models.ShipmentStatus.PUBLISHED,
operator=self.user,
)
resp = self.client.patch(
f"/api/v1/shipment/shipments/{self.shipment.id}/status/",
{"status": shipment_models.ShipmentStatus.REJECTED},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.shipment.refresh_from_db()
self.assertEqual(self.shipment.status, shipment_models.ShipmentStatus.REJECTED)
def test_patch_status_to_cancelled_sets_cancelled_by(self):
from shipment.services import modify_status
modify_status(
self.shipment,
target_status=shipment_models.ShipmentStatus.PUBLISHED,
operator=self.user,
)
resp = self.client.patch(
f"/api/v1/shipment/shipments/{self.shipment.id}/status/",
{"status": shipment_models.ShipmentStatus.CANCELLED},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.shipment.refresh_from_db()
self.assertEqual(self.shipment.status, shipment_models.ShipmentStatus.CANCELLED)
self.assertEqual(self.shipment.cancelled_by, self.user)
def test_patch_status_rejects_invalid_transition(self):
resp = self.client.patch(
f"/api/v1/shipment/shipments/{self.shipment.id}/status/",
{"status": shipment_models.ShipmentStatus.APPROVED},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("不允许将出货单状态从 草稿(未发布) 修改为 已审核", resp.json()["detail"])
def test_patch_status_other_merchant_404(self):
self.client.force_authenticate(user=self.other_user)
resp = self.client.patch(
f"/api/v1/shipment/shipments/{self.shipment.id}/status/",
{"status": shipment_models.ShipmentStatus.PUBLISHED},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
def test_patch_status_unauthenticated(self):
self.client.logout()
resp = self.client.patch(
f"/api/v1/shipment/shipments/{self.shipment.id}/status/",
{"status": shipment_models.ShipmentStatus.PUBLISHED},
format="json",
)
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
class SalesItemCreateAPITestCase(APITestCase):
"""销售品创建 API 测试"""