1
0
forked from erp-dev/erp

fix: appversions

This commit is contained in:
2026-07-11 00:05:05 +08:00
parent 48e4782e1e
commit e91d06e4b6
26 changed files with 2582 additions and 19 deletions

View File

@@ -9,6 +9,7 @@ from django.contrib.auth import get_user_model
from basic_info import models as basic_models
from printing import models as printing_models
from stateflow import models as stateflow_models
User = get_user_model()
@@ -318,3 +319,224 @@ class PlateOrderSummaryAPITestCase(TestCase):
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
class PlateOrderDesignerSummaryAPITestCase(TestCase):
"""测试开版订单设计师统计 API"""
def setUp(self):
self.client = APIClient()
self.merchant = basic_models.Merchant.objects.create(
name='测试商户',
type=basic_models.MerchantTypeEnum.FACTORY
)
self.user = User.objects.create_user(
username='designer-summary-user',
password='testpass123'
)
self.employee = basic_models.Employee.objects.create(
sys_user=self.user,
merchant=self.merchant,
name='测试员工'
)
self.customer = basic_models.Customer.objects.create(
merchant=self.merchant,
name='测试客户',
created_by=self.employee
)
self.process = stateflow_models.Process.objects.create(name='开版流程')
self.drawing_done = stateflow_models.State.objects.create(name='画图完成')
self.color_done = stateflow_models.State.objects.create(name='调色完成')
self.client.force_authenticate(user=self.user)
def _create_plate_order(self, *, plate_date=None):
plate_order = printing_models.PlateOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
plate_type='首版',
production_method='定位',
plate_date=timezone.make_aware(datetime.combine(plate_date or date(2026, 2, 8), datetime.min.time()))
)
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PlateOrder-{plate_order.id}',
process=self.process,
description='',
)
plate_order.business_object = business_object
plate_order.save(update_fields=['business_object'])
return plate_order
def _add_designer_param(self, plate_order, *, designer_name, state=None):
state_log = stateflow_models.StateFlowRecord.objects.create(
business_object=plate_order.business_object,
state=state or self.drawing_done,
completed_by=self.user,
)
stateflow_models.StateLogParameterRecord.objects.create(
state_log=state_log,
parameters={'设计师名称': designer_name},
)
def test_returns_designer_summary(self):
plate_order = self._create_plate_order()
self._add_designer_param(plate_order, designer_name='设计师A')
response = self.client.get(
'/api/v1/settlement/plate-orders/designer-summary/?date=2026-02-08'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['meta']['state_names'], ['画图完成'])
self.assertEqual(len(response.data['data']), 1)
self.assertEqual(response.data['data'][0]['designer_name'], '设计师A')
self.assertEqual(response.data['data'][0]['plate_order_count'][0]['today'], 1)
def test_accepts_state_names_query_param(self):
plate_order = self._create_plate_order()
self._add_designer_param(plate_order, designer_name='设计师A', state=self.color_done)
response = self.client.get(
'/api/v1/settlement/plate-orders/designer-summary/?date=2026-02-08&state_names=调色完成'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['meta']['state_names'], ['调色完成'])
self.assertEqual(response.data['data'][0]['designer_name'], '设计师A')
def test_requires_date_parameter(self):
response = self.client.get('/api/v1/settlement/plate-orders/designer-summary/')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('缺少 date 参数', response.data['error'])
def test_invalid_state_names(self):
response = self.client.get(
'/api/v1/settlement/plate-orders/designer-summary/?date=2026-02-08&state_names=,,'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('state_names 不能为空', response.data['error'])
class DesignerWorkflowTaskSummaryAPITestCase(TestCase):
"""测试设计师工序任务量统计 API"""
def setUp(self):
self.client = APIClient()
self.merchant = basic_models.Merchant.objects.create(
name='测试商户',
type=basic_models.MerchantTypeEnum.FACTORY
)
self.user = User.objects.create_user(
username='designer-task-summary-user',
password='testpass123'
)
self.employee = basic_models.Employee.objects.create(
sys_user=self.user,
merchant=self.merchant,
name='测试员工'
)
self.customer = basic_models.Customer.objects.create(
merchant=self.merchant,
name='测试客户',
created_by=self.employee
)
self.process = stateflow_models.Process.objects.create(name='开版流程')
self.drawing_done = stateflow_models.State.objects.create(name='画图完成')
self.color_done = stateflow_models.State.objects.create(name='调色完成')
self.drawing_in_progress = stateflow_models.State.objects.create(name='画图中')
self.client.force_authenticate(user=self.user)
def _create_plate_order(self):
plate_order = printing_models.PlateOrder.objects.create(
merchant=self.merchant,
customer=self.customer,
plate_type='首版',
production_method='定位',
plate_date=timezone.make_aware(datetime.combine(date(2026, 2, 8), datetime.min.time()))
)
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PlateOrder-{plate_order.id}',
process=self.process,
description='',
)
plate_order.business_object = business_object
plate_order.save(update_fields=['business_object'])
return plate_order
def _add_task(self, *, designer_name='左威', quantity='3', state=None):
plate_order = self._create_plate_order()
state_log = stateflow_models.StateFlowRecord.objects.create(
business_object=plate_order.business_object,
state=state or self.drawing_done,
completed_by=self.user,
)
stateflow_models.StateLogParameterRecord.objects.create(
state_log=state_log,
parameters={
'设计师名称': designer_name,
'完成数量': quantity,
'完成时间': '2026-02-08 10:00:00',
},
)
def test_returns_workflow_task_summary(self):
self._add_task(designer_name='左威', quantity='3')
response = self.client.get(
'/api/v1/settlement/workflows/designer-task-summary/?date=2026-02-08'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['meta']['state_names'], [])
self.assertEqual(response.data['meta']['state_filter'], {'mode': 'suffix', 'suffix': '完成'})
self.assertEqual(response.data['meta']['quantity_param_key'], '完成数量')
self.assertEqual(len(response.data['data']), 1)
self.assertEqual(response.data['data'][0]['designer_name'], '左威')
self.assertEqual(response.data['data'][0]['today'], 3)
self.assertEqual(response.data['data'][0]['task_count'][0]['state_name'], '画图完成')
def test_default_state_filter_includes_only_done_suffix(self):
self._add_task(designer_name='左威', quantity='3', state=self.drawing_done)
self._add_task(designer_name='左威', quantity='2', state=self.color_done)
self._add_task(designer_name='左威', quantity='10', state=self.drawing_in_progress)
response = self.client.get(
'/api/v1/settlement/workflows/designer-task-summary/?date=2026-02-08'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['data'][0]['today'], 5)
states = {item['state_name'] for item in response.data['data'][0]['task_count']}
self.assertEqual(states, {'画图完成', '调色完成'})
def test_accepts_state_and_designer_filters(self):
self._add_task(designer_name='左威', quantity='3', state=self.drawing_done)
self._add_task(designer_name='王五', quantity='2', state=self.color_done)
response = self.client.get(
'/api/v1/settlement/workflows/designer-task-summary/'
'?date=2026-02-08&state_names=调色完成&designer_names=王五'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['meta']['state_names'], ['调色完成'])
self.assertEqual(response.data['meta']['state_filter'], {'mode': 'exact', 'state_names': ['调色完成']})
self.assertEqual(response.data['meta']['designer_names'], ['王五'])
self.assertEqual(len(response.data['data']), 1)
self.assertEqual(response.data['data'][0]['designer_name'], '王五')
self.assertEqual(response.data['data'][0]['today'], 2)
def test_requires_date_parameter(self):
response = self.client.get('/api/v1/settlement/workflows/designer-task-summary/')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('缺少 date 参数', response.data['error'])
def test_invalid_designer_names(self):
response = self.client.get(
'/api/v1/settlement/workflows/designer-task-summary/?date=2026-02-08&designer_names=,,'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('designer_names 不能为空', response.data['error'])

View File

@@ -13,7 +13,16 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from basic_info.models import Merchant
from settlement.services import get_plate_order_summary_by_customer
from settlement.services import (
DEFAULT_DESIGNER_SUMMARY_STATE_NAMES,
DEFAULT_DESIGNER_TASK_STATE_NAME_SUFFIX,
DEFAULT_DESIGNER_PARAM_KEY,
DEFAULT_DESIGNER_TASK_QUANTITY_PARAM_KEY,
DEFAULT_DESIGNER_TASK_TIME_PARAM_KEY,
get_plate_order_summary_by_customer,
get_plate_order_summary_by_designer,
get_designer_workflow_task_summary,
)
from .mixins import SettlementVisibilityMixin
logger = logging.getLogger(__name__)
@@ -120,3 +129,189 @@ class PlateOrderSummaryView(SettlementVisibilityMixin, APIView):
{"error": "获取统计数据失败"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
class PlateOrderDesignerSummaryView(SettlementVisibilityMixin, APIView):
"""
开版订单设计师统计 API
GET /api/v1/settlement/plate-orders/designer-summary/
参数:
date: 统计日期YYYY-MM-DD
state_names: 可选,逗号分隔或重复传参的 state 名称,默认“画图完成”
"""
authentication_classes = PlateOrderSummaryView.authentication_classes
permission_classes = [IsAuthenticated]
view_all_permission = "printing.view_all_plateorders"
def get(self, request):
date_str = request.query_params.get("date")
if not date_str:
return Response(
{"error": "缺少 date 参数"}, status=status.HTTP_400_BAD_REQUEST
)
if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str):
return Response(
{"error": "日期格式错误,请使用 YYYY-MM-DD 格式"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
settlement_date = date.fromisoformat(date_str)
except ValueError:
return Response({"error": "日期不存在"}, status=status.HTTP_403_FORBIDDEN)
emp = getattr(request.user, "employee", None)
if emp is None or emp.merchant is None:
if request.user.is_superuser:
merchant = Merchant.objects.first()
if merchant is None:
return Response(
{"error": "系统中没有商户"}, status=status.HTTP_403_FORBIDDEN
)
merchant_id = merchant.id
else:
return Response(
{"error": "用户未关联商户"}, status=status.HTTP_403_FORBIDDEN
)
else:
merchant_id = emp.merchant.id
state_names = _parse_state_names(request)
try:
data = get_plate_order_summary_by_designer(
merchant_id=merchant_id,
settlement_date=settlement_date,
user=self.get_service_user(request.user),
state_names=state_names,
)
return Response({
"data": data,
"meta": {
"state_names": list(state_names or DEFAULT_DESIGNER_SUMMARY_STATE_NAMES),
"designer_param_key": DEFAULT_DESIGNER_PARAM_KEY,
},
})
except ValueError as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.exception(f"[settlement.views] 获取开版订单设计师统计失败: {e}")
return Response(
{"error": "获取统计数据失败"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
def _parse_state_names(request):
"""解析 state_names支持逗号分隔和重复传参。"""
return _parse_csv_query_param(request, "state_names")
def _parse_designer_names(request):
"""解析 designer_names支持逗号分隔和重复传参。"""
return _parse_csv_query_param(request, "designer_names")
def _parse_csv_query_param(request, name: str):
raw_values = request.query_params.getlist("state_names")
if name != "state_names":
raw_values = request.query_params.getlist(name)
if not raw_values:
return None
values = []
for raw_value in raw_values:
values.extend(
item.strip() for item in str(raw_value).split(',') if item.strip()
)
return values
class DesignerWorkflowTaskSummaryView(SettlementVisibilityMixin, APIView):
"""
设计师工序任务量统计 API
GET /api/v1/settlement/workflows/designer-task-summary/
"""
authentication_classes = PlateOrderSummaryView.authentication_classes
permission_classes = [IsAuthenticated]
view_all_permission = "printing.view_all_plateorders"
def get(self, request):
date_str = request.query_params.get("date")
if not date_str:
return Response(
{"error": "缺少 date 参数"}, status=status.HTTP_400_BAD_REQUEST
)
if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str):
return Response(
{"error": "日期格式错误,请使用 YYYY-MM-DD 格式"},
status=status.HTTP_400_BAD_REQUEST,
)
try:
settlement_date = date.fromisoformat(date_str)
except ValueError:
return Response({"error": "日期不存在"}, status=status.HTTP_403_FORBIDDEN)
emp = getattr(request.user, "employee", None)
if emp is None or emp.merchant is None:
if request.user.is_superuser:
merchant = Merchant.objects.first()
if merchant is None:
return Response(
{"error": "系统中没有商户"}, status=status.HTTP_403_FORBIDDEN
)
merchant_id = merchant.id
else:
return Response(
{"error": "用户未关联商户"}, status=status.HTTP_403_FORBIDDEN
)
else:
merchant_id = emp.merchant.id
state_names = _parse_state_names(request)
designer_names = _parse_designer_names(request)
try:
data = get_designer_workflow_task_summary(
merchant_id=merchant_id,
settlement_date=settlement_date,
user=self.get_service_user(request.user),
state_names=state_names,
designer_names=designer_names,
)
return Response({
"data": data,
"meta": {
"date": settlement_date.isoformat(),
"state_names": list(state_names or []),
"state_filter": (
{"mode": "exact", "state_names": list(state_names)}
if state_names is not None
else {"mode": "suffix", "suffix": DEFAULT_DESIGNER_TASK_STATE_NAME_SUFFIX}
),
"designer_names": list(designer_names or []),
"designer_param_key": DEFAULT_DESIGNER_PARAM_KEY,
"quantity_param_key": DEFAULT_DESIGNER_TASK_QUANTITY_PARAM_KEY,
"time_param_key": DEFAULT_DESIGNER_TASK_TIME_PARAM_KEY,
"empty_quantity_default": 1,
"time_source": "parameters.完成时间; fallback=StateFlowRecord.completed_at",
},
})
except ValueError as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logger.exception(f"[settlement.views] 获取设计师工序任务量统计失败: {e}")
return Response(
{"error": "获取统计数据失败"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)

View File

@@ -15,6 +15,7 @@ from .views import (
ShipmentDeliveryBindShipmentsView,
ShipmentDeliveryByPrintingOrderView,
ShipmentDeliveryDetailView,
ShipmentDeliveryPrintingJobListView,
ShipmentDeliveryListCreateView,
ShipmentDeliveryCancelView,
ShipmentDeliveryStatusUpdateView,
@@ -22,6 +23,7 @@ from .views import (
ShipmentListCreateView,
ShipmentDetailView,
ShipmentExternalCreateView,
ShipmentPrintingJobListView,
)
__all__ = [
@@ -36,6 +38,7 @@ __all__ = [
'ShipmentDeliveryBindShipmentsView',
'ShipmentDeliveryByPrintingOrderView',
'ShipmentDeliveryDetailView',
'ShipmentDeliveryPrintingJobListView',
'ShipmentDeliveryListCreateView',
'ShipmentDeliveryCancelView',
'ShipmentDeliveryStatusUpdateView',
@@ -43,4 +46,5 @@ __all__ = [
'ShipmentListCreateView',
'ShipmentDetailView',
'ShipmentExternalCreateView',
'ShipmentPrintingJobListView',
]

View File

@@ -650,6 +650,9 @@ class ShipmentSalesItemCustomerSerializer(serializers.Serializer):
class ShipmentDeliveryShipmentSummarySerializer(serializers.ModelSerializer):
customer_name = serializers.CharField(source="customer.name", read_only=True)
address_id = serializers.IntegerField(
source="customer_address.id", read_only=True, allow_null=True
)
status_display = serializers.CharField(source="get_status_display", read_only=True)
fabric = serializers.SerializerMethodField()
order_description = serializers.SerializerMethodField()
@@ -661,6 +664,14 @@ class ShipmentDeliveryShipmentSummarySerializer(serializers.ModelSerializer):
"id",
"customer",
"customer_name",
"address_id",
"address",
"contact_name",
"contact_phone",
"area",
"coordinates",
"geo_coordinates",
"extra",
"fabric",
"order_description",
"shipment_date",
@@ -822,6 +833,21 @@ class ShipmentDeliveryByPrintingOrderSerializer(serializers.ModelSerializer):
).data
class ShipmentPrintingJobSummarySerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
printing_order_id = serializers.IntegerField(read_only=True)
external_order_id = serializers.CharField(
source="printing_order.external_order_id",
read_only=True,
allow_null=True,
)
customer_name = serializers.CharField(
source="printing_order.customer.name",
read_only=True,
allow_null=True,
)
class ShipmentDeliveryCreateSerializer(serializers.Serializer):
driver_name = serializers.CharField(max_length=100, help_text="司机名")
vehicle_trip = serializers.CharField(max_length=100, help_text="车次")

View File

@@ -2527,6 +2527,128 @@ class ShipmentQueryAPITestCase(TestCase):
self.assertIn("external_finished_products", result)
self.assertIsInstance(result["external_finished_products"], list)
def test_list_shipment_printing_jobs_returns_minimal_paginated_jobs(self):
other_order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant1,
customer=self.customer1,
fabric="第二生产单面料",
width="160cm",
process=self.process1,
created_by=self.user1,
external_order_id="QUERY-PO-002",
)
other_job = printing_models.PrintingJob.objects.create(
merchant=self.merchant1,
printing_order=other_order,
product=self.product1,
quantity=10,
unit="",
created_by=self.user1,
)
shipment_models.SalesItem.objects.create(
merchant=self.merchant1,
shipment=self.shipment1,
name="重复销售品",
quantity=Decimal("1.00"),
unit=shipment_models.UnitChoices.METER,
printing_job_id=self.printing_job1.id,
customer_id=self.customer1.id,
created_by=self.user1,
)
shipment_models.SalesItem.objects.create(
merchant=self.merchant1,
shipment=self.shipment1,
name="第二销售品",
quantity=Decimal("2.00"),
unit=shipment_models.UnitChoices.METER,
printing_job_id=other_job.id,
customer_id=self.customer1.id,
created_by=self.user1,
)
resp = self.client.get(f"/api/v1/shipment/shipments/{self.shipment1.id}/printing-jobs/?limit=1")
self.assertEqual(resp.status_code, status.HTTP_200_OK)
data = resp.json()
self.assertEqual(data["count"], 2)
self.assertEqual(len(data["results"]), 1)
item = data["results"][0]
self.assertEqual(set(item.keys()), {"id", "printing_order_id", "external_order_id", "customer_name"})
self.assertEqual(item["id"], self.printing_job1.id)
self.assertEqual(item["printing_order_id"], self.printing_order1.id)
self.assertEqual(item["external_order_id"], "QUERY-PO-001")
self.assertEqual(item["customer_name"], "客户1")
def test_list_delivery_printing_jobs_returns_jobs_across_shipments(self):
delivery = shipment_models.ShipmentDelivery.objects.create(
merchant=self.merchant1,
driver_name="生产任务司机",
vehicle_trip="JOB-DELIVERY",
created_by=self.user1,
)
self.shipment1.delivery = delivery
self.shipment1.save(update_fields=["delivery", "updated_at"])
other_order = printing_models.PrintingOrder.objects.create(
merchant=self.merchant1,
customer=self.customer1,
fabric="送货单生产单面料",
width="160cm",
process=self.process1,
created_by=self.user1,
external_order_id="QUERY-PO-DELIVERY",
)
other_job = printing_models.PrintingJob.objects.create(
merchant=self.merchant1,
printing_order=other_order,
product=self.product1,
quantity=10,
unit="",
created_by=self.user1,
)
other_shipment = shipment_models.Shipment.objects.create(
merchant=self.merchant1,
customer=self.customer1,
shipment_date="2026-01-24",
created_by=self.user1,
delivery=delivery,
)
shipment_models.SalesItem.objects.create(
merchant=self.merchant1,
shipment=other_shipment,
name="送货单销售品",
quantity=Decimal("2.00"),
unit=shipment_models.UnitChoices.METER,
printing_job_id=other_job.id,
customer_id=self.customer1.id,
created_by=self.user1,
)
resp = self.client.get(f"/api/v1/shipment/deliveries/{delivery.id}/printing-jobs/?limit=10")
self.assertEqual(resp.status_code, status.HTTP_200_OK)
data = resp.json()
self.assertEqual(data["count"], 2)
ids = [item["id"] for item in data["results"]]
self.assertEqual(ids, [self.printing_job1.id, other_job.id])
def test_printing_jobs_sub_endpoints_respect_merchant_scope(self):
other_delivery = shipment_models.ShipmentDelivery.objects.create(
merchant=self.merchant2,
driver_name="其他商户司机",
vehicle_trip="OTHER-MERCHANT",
created_by=self.user2,
)
shipment_resp = self.client.get(
f"/api/v1/shipment/shipments/{self.shipment2.id}/printing-jobs/"
)
delivery_resp = self.client.get(
f"/api/v1/shipment/deliveries/{other_delivery.id}/printing-jobs/"
)
self.assertEqual(shipment_resp.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(delivery_resp.status_code, status.HTTP_404_NOT_FOUND)
def test_patch_shipment_area_success(self):
"""
新增字段 area支持更新PATCH并回显。
@@ -3548,14 +3670,43 @@ class ShipmentDeliveryAPITestCase(APITestCase):
self.assertEqual(data["results"][0]["id"], delivery.id)
def test_detail_returns_nested_shipment_summaries(self):
customer_address = basic_models.CustomerAddress.objects.create(
merchant=self.merchant,
customer=self.customer,
address="客户地址库地址",
contact_name="地址库联系人",
contact_phone="13600136000",
area="杭州",
coordinates="120.1551,30.2741",
created_by=self.employee,
)
delivery = shipment_models.ShipmentDelivery.objects.create(
merchant=self.merchant,
driver_name="张司机",
vehicle_trip="KD-004",
created_by=self.user,
)
self.shipment1.customer_address = customer_address
self.shipment1.address = "杭州市测试路 1 号"
self.shipment1.contact_name = "张三"
self.shipment1.contact_phone = "13800138000"
self.shipment1.area = "华东"
self.shipment1.coordinates = "120.1551,30.2741"
self.shipment1.geo_coordinates = {"lng": 120.1551, "lat": 30.2741}
self.shipment1.extra = {"dock": "A"}
self.shipment1.delivery = delivery
self.shipment1.save(update_fields=["delivery", "updated_at"])
self.shipment1.save(update_fields=[
"customer_address",
"address",
"contact_name",
"contact_phone",
"area",
"coordinates",
"geo_coordinates",
"extra",
"delivery",
"updated_at",
])
self.shipment2.delivery = delivery
self.shipment2.save(update_fields=["delivery", "updated_at"])
@@ -3572,6 +3723,14 @@ class ShipmentDeliveryAPITestCase(APITestCase):
self.assertIn("fabric", item)
self.assertIn("order_description", item)
first_shipment = next(item for item in data["shipments"] if item["id"] == self.shipment1.id)
self.assertEqual(first_shipment["address_id"], customer_address.id)
self.assertEqual(first_shipment["address"], "杭州市测试路 1 号")
self.assertEqual(first_shipment["contact_name"], "张三")
self.assertEqual(first_shipment["contact_phone"], "13800138000")
self.assertEqual(first_shipment["area"], "华东")
self.assertEqual(first_shipment["coordinates"], "120.1551,30.2741")
self.assertEqual(first_shipment["geo_coordinates"], {"lng": 120.1551, "lat": 30.2741})
self.assertEqual(first_shipment["extra"], {"dock": "A"})
self.assertIsNone(first_shipment["fabric"])
self.assertIsNone(first_shipment["order_description"])

View File

@@ -12,7 +12,7 @@ from rest_framework.views import APIView
from django.utils.dateparse import parse_date, parse_datetime
from flower.viewsets import LimitedLimitOffsetPagination
from shipment.models import Shipment, ShipmentDelivery, ShipmentDeliveryStatus, ShipmentStatus
from shipment.models import SalesItem, Shipment, ShipmentDelivery, ShipmentDeliveryStatus, ShipmentStatus
from .serializers import (
SalesItemDetailSerializer,
@@ -26,6 +26,7 @@ from .serializers import (
ShipmentDeliverySerializer,
ShipmentDeliveryStatusUpdateSerializer,
ShipmentDeliveryUpdateSerializer,
ShipmentPrintingJobSummarySerializer,
ShipmentSerializer,
ShipmentCreateNormalSerializer,
ShipmentCreateExternalSerializer,
@@ -97,6 +98,26 @@ def _build_sales_item_serializer_context(items):
}
def _get_printing_jobs_for_shipment_ids(*, merchant, shipment_ids):
from printing.models import PrintingJob
printing_job_ids = (
SalesItem.objects.filter(
merchant=merchant,
shipment_id__in=shipment_ids,
delete_at__isnull=True,
printing_job_id__isnull=False,
)
.values_list("printing_job_id", flat=True)
.distinct()
)
return (
PrintingJob.objects.filter(id__in=printing_job_ids)
.select_related("printing_order", "printing_order__customer")
.order_by("id")
)
class ShipmentListCreateView(ListModelMixin, GenericAPIView):
"""
出货单:查询列表 / 创建
@@ -417,6 +438,40 @@ class ShipmentStatusUpdateView(APIView):
return self.patch(request, pk=pk)
class ShipmentPrintingJobListView(GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = ShipmentPrintingJobSummarySerializer
pagination_class = LimitedLimitOffsetPagination
def get_shipment_queryset(self):
qs = Shipment.objects.all().select_related("merchant")
user = self.request.user
if getattr(user, "is_superuser", False):
return qs
emp = getattr(user, "employee", None)
merchant = getattr(emp, "merchant", None) if emp else None
if not merchant:
return Shipment.objects.none()
return qs.filter(merchant=merchant)
def get(self, request, pk: int):
shipment = self.get_shipment_queryset().filter(id=pk).first()
if shipment is None:
return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
queryset = _get_printing_jobs_for_shipment_ids(
merchant=shipment.merchant,
shipment_ids=[shipment.id],
)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class ShipmentDeliveryListCreateView(ListModelMixin, GenericAPIView):
"""
送货单:查询列表 / 创建
@@ -435,6 +490,7 @@ class ShipmentDeliveryListCreateView(ListModelMixin, GenericAPIView):
).prefetch_related(
"shipments",
"shipments__customer",
"shipments__customer_address",
)
user = self.request.user
@@ -489,6 +545,7 @@ class ShipmentDeliveryListCreateView(ListModelMixin, GenericAPIView):
).prefetch_related(
"shipments",
"shipments__customer",
"shipments__customer_address",
).get(id=delivery.id)
return Response(
@@ -652,7 +709,11 @@ class ShipmentDeliveryByPrintingOrderView(GenericAPIView):
shipments__items__delete_at__isnull=True,
)
.select_related("merchant", "created_by", "operator", "cancelled_by")
.prefetch_related("shipments", "shipments__customer")
.prefetch_related(
"shipments",
"shipments__customer",
"shipments__customer_address",
)
)
queryset = self._apply_filters(queryset)
@@ -682,6 +743,7 @@ class ShipmentDeliveryDetailView(RetrieveModelMixin, GenericAPIView):
).prefetch_related(
"shipments",
"shipments__customer",
"shipments__customer_address",
)
user = self.request.user
@@ -744,6 +806,41 @@ class ShipmentDeliveryDetailView(RetrieveModelMixin, GenericAPIView):
return Response(status=status.HTTP_204_NO_CONTENT)
class ShipmentDeliveryPrintingJobListView(GenericAPIView):
permission_classes = [IsAuthenticated]
serializer_class = ShipmentPrintingJobSummarySerializer
pagination_class = LimitedLimitOffsetPagination
def get_delivery_queryset(self):
qs = ShipmentDelivery.objects.all().select_related("merchant")
user = self.request.user
if getattr(user, "is_superuser", False):
return qs
emp = getattr(user, "employee", None)
merchant = getattr(emp, "merchant", None) if emp else None
if not merchant:
return ShipmentDelivery.objects.none()
return qs.filter(merchant=merchant)
def get(self, request, pk: int):
delivery = self.get_delivery_queryset().filter(id=pk).first()
if delivery is None:
return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
shipment_ids = delivery.shipments.values_list("id", flat=True)
queryset = _get_printing_jobs_for_shipment_ids(
merchant=delivery.merchant,
shipment_ids=shipment_ids,
)
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class ShipmentDeliveryStatusUpdateView(APIView):
"""
修改送货单状态
@@ -787,6 +884,7 @@ class ShipmentDeliveryStatusUpdateView(APIView):
).prefetch_related(
"shipments",
"shipments__customer",
"shipments__customer_address",
).get(id=delivery.id)
return Response(
@@ -837,6 +935,7 @@ class ShipmentDeliveryCancelView(APIView):
).prefetch_related(
"shipments",
"shipments__customer",
"shipments__customer_address",
).get(id=delivery.id)
return Response(
@@ -888,6 +987,7 @@ class ShipmentDeliveryBindShipmentsView(APIView):
).prefetch_related(
"shipments",
"shipments__customer",
"shipments__customer_address",
).get(id=delivery.id)
return Response(