forked from erp-dev/erp
feat: salesitem can change/soft delete
This commit is contained in:
@@ -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 测试"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user