forked from erp-dev/erp
feat: mes module
This commit is contained in:
403
api_v2/test_mes_api.py
Normal file
403
api_v2/test_mes_api.py
Normal file
@@ -0,0 +1,403 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.test import TestCase
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from mes import models as mes_models
|
||||
from printing.models import PrintingOrder
|
||||
|
||||
|
||||
class MesV2APITest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.merchant = basic_models.Merchant.objects.create(name="MES商户", type=basic_models.MerchantTypeEnum.STORE)
|
||||
self.other_merchant = basic_models.Merchant.objects.create(name="其他商户", type=basic_models.MerchantTypeEnum.STORE)
|
||||
self.user = get_user_model().objects.create_user(username="mes-api-user", password="pass12345")
|
||||
self.other_user = get_user_model().objects.create_user(username="mes-api-other-user", password="pass12345")
|
||||
self.employee = basic_models.Employee.objects.create(merchant=self.merchant, sys_user=self.user, name="MES员工")
|
||||
self.other_employee = basic_models.Employee.objects.create(merchant=self.other_merchant, sys_user=self.other_user, name="其他员工")
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
def test_create_device_category(self):
|
||||
resp = self.client.post("/api/v2/mes/device-categories/", {"name": "打印机"}, format="json")
|
||||
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
self.assertEqual(resp.data["name"], "打印机")
|
||||
self.assertEqual(resp.data["merchant"], self.merchant.id)
|
||||
self.assertEqual(resp.data["created_by"]["id"], self.user.id)
|
||||
self.assertEqual(resp.data["operator"]["id"], self.employee.id)
|
||||
|
||||
def test_list_device_categories_supports_name_filter(self):
|
||||
mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="打印机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="滚筒机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.get("/api/v2/mes/device-categories/?name=打印")
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual([item["name"] for item in resp.data], ["打印机"])
|
||||
|
||||
def test_update_device_category(self):
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="打印机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.patch(f"/api/v2/mes/device-categories/{category.id}/", {"name": "热转印机"}, format="json")
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(resp.data["name"], "热转印机")
|
||||
|
||||
def test_delete_device_category_rejects_when_devices_exist(self):
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="打印机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name="设备A",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.delete(f"/api/v2/mes/device-categories/{category.id}/")
|
||||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn("不能删除", resp.data["detail"])
|
||||
|
||||
def test_create_device(self):
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="打印机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.post(
|
||||
"/api/v2/mes/devices/",
|
||||
{
|
||||
"name": "设备A",
|
||||
"category": category.id,
|
||||
"peak_capacity": 120,
|
||||
"capacity_unit": 1,
|
||||
"extra": {"capacity": {"per_hour": 120}},
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
self.assertEqual(resp.data["name"], "设备A")
|
||||
self.assertEqual(resp.data["category"], category.id)
|
||||
self.assertEqual(resp.data["category_name"], "打印机")
|
||||
self.assertEqual(resp.data['peak_capacity'], 120)
|
||||
self.assertEqual(resp.data['capacity_unit'], 1)
|
||||
self.assertEqual(resp.data['capacity_unit_label'], '米')
|
||||
self.assertEqual(resp.data["extra"], {"capacity": {"per_hour": 120}})
|
||||
|
||||
def test_create_device_rejects_cross_merchant_category(self):
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.other_merchant,
|
||||
name="外部分类",
|
||||
created_by=self.other_user,
|
||||
operator=self.other_employee,
|
||||
)
|
||||
|
||||
resp = self.client.post(
|
||||
"/api/v2/mes/devices/",
|
||||
{"name": "设备A", "category": category.id, "peak_capacity": 120},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn("category", resp.data)
|
||||
|
||||
def test_list_devices_supports_category_filter(self):
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="打印机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
other_category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="滚筒机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
target = mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name="设备A",
|
||||
peak_capacity=120,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=other_category,
|
||||
name="设备B",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.get(f"/api/v2/mes/devices/?category={category.id}")
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual([item["id"] for item in resp.data], [target.id])
|
||||
|
||||
def test_update_device_supports_clearing_extra(self):
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="打印机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
device = mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name="设备A",
|
||||
peak_capacity=120,
|
||||
extra={"power": 220},
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.patch(
|
||||
f"/api/v2/mes/devices/{device.id}/",
|
||||
{"extra": None, "capacity_unit": 2, "peak_capacity": 150},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(resp.data['peak_capacity'], 150)
|
||||
self.assertEqual(resp.data['capacity_unit'], 2)
|
||||
self.assertEqual(resp.data['capacity_unit_label'], '码')
|
||||
self.assertIsNone(resp.data["extra"])
|
||||
|
||||
def test_list_devices_supports_extra_json_filter(self):
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name="打印机",
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
target = mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name="设备A",
|
||||
peak_capacity=120,
|
||||
extra={"capacity": {"per_hour": 120}},
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name="设备B",
|
||||
peak_capacity=80,
|
||||
extra={"capacity": {"per_hour": 80}},
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.get('/api/v2/mes/devices/?extra_path=capacity.per_hour&extra_value=120')
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual([item['id'] for item in resp.data], [target.id])
|
||||
|
||||
def test_list_devices_rejects_incomplete_extra_json_filter(self):
|
||||
resp = self.client.get('/api/v2/mes/devices/?extra_path=capacity.per_hour')
|
||||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertIn('detail', resp.data)
|
||||
|
||||
def test_cross_merchant_device_detail_returns_404(self):
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.other_merchant,
|
||||
name="外部分类",
|
||||
created_by=self.other_user,
|
||||
operator=self.other_employee,
|
||||
)
|
||||
device = mes_models.Device.objects.create(
|
||||
merchant=self.other_merchant,
|
||||
category=category,
|
||||
name="外部设备",
|
||||
peak_capacity=120,
|
||||
created_by=self.other_user,
|
||||
operator=self.other_employee,
|
||||
)
|
||||
|
||||
resp = self.client.get(f"/api/v2/mes/devices/{device.id}/")
|
||||
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
|
||||
def test_create_production_assignment(self):
|
||||
customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试客户',
|
||||
mobile='13800138002',
|
||||
)
|
||||
printing_order = PrintingOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=customer,
|
||||
fabric='测试面料',
|
||||
width='150cm',
|
||||
created_by=self.user,
|
||||
)
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='打印机',
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
device = mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name='设备A',
|
||||
peak_capacity=120,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
content_type = ContentType.objects.get_for_model(PrintingOrder)
|
||||
|
||||
resp = self.client.post(
|
||||
'/api/v2/mes/production-assignments/',
|
||||
{
|
||||
'device': device.id,
|
||||
'content_type': content_type.id,
|
||||
'object_id': printing_order.id,
|
||||
'assigner': self.employee.id,
|
||||
'production_quantity': 300,
|
||||
'extra': {'batch': 'A1'},
|
||||
},
|
||||
format='json',
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
self.assertEqual(resp.data['device'], device.id)
|
||||
self.assertEqual(resp.data['production_quantity'], 300)
|
||||
self.assertEqual(resp.data['status'], mes_models.ProductionAssignmentStatusEnum.DRAFT)
|
||||
self.assertEqual(resp.data['status_label'], 'Draft')
|
||||
|
||||
def test_update_production_assignment_rejects_cancel_after_accepted(self):
|
||||
customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试客户2',
|
||||
mobile='13800138003',
|
||||
)
|
||||
printing_order = PrintingOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=customer,
|
||||
fabric='测试面料',
|
||||
width='150cm',
|
||||
created_by=self.user,
|
||||
)
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='打印机',
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
device = mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name='设备A',
|
||||
peak_capacity=120,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
content_type = ContentType.objects.get_for_model(PrintingOrder)
|
||||
assignment = mes_models.ProductionAssignment.objects.create(
|
||||
merchant=self.merchant,
|
||||
device=device,
|
||||
content_type=content_type,
|
||||
object_id=printing_order.id,
|
||||
assigner=self.employee,
|
||||
assignee=self.employee,
|
||||
production_quantity=300,
|
||||
status=mes_models.ProductionAssignmentStatusEnum.ACCEPTED,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.patch(
|
||||
f'/api/v2/mes/production-assignments/{assignment.id}/',
|
||||
{'status': mes_models.ProductionAssignmentStatusEnum.CANCELLED},
|
||||
format='json',
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
self.assertEqual(resp.data['detail'], '当前状态不允许变更为目标状态')
|
||||
|
||||
def test_list_production_assignments_supports_status_filter(self):
|
||||
customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试客户3',
|
||||
mobile='13800138004',
|
||||
)
|
||||
printing_order = PrintingOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=customer,
|
||||
fabric='测试面料',
|
||||
width='150cm',
|
||||
created_by=self.user,
|
||||
)
|
||||
category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='打印机',
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
device = mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=category,
|
||||
name='设备A',
|
||||
peak_capacity=120,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
content_type = ContentType.objects.get_for_model(PrintingOrder)
|
||||
target = mes_models.ProductionAssignment.objects.create(
|
||||
merchant=self.merchant,
|
||||
device=device,
|
||||
content_type=content_type,
|
||||
object_id=printing_order.id,
|
||||
assigner=self.employee,
|
||||
production_quantity=300,
|
||||
status=mes_models.ProductionAssignmentStatusEnum.PUBLISHED,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
mes_models.ProductionAssignment.objects.create(
|
||||
merchant=self.merchant,
|
||||
device=device,
|
||||
content_type=content_type,
|
||||
object_id=printing_order.id,
|
||||
assigner=self.employee,
|
||||
production_quantity=100,
|
||||
status=mes_models.ProductionAssignmentStatusEnum.DRAFT,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
|
||||
resp = self.client.get(
|
||||
f'/api/v2/mes/production-assignments/?status={mes_models.ProductionAssignmentStatusEnum.PUBLISHED}'
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual([item['id'] for item in resp.data], [target.id])
|
||||
220
api_v2/tests.py
220
api_v2/tests.py
@@ -15,6 +15,7 @@ from api_v2.views.printing import PrintingJobByCustomerView
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from stateflow import models as stateflow_models
|
||||
from django.test.utils import override_settings
|
||||
from mes import models as mes_models
|
||||
|
||||
|
||||
class QuickCreateEmployeeUserAPITest(TestCase):
|
||||
@@ -2292,3 +2293,222 @@ class AgentTransportVehicleAPITest(TestCase):
|
||||
url = f'/api/v2/ai/transport-vehicles/{self.vehicle_a.license_plate}/'
|
||||
response = self.client.get(url, {'merchant_id': self.merchant.id})
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
|
||||
@override_settings(AGENT_ACCESS_KEY='agent-test-key')
|
||||
class AgentMesAPITest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.device_list_url = '/api/v2/ai/mes/devices/'
|
||||
self.assignment_list_url = '/api/v2/ai/mes/production-assignments/'
|
||||
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='MES Agent 商户',
|
||||
type=basic_models.MerchantTypeEnum.FACTORY,
|
||||
)
|
||||
self.other_merchant = basic_models.Merchant.objects.create(
|
||||
name='其他 MES 商户',
|
||||
type=basic_models.MerchantTypeEnum.FACTORY,
|
||||
)
|
||||
self.user = get_user_model().objects.create_user(username='agent-mes-user', password='pass12345')
|
||||
self.other_user = get_user_model().objects.create_user(username='agent-mes-other-user', password='pass12345')
|
||||
self.employee = basic_models.Employee.objects.create(merchant=self.merchant, sys_user=self.user, name='MES员工')
|
||||
self.other_employee = basic_models.Employee.objects.create(merchant=self.other_merchant, sys_user=self.other_user, name='其他MES员工')
|
||||
|
||||
self.category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='打印机',
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
self.device_a = mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
name='设备A',
|
||||
peak_capacity=120,
|
||||
capacity_unit=mes_models.CapacityUnitEnum.METER,
|
||||
extra={'capacity': {'per_hour': 120}},
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
self.device_b = mes_models.Device.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
name='设备B',
|
||||
peak_capacity=90,
|
||||
capacity_unit=mes_models.CapacityUnitEnum.YARD,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
self.other_category = mes_models.DeviceCategory.objects.create(
|
||||
merchant=self.other_merchant,
|
||||
name='他商户分类',
|
||||
created_by=self.other_user,
|
||||
operator=self.other_employee,
|
||||
)
|
||||
self.other_device = mes_models.Device.objects.create(
|
||||
merchant=self.other_merchant,
|
||||
category=self.other_category,
|
||||
name='他商户设备',
|
||||
peak_capacity=60,
|
||||
created_by=self.other_user,
|
||||
operator=self.other_employee,
|
||||
)
|
||||
|
||||
self.customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试客户',
|
||||
mobile='13800138011',
|
||||
)
|
||||
self.other_customer = basic_models.Customer.objects.create(
|
||||
merchant=self.other_merchant,
|
||||
name='他商户客户',
|
||||
mobile='13800138012',
|
||||
)
|
||||
self.printing_order = printing_models.PrintingOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=self.customer,
|
||||
fabric='测试面料',
|
||||
width='150cm',
|
||||
created_by=self.user,
|
||||
)
|
||||
self.other_printing_order = printing_models.PrintingOrder.objects.create(
|
||||
merchant=self.other_merchant,
|
||||
customer=self.other_customer,
|
||||
fabric='其他面料',
|
||||
width='160cm',
|
||||
created_by=self.other_user,
|
||||
)
|
||||
self.content_type = ContentType.objects.get_for_model(printing_models.PrintingOrder)
|
||||
|
||||
self.assignment_published = mes_models.ProductionAssignment.objects.create(
|
||||
merchant=self.merchant,
|
||||
device=self.device_a,
|
||||
content_type=self.content_type,
|
||||
object_id=self.printing_order.id,
|
||||
assigner=self.employee,
|
||||
assignee=self.employee,
|
||||
production_quantity=300,
|
||||
status=mes_models.ProductionAssignmentStatusEnum.PUBLISHED,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
self.assignment_draft = mes_models.ProductionAssignment.objects.create(
|
||||
merchant=self.merchant,
|
||||
device=self.device_b,
|
||||
content_type=self.content_type,
|
||||
object_id=self.printing_order.id,
|
||||
assigner=self.employee,
|
||||
production_quantity=100,
|
||||
status=mes_models.ProductionAssignmentStatusEnum.DRAFT,
|
||||
created_by=self.user,
|
||||
operator=self.employee,
|
||||
)
|
||||
self.other_assignment = mes_models.ProductionAssignment.objects.create(
|
||||
merchant=self.other_merchant,
|
||||
device=self.other_device,
|
||||
content_type=self.content_type,
|
||||
object_id=self.other_printing_order.id,
|
||||
assigner=self.other_employee,
|
||||
production_quantity=50,
|
||||
status=mes_models.ProductionAssignmentStatusEnum.PUBLISHED,
|
||||
created_by=self.other_user,
|
||||
operator=self.other_employee,
|
||||
)
|
||||
|
||||
mes_models.ProductionAssignment.objects.filter(id=self.assignment_published.id).update(
|
||||
created_at=timezone.make_aware(datetime.datetime(2026, 5, 1, 10, 0, 0))
|
||||
)
|
||||
mes_models.ProductionAssignment.objects.filter(id=self.assignment_draft.id).update(
|
||||
created_at=timezone.make_aware(datetime.datetime(2026, 5, 3, 12, 0, 0))
|
||||
)
|
||||
mes_models.ProductionAssignment.objects.filter(id=self.other_assignment.id).update(
|
||||
created_at=timezone.make_aware(datetime.datetime(2026, 5, 2, 9, 0, 0))
|
||||
)
|
||||
|
||||
def _headers(self, key='agent-test-key'):
|
||||
return {'HTTP_AUTHORIZATION': key}
|
||||
|
||||
def test_list_mes_devices_success(self):
|
||||
response = self.client.get(
|
||||
self.device_list_url,
|
||||
{'merchant_id': self.merchant.id},
|
||||
**self._headers(),
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['count'], 2)
|
||||
self.assertEqual(response.data['results'][0]['category']['name'], '打印机')
|
||||
|
||||
def test_list_mes_devices_isolates_by_merchant(self):
|
||||
response = self.client.get(
|
||||
self.device_list_url,
|
||||
{'merchant_id': self.other_merchant.id},
|
||||
**self._headers(),
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.other_device.id)
|
||||
|
||||
def test_list_mes_devices_requires_access_key(self):
|
||||
response = self.client.get(self.device_list_url, {'merchant_id': self.merchant.id})
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
def test_list_mes_production_assignments_filters_by_date_range(self):
|
||||
response = self.client.get(
|
||||
self.assignment_list_url,
|
||||
{
|
||||
'merchant_id': self.merchant.id,
|
||||
'start_date': '2026-05-01',
|
||||
'end_date': '2026-05-02',
|
||||
},
|
||||
**self._headers(),
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.assignment_published.id)
|
||||
self.assertEqual(response.data['results'][0]['device']['id'], self.device_a.id)
|
||||
|
||||
def test_list_mes_production_assignments_supports_status_and_device_filter(self):
|
||||
response = self.client.get(
|
||||
self.assignment_list_url,
|
||||
{
|
||||
'merchant_id': self.merchant.id,
|
||||
'start_date': '2026-05-01',
|
||||
'end_date': '2026-05-31',
|
||||
'device_id': self.device_b.id,
|
||||
'status': mes_models.ProductionAssignmentStatusEnum.DRAFT,
|
||||
},
|
||||
**self._headers(),
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.data['count'], 1)
|
||||
self.assertEqual(response.data['results'][0]['id'], self.assignment_draft.id)
|
||||
|
||||
def test_list_mes_production_assignments_rejects_invalid_date_range(self):
|
||||
response = self.client.get(
|
||||
self.assignment_list_url,
|
||||
{
|
||||
'merchant_id': self.merchant.id,
|
||||
'start_date': '2026-05-31',
|
||||
'end_date': '2026-05-01',
|
||||
},
|
||||
**self._headers(),
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertIn('end_date', response.data)
|
||||
|
||||
def test_list_mes_production_assignments_requires_access_key(self):
|
||||
response = self.client.get(
|
||||
self.assignment_list_url,
|
||||
{
|
||||
'merchant_id': self.merchant.id,
|
||||
'start_date': '2026-05-01',
|
||||
'end_date': '2026-05-31',
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 401)
|
||||
|
||||
@@ -26,11 +26,19 @@ from api_v2.views import (
|
||||
MissionReplyListCreateView,
|
||||
MissionReplyRejectView,
|
||||
MissionSetUrgentView,
|
||||
DeviceCategoryListCreateView,
|
||||
DeviceCategoryDetailView,
|
||||
DeviceListCreateView,
|
||||
DeviceDetailView,
|
||||
ProductionAssignmentListCreateView,
|
||||
ProductionAssignmentDetailView,
|
||||
ShipmentDeliveryPhotoDetailView,
|
||||
ShipmentDeliveryPhotoListCreateView,
|
||||
AgentUnshippedShipmentListView,
|
||||
AgentTransportVehicleListView,
|
||||
AgentTransportVehicleDetailView,
|
||||
AgentMesDeviceListView,
|
||||
AgentMesProductionAssignmentListView,
|
||||
)
|
||||
from api_v2.views.basic_info import CustomerEmployeeBindingView, MyVisiblePagesView
|
||||
|
||||
@@ -55,6 +63,8 @@ urlpatterns = [
|
||||
path('ai/shipments/unshipped/', AgentUnshippedShipmentListView.as_view(), name='api_v2_ai_unshipped_shipment_list'),
|
||||
path('ai/transport-vehicles/', AgentTransportVehicleListView.as_view(), name='api_v2_ai_transport_vehicle_list'),
|
||||
path('ai/transport-vehicles/<str:license_plate>/', AgentTransportVehicleDetailView.as_view(), name='api_v2_ai_transport_vehicle_detail'),
|
||||
path('ai/mes/devices/', AgentMesDeviceListView.as_view(), name='api_v2_ai_mes_device_list'),
|
||||
path('ai/mes/production-assignments/', AgentMesProductionAssignmentListView.as_view(), name='api_v2_ai_mes_production_assignment_list'),
|
||||
path('mission-categories/', MissionCategoryListCreateView.as_view(), name='api_v2_mission_category_list_create'),
|
||||
path('mission-categories/<int:category_id>/', MissionCategoryDetailView.as_view(), name='api_v2_mission_category_detail'),
|
||||
path('missions/', MissionListCreateView.as_view(), name='api_v2_mission_list_create'),
|
||||
@@ -65,6 +75,12 @@ urlpatterns = [
|
||||
path('missions/<int:mission_id>/cancel/', MissionCancelView.as_view(), name='api_v2_mission_cancel'),
|
||||
path('missions/<int:mission_id>/set-urgent/', MissionSetUrgentView.as_view(), name='api_v2_mission_set_urgent'),
|
||||
path('mission-replies/<int:reply_id>/reject/', MissionReplyRejectView.as_view(), name='api_v2_mission_reply_reject'),
|
||||
path('mes/device-categories/', DeviceCategoryListCreateView.as_view(), name='api_v2_mes_device_category_list_create'),
|
||||
path('mes/device-categories/<int:category_id>/', DeviceCategoryDetailView.as_view(), name='api_v2_mes_device_category_detail'),
|
||||
path('mes/devices/', DeviceListCreateView.as_view(), name='api_v2_mes_device_list_create'),
|
||||
path('mes/devices/<int:device_id>/', DeviceDetailView.as_view(), name='api_v2_mes_device_detail'),
|
||||
path('mes/production-assignments/', ProductionAssignmentListCreateView.as_view(), name='api_v2_mes_production_assignment_list_create'),
|
||||
path('mes/production-assignments/<int:assignment_id>/', ProductionAssignmentDetailView.as_view(), name='api_v2_mes_production_assignment_detail'),
|
||||
path('shipment-delivery-photos/', ShipmentDeliveryPhotoListCreateView.as_view(), name='api_v2_shipment_delivery_photo_list_create'),
|
||||
path('shipment-delivery-photos/<int:photo_id>/', ShipmentDeliveryPhotoDetailView.as_view(), name='api_v2_shipment_delivery_photo_detail'),
|
||||
]
|
||||
|
||||
@@ -31,7 +31,21 @@ from .mission import (
|
||||
MissionReplyRejectView,
|
||||
MissionSetUrgentView,
|
||||
)
|
||||
from .ai import AgentUnshippedShipmentListView, AgentTransportVehicleListView, AgentTransportVehicleDetailView
|
||||
from .mes import (
|
||||
DeviceCategoryDetailView,
|
||||
DeviceCategoryListCreateView,
|
||||
DeviceDetailView,
|
||||
DeviceListCreateView,
|
||||
ProductionAssignmentDetailView,
|
||||
ProductionAssignmentListCreateView,
|
||||
)
|
||||
from .ai import (
|
||||
AgentUnshippedShipmentListView,
|
||||
AgentTransportVehicleListView,
|
||||
AgentTransportVehicleDetailView,
|
||||
AgentMesDeviceListView,
|
||||
AgentMesProductionAssignmentListView,
|
||||
)
|
||||
from .shipment_delivery_photo import ShipmentDeliveryPhotoDetailView, ShipmentDeliveryPhotoListCreateView
|
||||
|
||||
__all__ = [
|
||||
@@ -61,9 +75,17 @@ __all__ = [
|
||||
'MissionSetUrgentView',
|
||||
'ContentTypeListView',
|
||||
'MissionByPrintingOrderView',
|
||||
'DeviceCategoryListCreateView',
|
||||
'DeviceCategoryDetailView',
|
||||
'DeviceListCreateView',
|
||||
'DeviceDetailView',
|
||||
'ProductionAssignmentListCreateView',
|
||||
'ProductionAssignmentDetailView',
|
||||
'AgentUnshippedShipmentListView',
|
||||
'AgentTransportVehicleListView',
|
||||
'AgentTransportVehicleDetailView',
|
||||
'AgentMesDeviceListView',
|
||||
'AgentMesProductionAssignmentListView',
|
||||
'ShipmentDeliveryPhotoListCreateView',
|
||||
'ShipmentDeliveryPhotoDetailView',
|
||||
]
|
||||
|
||||
@@ -12,6 +12,7 @@ from rest_framework.views import APIView
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from flower.viewsets import LimitedLimitOffsetPagination
|
||||
from mes import models as mes_models
|
||||
from printing.models import PrintingJob
|
||||
from shipment.models import SalesItem, Shipment
|
||||
|
||||
@@ -330,3 +331,175 @@ class AgentTransportVehicleDetailView(APIView):
|
||||
raise Http404
|
||||
|
||||
return Response(AgentTransportVehicleSerializer(vehicle).data)
|
||||
|
||||
|
||||
class AgentMesDeviceCategorySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = mes_models.DeviceCategory
|
||||
fields = ["id", "merchant", "name", "created_at", "updated_at"]
|
||||
|
||||
|
||||
class AgentMesDeviceSerializer(serializers.ModelSerializer):
|
||||
category = AgentMesDeviceCategorySerializer(read_only=True)
|
||||
capacity_unit_label = serializers.CharField(source='get_capacity_unit_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = mes_models.Device
|
||||
fields = [
|
||||
"id",
|
||||
"merchant",
|
||||
"category",
|
||||
"name",
|
||||
"peak_capacity",
|
||||
"capacity_unit",
|
||||
"capacity_unit_label",
|
||||
"extra",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class AgentMesDeviceListView(mixins.ListModelMixin, GenericAPIView):
|
||||
"""
|
||||
Agent 专用:查询商户下所有 MES 设备,并返回设备分类信息。
|
||||
|
||||
GET /api/v2/ai/mes/devices/?merchant_id=<id>
|
||||
"""
|
||||
|
||||
authentication_classes = [AgentAccessKeyAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = AgentMesDeviceSerializer
|
||||
pagination_class = LimitedLimitOffsetPagination
|
||||
|
||||
def get(self, request):
|
||||
query_serializer = AgentMerchantQuerySerializer(data=request.query_params)
|
||||
query_serializer.is_valid(raise_exception=True)
|
||||
self.merchant = query_serializer.validated_data["merchant_id"]
|
||||
return self.list(request)
|
||||
|
||||
def get_queryset(self):
|
||||
merchant = getattr(self, "merchant", None)
|
||||
if merchant is None:
|
||||
query_serializer = AgentMerchantQuerySerializer(data=self.request.query_params)
|
||||
query_serializer.is_valid(raise_exception=True)
|
||||
merchant = query_serializer.validated_data["merchant_id"]
|
||||
return mes_models.Device.objects.filter(merchant=merchant).select_related("category").order_by("id")
|
||||
|
||||
|
||||
class AgentMesProductionAssignmentQuerySerializer(AgentMerchantQuerySerializer):
|
||||
start_date = serializers.DateField()
|
||||
end_date = serializers.DateField()
|
||||
device_id = serializers.IntegerField(required=False, min_value=1)
|
||||
status = serializers.ChoiceField(required=False, choices=mes_models.ProductionAssignmentStatusEnum.choices)
|
||||
|
||||
def validate(self, attrs):
|
||||
attrs = super().validate(attrs)
|
||||
if attrs["start_date"] > attrs["end_date"]:
|
||||
raise serializers.ValidationError({"end_date": "结束日期不能早于开始日期"})
|
||||
|
||||
merchant = attrs["merchant_id"]
|
||||
device_id = attrs.get("device_id")
|
||||
if device_id is not None:
|
||||
device = mes_models.Device.objects.filter(id=device_id, merchant=merchant).first()
|
||||
if device is None:
|
||||
raise serializers.ValidationError({"device_id": "设备不存在"})
|
||||
attrs["device"] = device
|
||||
return attrs
|
||||
|
||||
|
||||
class AgentMesProductionAssignmentEmployeeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = basic_models.Employee
|
||||
fields = ["id", "name", "merchant_id"]
|
||||
|
||||
|
||||
class AgentMesProductionAssignmentDeviceSerializer(serializers.ModelSerializer):
|
||||
category = AgentMesDeviceCategorySerializer(read_only=True)
|
||||
capacity_unit_label = serializers.CharField(source='get_capacity_unit_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = mes_models.Device
|
||||
fields = [
|
||||
"id",
|
||||
"merchant",
|
||||
"category",
|
||||
"name",
|
||||
"peak_capacity",
|
||||
"capacity_unit",
|
||||
"capacity_unit_label",
|
||||
"extra",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class AgentMesProductionAssignmentSerializer(serializers.ModelSerializer):
|
||||
device = AgentMesProductionAssignmentDeviceSerializer(read_only=True)
|
||||
assigner = AgentMesProductionAssignmentEmployeeSerializer(read_only=True)
|
||||
assignee = AgentMesProductionAssignmentEmployeeSerializer(read_only=True)
|
||||
status_label = serializers.CharField(source='get_status_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = mes_models.ProductionAssignment
|
||||
fields = [
|
||||
"id",
|
||||
"merchant",
|
||||
"device",
|
||||
"content_type",
|
||||
"object_id",
|
||||
"assigner",
|
||||
"assignee",
|
||||
"production_quantity",
|
||||
"status",
|
||||
"status_label",
|
||||
"extra",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
|
||||
class AgentMesProductionAssignmentListView(mixins.ListModelMixin, GenericAPIView):
|
||||
"""
|
||||
Agent 专用:查询指定日期范围内创建的生产安排。
|
||||
|
||||
GET /api/v2/ai/mes/production-assignments/?merchant_id=<id>&start_date=<YYYY-MM-DD>&end_date=<YYYY-MM-DD>
|
||||
"""
|
||||
|
||||
authentication_classes = [AgentAccessKeyAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = AgentMesProductionAssignmentSerializer
|
||||
pagination_class = LimitedLimitOffsetPagination
|
||||
|
||||
def get(self, request):
|
||||
query_serializer = AgentMesProductionAssignmentQuerySerializer(data=request.query_params)
|
||||
query_serializer.is_valid(raise_exception=True)
|
||||
self.validated_query = query_serializer.validated_data
|
||||
return self.list(request)
|
||||
|
||||
def get_queryset(self):
|
||||
validated_query = getattr(self, "validated_query", None)
|
||||
if validated_query is None:
|
||||
query_serializer = AgentMesProductionAssignmentQuerySerializer(data=self.request.query_params)
|
||||
query_serializer.is_valid(raise_exception=True)
|
||||
validated_query = query_serializer.validated_data
|
||||
|
||||
merchant = validated_query["merchant_id"]
|
||||
queryset = (
|
||||
mes_models.ProductionAssignment.objects.filter(
|
||||
merchant=merchant,
|
||||
created_at__date__gte=validated_query["start_date"],
|
||||
created_at__date__lte=validated_query["end_date"],
|
||||
)
|
||||
.select_related("device", "device__category", "assigner", "assignee", "content_type")
|
||||
.order_by("-created_at", "-id")
|
||||
)
|
||||
|
||||
device = validated_query.get("device")
|
||||
if device is not None:
|
||||
queryset = queryset.filter(device=device)
|
||||
|
||||
status_value = validated_query.get("status")
|
||||
if status_value is not None:
|
||||
queryset = queryset.filter(status=status_value)
|
||||
|
||||
return queryset
|
||||
|
||||
467
api_v2/views/mes.py
Normal file
467
api_v2/views/mes.py
Normal file
@@ -0,0 +1,467 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import ProtectedError
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import permissions, serializers, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from mes.models import CapacityUnitEnum, ProductionAssignment, ProductionAssignmentStatusEnum
|
||||
from mes import services as mes_services
|
||||
from mes.models import Device, DeviceCategory
|
||||
|
||||
|
||||
EXTRA_JSON_PATH_SEGMENT_RE = re.compile(r'^[A-Za-z0-9_-]+$')
|
||||
|
||||
|
||||
def _get_employee(request):
|
||||
employee = getattr(request.user, "employee", None)
|
||||
if employee is None:
|
||||
raise serializers.ValidationError("当前用户未关联员工")
|
||||
return employee
|
||||
|
||||
|
||||
def _employee_payload(employee):
|
||||
if employee is None:
|
||||
return None
|
||||
return {
|
||||
"id": employee.id,
|
||||
"name": employee.name,
|
||||
"merchant_id": employee.merchant_id,
|
||||
}
|
||||
|
||||
|
||||
def _user_payload(user):
|
||||
if user is None:
|
||||
return None
|
||||
return {
|
||||
"id": user.id,
|
||||
"username": user.username,
|
||||
}
|
||||
|
||||
|
||||
def _parse_extra_filter(extra_path: str | None, extra_value: str | None):
|
||||
if not extra_path and extra_value is None:
|
||||
return None, mes_services.UNSET
|
||||
if not extra_path or extra_value is None:
|
||||
raise serializers.ValidationError({'detail': 'extra_path 和 extra_value 必须同时传入'})
|
||||
|
||||
normalized_segments = [segment.strip() for segment in extra_path.split('.') if segment.strip()]
|
||||
if not normalized_segments:
|
||||
raise serializers.ValidationError({'detail': 'extra_path 不合法'})
|
||||
for segment in normalized_segments:
|
||||
if not EXTRA_JSON_PATH_SEGMENT_RE.match(segment):
|
||||
raise serializers.ValidationError({'detail': 'extra_path 不合法'})
|
||||
|
||||
try:
|
||||
parsed_value = json.loads(extra_value)
|
||||
except json.JSONDecodeError:
|
||||
parsed_value = extra_value
|
||||
return normalized_segments, parsed_value
|
||||
|
||||
|
||||
class DeviceCategoryWriteSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(allow_blank=False, max_length=100)
|
||||
|
||||
|
||||
class DeviceWriteSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(required=False, allow_blank=False, max_length=100)
|
||||
category = serializers.IntegerField(required=False, min_value=1)
|
||||
peak_capacity = serializers.IntegerField(required=False, min_value=1)
|
||||
capacity_unit = serializers.ChoiceField(required=False, choices=CapacityUnitEnum.choices)
|
||||
extra = serializers.JSONField(required=False, allow_null=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.is_create = kwargs.pop("is_create", False)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.is_create:
|
||||
self.fields["name"].required = True
|
||||
self.fields["category"].required = True
|
||||
self.fields["peak_capacity"].required = True
|
||||
|
||||
def validate_category(self, value):
|
||||
employee = self.context["employee"]
|
||||
try:
|
||||
return DeviceCategory.objects.get(id=value, merchant=employee.merchant)
|
||||
except DeviceCategory.DoesNotExist as exc:
|
||||
raise serializers.ValidationError("设备分类不存在") from exc
|
||||
|
||||
|
||||
class DeviceCategorySerializer(serializers.ModelSerializer):
|
||||
created_by = serializers.SerializerMethodField()
|
||||
operator = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = DeviceCategory
|
||||
fields = [
|
||||
"id",
|
||||
"merchant",
|
||||
"name",
|
||||
"created_by",
|
||||
"operator",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
def get_created_by(self, obj):
|
||||
return _user_payload(obj.created_by)
|
||||
|
||||
def get_operator(self, obj):
|
||||
return _employee_payload(obj.operator)
|
||||
|
||||
|
||||
class DeviceSerializer(serializers.ModelSerializer):
|
||||
created_by = serializers.SerializerMethodField()
|
||||
operator = serializers.SerializerMethodField()
|
||||
category_name = serializers.CharField(source="category.name", read_only=True)
|
||||
category = serializers.IntegerField(source="category_id", read_only=True)
|
||||
capacity_unit_label = serializers.CharField(source='get_capacity_unit_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = [
|
||||
"id",
|
||||
"merchant",
|
||||
"category",
|
||||
"category_name",
|
||||
"name",
|
||||
"peak_capacity",
|
||||
"capacity_unit",
|
||||
"capacity_unit_label",
|
||||
"extra",
|
||||
"created_by",
|
||||
"operator",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
def get_created_by(self, obj):
|
||||
return _user_payload(obj.created_by)
|
||||
|
||||
def get_operator(self, obj):
|
||||
return _employee_payload(obj.operator)
|
||||
|
||||
|
||||
class ProductionAssignmentWriteSerializer(serializers.Serializer):
|
||||
device = serializers.IntegerField(required=False, min_value=1)
|
||||
content_type = serializers.IntegerField(required=False, min_value=1)
|
||||
object_id = serializers.IntegerField(required=False, min_value=1)
|
||||
assigner = serializers.IntegerField(required=False, min_value=1)
|
||||
assignee = serializers.IntegerField(required=False, min_value=1, allow_null=True)
|
||||
production_quantity = serializers.IntegerField(required=False, min_value=1)
|
||||
status = serializers.ChoiceField(required=False, choices=ProductionAssignmentStatusEnum.choices)
|
||||
extra = serializers.JSONField(required=False, allow_null=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.is_create = kwargs.pop("is_create", False)
|
||||
super().__init__(*args, **kwargs)
|
||||
if self.is_create:
|
||||
self.fields["device"].required = True
|
||||
self.fields["content_type"].required = True
|
||||
self.fields["object_id"].required = True
|
||||
self.fields["assigner"].required = True
|
||||
self.fields["production_quantity"].required = True
|
||||
|
||||
def validate_device(self, value):
|
||||
employee = self.context["employee"]
|
||||
try:
|
||||
return Device.objects.get(id=value, merchant=employee.merchant)
|
||||
except Device.DoesNotExist as exc:
|
||||
raise serializers.ValidationError("设备不存在") from exc
|
||||
|
||||
def validate_content_type(self, value):
|
||||
try:
|
||||
return ContentType.objects.get(id=value)
|
||||
except ContentType.DoesNotExist as exc:
|
||||
raise serializers.ValidationError("关联对象类型不存在") from exc
|
||||
|
||||
def validate_assigner(self, value):
|
||||
employee = self.context["employee"]
|
||||
try:
|
||||
return employee.merchant.employees.get(id=value)
|
||||
except Exception as exc:
|
||||
raise serializers.ValidationError("指派者不存在") from exc
|
||||
|
||||
def validate_assignee(self, value):
|
||||
if value is None:
|
||||
return None
|
||||
employee = self.context["employee"]
|
||||
try:
|
||||
return employee.merchant.employees.get(id=value)
|
||||
except Exception as exc:
|
||||
raise serializers.ValidationError("被指派人不存在") from exc
|
||||
|
||||
|
||||
class ProductionAssignmentSerializer(serializers.ModelSerializer):
|
||||
device_name = serializers.CharField(source='device.name', read_only=True)
|
||||
assigner = serializers.SerializerMethodField()
|
||||
assignee = serializers.SerializerMethodField()
|
||||
created_by = serializers.SerializerMethodField()
|
||||
operator = serializers.SerializerMethodField()
|
||||
status_label = serializers.CharField(source='get_status_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ProductionAssignment
|
||||
fields = [
|
||||
'id',
|
||||
'merchant',
|
||||
'device',
|
||||
'device_name',
|
||||
'content_type',
|
||||
'object_id',
|
||||
'assigner',
|
||||
'assignee',
|
||||
'production_quantity',
|
||||
'status',
|
||||
'status_label',
|
||||
'extra',
|
||||
'created_by',
|
||||
'operator',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
def get_assigner(self, obj):
|
||||
return _employee_payload(obj.assigner)
|
||||
|
||||
def get_assignee(self, obj):
|
||||
return _employee_payload(obj.assignee)
|
||||
|
||||
def get_created_by(self, obj):
|
||||
return _user_payload(obj.created_by)
|
||||
|
||||
def get_operator(self, obj):
|
||||
return _employee_payload(obj.operator)
|
||||
|
||||
|
||||
class DeviceCategoryListCreateView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
employee = _get_employee(request)
|
||||
queryset = mes_services.list_device_categories_for_merchant(merchant=employee.merchant)
|
||||
name = request.query_params.get("name")
|
||||
if name:
|
||||
queryset = queryset.filter(name__icontains=name.strip())
|
||||
return Response(DeviceCategorySerializer(queryset, many=True).data)
|
||||
|
||||
def post(self, request):
|
||||
employee = _get_employee(request)
|
||||
serializer = DeviceCategoryWriteSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
category = mes_services.create_device_category(
|
||||
merchant=employee.merchant,
|
||||
name=serializer.validated_data["name"],
|
||||
created_by=request.user,
|
||||
operator=employee,
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(DeviceCategorySerializer(category).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class DeviceCategoryDetailView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_object(self, request, category_id):
|
||||
employee = _get_employee(request)
|
||||
return get_object_or_404(
|
||||
mes_services.list_device_categories_for_merchant(merchant=employee.merchant),
|
||||
id=category_id,
|
||||
)
|
||||
|
||||
def get(self, request, category_id):
|
||||
category = self.get_object(request, category_id)
|
||||
return Response(DeviceCategorySerializer(category).data)
|
||||
|
||||
def patch(self, request, category_id):
|
||||
employee = _get_employee(request)
|
||||
category = self.get_object(request, category_id)
|
||||
serializer = DeviceCategoryWriteSerializer(data=request.data, partial=True)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
category = mes_services.update_device_category(
|
||||
category=category,
|
||||
operator=employee,
|
||||
name=serializer.validated_data.get("name", mes_services.UNSET),
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(DeviceCategorySerializer(category).data)
|
||||
|
||||
def delete(self, request, category_id):
|
||||
employee = _get_employee(request)
|
||||
category = self.get_object(request, category_id)
|
||||
try:
|
||||
mes_services.delete_device_category(category=category, operator=employee)
|
||||
except ProtectedError:
|
||||
return Response({"detail": "设备分类已被设备引用,不能删除"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class DeviceListCreateView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
employee = _get_employee(request)
|
||||
category_id = request.query_params.get("category")
|
||||
extra_json_path, extra_json_value = _parse_extra_filter(
|
||||
request.query_params.get("extra_path"),
|
||||
request.query_params.get("extra_value"),
|
||||
)
|
||||
queryset = mes_services.list_devices_for_merchant(
|
||||
merchant=employee.merchant,
|
||||
category_id=int(category_id) if category_id else None,
|
||||
extra_json_path=extra_json_path,
|
||||
extra_json_value=extra_json_value,
|
||||
)
|
||||
name = request.query_params.get("name")
|
||||
if name:
|
||||
queryset = queryset.filter(name__icontains=name.strip())
|
||||
return Response(DeviceSerializer(queryset, many=True).data)
|
||||
|
||||
def post(self, request):
|
||||
employee = _get_employee(request)
|
||||
serializer = DeviceWriteSerializer(data=request.data, is_create=True, context={"employee": employee})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
device = mes_services.create_device(
|
||||
merchant=employee.merchant,
|
||||
category=serializer.validated_data["category"],
|
||||
name=serializer.validated_data["name"],
|
||||
created_by=request.user,
|
||||
operator=employee,
|
||||
peak_capacity=serializer.validated_data['peak_capacity'],
|
||||
capacity_unit=serializer.validated_data.get('capacity_unit', CapacityUnitEnum.METER),
|
||||
extra=serializer.validated_data.get("extra"),
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(DeviceSerializer(device).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class DeviceDetailView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_object(self, request, device_id):
|
||||
employee = _get_employee(request)
|
||||
return get_object_or_404(
|
||||
mes_services.list_devices_for_merchant(merchant=employee.merchant),
|
||||
id=device_id,
|
||||
)
|
||||
|
||||
def get(self, request, device_id):
|
||||
device = self.get_object(request, device_id)
|
||||
return Response(DeviceSerializer(device).data)
|
||||
|
||||
def patch(self, request, device_id):
|
||||
employee = _get_employee(request)
|
||||
device = self.get_object(request, device_id)
|
||||
serializer = DeviceWriteSerializer(data=request.data, partial=True, context={"employee": employee})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
device = mes_services.update_device(
|
||||
device=device,
|
||||
operator=employee,
|
||||
name=serializer.validated_data.get("name", mes_services.UNSET),
|
||||
category=serializer.validated_data.get("category", mes_services.UNSET),
|
||||
peak_capacity=serializer.validated_data.get('peak_capacity', mes_services.UNSET),
|
||||
capacity_unit=serializer.validated_data.get('capacity_unit', mes_services.UNSET),
|
||||
extra=serializer.validated_data.get("extra", mes_services.UNSET),
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(DeviceSerializer(device).data)
|
||||
|
||||
def delete(self, request, device_id):
|
||||
employee = _get_employee(request)
|
||||
device = self.get_object(request, device_id)
|
||||
mes_services.delete_device(device=device, operator=employee)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class ProductionAssignmentListCreateView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get(self, request):
|
||||
employee = _get_employee(request)
|
||||
device_id = request.query_params.get('device')
|
||||
content_type_id = request.query_params.get('content_type')
|
||||
object_id = request.query_params.get('object_id')
|
||||
status_value = request.query_params.get('status')
|
||||
queryset = mes_services.list_production_assignments_for_merchant(
|
||||
merchant=employee.merchant,
|
||||
device_id=int(device_id) if device_id else None,
|
||||
content_type_id=int(content_type_id) if content_type_id else None,
|
||||
object_id=int(object_id) if object_id else None,
|
||||
status=int(status_value) if status_value else None,
|
||||
)
|
||||
return Response(ProductionAssignmentSerializer(queryset, many=True).data)
|
||||
|
||||
def post(self, request):
|
||||
employee = _get_employee(request)
|
||||
serializer = ProductionAssignmentWriteSerializer(data=request.data, is_create=True, context={'employee': employee})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
assignment = mes_services.create_production_assignment(
|
||||
merchant=employee.merchant,
|
||||
device=serializer.validated_data['device'],
|
||||
content_type=serializer.validated_data['content_type'],
|
||||
object_id=serializer.validated_data['object_id'],
|
||||
assigner=serializer.validated_data['assigner'],
|
||||
assignee=serializer.validated_data.get('assignee'),
|
||||
production_quantity=serializer.validated_data['production_quantity'],
|
||||
status=serializer.validated_data.get('status', ProductionAssignmentStatusEnum.DRAFT),
|
||||
created_by=request.user,
|
||||
operator=employee,
|
||||
extra=serializer.validated_data.get('extra'),
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({'detail': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(ProductionAssignmentSerializer(assignment).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
class ProductionAssignmentDetailView(APIView):
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def get_object(self, request, assignment_id):
|
||||
employee = _get_employee(request)
|
||||
return get_object_or_404(
|
||||
mes_services.list_production_assignments_for_merchant(merchant=employee.merchant),
|
||||
id=assignment_id,
|
||||
)
|
||||
|
||||
def get(self, request, assignment_id):
|
||||
assignment = self.get_object(request, assignment_id)
|
||||
return Response(ProductionAssignmentSerializer(assignment).data)
|
||||
|
||||
def patch(self, request, assignment_id):
|
||||
employee = _get_employee(request)
|
||||
assignment = self.get_object(request, assignment_id)
|
||||
serializer = ProductionAssignmentWriteSerializer(data=request.data, partial=True, context={'employee': employee})
|
||||
serializer.is_valid(raise_exception=True)
|
||||
try:
|
||||
assignment = mes_services.update_production_assignment(
|
||||
assignment=assignment,
|
||||
operator=employee,
|
||||
device=serializer.validated_data.get('device', mes_services.UNSET),
|
||||
assignee=serializer.validated_data.get('assignee', mes_services.UNSET),
|
||||
production_quantity=serializer.validated_data.get('production_quantity', mes_services.UNSET),
|
||||
status=serializer.validated_data.get('status', mes_services.UNSET),
|
||||
extra=serializer.validated_data.get('extra', mes_services.UNSET),
|
||||
)
|
||||
except ValueError as exc:
|
||||
return Response({'detail': str(exc)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(ProductionAssignmentSerializer(assignment).data)
|
||||
|
||||
def delete(self, request, assignment_id):
|
||||
employee = _get_employee(request)
|
||||
assignment = self.get_object(request, assignment_id)
|
||||
mes_services.delete_production_assignment(assignment=assignment, operator=employee)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
Reference in New Issue
Block a user