1
0
forked from erp-dev/erp

feat: added mdy stagging upload to tiia, and change outgoing_date to datetime type

This commit is contained in:
2026-01-22 17:06:23 +08:00
parent 49ac17cf37
commit 7fd44c64aa
18 changed files with 661 additions and 11 deletions

View File

@@ -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`)

View File

@@ -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

View File

@@ -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):
"""测试获取订单列表"""
# 创建测试订单

View File

@@ -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