forked from erp-dev/erp
feat: added mdy stagging upload to tiia, and change outgoing_date to datetime type
This commit is contained in:
@@ -39,7 +39,7 @@
|
||||
- `is_fabric_received`: 布料是否已收 (`true`/`false`)
|
||||
- `is_invalid`: 是否作废 (`true`/`false`)
|
||||
- `area`: 地区 (模糊查询)
|
||||
- `outgoing_date_from`/`to`: 出货日期范围
|
||||
- `outgoing_date_from`/`to`: 出货日期时间范围(支持传 `YYYY-MM-DD` 或日期时间;`to` 传日期时表示包含整天)
|
||||
- `created_date_from`/`to`: 创建日期范围
|
||||
- `search`: 全文搜索 (客户名称, 面料, 地区, 工艺)
|
||||
- `ordering`: 排序字段 (e.g., `-created_at`)
|
||||
|
||||
@@ -255,6 +255,20 @@ class PrintingOrderDetailSerializer(serializers.ModelSerializer):
|
||||
|
||||
class PrintingOrderCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""印染订单创建/更新序列化器"""
|
||||
|
||||
# 兼容前端只传日期(YYYY-MM-DD):会解析为 00:00:00
|
||||
# 同时也接受 ISO8601 datetime(含时区)。
|
||||
outgoing_date = serializers.DateTimeField(
|
||||
required=False,
|
||||
allow_null=True,
|
||||
input_formats=[
|
||||
"iso-8601",
|
||||
"%Y-%m-%d",
|
||||
"%Y-%m-%d %H:%M:%S",
|
||||
"%Y-%m-%d %H:%M",
|
||||
],
|
||||
help_text="出货日期时间(可仅传 YYYY-MM-DD,将自动视为 00:00:00)",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = models.PrintingOrder
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""
|
||||
PrintingOrder API 测试
|
||||
"""
|
||||
from datetime import datetime
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from rest_framework.test import APIClient
|
||||
from rest_framework import status
|
||||
from django.contrib.auth import get_user_model
|
||||
@@ -111,7 +113,42 @@ class PrintingOrderAPITestCase(TestCase):
|
||||
self.assertEqual(order.fabric, '纯棉布料')
|
||||
# 验证 merchant 自动绑定
|
||||
self.assertEqual(order.merchant.id, self.merchant.id)
|
||||
# 验证 outgoing_date 接受日期字符串并归一为 00:00:00(按当前时区)
|
||||
self.assertIsNotNone(order.outgoing_date)
|
||||
local_dt = timezone.localtime(order.outgoing_date)
|
||||
self.assertEqual(str(local_dt.date()), '2025-11-20')
|
||||
self.assertEqual(local_dt.hour, 0)
|
||||
self.assertEqual(local_dt.minute, 0)
|
||||
|
||||
def test_filter_outgoing_date_to_includes_whole_day(self):
|
||||
"""
|
||||
outgoing_date_to 传 YYYY-MM-DD 时应包含整天:
|
||||
- outgoing_date < 次日 00:00:00
|
||||
"""
|
||||
tz = timezone.get_current_timezone()
|
||||
d0 = timezone.make_aware(datetime(2025, 11, 20, 23, 0, 0), tz)
|
||||
d1 = timezone.make_aware(datetime(2025, 11, 21, 0, 0, 0), tz)
|
||||
printing_models.PrintingOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=self.customer,
|
||||
fabric='布料A',
|
||||
width='150cm',
|
||||
outgoing_date=d0,
|
||||
)
|
||||
printing_models.PrintingOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=self.customer,
|
||||
fabric='布料B',
|
||||
width='150cm',
|
||||
outgoing_date=d1,
|
||||
)
|
||||
|
||||
resp = self.client.get('/api/v1/printing-orders/?limit=50&offset=0&outgoing_date_to=2025-11-20')
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
fabrics = [row['fabric'] for row in resp.data['results']]
|
||||
self.assertIn('布料A', fabrics)
|
||||
self.assertNotIn('布料B', fabrics)
|
||||
|
||||
def test_list_printing_orders(self):
|
||||
"""测试获取订单列表"""
|
||||
# 创建测试订单
|
||||
|
||||
@@ -7,14 +7,18 @@ from rest_framework.response import Response
|
||||
from rest_framework.permissions import BasePermission
|
||||
from rest_framework.permissions import DjangoModelPermissions
|
||||
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import CharField, Prefetch
|
||||
from django.db.models.functions import Cast, Coalesce
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.http import condition
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils import timezone
|
||||
from django.utils.dateparse import parse_date, parse_datetime
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters import rest_framework as django_filters
|
||||
from django_filters import IsoDateTimeFilter
|
||||
from datetime import datetime, time, timedelta
|
||||
|
||||
from flower.viewsets import LimitedModelViewSet
|
||||
from printing import models
|
||||
@@ -75,8 +79,8 @@ class PrintingOrderFilterSet(django_filters.FilterSet):
|
||||
is_fabric_received = django_filters.BooleanFilter()
|
||||
is_invalid = django_filters.BooleanFilter()
|
||||
area = django_filters.CharFilter(lookup_expr='icontains')
|
||||
outgoing_date_from = django_filters.DateFilter(field_name='outgoing_date', lookup_expr='gte')
|
||||
outgoing_date_to = django_filters.DateFilter(field_name='outgoing_date', lookup_expr='lte')
|
||||
outgoing_date_from = django_filters.CharFilter(method='filter_outgoing_date_from')
|
||||
outgoing_date_to = django_filters.CharFilter(method='filter_outgoing_date_to')
|
||||
created_date_from = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
|
||||
created_date_to = django_filters.DateFilter(field_name='created_at', method='filter_created_date_to')
|
||||
|
||||
@@ -84,6 +88,58 @@ class PrintingOrderFilterSet(django_filters.FilterSet):
|
||||
model = models.PrintingOrder
|
||||
fields = ['customer', 'is_urgent', 'is_fabric_received', 'is_invalid']
|
||||
|
||||
@staticmethod
|
||||
def _parse_date_or_datetime(value: str | None) -> tuple[datetime | None, bool]:
|
||||
"""
|
||||
解析日期/日期时间字符串为“当前时区”的 aware datetime。
|
||||
|
||||
返回:
|
||||
- (dt, has_time)
|
||||
- has_time=True:输入包含时间(datetime)
|
||||
- has_time=False:输入为纯日期(date-only)
|
||||
"""
|
||||
s = (value or '').strip()
|
||||
if not s:
|
||||
return None, False
|
||||
|
||||
tz = timezone.get_current_timezone()
|
||||
|
||||
# 注意:Django 的 parse_datetime 在某些版本会把纯日期(YYYY-MM-DD)也解析成 00:00:00,
|
||||
# 这会让“to=日期”语义从“包含整天”退化为“<= 当天 00:00:00”。因此这里先尝试 parse_date。
|
||||
d = parse_date(s)
|
||||
if d is not None:
|
||||
dt0 = timezone.make_aware(datetime.combine(d, time.min), tz)
|
||||
return dt0, False
|
||||
|
||||
dt = parse_datetime(s)
|
||||
if dt is not None:
|
||||
if timezone.is_naive(dt):
|
||||
dt = timezone.make_aware(dt, tz)
|
||||
else:
|
||||
dt = timezone.localtime(dt, tz)
|
||||
return dt, True
|
||||
|
||||
return None, False
|
||||
|
||||
def filter_outgoing_date_from(self, queryset, name, value):
|
||||
dt, _has_time = self._parse_date_or_datetime(value)
|
||||
if dt is None:
|
||||
# 与 DRF/DjangoFilterBackend 行为保持一致:参数非法返回 400
|
||||
raise ValidationError('outgoing_date_from 必须是日期或日期时间')
|
||||
return queryset.filter(outgoing_date__gte=dt)
|
||||
|
||||
def filter_outgoing_date_to(self, queryset, name, value):
|
||||
dt, has_time = self._parse_date_or_datetime(value)
|
||||
if dt is None:
|
||||
raise ValidationError('outgoing_date_to 必须是日期或日期时间')
|
||||
|
||||
# 纯日期:包含整天 => outgoing_date < 次日 00:00:00
|
||||
if not has_time:
|
||||
return queryset.filter(outgoing_date__lt=dt + timedelta(days=1))
|
||||
|
||||
# 日期时间:按“<=”包含到秒
|
||||
return queryset.filter(outgoing_date__lte=dt)
|
||||
|
||||
def filter_created_date_to(self, queryset, name, value):
|
||||
"""
|
||||
过滤 created_at <= 指定日期的 23:59:59.999999
|
||||
|
||||
Reference in New Issue
Block a user