forked from erp-dev/erp
feat: added print_count api, added merchant setting model, added manual mode setting for purchase order
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin import action
|
||||
from api_v1.models import UploadedFile
|
||||
from .tasks import backup_database
|
||||
|
||||
|
||||
@admin.register(UploadedFile)
|
||||
@@ -10,7 +12,12 @@ class UploadedFileAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ['created_at', 'file_size', 'content_type']
|
||||
date_hierarchy = 'created_at'
|
||||
ordering = ['-created_at']
|
||||
actions = ['backup_database']
|
||||
|
||||
def get_queryset(self, request):
|
||||
# 在管理界面显示所有文件,包括已删除的
|
||||
return super().get_queryset(request).select_related('owner')
|
||||
|
||||
@action(description='备份数据库')
|
||||
def backup_database(self, request, queryset):
|
||||
backup_database.delay()
|
||||
|
||||
11
api_v1/enums.py
Normal file
11
api_v1/enums.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class PrintCountObjectType(models.TextChoices):
|
||||
PRINTING_ORDER = 'printing_order', '印染订单'
|
||||
PLATE_ORDER = 'plate_order', '开版订单'
|
||||
|
||||
@classmethod
|
||||
def values(cls):
|
||||
return [choice.value for choice in cls]
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import List, Dict, Any
|
||||
from stock import models as stock_models
|
||||
from basic_info import models as basic_info_models
|
||||
from . import models as api_models
|
||||
from .enums import PrintCountObjectType
|
||||
|
||||
|
||||
class ProductStockChangeSerializer(serializers.Serializer):
|
||||
@@ -193,4 +194,25 @@ class FileUploadSerializer(serializers.Serializer):
|
||||
if value.size > max_size:
|
||||
raise serializers.ValidationError(f'文件大小不能超过 {max_size // (1024*1024)}MB')
|
||||
|
||||
return value
|
||||
return value
|
||||
|
||||
|
||||
class PrintCountDeltaSerializer(serializers.Serializer):
|
||||
"""打印次数增量请求"""
|
||||
|
||||
object_type = serializers.ChoiceField(choices=PrintCountObjectType.choices)
|
||||
object_id = serializers.IntegerField(min_value=1)
|
||||
delta = serializers.CharField(required=False, allow_null=True, allow_blank=True)
|
||||
|
||||
def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]:
|
||||
raw_delta = attrs.get('delta')
|
||||
try:
|
||||
delta_value = int(raw_delta)
|
||||
except (TypeError, ValueError):
|
||||
delta_value = 1
|
||||
|
||||
if delta_value < 1:
|
||||
delta_value = 1
|
||||
|
||||
attrs['delta'] = delta_value
|
||||
return attrs
|
||||
91
api_v1/test_print_count_delta.py
Normal file
91
api_v1/test_print_count_delta.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from basic_info.models import Merchant, MerchantTypeEnum, Customer
|
||||
from printing import models as printing_models
|
||||
|
||||
|
||||
class PrintCountDeltaAPITestCase(TestCase):
|
||||
"""打印次数增量接口测试"""
|
||||
|
||||
url = '/api/v1/print-count/delta/'
|
||||
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.user = get_user_model().objects.create_user(username='tester', password='test123')
|
||||
self.client.force_authenticate(self.user)
|
||||
|
||||
self.merchant = Merchant.objects.create(name='测试商户', type=MerchantTypeEnum.FACTORY)
|
||||
self.customer = Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='客户甲',
|
||||
mobile='13900000000',
|
||||
created_by=None,
|
||||
)
|
||||
|
||||
self.printing_order = printing_models.PrintingOrder.objects.create(
|
||||
customer=self.customer,
|
||||
fabric='棉布',
|
||||
width='150cm',
|
||||
)
|
||||
self.plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DES-001',
|
||||
)
|
||||
|
||||
def test_increment_printing_order_with_delta(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{
|
||||
'object_type': 'printing_order',
|
||||
'object_id': self.printing_order.id,
|
||||
'delta': 3,
|
||||
},
|
||||
format='json',
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.printing_order.refresh_from_db()
|
||||
self.assertEqual(self.printing_order.print_count, 3)
|
||||
self.assertEqual(response.data['print_count'], 3)
|
||||
self.assertEqual(response.data['delta'], 3)
|
||||
|
||||
def test_delta_defaults_to_one_when_invalid(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{
|
||||
'object_type': 'plate_order',
|
||||
'object_id': self.plate_order.id,
|
||||
'delta': 'invalid',
|
||||
},
|
||||
format='json',
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.plate_order.refresh_from_db()
|
||||
self.assertEqual(self.plate_order.print_count, 1)
|
||||
self.assertEqual(response.data['delta'], 1)
|
||||
|
||||
def test_invalid_object_type_returns_400(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{
|
||||
'object_type': 'unknown_type',
|
||||
'object_id': self.printing_order.id,
|
||||
},
|
||||
format='json',
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('object_type', response.data)
|
||||
|
||||
def test_not_found_returns_404(self):
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
{
|
||||
'object_type': 'printing_order',
|
||||
'object_id': 999999,
|
||||
},
|
||||
format='json',
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@@ -139,11 +139,16 @@ class PurchaseOrderAPITestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = Merchant.objects.create(name='PO商户', type=MerchantTypeEnum.FACTORY)
|
||||
self.supplier = Supplier.objects.create(merchant=self.merchant, name='供应商A')
|
||||
self.warehouse = WareHouse.objects.create(
|
||||
self.warehouse_strict = WareHouse.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='主仓',
|
||||
name='严进仓',
|
||||
mode=WareHouseModeEnum.RESTRICT_IN,
|
||||
)
|
||||
self.warehouse_relaxed = WareHouse.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='宽进仓',
|
||||
mode=WareHouseModeEnum.UNRESTRICTED,
|
||||
)
|
||||
category = ProductCategory.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='品类',
|
||||
@@ -164,39 +169,66 @@ class PurchaseOrderAPITestCase(TestCase):
|
||||
)
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
self.payload = {
|
||||
self.strict_payload = {
|
||||
'supplier': self.supplier.id,
|
||||
'warehouse': self.warehouse.id,
|
||||
'warehouse': self.warehouse_strict.id,
|
||||
'order_date': '2025-11-26',
|
||||
'total_amount': '1500.00',
|
||||
'items': [
|
||||
{
|
||||
'product_id': self.product.id,
|
||||
'quantities': ['10.0', '5.0'],
|
||||
'numbers': [10, 5],
|
||||
'price': '12.5',
|
||||
'unit': '米',
|
||||
}
|
||||
],
|
||||
'remarks': '接口测试',
|
||||
}
|
||||
self.relaxed_payload = {
|
||||
'supplier': self.supplier.id,
|
||||
'warehouse': self.warehouse_relaxed.id,
|
||||
'order_date': '2025-11-26',
|
||||
'items': [
|
||||
{
|
||||
'product_id': self.product.id,
|
||||
'quantity': 120,
|
||||
'num_of_rolls': 3,
|
||||
'price': '10.5',
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
def test_create_purchase_order_success(self):
|
||||
def test_create_purchase_order_success_strict(self):
|
||||
with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay:
|
||||
response = self.client.post('/api/v1/purchase-orders/', self.payload, format='json')
|
||||
response = self.client.post('/api/v1/purchase-orders/', self.strict_payload, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertIn('id', response.data)
|
||||
mock_delay.assert_called_once()
|
||||
|
||||
def test_create_purchase_order_invalid_supplier(self):
|
||||
payload = {**self.payload, 'supplier': 999}
|
||||
payload = {**self.strict_payload, 'supplier': 999}
|
||||
response = self.client.post('/api/v1/purchase-orders/', payload, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('不存在', response.data['error'])
|
||||
|
||||
def test_create_purchase_order_unauthenticated(self):
|
||||
self.client.force_authenticate(user=None)
|
||||
response = self.client.post('/api/v1/purchase-orders/', self.payload, format='json')
|
||||
response = self.client.post('/api/v1/purchase-orders/', self.strict_payload, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
def test_create_purchase_order_relaxed_mode(self):
|
||||
with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay:
|
||||
response = self.client.post('/api/v1/purchase-orders/', self.relaxed_payload, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
mock_delay.assert_called_once()
|
||||
|
||||
def test_mode_mismatch_raises(self):
|
||||
payload = {**self.relaxed_payload}
|
||||
payload['warehouse'] = self.warehouse_strict.id # 严进仓却传宽进参数
|
||||
response = self.client.post('/api/v1/purchase-orders/', payload, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('严进模式', response.data['error'])
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_TASK_ALWAYS_EAGER=True,
|
||||
|
||||
@@ -9,6 +9,7 @@ from .views import (
|
||||
product_image,
|
||||
stateflow,
|
||||
users,
|
||||
print_count,
|
||||
)
|
||||
from .views.stock_change_views.snapshot import StockSnapshotListView
|
||||
from .views.printing.views import PrintingOrderViewSet, PrintingJobViewSet, PlateOrderViewSet
|
||||
@@ -57,6 +58,7 @@ urlpatterns = [
|
||||
path('inventory/', inventory.InventoryAPIView.as_view(), name='inventory'),
|
||||
path('purchase-orders/', purchase_order.PurchaseOrderView.as_view(), name='purchase_orders'),
|
||||
path('health/', healthy.HealthCheckView.as_view(), name='health_check'),
|
||||
path('print-count/delta/', print_count.adjust_print_count, name='print_count_delta'),
|
||||
|
||||
# 产品图片上传 API
|
||||
path('products/<int:product_id>/image/', product_image.ProductImageUploadView.as_view(), name='product_image_upload'),
|
||||
|
||||
57
api_v1/views/print_count.py
Normal file
57
api_v1/views/print_count.py
Normal file
@@ -0,0 +1,57 @@
|
||||
from django.db import transaction
|
||||
from django.db.models import F
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from api_v1.serializers import PrintCountDeltaSerializer
|
||||
from api_v1.enums import PrintCountObjectType
|
||||
from printing import models as printing_models
|
||||
|
||||
|
||||
class PrintCountDeltaView(APIView):
|
||||
"""通用打印次数递增接口"""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
OBJECT_MODEL_MAP = {
|
||||
PrintCountObjectType.PRINTING_ORDER: printing_models.PrintingOrder,
|
||||
PrintCountObjectType.PLATE_ORDER: printing_models.PlateOrder,
|
||||
}
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
serializer = PrintCountDeltaSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
data = serializer.validated_data
|
||||
|
||||
model = self.OBJECT_MODEL_MAP.get(data['object_type'])
|
||||
if model is None:
|
||||
return Response(
|
||||
{'detail': '不支持的对象类型'},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
updated = model.objects.filter(id=data['object_id']).update(
|
||||
print_count=F('print_count') + data['delta']
|
||||
)
|
||||
if not updated:
|
||||
return Response(
|
||||
{'detail': '指定对象不存在'},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
fresh_value = model.objects.only('print_count').get(id=data['object_id']).print_count
|
||||
|
||||
return Response(
|
||||
{
|
||||
'object_type': data['object_type'],
|
||||
'object_id': data['object_id'],
|
||||
'delta': data['delta'],
|
||||
'print_count': fresh_value,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
|
||||
adjust_print_count = PrintCountDeltaView.as_view()
|
||||
|
||||
@@ -199,7 +199,7 @@
|
||||
- `plate_type`, `urgency_level`, `fabric`, `style_name`: 模糊查询
|
||||
- `is_invalid`, `is_ordered`, `is_mark_frame`: 布尔过滤
|
||||
- `plate_date_from`/`to`: 开版日期范围
|
||||
- `required_completion_date_from`/`to`: 要求完成日期范围
|
||||
- `required_completion_date_from`/`to`: 要求完成时间范围(ISO8601,含具体时间)
|
||||
- `created_date_from`/`to`: 创建日期范围
|
||||
- `search`: 全文搜索 (设计编号, 款式名称, 客户名称, 面料)
|
||||
- `ordering`: 排序字段。
|
||||
|
||||
@@ -2,9 +2,99 @@
|
||||
Printing API 序列化器
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
|
||||
from api_v1.models import UploadedFile
|
||||
from printing import models
|
||||
from .services import PrintingOrderService, PrintingJobService
|
||||
from basic_info.models import Customer, Employee
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def _build_absolute_media_url(url: str | None, request):
|
||||
if not url:
|
||||
return None
|
||||
if isinstance(url, str) and url.startswith(('http://', 'https://')):
|
||||
return url
|
||||
if not isinstance(url, str):
|
||||
return None
|
||||
if request:
|
||||
if url.startswith('/'):
|
||||
return request.build_absolute_uri(url)
|
||||
media_prefix = (settings.MEDIA_URL or '/media/').rstrip('/')
|
||||
return request.build_absolute_uri(f'{media_prefix}/{url.lstrip("/")}')
|
||||
return url
|
||||
|
||||
|
||||
def _serialize_plate_images(raw_value, request):
|
||||
if not raw_value:
|
||||
return []
|
||||
serialized = []
|
||||
if isinstance(raw_value, list):
|
||||
iterable = raw_value
|
||||
elif isinstance(raw_value, dict):
|
||||
iterable = [raw_value]
|
||||
elif isinstance(raw_value, str):
|
||||
iterable = [{'url': raw_value}]
|
||||
else:
|
||||
iterable = []
|
||||
|
||||
for entry in iterable:
|
||||
if isinstance(entry, str):
|
||||
data = {'url': entry}
|
||||
elif isinstance(entry, dict):
|
||||
data = dict(entry)
|
||||
else:
|
||||
continue
|
||||
url = data.get('url') or data.get('path')
|
||||
data['url'] = _build_absolute_media_url(url, request)
|
||||
if 'path' not in data and isinstance(url, str):
|
||||
data['path'] = url
|
||||
serialized.append(data)
|
||||
return serialized
|
||||
|
||||
|
||||
def _build_plate_image_payload(items, request_user):
|
||||
if items is None:
|
||||
return None
|
||||
if not items:
|
||||
return []
|
||||
file_ids = [item['file_id'] for item in items if item.get('file_id') is not None]
|
||||
if not file_ids:
|
||||
return []
|
||||
|
||||
queryset = UploadedFile.objects.filter(id__in=file_ids, is_deleted=False)
|
||||
if request_user and request_user.is_authenticated:
|
||||
queryset = queryset.filter(owner=request_user)
|
||||
|
||||
files_map = {file.id: file for file in queryset}
|
||||
missing = [str(fid) for fid in file_ids if fid not in files_map]
|
||||
if missing:
|
||||
raise serializers.ValidationError({'plate_image': f'以下文件不存在或已删除: {", ".join(missing)}'})
|
||||
|
||||
payload = []
|
||||
for item in items:
|
||||
file = files_map[item['file_id']]
|
||||
name = item.get('name') or file.original_filename or file.path.name
|
||||
payload.append({
|
||||
'file_id': file.id,
|
||||
'name': name,
|
||||
'path': file.path.name,
|
||||
'url': file.file_url,
|
||||
'size': file.file_size,
|
||||
'content_type': file.content_type,
|
||||
'uploaded_at': file.created_at.isoformat(),
|
||||
})
|
||||
return payload
|
||||
|
||||
|
||||
class PlateImageInputSerializer(serializers.Serializer):
|
||||
file_id = serializers.IntegerField(min_value=1, help_text='上传文件的 ID')
|
||||
name = serializers.CharField(
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
allow_null=True,
|
||||
help_text='可选的图片名称,默认使用文件原始名称'
|
||||
)
|
||||
|
||||
|
||||
class PrintingOrderListSerializer(serializers.ModelSerializer):
|
||||
@@ -20,10 +110,10 @@ class PrintingOrderListSerializer(serializers.ModelSerializer):
|
||||
'id', 'human_id', 'customer', 'customer_name', 'customer_phone',
|
||||
'fabric', 'width', 'is_urgent', 'area', 'address', 'curve',
|
||||
'is_fabric_received', 'outgoing_date', 'is_invalid', 'new_curve',
|
||||
'process', 'process_name', 'progress', 'position',
|
||||
'process', 'process_name', 'progress', 'position', 'print_count',
|
||||
'created_at', 'updated_at',
|
||||
]
|
||||
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress']
|
||||
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress', 'print_count']
|
||||
|
||||
|
||||
class PrintingOrderDetailSerializer(serializers.ModelSerializer):
|
||||
@@ -42,10 +132,10 @@ class PrintingOrderDetailSerializer(serializers.ModelSerializer):
|
||||
'is_fabric_received', 'craft', 'description', 'outgoing_date',
|
||||
'curve', 'new_curve', 'position',
|
||||
'printing_warn', 'rolling_warn', 'production_warn',
|
||||
'is_invalid', 'process', 'process_name', 'progress',
|
||||
'is_invalid', 'process', 'process_name', 'progress', 'print_count',
|
||||
'created_at', 'updated_at'
|
||||
]
|
||||
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress']
|
||||
read_only_fields = ['id', 'human_id', 'created_at', 'updated_at', 'progress', 'print_count']
|
||||
|
||||
|
||||
class PrintingOrderCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
@@ -214,6 +304,7 @@ class PlateOrderDesignCodeMixin:
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
data['design_code'] = self._normalize_design_code(data.get('design_code'), instance.id)
|
||||
data['plate_image'] = _serialize_plate_images(getattr(instance, 'plate_image', None), self.context.get('request'))
|
||||
return data
|
||||
|
||||
|
||||
@@ -244,7 +335,7 @@ class PlateOrderListSerializer(PlateOrderDesignCodeMixin, serializers.ModelSeria
|
||||
'sample_rating', 'difficulty_rating',
|
||||
'sample_meter', 'required_sample_meters',
|
||||
'required_completion_date', 'completion_date',
|
||||
'approval_result', 'is_ordered', 'customer_feedback',
|
||||
'approval_result', 'is_ordered', 'customer_feedback', 'print_count',
|
||||
'process', 'process_name',
|
||||
'status', 'status_id', 'is_completed', 'has_started',
|
||||
'progress_percentage', 'business_object_id',
|
||||
@@ -252,17 +343,12 @@ class PlateOrderListSerializer(PlateOrderDesignCodeMixin, serializers.ModelSeria
|
||||
]
|
||||
read_only_fields = [
|
||||
'id', 'status', 'progress_percentage',
|
||||
'created_at', 'updated_at'
|
||||
'created_at', 'updated_at', 'print_count'
|
||||
]
|
||||
|
||||
def get_plate_image_url(self, obj):
|
||||
"""获取图片完整URL"""
|
||||
if obj.plate_image:
|
||||
request = self.context.get("request")
|
||||
if request:
|
||||
return request.build_absolute_uri(obj.plate_image.url)
|
||||
return obj.plate_image.url
|
||||
return None
|
||||
images = _serialize_plate_images(getattr(obj, 'plate_image', None), self.context.get('request'))
|
||||
return [entry.get('url') for entry in images if entry.get('url')]
|
||||
|
||||
|
||||
def get_process_name(self, obj) ->str | None:
|
||||
@@ -308,7 +394,7 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
|
||||
'sample_rating', 'difficulty_rating',
|
||||
'sample_meter', 'required_sample_meters',
|
||||
'required_completion_date', 'completion_date',
|
||||
'approval_result', 'is_ordered', 'customer_feedback',
|
||||
'approval_result', 'is_ordered', 'customer_feedback', 'print_count',
|
||||
'process', 'process_name',
|
||||
'status', 'status_id', 'is_completed', 'has_started',
|
||||
'progress_percentage', 'business_object_id',
|
||||
@@ -317,7 +403,7 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
|
||||
read_only_fields = [
|
||||
'id', 'status', 'status_id', 'is_completed', 'has_started',
|
||||
'progress_percentage', 'business_object_id',
|
||||
'created_at', 'updated_at'
|
||||
'created_at', 'updated_at', 'print_count'
|
||||
]
|
||||
|
||||
def get_business_object_id(self, obj):
|
||||
@@ -325,13 +411,8 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
|
||||
return obj.business_object.id if obj.business_object else None
|
||||
|
||||
def get_plate_image_url(self, obj):
|
||||
"""获取图片完整URL"""
|
||||
if obj.plate_image:
|
||||
request = self.context.get("request")
|
||||
if request:
|
||||
return request.build_absolute_uri(obj.plate_image.url)
|
||||
return obj.plate_image.url
|
||||
return None
|
||||
images = _serialize_plate_images(getattr(obj, 'plate_image', None), self.context.get('request'))
|
||||
return [entry.get('url') for entry in images if entry.get('url')]
|
||||
|
||||
def get_process_name(self, obj):
|
||||
"""获取流程名称"""
|
||||
@@ -347,7 +428,13 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
|
||||
|
||||
class PlateOrderCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""开版订单创建/更新序列化器"""
|
||||
|
||||
plate_image = PlateImageInputSerializer(
|
||||
many=True,
|
||||
required=False,
|
||||
allow_null=True,
|
||||
help_text="开版图片列表,需提供已上传的 file_id,可选 name 字段"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = models.PlateOrder
|
||||
fields = [
|
||||
@@ -365,7 +452,10 @@ class PlateOrderCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"process"
|
||||
]
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
def validate_plate_image(self, value):
|
||||
return value or []
|
||||
|
||||
def validate_customer(self, value):
|
||||
"""验证客户是否存在"""
|
||||
if not value:
|
||||
@@ -415,18 +505,18 @@ class PlateOrderCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
return attrs
|
||||
|
||||
def create(self, validated_data):
|
||||
"""创建开版订单"""
|
||||
user = self.context["request"].user
|
||||
|
||||
# 创建 PlateOrder,save 方法会自动创建 BusinessObject
|
||||
plate_order = models.PlateOrder.objects.create(**validated_data)
|
||||
|
||||
return plate_order
|
||||
plate_images = validated_data.pop('plate_image', None)
|
||||
if plate_images is not None:
|
||||
validated_data['plate_image'] = _build_plate_image_payload(plate_images, self._request_user)
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""更新开版订单"""
|
||||
# 直接更新字段,save 方法会自动处理 BusinessObject 的创建
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
instance.save()
|
||||
return instance
|
||||
plate_images = validated_data.pop('plate_image', None)
|
||||
if plate_images is not None:
|
||||
validated_data['plate_image'] = _build_plate_image_payload(plate_images, self._request_user)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
@property
|
||||
def _request_user(self):
|
||||
request = self.context.get('request')
|
||||
return getattr(request, 'user', None)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
PlateOrder API 测试
|
||||
"""
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test import TestCase
|
||||
from rest_framework.test import APIClient
|
||||
from rest_framework import status
|
||||
@@ -9,6 +10,7 @@ from django.contrib.auth.models import Permission
|
||||
from basic_info import models as basic_models
|
||||
from printing import models as printing_models
|
||||
from stateflow import models as stateflow_models
|
||||
from api_v1 import models as api_models
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -95,6 +97,18 @@ class PlateOrderAPITestCase(TestCase):
|
||||
stateflow_models.ProcessNode.objects.create(process=self.process, state=self.state1, order=0)
|
||||
stateflow_models.ProcessNode.objects.create(process=self.process, state=self.state2, order=1)
|
||||
stateflow_models.ProcessNode.objects.create(process=self.process, state=self.state3, order=2)
|
||||
self.upload_file_primary = self._create_uploaded_file('plate-main.jpg')
|
||||
self.upload_file_secondary = self._create_uploaded_file('plate-secondary.jpg')
|
||||
|
||||
def _create_uploaded_file(self, filename):
|
||||
file = SimpleUploadedFile(filename, b'test-image-content', content_type='image/jpeg')
|
||||
return api_models.UploadedFile.objects.create(
|
||||
owner=self.user,
|
||||
path=file,
|
||||
original_filename=filename,
|
||||
file_size=file.size,
|
||||
content_type='image/jpeg',
|
||||
)
|
||||
|
||||
def test_create_plate_order(self):
|
||||
"""测试创建开版订单"""
|
||||
@@ -109,6 +123,9 @@ class PlateOrderAPITestCase(TestCase):
|
||||
'salesperson': self.salesperson.id,
|
||||
'merchandiser': self.merchandiser.id,
|
||||
'image_name': 'sample.png',
|
||||
'plate_image': [
|
||||
{'file_id': self.upload_file_primary.id, 'name': '主图'}
|
||||
],
|
||||
}
|
||||
|
||||
response = self.client.post('/api/v1/plate-orders/', data, format='json')
|
||||
@@ -116,6 +133,8 @@ class PlateOrderAPITestCase(TestCase):
|
||||
self.assertEqual(response.data['design_code'], 'DESIGN001')
|
||||
self.assertEqual(response.data['style_name'], '测试款式')
|
||||
self.assertEqual(response.data['image_name'], 'sample.png')
|
||||
self.assertEqual(len(response.data['plate_image']), 1)
|
||||
self.assertEqual(response.data['plate_image'][0]['file_id'], self.upload_file_primary.id)
|
||||
|
||||
# 验证数据库中创建了记录
|
||||
self.assertTrue(printing_models.PlateOrder.objects.filter(design_code='DESIGN001').exists())
|
||||
@@ -145,11 +164,22 @@ class PlateOrderAPITestCase(TestCase):
|
||||
response = self.client.post('/api/v1/plate-orders/', data, format='json')
|
||||
# CharField 不会验证内容,所以应该成功
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
def test_create_plate_order_with_invalid_plate_image_file(self):
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'design_code': 'DESIGN_IMG',
|
||||
'plate_type': '圆网',
|
||||
'plate_image': [{'file_id': 999}],
|
||||
}
|
||||
response = self.client.post('/api/v1/plate-orders/', data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('plate_image', response.data)
|
||||
|
||||
def test_list_plate_orders(self):
|
||||
"""测试获取开版订单列表"""
|
||||
# 创建测试数据
|
||||
printing_models.PlateOrder.objects.create(
|
||||
order_one = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
@@ -158,7 +188,17 @@ class PlateOrderAPITestCase(TestCase):
|
||||
salesperson=self.salesperson,
|
||||
image_name='first.png',
|
||||
)
|
||||
printing_models.PlateOrder.objects.create(
|
||||
order_one.plate_image = [
|
||||
{
|
||||
'file_id': self.upload_file_primary.id,
|
||||
'name': 'A 面',
|
||||
'url': self.upload_file_primary.file_url,
|
||||
'path': self.upload_file_primary.path.name,
|
||||
}
|
||||
]
|
||||
order_one.save(update_fields=['plate_image'])
|
||||
|
||||
order_two = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN002',
|
||||
plate_type='平网',
|
||||
@@ -167,6 +207,15 @@ class PlateOrderAPITestCase(TestCase):
|
||||
merchandiser=self.merchandiser,
|
||||
image_name='second.png',
|
||||
)
|
||||
order_two.plate_image = [
|
||||
{
|
||||
'file_id': self.upload_file_secondary.id,
|
||||
'name': 'B 面',
|
||||
'url': self.upload_file_secondary.file_url,
|
||||
'path': self.upload_file_secondary.path.name,
|
||||
}
|
||||
]
|
||||
order_two.save(update_fields=['plate_image'])
|
||||
|
||||
response = self.client.get('/api/v1/plate-orders/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
@@ -174,11 +223,13 @@ class PlateOrderAPITestCase(TestCase):
|
||||
if isinstance(response.data, dict):
|
||||
self.assertEqual(response.data['count'], 2)
|
||||
collection = response.data.get('results') or response.data.get('data') or []
|
||||
if collection:
|
||||
self.assertIn('image_name', collection[0])
|
||||
else:
|
||||
self.assertEqual(len(response.data), 2)
|
||||
self.assertIn('image_name', response.data[0])
|
||||
collection = response.data
|
||||
self.assertEqual(len(collection), 2)
|
||||
|
||||
if collection:
|
||||
self.assertIn('plate_image', collection[0])
|
||||
self.assertIsInstance(collection[0]['plate_image'], list)
|
||||
|
||||
def test_retrieve_plate_order(self):
|
||||
"""测试获取单个开版订单详情"""
|
||||
@@ -193,6 +244,15 @@ class PlateOrderAPITestCase(TestCase):
|
||||
merchandiser=self.merchandiser,
|
||||
image_name='detail.png',
|
||||
)
|
||||
plate_order.plate_image = [
|
||||
{
|
||||
'file_id': self.upload_file_primary.id,
|
||||
'name': '详情图',
|
||||
'url': self.upload_file_primary.file_url,
|
||||
'path': self.upload_file_primary.path.name,
|
||||
}
|
||||
]
|
||||
plate_order.save(update_fields=['plate_image'])
|
||||
|
||||
response = self.client.get(f'/api/v1/plate-orders/{plate_order.id}/')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
@@ -202,6 +262,8 @@ class PlateOrderAPITestCase(TestCase):
|
||||
self.assertIn('customer_name', response.data)
|
||||
self.assertIn('salesperson_name', response.data)
|
||||
self.assertEqual(response.data['image_name'], 'detail.png')
|
||||
self.assertIsInstance(response.data['plate_image'], list)
|
||||
self.assertEqual(response.data['plate_image'][0]['file_id'], self.upload_file_primary.id)
|
||||
|
||||
def test_design_code_fallback_in_detail(self):
|
||||
"""design_code 为空时返回主键ID"""
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
"""
|
||||
测试 PlateOrder 文件上传功能
|
||||
"""
|
||||
from django.test import TestCase
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from rest_framework.test import APIClient
|
||||
from rest_framework import status
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Permission
|
||||
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()
|
||||
|
||||
|
||||
class PlateOrderFileUploadTestCase(TestCase):
|
||||
"""测试 PlateOrder 文件上传(PATCH 请求)"""
|
||||
|
||||
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='testuser',
|
||||
password='testpass123',
|
||||
email='test@example.com'
|
||||
)
|
||||
|
||||
# 创建员工并关联商户
|
||||
self.employee = basic_models.Employee.objects.create(
|
||||
sys_user=self.user,
|
||||
merchant=self.merchant,
|
||||
name='测试员工',
|
||||
mobile='13800138000',
|
||||
status=basic_models.EmployeeStatusEnum.ACTIVE
|
||||
)
|
||||
|
||||
# 创建客户
|
||||
self.customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
name='测试客户',
|
||||
mobile='13900139000',
|
||||
area='测试地区'
|
||||
)
|
||||
|
||||
# 认证用户
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
# 给用户添加所有 PlateOrder 权限
|
||||
perms = Permission.objects.filter(
|
||||
content_type__app_label='printing',
|
||||
content_type__model='plateorder'
|
||||
)
|
||||
self.user.user_permissions.add(*perms)
|
||||
|
||||
def test_patch_with_file_upload(self):
|
||||
"""测试 PATCH 请求上传文件(问题场景)"""
|
||||
# 创建开版订单
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN001',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式',
|
||||
fabric='棉布',
|
||||
)
|
||||
|
||||
# 创建一个测试图片文件
|
||||
image_content = b'fake image content for testing'
|
||||
image_file = SimpleUploadedFile(
|
||||
"test_plate_image.jpg",
|
||||
image_content,
|
||||
content_type="image/jpeg"
|
||||
)
|
||||
|
||||
# PATCH 请求,同时更新文字字段和上传文件
|
||||
data = {
|
||||
'urgency_level': '加急',
|
||||
'plate_image': image_file,
|
||||
'is_mark_frame': True,
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
f'/api/v1/plate-orders/{plate_order.id}/',
|
||||
data,
|
||||
format='multipart' # 重要:使用 multipart 格式
|
||||
)
|
||||
|
||||
# 验证响应
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK,
|
||||
f"Expected 200, got {response.status_code}: {response.data}")
|
||||
self.assertEqual(response.data['urgency_level'], '加急')
|
||||
self.assertEqual(response.data['is_mark_frame'], True)
|
||||
|
||||
# 验证文件已上传
|
||||
plate_order.refresh_from_db()
|
||||
self.assertTrue(plate_order.plate_image)
|
||||
self.assertIn('test_plate_image', plate_order.plate_image.name)
|
||||
|
||||
def test_patch_without_file(self):
|
||||
"""测试 PATCH 请求不上传文件(正常场景)"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN002',
|
||||
plate_type='圆网',
|
||||
style_name='测试款式2',
|
||||
)
|
||||
|
||||
# 纯 JSON 数据
|
||||
data = {
|
||||
'urgency_level': '特急',
|
||||
'is_mark_frame': False,
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
f'/api/v1/plate-orders/{plate_order.id}/',
|
||||
data,
|
||||
format='json'
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(response.data['urgency_level'], '特急')
|
||||
self.assertEqual(response.data['is_mark_frame'], False)
|
||||
|
||||
def test_patch_only_file(self):
|
||||
"""测试 PATCH 请求仅上传文件"""
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
customer=self.customer,
|
||||
design_code='DESIGN003',
|
||||
plate_type='平网',
|
||||
style_name='测试款式3',
|
||||
urgency_level='正常',
|
||||
)
|
||||
|
||||
# 创建测试文件
|
||||
file_content = b'another fake image'
|
||||
image_file = SimpleUploadedFile(
|
||||
"plate_design.png",
|
||||
file_content,
|
||||
content_type="image/png"
|
||||
)
|
||||
|
||||
data = {
|
||||
'plate_image': image_file,
|
||||
}
|
||||
|
||||
response = self.client.patch(
|
||||
f'/api/v1/plate-orders/{plate_order.id}/',
|
||||
data,
|
||||
format='multipart'
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
# 验证其他字段未被改变
|
||||
plate_order.refresh_from_db()
|
||||
self.assertEqual(plate_order.urgency_level, '正常')
|
||||
self.assertEqual(plate_order.design_code, 'DESIGN003')
|
||||
self.assertTrue(plate_order.plate_image)
|
||||
|
||||
def test_create_with_file(self):
|
||||
"""测试 POST 创建时上传文件"""
|
||||
file_content = b'initial image'
|
||||
image_file = SimpleUploadedFile(
|
||||
"initial_plate.jpg",
|
||||
file_content,
|
||||
content_type="image/jpeg"
|
||||
)
|
||||
|
||||
data = {
|
||||
'customer': self.customer.id,
|
||||
'design_code': 'DESIGN_NEW',
|
||||
'plate_type': '圆网',
|
||||
'style_name': '新款式',
|
||||
'plate_image': image_file,
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
'/api/v1/plate-orders/',
|
||||
data,
|
||||
format='multipart'
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertIn('plate_image', response.data)
|
||||
|
||||
# 验证数据库
|
||||
plate_order = printing_models.PlateOrder.objects.get(design_code='DESIGN_NEW')
|
||||
self.assertTrue(plate_order.plate_image)
|
||||
|
||||
@@ -10,6 +10,7 @@ from rest_framework.permissions import DjangoModelPermissions
|
||||
from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters import rest_framework as django_filters
|
||||
from django_filters import IsoDateTimeFilter
|
||||
from printing import models
|
||||
from basic_info.models import MerchantTypeEnum
|
||||
|
||||
@@ -526,8 +527,8 @@ class PlateOrderFilterSet(django_filters.FilterSet):
|
||||
style_name = django_filters.CharFilter(lookup_expr='icontains')
|
||||
plate_date_from = django_filters.DateFilter(field_name='plate_date', lookup_expr='gte')
|
||||
plate_date_to = django_filters.DateFilter(field_name='plate_date', lookup_expr='lte')
|
||||
required_completion_date_from = django_filters.DateFilter(field_name='required_completion_date', lookup_expr='gte')
|
||||
required_completion_date_to = django_filters.DateFilter(field_name='required_completion_date', lookup_expr='lte')
|
||||
required_completion_date_from = IsoDateTimeFilter(field_name='required_completion_date', lookup_expr='gte')
|
||||
required_completion_date_to = IsoDateTimeFilter(field_name='required_completion_date', lookup_expr='lte')
|
||||
created_date_from = django_filters.DateFilter(field_name='created_at', lookup_expr='gte')
|
||||
created_date_to = django_filters.DateFilter(field_name='created_at', lookup_expr='lte')
|
||||
|
||||
@@ -574,8 +575,8 @@ class PlateOrderViewSet(viewsets.ModelViewSet):
|
||||
- style_name: 款式名称(模糊查询)
|
||||
- plate_date_from: 开版日期起始
|
||||
- plate_date_to: 开版日期结束
|
||||
- required_completion_date_from: 要求完成日期起始
|
||||
- required_completion_date_to: 要求完成日期结束
|
||||
- required_completion_date_from: 要求完成时间起始(含时分秒)
|
||||
- required_completion_date_to: 要求完成时间结束
|
||||
- created_date_from: 创建日期起始
|
||||
- created_date_to: 创建日期结束
|
||||
- search: 全文搜索(设计编号、款式名称、客户名称、面料)
|
||||
|
||||
@@ -1,18 +1,61 @@
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
from rest_framework import status, views
|
||||
from rest_framework import status, views, serializers, pagination
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from business import services as business_services
|
||||
from business import models as business_models
|
||||
from .stock_change_views.mixins import StockChangeViewMixin
|
||||
|
||||
|
||||
class PurchaseOrderItemSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = business_models.PurchaseOrderItem
|
||||
fields = [
|
||||
'id', 'product', 'price', 'color', 'quantity', 'unit',
|
||||
'empty_diff_percent', 'quantity_of_rolls', 'num_of_rolls',
|
||||
'batch_number', 'remarks', 'created_at', 'updated_at',
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class PurchaseOrderSerializer(serializers.ModelSerializer):
|
||||
supplier_name = serializers.CharField(source='supplier.name', read_only=True)
|
||||
operator_name = serializers.CharField(source='operator.name', read_only=True)
|
||||
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
|
||||
items = PurchaseOrderItemSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = business_models.PurchaseOrder
|
||||
fields = [
|
||||
'id', 'supplier', 'supplier_name', 'purchase_date', 'kind',
|
||||
'operator', 'operator_name', 'warehouse', 'warehouse_name',
|
||||
'status', 'remarks', 'created_at', 'updated_at', 'items',
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'items', 'supplier_name', 'operator_name', 'warehouse_name']
|
||||
|
||||
|
||||
class PurchaseOrderPagination(pagination.LimitOffsetPagination):
|
||||
default_limit = 20
|
||||
max_limit = 100
|
||||
|
||||
|
||||
class PurchaseOrderView(StockChangeViewMixin, views.APIView):
|
||||
"""创建采购订单并触发入库任务"""
|
||||
"""采购订单查询与创建"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
pagination_class = PurchaseOrderPagination
|
||||
|
||||
def get(self, request):
|
||||
if not self.check_employee_permission(request):
|
||||
return self.permission_error_response('无权限访问')
|
||||
|
||||
merchant = request.user.employee.merchant
|
||||
queryset = business_models.PurchaseOrder.objects.filter(merchant=merchant).prefetch_related('items', 'supplier', 'operator', 'warehouse')
|
||||
paginator = self.pagination_class()
|
||||
page = paginator.paginate_queryset(queryset.order_by('-created_at'), request, view=self)
|
||||
serializer = PurchaseOrderSerializer(page, many=True)
|
||||
return paginator.get_paginated_response(serializer.data)
|
||||
|
||||
def post(self, request):
|
||||
if not self.check_employee_permission(request):
|
||||
@@ -22,9 +65,8 @@ class PurchaseOrderView(StockChangeViewMixin, views.APIView):
|
||||
data = request.data or {}
|
||||
|
||||
supplier_id = data.get('supplier')
|
||||
warehouse_id = data.get('warehouse')
|
||||
warehouse_id = data.get('warehouse_id') or data.get('warehouse')
|
||||
order_date = data.get('order_date')
|
||||
total_amount = data.get('total_amount')
|
||||
items = data.get('items', [])
|
||||
remarks = data.get('remarks', '')
|
||||
|
||||
@@ -32,6 +74,10 @@ class PurchaseOrderView(StockChangeViewMixin, views.APIView):
|
||||
return Response({'error': '缺少供应商 ID'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not warehouse_id:
|
||||
return Response({'error': '缺少仓库 ID'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not order_date:
|
||||
return Response({'error': '缺少 order_date'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not isinstance(items, list) or not items:
|
||||
return Response({'error': 'items 需要为非空数组'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
supplier = basic_models.Supplier.objects.get(id=supplier_id, merchant=merchant)
|
||||
@@ -39,22 +85,19 @@ class PurchaseOrderView(StockChangeViewMixin, views.APIView):
|
||||
return Response({'error': f'供应商 {supplier_id} 不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
basic_models.WareHouse.objects.get(id=warehouse_id, merchant=merchant)
|
||||
warehouse = basic_models.WareHouse.objects.get(id=warehouse_id, merchant=merchant)
|
||||
except basic_models.WareHouse.DoesNotExist:
|
||||
return Response({'error': f'仓库 {warehouse_id} 不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
total_amount_decimal = Decimal(str(total_amount))
|
||||
except (InvalidOperation, TypeError):
|
||||
return Response({'error': 'total_amount 必须为合法数值'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
operator = request.user.employee
|
||||
|
||||
try:
|
||||
purchase_order = business_services.create_purchase_order(
|
||||
merchant=merchant,
|
||||
supplier=supplier,
|
||||
order_date=order_date,
|
||||
total_amount=total_amount_decimal,
|
||||
warehouse_id=warehouse_id,
|
||||
warehouse=warehouse,
|
||||
operator=operator,
|
||||
items=items,
|
||||
remarks=remarks,
|
||||
created_by=request.user,
|
||||
|
||||
@@ -38,6 +38,21 @@ class QuickInputAdmin(admin.ModelAdmin):
|
||||
list_filter = ('group',)
|
||||
|
||||
|
||||
@admin.register(models.MerchantSetting)
|
||||
class MerchantSettingAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'merchant', 'key', 'value',
|
||||
'type', 'val_str', 'val_int',
|
||||
'val_float', 'val_bool', 'description',
|
||||
)
|
||||
list_filter = ('merchant', 'type')
|
||||
search_fields = ('merchant__name', 'key', 'description')
|
||||
|
||||
@admin.display(description='设置值')
|
||||
def _value(self, obj):
|
||||
return obj.value()
|
||||
|
||||
|
||||
@admin.register(models.UserProfile)
|
||||
class UserProfileAdmin(AdminBase):
|
||||
list_display = ('user', 'merchant')
|
||||
|
||||
30
basic_info/migrations/0014_merchantsetting.py
Normal file
30
basic_info/migrations/0014_merchantsetting.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-28 06:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basic_info', '0013_alter_customer_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MerchantSetting',
|
||||
fields=[
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('key', models.CharField(max_length=100, verbose_name='设置项')),
|
||||
('val', models.CharField(max_length=100, verbose_name='设置值')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='备注')),
|
||||
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='settings', to='basic_info.merchant', verbose_name='所属商户')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商户设置',
|
||||
'verbose_name_plural': '商户设置',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-28 06:04
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basic_info', '0014_merchantsetting'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='merchantsetting',
|
||||
name='val',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='merchantsetting',
|
||||
name='val_bool',
|
||||
field=models.BooleanField(blank=True, null=True, verbose_name='设置值(布尔值)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='merchantsetting',
|
||||
name='val_float',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='设置值(浮点数)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='merchantsetting',
|
||||
name='val_int',
|
||||
field=models.IntegerField(blank=True, null=True, verbose_name='设置值(整数)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='merchantsetting',
|
||||
name='val_str',
|
||||
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='设置值(字符串)'),
|
||||
),
|
||||
]
|
||||
18
basic_info/migrations/0016_merchantsetting_type.py
Normal file
18
basic_info/migrations/0016_merchantsetting_type.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-28 06:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('basic_info', '0015_remove_merchantsetting_val_merchantsetting_val_bool_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='merchantsetting',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('str', '字符串'), ('int', '整数'), ('float', '浮点数'), ('bool', '布尔值')], default='str', max_length=20, verbose_name='设置类型'),
|
||||
),
|
||||
]
|
||||
@@ -452,3 +452,52 @@ class VehicleTransportRecord(ModelBase):
|
||||
class Meta:
|
||||
verbose_name = '司机车次'
|
||||
verbose_name_plural = '司机车次'
|
||||
|
||||
|
||||
class MerchantSettingTypeEnum(models.TextChoices):
|
||||
"""商户设置类型枚举"""
|
||||
STR = 'str', '字符串'
|
||||
INT = 'int', '整数'
|
||||
FLOAT = 'float', '浮点数'
|
||||
BOOL = 'bool', '布尔值'
|
||||
|
||||
|
||||
class MerchantSetting(ModelBase):
|
||||
id = models.BigAutoField(primary_key=True)
|
||||
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='settings', verbose_name='所属商户')
|
||||
key = models.CharField(max_length=100, verbose_name='设置项')
|
||||
type = models.CharField(
|
||||
max_length=20,
|
||||
choices=MerchantSettingTypeEnum.choices,
|
||||
default=MerchantSettingTypeEnum.STR,
|
||||
verbose_name='设置类型',
|
||||
)
|
||||
val_str = models.CharField(max_length=100, null=True, blank=True, verbose_name='设置值(字符串)')
|
||||
val_int = models.IntegerField(null=True, blank=True, verbose_name='设置值(整数)')
|
||||
val_float = models.FloatField(null=True, blank=True, verbose_name='设置值(浮点数)')
|
||||
val_bool = models.BooleanField(null=True, blank=True, verbose_name='设置值(布尔值)')
|
||||
description = models.TextField(blank=True, null=True, verbose_name='备注')
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
if self.type == MerchantSettingTypeEnum.STR:
|
||||
return self.val_str
|
||||
elif self.type == MerchantSettingTypeEnum.INT:
|
||||
return self.val_int
|
||||
elif self.type == MerchantSettingTypeEnum.FLOAT:
|
||||
return self.val_float
|
||||
elif self.type == MerchantSettingTypeEnum.BOOL:
|
||||
return self.val_bool
|
||||
raise ValueError(f'Invalid setting type: {self.type}')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.merchant.name} - {self.key}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = '商户设置'
|
||||
verbose_name_plural = '商户设置'
|
||||
|
||||
|
||||
class MerchantSettingKeyEnum(models.TextChoices):
|
||||
"""商户设置项枚举"""
|
||||
AUTO_CREATE_STOCK_CHANGE_TASKS = 'auto_create_stock_change_tasks', '自动创建出入库记录任务'
|
||||
|
||||
@@ -73,3 +73,9 @@ class DataVisibilityService:
|
||||
return False
|
||||
|
||||
return basic_models.WareHouse.objects.filter(id=warehouse_id).filter(merchant=emp.merchant).exists()
|
||||
|
||||
|
||||
class MerchantSettingService:
|
||||
@staticmethod
|
||||
def get_setting(merchant: basic_models.Merchant, key: basic_models.MerchantSettingKeyEnum):
|
||||
return basic_models.MerchantSetting.objects.get(merchant=merchant, key=key)
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, List
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
|
||||
from basic_info import models as basic_info_models
|
||||
from stock import models as stock_models
|
||||
from stock.services import StockFlowService
|
||||
from basic_info.services import MerchantSettingService
|
||||
|
||||
from . import models
|
||||
from .tasks import create_purchase_order_stock_entries
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _normalize_order_date(value) -> date:
|
||||
if isinstance(value, date):
|
||||
return value
|
||||
@@ -34,8 +38,8 @@ def create_purchase_order(
|
||||
merchant: basic_info_models.Merchant,
|
||||
supplier: basic_info_models.Supplier,
|
||||
order_date,
|
||||
total_amount,
|
||||
warehouse_id: int,
|
||||
warehouse: basic_info_models.WareHouse,
|
||||
operator: basic_info_models.Employee,
|
||||
items: List[Dict[str, Any]],
|
||||
remarks: str | None = '',
|
||||
created_by=None,
|
||||
@@ -47,40 +51,190 @@ def create_purchase_order(
|
||||
merchant: 采购单所属商户
|
||||
supplier: 供应商
|
||||
order_date: 订单日期 (date)
|
||||
total_amount: 总金额
|
||||
warehouse_id: 入库仓库ID
|
||||
items: 产品明细,格式示例:
|
||||
[
|
||||
{'product_id': 1, 'quantities': ['10.00', '5.00']},
|
||||
{'product_id': 2, 'quantities': ['3.50']},
|
||||
]
|
||||
该结构会被传递给 StockFlowService,需满足其模式要求。
|
||||
warehouse: 入库仓库实例
|
||||
operator: 经办人
|
||||
items: 产品明细,字段会根据仓库模式校验
|
||||
remarks: 备注
|
||||
created_by: 创建者用户(可选,用于 stock 记录中的 created_by)
|
||||
"""
|
||||
if not items:
|
||||
raise ValueError('items 不能为空')
|
||||
if not warehouse_id:
|
||||
raise ValueError('warehouse_id 不能为空')
|
||||
|
||||
normalized_date = _normalize_order_date(order_date)
|
||||
total_amount = Decimal(str(total_amount))
|
||||
purchase_items, stock_flow_items = _normalize_purchase_items(
|
||||
merchant=merchant,
|
||||
warehouse=warehouse,
|
||||
items=items,
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
purchase_order = models.PurchaseOrder.objects.create(
|
||||
merchant=merchant,
|
||||
supplier=supplier,
|
||||
order_date=normalized_date,
|
||||
total_amount=total_amount,
|
||||
purchase_date=normalized_date,
|
||||
operator=operator,
|
||||
warehouse=warehouse,
|
||||
remarks=remarks,
|
||||
)
|
||||
bulk_objects = [
|
||||
models.PurchaseOrderItem(
|
||||
purchase_order=purchase_order,
|
||||
product=item_data['product'],
|
||||
price=item_data['price'],
|
||||
color=item_data.get('color'),
|
||||
quantity=item_data['quantity'],
|
||||
unit=item_data['unit'],
|
||||
empty_diff_percent=item_data['empty_diff_percent'],
|
||||
quantity_of_rolls=item_data.get('quantity_of_rolls'),
|
||||
num_of_rolls=item_data['num_of_rolls'],
|
||||
batch_number=item_data.get('batch_number'),
|
||||
remarks=item_data.get('remarks'),
|
||||
)
|
||||
for item_data in purchase_items
|
||||
]
|
||||
models.PurchaseOrderItem.objects.bulk_create(bulk_objects)
|
||||
|
||||
created_by_id = getattr(created_by, 'id', None)
|
||||
create_purchase_order_stock_entries.delay(
|
||||
purchase_order_id=purchase_order.id,
|
||||
warehouse_id=warehouse_id,
|
||||
items=items,
|
||||
created_by_id=created_by_id,
|
||||
)
|
||||
if MerchantSettingService.get_setting(merchant, basic_info_models.MerchantSettingKeyEnum.AUTO_CREATE_STOCK_CHANGE_TASKS).value is True:
|
||||
logger.info('自动创建出入库记录任务已开启,创建入库记录任务')
|
||||
create_purchase_order_stock_entries.delay(
|
||||
purchase_order_id=purchase_order.id,
|
||||
warehouse_id=warehouse.id,
|
||||
items=stock_flow_items,
|
||||
created_by_id=created_by_id,
|
||||
)
|
||||
return purchase_order
|
||||
|
||||
|
||||
def _normalize_purchase_items(
|
||||
*,
|
||||
merchant: basic_info_models.Merchant,
|
||||
warehouse: basic_info_models.WareHouse,
|
||||
items: List[Dict[str, Any]],
|
||||
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
|
||||
"""
|
||||
根据仓库模式校验采购明细,并返回
|
||||
- purchase_items: 用于创建 PurchaseOrderItem
|
||||
- stock_flow_items: 传递给 StockFlowService 的 items 结构
|
||||
"""
|
||||
purchase_items: List[Dict[str, Any]] = []
|
||||
stock_flow_items: List[Dict[str, Any]] = []
|
||||
|
||||
warehouse_mode = warehouse.mode
|
||||
|
||||
for index, raw_item in enumerate(items):
|
||||
product_id = raw_item.get('product_id')
|
||||
if not product_id:
|
||||
raise ValueError(f'items[{index}].product_id 不能为空')
|
||||
try:
|
||||
product = basic_info_models.Product.objects.get(id=product_id, merchant=merchant)
|
||||
except basic_info_models.Product.DoesNotExist as exc:
|
||||
raise ValueError(f'产品 {product_id} 不存在或不属于当前商户') from exc
|
||||
|
||||
price = _to_decimal(raw_item.get('price', '0'), f'items[{index}].price')
|
||||
empty_diff_percent = _to_decimal(raw_item.get('empty_diff_percent', '0'), f'items[{index}].empty_diff_percent')
|
||||
color = raw_item.get('color')
|
||||
batch_number = raw_item.get('batch_number')
|
||||
remarks = raw_item.get('remarks')
|
||||
unit = raw_item.get('unit') or product.get_unit_display() or '米'
|
||||
|
||||
if warehouse_mode == basic_info_models.WareHouseModeEnum.UNRESTRICTED:
|
||||
if 'numbers' in raw_item and raw_item['numbers']:
|
||||
raise ValueError(f'仓库为宽进模式,items[{index}] 不应提供 numbers')
|
||||
quantity = _to_positive_int(raw_item.get('quantity'), f'items[{index}].quantity')
|
||||
num_of_rolls = _to_positive_int(raw_item.get('num_of_rolls'), f'items[{index}].num_of_rolls')
|
||||
quantity_of_rolls = None
|
||||
stock_flow_items.append({
|
||||
'product_id': product.id,
|
||||
'value': str(quantity),
|
||||
'num_of_rolls': num_of_rolls,
|
||||
})
|
||||
else:
|
||||
numbers = raw_item.get('numbers')
|
||||
if not numbers or not isinstance(numbers, list):
|
||||
raise ValueError(f'仓库为严进模式,items[{index}] 需要提供 numbers 数组')
|
||||
normalized_numbers = [
|
||||
str(_to_positive_int(value, f'items[{index}].numbers[{pos}]'))
|
||||
for pos, value in enumerate(numbers)
|
||||
]
|
||||
num_of_rolls = len(normalized_numbers)
|
||||
quantity = sum(int(val) for val in normalized_numbers)
|
||||
quantity_of_rolls = ','.join(normalized_numbers)
|
||||
stock_flow_items.append({
|
||||
'product_id': product.id,
|
||||
'quantities': normalized_numbers,
|
||||
})
|
||||
|
||||
purchase_items.append({
|
||||
'product': product,
|
||||
'price': price,
|
||||
'color': color,
|
||||
'quantity': quantity,
|
||||
'unit': unit,
|
||||
'empty_diff_percent': empty_diff_percent,
|
||||
'quantity_of_rolls': quantity_of_rolls,
|
||||
'num_of_rolls': num_of_rolls,
|
||||
'batch_number': batch_number,
|
||||
'remarks': remarks,
|
||||
})
|
||||
|
||||
return purchase_items, stock_flow_items
|
||||
|
||||
|
||||
def create_purchase_order_stock_entries_sync(
|
||||
*,
|
||||
purchase_order_id: int,
|
||||
warehouse_id: int,
|
||||
items: List[Dict[str, Any]],
|
||||
created_by_id: int | None = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
根据采购单生成入库记录。
|
||||
"""
|
||||
try:
|
||||
purchase_order = models.PurchaseOrder.objects.select_related('merchant').get(id=purchase_order_id)
|
||||
except models.PurchaseOrder.DoesNotExist:
|
||||
logger.error('PurchaseOrder %s 不存在,无法创建入库单', purchase_order_id)
|
||||
return {'error': 'purchase_order_not_found', 'purchase_order_id': purchase_order_id}
|
||||
|
||||
merchant = purchase_order.merchant
|
||||
|
||||
created_by = None
|
||||
if created_by_id:
|
||||
UserModel = get_user_model()
|
||||
created_by = UserModel.objects.filter(id=created_by_id).first()
|
||||
|
||||
service = StockFlowService(merchant=merchant, created_by=created_by)
|
||||
record, details, created_count = service.stock_in(
|
||||
warehouse_id=warehouse_id,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=purchase_order.id,
|
||||
items=items,
|
||||
)
|
||||
|
||||
payload = {
|
||||
'purchase_order_id': purchase_order.id,
|
||||
'stock_change_record_id': getattr(record, 'id', None),
|
||||
'created_details_count': created_count,
|
||||
}
|
||||
logger.info('采购单 %s 入库任务完成: %s', purchase_order.id, payload)
|
||||
return payload
|
||||
|
||||
|
||||
def _to_decimal(value, field_name: str) -> Decimal:
|
||||
try:
|
||||
return Decimal(str(value))
|
||||
except (InvalidOperation, TypeError) as exc:
|
||||
raise ValueError(f'{field_name} 必须是合法数值') from exc
|
||||
|
||||
|
||||
def _to_positive_int(value, field_name: str) -> int:
|
||||
if value is None:
|
||||
raise ValueError(f'{field_name} 不能为空')
|
||||
decimal_value = _to_decimal(value, field_name)
|
||||
if decimal_value <= 0:
|
||||
raise ValueError(f'{field_name} 必须大于 0')
|
||||
if decimal_value != decimal_value.to_integral_value():
|
||||
raise ValueError(f'{field_name} 必须为整数')
|
||||
return int(decimal_value)
|
||||
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import logging
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from celery import shared_task
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from business import models as business_models
|
||||
from stock import models as stock_models
|
||||
from stock.services import StockFlowService
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
from business import services as business_services
|
||||
|
||||
|
||||
@shared_task(bind=True)
|
||||
@@ -20,37 +13,13 @@ def create_purchase_order_stock_entries(
|
||||
warehouse_id: int,
|
||||
items: List[Dict[str, Any]],
|
||||
created_by_id: int | None = None,
|
||||
):
|
||||
"""
|
||||
为采购单创建入库记录。仅支持入库(StockFlowService.stock_in)。
|
||||
"""
|
||||
try:
|
||||
purchase_order = business_models.PurchaseOrder.objects.select_related('merchant').get(id=purchase_order_id)
|
||||
except business_models.PurchaseOrder.DoesNotExist:
|
||||
logger.error('PurchaseOrder %s 不存在,无法创建入库单', purchase_order_id)
|
||||
return {'error': 'purchase_order_not_found', 'purchase_order_id': purchase_order_id}
|
||||
|
||||
merchant = purchase_order.merchant
|
||||
|
||||
created_by = None
|
||||
if created_by_id:
|
||||
UserModel = get_user_model()
|
||||
created_by = UserModel.objects.filter(id=created_by_id).first()
|
||||
|
||||
service = StockFlowService(merchant=merchant, created_by=created_by)
|
||||
record, details, created_count = service.stock_in(
|
||||
) -> Dict[str, Any]:
|
||||
payload = business_services.create_purchase_order_stock_entries_sync(
|
||||
purchase_order_id=purchase_order_id,
|
||||
warehouse_id=warehouse_id,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=purchase_order.id,
|
||||
items=items,
|
||||
created_by_id=created_by_id,
|
||||
)
|
||||
|
||||
payload = {
|
||||
'task_id': self.request.id,
|
||||
'purchase_order_id': purchase_order.id,
|
||||
'stock_change_record_id': getattr(record, 'id', None),
|
||||
'created_details_count': created_count,
|
||||
}
|
||||
logger.info('采购单 %s 入库任务完成: %s', purchase_order.id, payload)
|
||||
payload['task_id'] = self.request.id
|
||||
return payload
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from decimal import Decimal
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
@@ -20,11 +19,16 @@ def create_basic_fixtures():
|
||||
merchant=merchant,
|
||||
name='测试供应商',
|
||||
)
|
||||
warehouse = basic_models.WareHouse.objects.create(
|
||||
warehouse_strict = basic_models.WareHouse.objects.create(
|
||||
merchant=merchant,
|
||||
name='主仓',
|
||||
name='严进仓',
|
||||
mode=basic_models.WareHouseModeEnum.RESTRICT_IN,
|
||||
)
|
||||
warehouse_relaxed = basic_models.WareHouse.objects.create(
|
||||
merchant=merchant,
|
||||
name='宽进仓',
|
||||
mode=basic_models.WareHouseModeEnum.UNRESTRICTED,
|
||||
)
|
||||
category = basic_models.ProductCategory.objects.create(
|
||||
merchant=merchant,
|
||||
name='面料',
|
||||
@@ -37,36 +41,56 @@ def create_basic_fixtures():
|
||||
human_id='FAB-001',
|
||||
unit=basic_models.ProductUnitEnum.METER,
|
||||
)
|
||||
return merchant, supplier, warehouse, product
|
||||
operator = basic_models.Employee.objects.create(
|
||||
merchant=merchant,
|
||||
name='经办人',
|
||||
status=basic_models.EmployeeStatusEnum.ACTIVE,
|
||||
)
|
||||
return merchant, supplier, warehouse_strict, warehouse_relaxed, product, operator
|
||||
|
||||
|
||||
class PurchaseOrderServiceTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant, self.supplier, self.warehouse, self.product = create_basic_fixtures()
|
||||
(
|
||||
self.merchant,
|
||||
self.supplier,
|
||||
self.warehouse_strict,
|
||||
self.warehouse_relaxed,
|
||||
self.product,
|
||||
self.operator,
|
||||
) = create_basic_fixtures()
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username='creator', password='pass123')
|
||||
self.items: List[Dict[str, Any]] = [
|
||||
{'product_id': self.product.id, 'quantities': ['10.00', '5.00']},
|
||||
self.strict_items: List[Dict[str, Any]] = [
|
||||
{'product_id': self.product.id, 'numbers': [10, 5], 'price': '12.5', 'unit': '米'}
|
||||
]
|
||||
self.relaxed_items: List[Dict[str, Any]] = [
|
||||
{'product_id': self.product.id, 'quantity': 120, 'num_of_rolls': 3, 'price': '10.0', 'unit': '米'}
|
||||
]
|
||||
|
||||
def test_create_purchase_order_triggers_task(self):
|
||||
def test_create_purchase_order_triggers_task_strict(self):
|
||||
with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay:
|
||||
purchase_order = services.create_purchase_order(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
total_amount=Decimal('100.00'),
|
||||
warehouse_id=self.warehouse.id,
|
||||
items=self.items,
|
||||
warehouse=self.warehouse_strict,
|
||||
operator=self.operator,
|
||||
items=self.strict_items,
|
||||
remarks='自动化测试',
|
||||
created_by=self.user,
|
||||
)
|
||||
|
||||
self.assertIsInstance(purchase_order, business_models.PurchaseOrder)
|
||||
self.assertEqual(purchase_order.warehouse, self.warehouse_strict)
|
||||
self.assertEqual(purchase_order.operator, self.operator)
|
||||
self.assertEqual(purchase_order.items.count(), 1)
|
||||
item = purchase_order.items.first()
|
||||
self.assertEqual(item.quantity_of_rolls, '10,5')
|
||||
mock_delay.assert_called_once_with(
|
||||
purchase_order_id=purchase_order.id,
|
||||
warehouse_id=self.warehouse.id,
|
||||
items=self.items,
|
||||
warehouse_id=self.warehouse_strict.id,
|
||||
items=[{'product_id': self.product.id, 'quantities': ['10', '5']}],
|
||||
created_by_id=self.user.id,
|
||||
)
|
||||
|
||||
@@ -76,11 +100,75 @@ class PurchaseOrderServiceTestCase(TestCase):
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
total_amount=Decimal('50.00'),
|
||||
warehouse_id=self.warehouse.id,
|
||||
warehouse=self.warehouse_strict,
|
||||
operator=self.operator,
|
||||
items=[],
|
||||
)
|
||||
|
||||
def test_create_purchase_order_relaxed_items(self):
|
||||
with patch('business.services.create_purchase_order_stock_entries.delay') as mock_delay:
|
||||
purchase_order = services.create_purchase_order(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
warehouse=self.warehouse_relaxed,
|
||||
operator=self.operator,
|
||||
items=self.relaxed_items,
|
||||
created_by=self.user,
|
||||
)
|
||||
|
||||
self.assertEqual(purchase_order.items.first().quantity_of_rolls, None)
|
||||
mock_delay.assert_called_once_with(
|
||||
purchase_order_id=purchase_order.id,
|
||||
warehouse_id=self.warehouse_relaxed.id,
|
||||
items=[{'product_id': self.product.id, 'value': '120', 'num_of_rolls': 3}],
|
||||
created_by_id=self.user.id,
|
||||
)
|
||||
|
||||
|
||||
class PurchaseOrderStockServiceTestCase(TestCase):
|
||||
def setUp(self):
|
||||
(
|
||||
self.merchant,
|
||||
self.supplier,
|
||||
self.warehouse_strict,
|
||||
self.warehouse_relaxed,
|
||||
self.product,
|
||||
self.operator,
|
||||
) = create_basic_fixtures()
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username='svc-user', password='pass123')
|
||||
self.purchase_order = business_models.PurchaseOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
purchase_date=timezone.now().date(),
|
||||
operator=self.operator,
|
||||
warehouse=self.warehouse_strict,
|
||||
)
|
||||
self.items = [{'product_id': self.product.id, 'quantities': ['8']}]
|
||||
|
||||
def test_service_calls_stock_flow_service(self):
|
||||
with patch('business.services.StockFlowService') as mock_flow_cls:
|
||||
mock_instance = mock_flow_cls.return_value
|
||||
mock_instance.stock_in.return_value = (MagicMock(id=321), [], 2)
|
||||
|
||||
payload = services.create_purchase_order_stock_entries_sync(
|
||||
purchase_order_id=self.purchase_order.id,
|
||||
warehouse_id=self.warehouse_strict.id,
|
||||
items=self.items,
|
||||
created_by_id=self.user.id,
|
||||
)
|
||||
|
||||
mock_flow_cls.assert_called_once_with(merchant=self.merchant, created_by=self.user)
|
||||
mock_instance.stock_in.assert_called_once_with(
|
||||
warehouse_id=self.warehouse_strict.id,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=self.purchase_order.id,
|
||||
items=[{'product_id': self.product.id, 'quantities': ['8']}],
|
||||
)
|
||||
self.assertEqual(payload['purchase_order_id'], self.purchase_order.id)
|
||||
self.assertEqual(payload['stock_change_record_id'], 321)
|
||||
|
||||
|
||||
@override_settings(
|
||||
CELERY_TASK_ALWAYS_EAGER=True,
|
||||
@@ -88,39 +176,30 @@ class PurchaseOrderServiceTestCase(TestCase):
|
||||
)
|
||||
class PurchaseOrderStockTaskTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant, self.supplier, self.warehouse, self.product = create_basic_fixtures()
|
||||
User = get_user_model()
|
||||
self.user = User.objects.create_user(username='task-user', password='pass123')
|
||||
self.purchase_order = business_models.PurchaseOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
total_amount=Decimal('100.00'),
|
||||
)
|
||||
self.items = [{'product_id': self.product.id, 'quantities': ['8.00']}]
|
||||
self.purchase_order_id = 123
|
||||
self.warehouse_id = 456
|
||||
self.items = [{'product_id': 1, 'quantities': ['5']}]
|
||||
|
||||
def test_task_calls_stock_flow_service(self):
|
||||
with patch('business.tasks.StockFlowService') as mock_flow_cls:
|
||||
mock_instance = mock_flow_cls.return_value
|
||||
mock_instance.stock_in.return_value = (MagicMock(id=321), [], 2)
|
||||
def test_task_delegates_to_service(self):
|
||||
with patch('business.tasks.business_services.create_purchase_order_stock_entries_sync') as mock_sync:
|
||||
mock_sync.return_value = {'purchase_order_id': self.purchase_order_id}
|
||||
|
||||
async_result = tasks.create_purchase_order_stock_entries.delay(
|
||||
purchase_order_id=self.purchase_order.id,
|
||||
warehouse_id=self.warehouse.id,
|
||||
purchase_order_id=self.purchase_order_id,
|
||||
warehouse_id=self.warehouse_id,
|
||||
items=self.items,
|
||||
created_by_id=self.user.id,
|
||||
created_by_id=999,
|
||||
)
|
||||
payload = async_result.get(timeout=5)
|
||||
|
||||
mock_flow_cls.assert_called_once_with(merchant=self.merchant, created_by=self.user)
|
||||
mock_instance.stock_in.assert_called_once_with(
|
||||
warehouse_id=self.warehouse.id,
|
||||
source_type=stock_models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=self.purchase_order.id,
|
||||
mock_sync.assert_called_once_with(
|
||||
purchase_order_id=self.purchase_order_id,
|
||||
warehouse_id=self.warehouse_id,
|
||||
items=self.items,
|
||||
created_by_id=999,
|
||||
)
|
||||
self.assertEqual(payload['purchase_order_id'], self.purchase_order.id)
|
||||
self.assertEqual(payload['stock_change_record_id'], 321)
|
||||
self.assertEqual(payload['purchase_order_id'], self.purchase_order_id)
|
||||
self.assertIn('task_id', payload)
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
\restrict lAaLqLBC97fkSperdxYl5fjzjf6qhnkvpVyPWEkZyQ37Mu4fIXbcWZBIFk58Sg7
|
||||
\restrict 7vxIoMQ6fViE6Ury13lyTyPcnDejMchx1YmoWE9b14f1V4IdfU6SzZ7uNzJes6K
|
||||
|
||||
-- Dumped from database version 16.10
|
||||
-- Dumped by pg_dump version 17.6 (Debian 17.6-0+deb13u1)
|
||||
@@ -456,6 +456,41 @@ ALTER TABLE public.basic_info_merchant ALTER COLUMN id ADD GENERATED BY DEFAULT
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_merchantsetting; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.basic_info_merchantsetting (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
id bigint NOT NULL,
|
||||
key character varying(100) NOT NULL,
|
||||
description text,
|
||||
merchant_id bigint NOT NULL,
|
||||
val_bool boolean,
|
||||
val_float double precision,
|
||||
val_int integer,
|
||||
val_str character varying(100),
|
||||
type character varying(20) NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.basic_info_merchantsetting OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: basic_info_merchantsetting_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE public.basic_info_merchantsetting ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
|
||||
SEQUENCE NAME public.basic_info_merchantsetting_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_product; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -748,16 +783,59 @@ CREATE TABLE public.business_purchaseorder (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
id bigint NOT NULL,
|
||||
order_date date NOT NULL,
|
||||
total_amount numeric(15,2) NOT NULL,
|
||||
remarks text,
|
||||
merchant_id bigint NOT NULL,
|
||||
supplier_id bigint NOT NULL
|
||||
supplier_id bigint NOT NULL,
|
||||
kind integer NOT NULL,
|
||||
operator_id bigint NOT NULL,
|
||||
purchase_date date NOT NULL,
|
||||
status integer NOT NULL,
|
||||
warehouse_id bigint NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.business_purchaseorder OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorderitem; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE TABLE public.business_purchaseorderitem (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
id bigint NOT NULL,
|
||||
price numeric(15,2) NOT NULL,
|
||||
color character varying(50),
|
||||
quantity integer NOT NULL,
|
||||
unit character varying(50) NOT NULL,
|
||||
empty_diff_percent numeric(15,2) NOT NULL,
|
||||
quantity_of_rolls character varying(255),
|
||||
num_of_rolls integer NOT NULL,
|
||||
batch_number character varying(100),
|
||||
remarks text,
|
||||
product_id bigint NOT NULL,
|
||||
purchase_order_id bigint NOT NULL,
|
||||
CONSTRAINT business_purchaseorderitem_num_of_rolls_check CHECK ((num_of_rolls >= 0)),
|
||||
CONSTRAINT business_purchaseorderitem_quantity_check CHECK ((quantity >= 0))
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE public.business_purchaseorderitem OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorderitem_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE public.business_purchaseorderitem ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
|
||||
SEQUENCE NAME public.business_purchaseorderitem_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: django_admin_log; Type: TABLE; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -871,7 +949,7 @@ CREATE TABLE public.printing_plateorder (
|
||||
plate_type character varying(20),
|
||||
plate_date timestamp with time zone,
|
||||
plate_method character varying(50),
|
||||
plate_image character varying(100),
|
||||
plate_image jsonb NOT NULL,
|
||||
plate_notes text,
|
||||
reprint_reason text,
|
||||
urgency_level character varying(20) NOT NULL,
|
||||
@@ -882,7 +960,7 @@ CREATE TABLE public.printing_plateorder (
|
||||
color_matching_rating character varying(20),
|
||||
sample_rating character varying(20),
|
||||
difficulty_rating character varying(20),
|
||||
required_completion_date date,
|
||||
required_completion_date timestamp with time zone,
|
||||
completion_date timestamp with time zone,
|
||||
fabric character varying(100),
|
||||
width character varying(50),
|
||||
@@ -900,7 +978,10 @@ CREATE TABLE public.printing_plateorder (
|
||||
is_invalid boolean NOT NULL,
|
||||
process integer NOT NULL,
|
||||
fabric_source character varying(100),
|
||||
designer_id bigint
|
||||
designer_id bigint,
|
||||
image_name text,
|
||||
print_count integer NOT NULL,
|
||||
CONSTRAINT printing_plateorder_print_count_check CHECK ((print_count >= 0))
|
||||
);
|
||||
|
||||
|
||||
@@ -983,7 +1064,9 @@ CREATE TABLE public.printing_printingorder (
|
||||
production_warn text,
|
||||
customer_id bigint NOT NULL,
|
||||
is_invalid boolean NOT NULL,
|
||||
process_id bigint
|
||||
process_id bigint,
|
||||
print_count integer NOT NULL,
|
||||
CONSTRAINT printing_printingorder_print_count_check CHECK ((print_count >= 0))
|
||||
);
|
||||
|
||||
|
||||
@@ -1436,6 +1519,15 @@ ALTER TABLE public.stock_stocksnapshot ALTER COLUMN id ADD GENERATED BY DEFAULT
|
||||
|
||||
COPY public.api_uploaded_file (id, created_at, updated_at, path, is_deleted, original_filename, file_size, content_type, owner_id) FROM stdin;
|
||||
1 2025-11-21 02:46:27.837198+00 2025-11-21 02:46:27.837209+00 uploads/2025/11/21/21aed50ea13146c68d20b4ae3e1b3079.png f debug_group_4.png 159627 image/png 2
|
||||
2 2025-11-28 01:20:36.190015+00 2025-11-28 01:20:36.190028+00 uploads/2025/11/28/3f6310da200d4eed95fbf83e3a91c5d3.png f image.png 20215 image/png 2
|
||||
3 2025-11-28 01:32:56.717249+00 2025-11-28 01:32:56.717258+00 uploads/2025/11/28/669cb9d758ec4d1d812221634c0a8f0c.png f image.png 9562 image/png 2
|
||||
4 2025-11-28 01:33:05.201488+00 2025-11-28 01:33:05.201499+00 uploads/2025/11/28/7ceb226e55ff49f1ae19288bceb30043.png f image.png 27946 image/png 2
|
||||
5 2025-11-28 01:33:07.382898+00 2025-11-28 01:33:07.382907+00 uploads/2025/11/28/d8476a09bc65428186de1fff70acc8cb.png f image.png 19360 image/png 2
|
||||
6 2025-11-28 01:33:10.519571+00 2025-11-28 01:33:10.519581+00 uploads/2025/11/28/114c7bdecf9547dd8d55c19d814fcbe5.png f image.png 25338 image/png 2
|
||||
7 2025-11-28 02:45:21.799563+00 2025-11-28 02:45:21.799574+00 uploads/2025/11/28/ccaba46c20a249f1ba70cc00a4abe75a.png f image.png 17641 image/png 2
|
||||
8 2025-11-28 02:51:44.90851+00 2025-11-28 02:51:44.908523+00 uploads/2025/11/28/427aa1b8af0c44a3ba2ef930585e5f0e.png f image.png 29584 image/png 2
|
||||
9 2025-11-28 02:53:41.308123+00 2025-11-28 02:53:41.308132+00 uploads/2025/11/28/ebb3a627a5ce4eaaabc6fc4d9cd15bc7.png f image.png 55897 image/png 2
|
||||
10 2025-11-28 02:58:51.677179+00 2025-11-28 02:58:51.677189+00 uploads/2025/11/28/afaea93815684e2c8c3ea17124a702ed.png f image.png 35906 image/png 2
|
||||
\.
|
||||
|
||||
|
||||
@@ -1617,6 +1709,14 @@ COPY public.auth_permission (id, name, content_type_id, codename) FROM stdin;
|
||||
155 Can change 采购单 38 change_purchaseorder
|
||||
156 Can delete 采购单 38 delete_purchaseorder
|
||||
157 Can view 采购单 38 view_purchaseorder
|
||||
158 Can add 采购单明细 39 add_purchaseorderitem
|
||||
159 Can change 采购单明细 39 change_purchaseorderitem
|
||||
160 Can delete 采购单明细 39 delete_purchaseorderitem
|
||||
161 Can view 采购单明细 39 view_purchaseorderitem
|
||||
162 Can add 商户设置 40 add_merchantsetting
|
||||
163 Can change 商户设置 40 change_merchantsetting
|
||||
164 Can delete 商户设置 40 delete_merchantsetting
|
||||
165 Can view 商户设置 40 view_merchantsetting
|
||||
\.
|
||||
|
||||
|
||||
@@ -1625,12 +1725,12 @@ COPY public.auth_permission (id, name, content_type_id, codename) FROM stdin;
|
||||
--
|
||||
|
||||
COPY public.auth_user (id, password, last_login, is_superuser, username, first_name, last_name, email, is_staff, is_active, date_joined) FROM stdin;
|
||||
2 pbkdf2_sha256$1000000$FQEiwQ12oi0PN5Eiy8yBj8$5K56mG8hMRXmQDNAJaeacM1f7ILZ0cF/6Ad6wTI69QA= 2025-11-26 02:43:48.977068+00 f jimi f t 2025-11-18 05:59:00+00
|
||||
3 pbkdf2_sha256$1000000$mlevMIoViCC3iCxkVWY7N7$Hs9ug78RJIMHoORM5k4YzX52IkUubzlzOzXpMDAG5/k= 2025-11-23 12:57:16.773509+00 f 映雪 f t 2025-11-19 08:12:00+00
|
||||
5 pbkdf2_sha256$1000000$6QzgwlV3m9j08p5c6TimM6$dYqey1tpxL2fIuJj+qKVl2z5Dc41BrvHuZlQShmgIM4= \N f testuser_5599 testuser_5599@example.com f t 2025-11-24 05:45:22.629483+00
|
||||
6 pbkdf2_sha256$1000000$Gc8ss3NEXAAO5mK1nQ51l2$/DOLtcYC+F+dG1LVSuvWvyrLVNR9qViopLL9ntjqojo= \N f apitest2 f t 2025-11-24 06:08:19.098505+00
|
||||
7 pbkdf2_sha256$1000000$gXVjPP0SD9cgTNxOOsTPbG$/dtOlfY+1WsASF/pW+EtompccUEORJMTMyhL9YebZxo= \N f apitest666 f t 2025-11-24 06:16:49.620831+00
|
||||
1 pbkdf2_sha256$1000000$NyR8RyN6CWu3xEJP4rXclZ$vKH5DuApHqxxHaxiWd/b6Ek0BTozUZ+ZaJXcxtfRkiA= 2025-11-25 07:53:54.390262+00 t admin t t 2025-11-18 05:58:19.271303+00
|
||||
1 pbkdf2_sha256$1000000$NyR8RyN6CWu3xEJP4rXclZ$vKH5DuApHqxxHaxiWd/b6Ek0BTozUZ+ZaJXcxtfRkiA= 2025-11-28 06:05:13.233786+00 t admin t t 2025-11-18 05:58:19.271303+00
|
||||
2 pbkdf2_sha256$1000000$FQEiwQ12oi0PN5Eiy8yBj8$5K56mG8hMRXmQDNAJaeacM1f7ILZ0cF/6Ad6wTI69QA= 2025-11-28 06:15:55.446346+00 f jimi f t 2025-11-18 05:59:00+00
|
||||
\.
|
||||
|
||||
|
||||
@@ -1765,6 +1865,15 @@ COPY public.basic_info_merchant (created_at, updated_at, id, name, type, area, e
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: basic_info_merchantsetting; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.basic_info_merchantsetting (created_at, updated_at, id, key, description, merchant_id, val_bool, val_float, val_int, val_str, type) FROM stdin;
|
||||
2025-11-28 06:03:02.924489+00 2025-11-28 06:20:38.985597+00 1 auto_create_stock_change_tasks 1 f \N \N \N bool
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: basic_info_product; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -1898,6 +2007,17 @@ COPY public.business_object (id, created_at, updated_at, name, description, proc
|
||||
28 2025-11-22 02:39:05.939841+00 2025-11-22 02:39:05.939847+00 2 \N \N
|
||||
29 2025-11-23 13:11:03.642075+00 2025-11-23 13:11:03.642082+00 2 \N \N
|
||||
30 2025-11-24 11:33:15.711916+00 2025-11-24 11:33:15.711922+00 2 \N \N
|
||||
31 2025-11-27 07:23:06.022961+00 2025-11-27 07:23:06.022966+00 2 \N \N
|
||||
32 2025-11-27 08:08:49.410063+00 2025-11-27 08:08:49.41007+00 2 \N \N
|
||||
33 2025-11-27 08:09:00.364914+00 2025-11-27 08:09:00.364921+00 2 \N \N
|
||||
34 2025-11-27 09:01:25.14368+00 2025-11-27 09:01:25.143685+00 2 \N \N
|
||||
35 2025-11-27 09:04:52.038687+00 2025-11-27 09:04:52.038695+00 2 \N \N
|
||||
36 2025-11-28 01:32:58.420878+00 2025-11-28 01:32:58.420883+00 2 \N \N
|
||||
37 2025-11-28 01:33:14.216478+00 2025-11-28 01:33:14.216483+00 2 \N \N
|
||||
38 2025-11-28 01:43:59.359293+00 2025-11-28 01:43:59.359298+00 2 \N \N
|
||||
39 2025-11-28 02:39:41.390748+00 2025-11-28 02:39:41.390755+00 2 \N \N
|
||||
40 2025-11-28 02:57:18.735261+00 2025-11-28 02:57:18.735268+00 2 \N \N
|
||||
41 2025-11-28 02:58:57.702444+00 2025-11-28 02:58:57.70245+00 2 \N \N
|
||||
\.
|
||||
|
||||
|
||||
@@ -1905,7 +2025,32 @@ COPY public.business_object (id, created_at, updated_at, name, description, proc
|
||||
-- Data for Name: business_purchaseorder; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.business_purchaseorder (created_at, updated_at, id, order_date, total_amount, remarks, merchant_id, supplier_id) FROM stdin;
|
||||
COPY public.business_purchaseorder (created_at, updated_at, id, remarks, merchant_id, supplier_id, kind, operator_id, purchase_date, status, warehouse_id) FROM stdin;
|
||||
2025-11-27 08:13:33.430682+00 2025-11-27 08:13:33.430691+00 4 1 1 1 1 2025-11-27 2 2
|
||||
2025-11-27 08:32:59.523809+00 2025-11-27 08:32:59.523816+00 5 1 1 1 1 2025-11-27 1 4
|
||||
2025-11-28 03:45:15.401776+00 2025-11-28 03:45:15.401787+00 6 1 1 1 1 2025-11-28 1 2
|
||||
2025-11-28 03:47:11.102159+00 2025-11-28 03:47:11.102166+00 7 1 1 1 1 2025-11-28 1 3
|
||||
2025-11-28 03:48:02.675851+00 2025-11-28 03:48:02.675859+00 8 oooo 1 1 1 1 2025-11-28 1 4
|
||||
2025-11-28 03:49:04.580215+00 2025-11-28 03:49:04.580224+00 9 1 1 1 1 2025-11-28 1 2
|
||||
2025-11-28 06:18:30.813402+00 2025-11-28 06:18:30.813413+00 10 1 1 1 1 2025-11-28 1 3
|
||||
2025-11-28 06:18:54.718044+00 2025-11-28 06:18:54.718052+00 11 1 1 1 1 2025-11-28 1 4
|
||||
2025-11-28 06:20:58.855488+00 2025-11-28 06:20:58.855501+00 12 1 1 1 1 2025-11-28 1 2
|
||||
\.
|
||||
|
||||
|
||||
--
|
||||
-- Data for Name: business_purchaseorderitem; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.business_purchaseorderitem (created_at, updated_at, id, price, color, quantity, unit, empty_diff_percent, quantity_of_rolls, num_of_rolls, batch_number, remarks, product_id, purchase_order_id) FROM stdin;
|
||||
2025-11-27 08:14:29.091796+00 2025-11-27 08:14:29.091805+00 1 555.00 红色 25 米 3.34 200 1 1 1 4
|
||||
2025-11-27 08:35:24.029424+00 2025-11-27 08:48:57.608408+00 2 25.00 \N 100 米 5.00 \N 1 WWOODDP1 1 5
|
||||
2025-11-28 03:45:15.402492+00 2025-11-28 03:45:15.402496+00 3 0.00 \N 195 米 0.00 97,98 2 \N \N 1 6
|
||||
2025-11-28 03:47:11.102785+00 2025-11-28 03:47:11.10279+00 4 0.00 \N 195 米 0.00 97,98 2 \N \N 1 7
|
||||
2025-11-28 03:49:04.580819+00 2025-11-28 03:49:04.580824+00 5 0.00 \N 195 米 0.00 97,98 2 \N \N 1 9
|
||||
2025-11-28 06:18:30.814105+00 2025-11-28 06:18:30.814109+00 6 0.00 \N 195 米 0.00 97,98 2 \N \N 1 10
|
||||
2025-11-28 06:18:54.718646+00 2025-11-28 06:18:54.71865+00 7 0.00 \N 386 米 0.00 97,98,97,94 4 \N \N 1 11
|
||||
2025-11-28 06:20:58.85678+00 2025-11-28 06:20:58.856787+00 8 0.00 \N 195 米 0.00 97,98 2 \N \N 2 12
|
||||
\.
|
||||
|
||||
|
||||
@@ -2013,6 +2158,23 @@ COPY public.django_admin_log (id, action_time, object_id, object_repr, action_fl
|
||||
97 2025-11-25 10:12:01.371549+00 11 出入库记录 11 - 仓库:中大 1 [{"added": {}}] 20 1
|
||||
98 2025-11-25 10:12:41.75256+00 27 库存变动明细 27 - 记录ID: 11 1 [{"added": {}}] 21 1
|
||||
99 2025-11-25 10:12:59.643169+00 27 库存变动明细 27 - 记录ID: 11 2 [{"changed": {"fields": ["\\u6240\\u6d88\\u8017\\u7684\\u5165\\u5e93\\u660e\\u7ec6"]}}] 21 1
|
||||
100 2025-11-27 08:08:36.5688+00 13 出入库记录 13 - 仓库:中大 3 20 1
|
||||
101 2025-11-27 08:09:43.222937+00 3 采购订单 3 - 测试 3 38 1
|
||||
102 2025-11-27 08:09:43.222962+00 2 采购订单 2 - 测试 3 38 1
|
||||
103 2025-11-27 08:09:43.222973+00 1 采购订单 1 - 测试 3 38 1
|
||||
104 2025-11-27 08:13:33.431382+00 4 采购订单 4 - 测试 1 [{"added": {}}] 38 1
|
||||
105 2025-11-27 08:14:29.09261+00 1 PurchaseOrderItem object (1) 1 [{"added": {}}] 39 1
|
||||
106 2025-11-27 08:32:59.524449+00 5 采购订单 5 - 测试 1 [{"added": {}}] 38 1
|
||||
107 2025-11-27 08:35:24.030068+00 2 PurchaseOrderItem object (2) 1 [{"added": {}}] 39 1
|
||||
108 2025-11-27 08:35:32.980517+00 2 PurchaseOrderItem object (2) 2 [{"changed": {"fields": ["\\u7a7a\\u5dee\\u767e\\u5206\\u6bd4"]}}] 39 1
|
||||
109 2025-11-27 08:48:57.608904+00 2 PurchaseOrderItem object (2) 2 [{"changed": {"fields": ["\\u6279\\u6b21\\u53f7"]}}] 39 1
|
||||
110 2025-11-28 03:48:02.676475+00 8 采购订单 8 - 测试 1 [{"added": {}}] 38 1
|
||||
111 2025-11-28 06:03:02.925051+00 1 瑞彩印花 - auto_create_stock_tasks 1 [{"added": {}}] 40 1
|
||||
112 2025-11-28 06:03:10.044445+00 1 瑞彩印花 - auto_create_stock_tasks 2 [{"changed": {"fields": ["\\u8bbe\\u7f6e\\u503c"]}}] 40 1
|
||||
113 2025-11-28 06:04:43.402849+00 1 瑞彩印花 - auto_create_stock_tasks 2 [{"changed": {"fields": ["\\u8bbe\\u7f6e\\u503c(\\u5e03\\u5c14\\u503c)"]}}] 40 1
|
||||
114 2025-11-28 06:09:27.417148+00 1 瑞彩印花 - auto_create_stock_tasks 2 [{"changed": {"fields": ["\\u8bbe\\u7f6e\\u7c7b\\u578b"]}}] 40 1
|
||||
115 2025-11-28 06:12:01.751881+00 1 瑞彩印花 - auto_create_stock_change_tasks 2 [{"changed": {"fields": ["\\u8bbe\\u7f6e\\u9879"]}}] 40 1
|
||||
116 2025-11-28 06:20:38.986097+00 1 瑞彩印花 - auto_create_stock_change_tasks 2 [{"changed": {"fields": ["\\u8bbe\\u7f6e\\u503c(\\u5e03\\u5c14\\u503c)"]}}] 40 1
|
||||
\.
|
||||
|
||||
|
||||
@@ -2059,6 +2221,8 @@ COPY public.django_content_type (id, app_label, model) FROM stdin;
|
||||
36 basic_info employeetype
|
||||
37 basic_info userprofile
|
||||
38 business purchaseorder
|
||||
39 business purchaseorderitem
|
||||
40 basic_info merchantsetting
|
||||
\.
|
||||
|
||||
|
||||
@@ -2143,6 +2307,21 @@ COPY public.django_migrations (id, app, name, applied) FROM stdin;
|
||||
74 stock 0005_stockchangedetail_consume_fields 2025-11-24 09:06:44.307805+00
|
||||
75 business 0001_initial 2025-11-25 06:08:12.742536+00
|
||||
76 stock 0006_remove_purchaseorder 2025-11-25 06:08:12.745336+00
|
||||
77 printing 0016_plateorder_image_name 2025-11-27 06:53:13.195655+00
|
||||
78 business 0002_remove_purchaseorder_order_date_purchaseorder_kind_and_more 2025-11-27 07:29:19.575686+00
|
||||
79 printing 0017_alter_plateorder_image_name 2025-11-27 07:29:19.582348+00
|
||||
80 business 0003_purchaseorder_status 2025-11-27 07:34:05.728213+00
|
||||
81 business 0004_remove_purchaseorder_total_amount_and_more 2025-11-27 08:08:14.545561+00
|
||||
82 business 0005_alter_purchaseorder_operator_and_more 2025-11-27 08:10:45.002847+00
|
||||
83 business 0006_remove_purchaseorderitem_total_amount 2025-11-27 08:18:28.948263+00
|
||||
84 business 0007_alter_purchaseorderitem_batch_number 2025-11-27 08:45:44.548428+00
|
||||
85 printing 0018_plateorder_plate_image_to_jsonfield 2025-11-28 01:31:22.225727+00
|
||||
86 printing 0019_alter_plateorder_plate_image 2025-11-28 01:31:22.259774+00
|
||||
87 printing 0020_alter_plateorder_required_completion_date 2025-11-28 02:57:43.677396+00
|
||||
88 printing 0021_plateorder_print_count_printingorder_print_count 2025-11-28 03:29:26.824386+00
|
||||
89 basic_info 0014_merchantsetting 2025-11-28 06:00:48.6332+00
|
||||
90 basic_info 0015_remove_merchantsetting_val_merchantsetting_val_bool_and_more 2025-11-28 06:04:30.020931+00
|
||||
91 basic_info 0016_merchantsetting_type 2025-11-28 06:06:20.05325+00
|
||||
\.
|
||||
|
||||
|
||||
@@ -2151,9 +2330,11 @@ COPY public.django_migrations (id, app, name, applied) FROM stdin;
|
||||
--
|
||||
|
||||
COPY public.django_session (session_key, session_data, expire_date) FROM stdin;
|
||||
7yutj5xzsu9xicbn3bi2ziy5zzoamenm .eJzNm0tz4ygQgP9KyudMohcCzXHve9zTeivFM9ZElrR6ZGp2av77ArJjmWAbIU3Jl-BCTQNf8-gG8nPzgvtu99K3vHnJ2ebrJtw8jvMIpm-8VB_YN1y-Vk-0KrsmJ09K5OnwtX36s2K8-OMge6Zgh9udLM1RSlOUkpSnNEshQwTDOAGCxykNMxHFGYAxAjSNSZRBmXIex0GGUxRwFkZAKd3zsm-lrr9_bjcl3vPt5uvDdrPd9gkPsUxAhEOZpDhSCYAAbjePUiKXbR5kBW4eBP5C84YWfPi4Vy1v1edParOQQpkgFjGlLwbAQV_fFMO3Z8z2eflM-jYveds-131DJQxeNYw3z4MwZuwvN3kpeShDGo4Zbfo9sbfaF8avx4fZAH79oz7znKmvYRBEbkplM9OAywRymi7GOO_4fipnXeZeWE_BYpKPzYzQUmfCBRk1nyUqgTgg8-bNORVARKY6RtPQw7Rthzsuiur7h8Eq8o3T7rJhL5aYYlY_LlbEfixM8wGLbsC4GiQogrqKmEaqtQAGszjrXzVuZE3dtYXqYolVOPuyMDmnFt3jhslEhKnKjLNoFue6qahafxwAH0VXIevbe5MsvKkbRUhZDQYkmz-CnQfuPVB177lJFU3SLdsewlhlBiBZYvSWcheYMIK1-J3xnsLEpJ9ZaoIR0TNEbrmnepFIqbEuyUxCVAKEl2NpDOOiev1YghtOpTfjPAMsRVex0e8gZ1gsDCbWu6iN1K-JthkVuTub-FshNCdSYtvSY21rGSyq2QmJ7gxMApcI4Yp3eq42JTHXfQjE7DCkbvKyy8vXjx_fKnLZ0FZpdxv70rkJ2peIaeP4Zk2I4GjrHVh_4ncjsL4gfwfE3TmYjK3TRgSBnrgJWpBxIdciZ8An4XXoehIw6QIzw3acAaBQEQdEid6YWJLobSpzcWGvxtBYHQeAhGoOFA4LeuQSmn86HsFtTl_yUlTKD2M97ag0kBz_P64cj1wuM8GmnmyscbQfD9OEtijkXPc0E95mPYXxPbCdoMpga4tFEq7nM-BZvCTb77jhu6pvrwQfVumV-PoxMPnaoo1EZGTQrZQCkHidn55QtX1dF_nVA2qL8DpYp3bd4BnZYgEQC6S3oRgPjq72fknkc3Y5IvXOd7mU6xpctnXVdLeigNtFV0HuS8ckbzuMBglQuo8HeUFCtY0TNlrvIZq5__F9XVQ_OO9-1G5Lx1mBdZgvwsW0gNWVOa9pqbX6iHAS77tg7btSR9bYRy1QUlsU63seQnRcJVI-j-6eq-ursmt5p51vF8hmmXVYe_IwWVtjoHPdS43kI7dJkO-CrvdItt47DRHV0nRp33bV3tH1-BBeh64nAZOu7bYJCK7W9DSGWrfQTiMIU6_HBydg__Y5fcvLuncbvSPxdQh7UjAJ2-I9CCJkt57KTPWVAPV77HECqB6_yKhO5IXbxjeWX4X4MlRM_raY8LDWgyxYdP1g_D2nXP90AT4SX4W3LwWTsC0qPHjmSKB0Se_5GIq4Os9j-XUYe3IwGMe2SDFLhNaNtEOOWPSx385iTHD5himtekc3Yyy_CmNfDiZj804otG6OwxkKINpPh6kOR_X97bw7oVDj0PvL6aBm5tVfRd-Gv8oNfOW3L_7sBSYY1ROOjfM8IKZtrfHQqLW-ePPynZfd1YNsU3AdnM59NclZo5uzhoGQ6Ic56pBx1jAVDef_XX3NYYquznJK702y1sjmXHfMdBJh7RjNuhv-NLsZ73BeTFgODgXugbknF9MCtxf4QxQAw9RnVx0hbEtct7vq85Za9kWxLkznLpr4oJlhO9NDBCc60Xc7IEFUR1VoOMZNLM1vVZ3tLucF-4KL7ubeOY4b7B1QoY3dQurfBHTkc3kenETcR_4Cnb4RIF3rqGkW6yMxTi1vzdqjkvYLrV6vIHttqr6-wWyQWR2aU0cNZElgMrSGkQKBYRnSsWmUqfcqKBBsni-Y8EC5PomI9HsEgKDOFD6rPq7zl_fwua-LCjPOrh8G2IQnmM-ThvW-0o-AacXIzJAu_ubX_2MEg3k:1vOrxp:x8zwg7R0fMTGw-7dJv41hwLHzqn09JEBgXZ_mjqJeDM 2025-12-12 06:28:17.957326+00
|
||||
pbsoimfkt3qhp2caalbpzzfmnoia4ary .eJzNmktz4ygQgP9Kyuck1guB5rj3Pe5pPZXiaSuRJa0emcpO5b8vIMeRCbER8pZ8CQ40DXzNoxv0e_WE-2731Le8ecrZ6scqXN2P8wimL7xUBewZl9vqkVZl1-TkUYk8Hkrbxz8rxos_DrInCna43cnaHKU0RSlJeUqzFDJEMIwTIHic0jATUZwBGCNA05hEGZQp53EcZDhFAWdhBJTSPS_7Vur6-_dmVeI936x-3G1Wm02fcEFkAiIcyiRliUogDshmdS8lctnnQVbg5k7gB5o3tOBD4V71vFXFFrUh_lQLiMhkgmgaOqjtm2IoW2O2z8t12-GOi6L6tSZ9m5e8bSvyzGm3HsQxY3-51pCyh1qk4ZjRpt-T7wbgx-X9_u5aLN5_qmKeM1UaBkFk0Q0YB0pbBHUTMY1UbwEMZnHWv2rcyJY63rhwNmoswtmXhck5tuged0wmIkxVZpxFszjXTUXl5HQB_CG6CFnf0Ztkk4u6UYSU1WBAsvkz2Hni3gJV95GbVMEk3bLvIYxVZgCSa8zeUp4CE2awFr8x3lOYmPRTS0swInqFBMGo00ik1NiXZCYhKgECgPnzvai2xy244bRqmPMKsFRdxEb_BznTYnBiu1e1kfo10TajKjdnE38rIDMjtB3psba1dHrV6oREDwYmQTrPOz1Vm5KY6zEEQv2XBuo_yKlLI6a16yYvu7zcHn88V-R7Q1ul3W3sS-ciaF8ihknD4GJLMpaJdKbX2vrCT66Rc_7qN_I3QNydg8nYumxEEOiFm6ArMi7kXuQM-FN4GbqeBEy6kblLZbbGoFARB0SJPphYkuhjKnNxYc_G0Bgq7QnVHCgcNvTIJTQ3rUhwm9OnvBSV8sNYTzsqDSTn_9v3xjxTZ4JNPdlY42g_HqZNbVHIqe5pJrzMegrjW2A7QZXB1haLJFyvZ8Cz-Jpsf-GG76q-PRN8WKUX4uvHwORrizYSkZFBt1IKQOLjuIxQtX1dF_m5bd4mvAzWqUM3edpiARALpI-hGA-OrvZ-SeRzdzki9cp3uZTrGly2ddV0l6KAy1UXQe5LxySPbLoToHR_XORda6fg-7qo3rjbRnEUXoauJwGTrtVNUctDaotieE26e97QHS7dDrmj8DJ0PQkYdCNrXDP4mNemS_u2q_aOm_FReBm6ngRMuvaIhquNPo2h1i30MQrC1CeiGQH7p8_pS17WvdvsHYkvQ9iTgknY9pKECNExfxZcdf4y_ppTrn-6EB6JL0LYl4JJ2PaGdDgrkUDpKH6BaGY89-EcvNVuB9xYfhnGnhxMxrY4LkuE1o0SfTvJouN-P4sxweULprTqHY-5sfwijH05mIyB6VjYJvYhqgFE-y4w1Q6iflGZd0sbahx6f_sMnWZexlf0Zfir3JAtv3wVb68wwaiecGyc5wExbWuNfUa99cWbl6-87M5eLZmCy-B0HqtJzhq7nHQMhEQ_lauwf9Y0FQ3n_559XzVFF2c5ZfQmWWvccqo7ZjqJsA5DZ73WfFndjHc4LyZsB4cKt8Dck4thgdga25y2NHihMEx9TtURwrbEdburvh6pZV8Uy8J0HqKJzxa8ZCEdvMmIbbzfYQZuda_i5_bSO4xNeJE5OnnoJk_zASay3YAighOd6NtrkCCqoyQ0XFQlln63qs12l_OCPeCiu-iLQBCh8x6U-mLTbjn1QedaFX9vr08RdytdYdC2x37HgZp2sjnjcvexfE3TfihpH2i1PYNs21R9fYHZILM4NKeBmshM3zq2B40IDNu6_kwpytSLPAoEm_u2GChXMhGRfnEFCOpM4XOK4jp_eg3XfV1UmHEm8uKMv2ITnmA-TxrWFxk_AqbRoJmRvv9cvf8He7uCiw:1vLuTn:9mpos00LpzQqh1_HZUVfRUBLxHwEHsQ2fAFkpQ7KweM 2025-12-04 02:33:03.066594+00
|
||||
h4nbxaigtax9cnxud2hb4sd0uexilmw7 .eJzNmklz6ygQgP9KyucsWkCgd5z7HOc0mUqxxkpkSaMlrzKv8t8HUBaZYBshv5IvwUFNA1-Lphv0a_NAhn77MHSifSj45scm3lxP6yhhz6LSD_gTqR7rW1ZXfVvQWy1y-_60u_2z5qL84112T8GWdFvVWuCMZTijmchYniGOKUEpgFKkGYtzmaQ5RCmGLEtpkiNVCpGmUU4yHAkeJ1Ar3Ylq6JSuv3_dbyqyE_ebH1f3m_v7AYiYqAImJFZFRhJdQATR_eZaSRRqzKOsJO2VJDesaFkpxoc7PfJOP_6mNo8ZUgXmCdf6Ugg99A1tOT67I3xXVHd06IpKdN1dM7RMwRB1y0V7NwoTzv_yk1eS721oKwhn7bCj7lGHwni7vloM4O0f_VgUXD-NoyixK2JHL0BIOhkwB7pAJKLLrLfPAVKZ66mwLA4wYteTXsiy_vlpnpo-CdYfNuPBFnMMGcbFiTiMhW0-4NANuYBaW4JMFylL9GghihZxNr8a0qqe-mPL5WCLVTiHsrA5Q4fu6cBUIeNMV6Z5sohz09ZMexsPwB-iq5ANnb1NNjupGydYWw1FNF_-Bnu_uJdA1X_mNlU0S7cae4xSXRlBcI63t1K7wIw32IhfGO85TGz62NETSqhZIWrL_eoXy4xZfklVUqoLKIPCG-s1LuvHTxfcCqZiF-8V4Gi6io1-BznbYvnMfs9qI_1rpm0mTS7OJsFWiCPbLKlrS0-NrVXKolcnomYyCETZsuh0X21GU2HmEEn9Xxbp_5BgPp3Y1m7aouqL6vHzx1NNDxvaKe1v41A6J0GHErFtnJzsSWWiiV9240X7RHp3QP4CiPtzsBk7l42MIrNwAT4j41L5Im_AX8Lr0A0kYNMFdoUrh4ZI6owDYWA2Jg6A2aZynxD2aA5N9AEABMxwYGh06IlPav7tIIR0BXsoKlnrOIwPrGfKQOr9fz1yGHK4zQybBrJx5tFhPGwTurKQfd3zTHia9RzGl8B2hiqLrSsXAcKsZyjy9Jxsf5JWbOuhO5J8OKVX4hvGwObryjaAzOmoWyuFEIQELhNU3dA0ZXH0mNQhvA7WuVO3ebpyAZhKbLahlIyBrol-aRJydjkh9SK2hZLrW1J1Td32p7KA001XQR5KxyKfRC7dAGrdHwd5EWDGxoBP_D3CC_c_sWvK-lWI_rXxcx17DdZhfhYutgWcocx-T-fy1R8IZ_G-CNahnjpx5j7aQSltSYrOSXcn9GVV5RdmfAqvQzeQgE3XmfWMUf656bKh6-ud53b4KbwO3UACNl3nTZMU2s9kKTK6pQlkYJwFXct-Aft3KNhzUTWD39s7EV-HcCAFm7DrjgnBBLutpyszc0zNwq7BvwDqzwJUpiGL0s8ZT-VXIX4eKjZ_Vw6IKTWnXnl0Vv_BxUvBhPnpA3wivgrvUAo2YVcm-B4tYomzc0Z0H-Gxb0A3lV-HcSAHm7ErG8yBNLqxCRIxTz7320WMKameCWP14BlmTOVXYRzKwWac2ymjy3W_5_WQmtgRZSZFMneKy-4pYoPD7C9fhwcLr6Nq9jz-1WHgozh9GeVuMMOogXBcnJcBsUyZOjOgyWhD8RbVi6j6o4ertuA6OL3napNzZjd7A4MxNR-L6IOvRa-pbIX47-gXBrbo6iznzN4m68xs9nWn3BQJMYHRovvKb6ubi54U5Qx38N7gEpgHcrEt4Mx-9nsaswAUZyG76gRhV5Gm29bft9RqKMt1YXpP0cYH7QrXSR-mBJjC3DdAgJnJqvB4tAgcw-90n922ECW_IWV_cu-c5g3uCejUxm0h_QG1yXwOr4MvEf83_wyTPpEgHZuobRZXgK5Wi-P7p-5DSXfD6scjyB7bemhOMBtlVofmNVEbGbYrnGmkxHB0QyY3TXL9DQWOJF8WCwIR6dAHyMTckUOMTKUM8fqkKR5e4ruhKWvCBT9-GOASnmG-QBrOO7QwApbRgP3lUapC_M3b_4NBlrY:1vNpQw:JJKhKn0v0UW9V6_rJqP1yuhP01rBLH7J3y9JvZbBFK8 2025-12-09 09:34:02.767355+00
|
||||
0qv1nnfydmil7knzisuz8cbfc9a92fqv .eJzNmklz6ygQgP9KyucsWkCgd5z7HOc0mUqxxkpkSaMlrzKv8t8HUBaZYBshv5IvwUFNA1-Lphv0a_NAhn77MHSifSj45scm3lxP6yhhz6LSD_gTqR7rW1ZXfVvQWy1y-_60u_2z5qL84112T8GWdFvVWuCMZTijmchYniGOKUEpgFKkGYtzmaQ5RCmGLEtpkiNVCpGmUU4yHAkeJ1Ar3Ylq6JSuv3_dbyqyE_ebH1f3m_v7AYiYqAImJFZFRhJdQATR_eZaSRRqzKOsJO2VJDesaFkpxoc7PfJOP_6mNo8ZUgXmCdf6Ugg99A1tOT67I3xXVHd06IpKdN1dM7RMwRB1y0V7NwoTzv_yk1eS721oKwhn7bCj7lGHwni7vloM4O0f_VgUXD-NoyixK2JHL0BIOhkwB7pAJKLLrLfPAVKZ66mwLA4wYteTXsiy_vlpnpo-CdYfNuPBFnMMGcbFiTiMhW0-4NANuYBaW4JMFylL9GghihZxNr8a0qqe-mPL5WCLVTiHsrA5Q4fu6cBUIeNMV6Z5sohz09ZMexsPwB-iq5ANnb1NNjupGydYWw1FNF_-Bnu_uJdA1X_mNlU0S7cae4xSXRlBcI63t1K7wIw32IhfGO85TGz62NETSqhZIWrL_eoXy4xZfklVUqoLKIPCG-s1LuvHTxfcCqZiF-8V4Gi6io1-BznbYvnMfs9qI_1rpm0mTS7OJsFWiCPbLKlrS0-NrVXKolcnomYyCETZsuh0X21GU2HmEEn9Xxbp_5BgPp3Y1m7aouqL6vHzx1NNDxvaKe1v41A6J0GHErFtnJzsSWWiiV9240X7RHp3QP4CiPtzsBk7l42MIrNwAT4j41L5Im_AX8Lr0A0kYNMFdoUrh4ZI6owDYWA2Jg6A2aZynxD2aA5N9AEABMxwYGh06IlPav7tIIR0BXsoKlnrOIwPrGfKQOr9fz1yGHK4zQybBrJx5tFhPGwTurKQfd3zTHia9RzGl8B2hiqLrSsXAcKsZyjy9Jxsf5JWbOuhO5J8OKVX4hvGwObryjaAzOmoWyuFEIQELhNU3dA0ZXH0mNQhvA7WuVO3ebpyAZhKbLahlIyBrol-aRJydjkh9SK2hZLrW1J1Td32p7KA001XQR5KxyKfRC7dAGrdHwd5EWDGxoBP_D3CC_c_sWvK-lWI_rXxcx17DdZhfhYutgWcocx-T-fy1R8IZ_G-CNahnjpx5j7aQSltSYrOSXcn9GVV5RdmfAqvQzeQgE3XmfWMUf656bKh6-ud53b4KbwO3UACNl3nTZMU2s9kKTK6pQlkYJwFXct-Aft3KNhzUTWD39s7EV-HcCAFm7DrjgnBBLutpyszc0zNwq7BvwDqzwJUpiGL0s8ZT-VXIX4eKjZ_Vw6IKTWnXnl0Vv_BxUvBhPnpA3wivgrvUAo2YVcm-B4tYomzc0Z0H-Gxb0A3lV-HcSAHm7ErG8yBNLqxCRIxTz7320WMKameCWP14BlmTOVXYRzKwWac2ymjy3W_5_WQmtgRZSZFMneKy-4pYoPD7C9fhwcLr6Nq9jz-1WHgozh9GeVuMMOogXBcnJcBsUyZOjOgyWhD8RbVi6j6o4ertuA6OL3napNzZjd7A4MxNR-L6IOvRa-pbIX47-gXBrbo6iznzN4m68xs9nWn3BQJMYHRovvKb6ubi54U5Qx38N7gEpgHcrEt4Mx-9nsaswAUZyG76gRhV5Gm29bft9RqKMt1YXpP0cYH7QrXSR-mBJjC3DdAgJnJqvB4tAgcw-90n922ECW_IWV_cu-c5g3uCejUxm0h_QG1yXwOr4MvEf83_wyTPpEgHZuobRZXgK5Wi-P7p-5DSXfD6scjyB7bemhOMBtlVofmNVEbGbYrnGmkxHB0QyY3TXL9DQWOJF8WCwIR6dAHyMTckUOMTKUM8fqkKR5e4ruhKWvCBT9-GOASnmG-QBrOO7QwApbRgP3lUapC_M3b_4NBlrY:1vOFTv:avJ7TQz_fiJiww_ZY9F1EAtmvUPKd221DjK5dvhiC2A 2025-12-10 13:22:51.388841+00
|
||||
nie1ws9ihz1c2tlxhfbz47rmvgtv4p6c .eJzNmklz6ygQgP9KyucsWkCgd5z7HOc0mUqxxkpkSaMlrzKv8t8HUBaZYBshv5IvwUFNA1-Lphv0a_NAhn77MHSifSj45scm3lxP6yhhz6LSD_gTqR7rW1ZXfVvQWy1y-_60u_2z5qL84112T8GWdFvVWuCMZTijmchYniGOKUEpgFKkGYtzmaQ5RCmGLEtpkiNVCpGmUU4yHAkeJ1Ar3Ylq6JSuv3_dbyqyE_ebH1f3m_v7AYiYqAImJFZFRhJdQATR_eZaSRRqzKOsJO2VJDesaFkpxoc7PfJOP_6mNo8ZUgXmCdf6Ugg99A1tOT67I3xXVHd06IpKdN1dM7RMwRB1y0V7NwoTzv_yk1eS721oKwhn7bCj7lGHwni7vloM4O0f_VgUXD-NoyixK2JHL0BIOhkwB7pAJKLLrLfPAVKZ66mwLA4wYteTXsiy_vlpnpo-CdYfNuPBFnMMGcbFiTiMhW0-4NANuYBaW4JMFylL9GghihZxNr8a0qqe-mPL5WCLVTiHsrA5Q4fu6cBUIeNMV6Z5sohz09ZMexsPwB-iq5ANnb1NNjupGydYWw1FNF_-Bnu_uJdA1X_mNlU0S7cae4xSXRlBcI63t1K7wIw32IhfGO85TGz62NETSqhZIWrL_eoXy4xZfklVUqoLKIPCG-s1LuvHTxfcCqZiF-8V4Gi6io1-BznbYvnMfs9qI_1rpm0mTS7OJsFWiCPbLKlrS0-NrVXKolcnomYyCETZsuh0X21GU2HmEEn9Xxbp_5BgPp3Y1m7aouqL6vHzx1NNDxvaKe1v41A6J0GHErFtnJzsSWWiiV9240X7RHp3QP4CiPtzsBk7l42MIrNwAT4j41L5Im_AX8Lr0A0kYNMFdoUrh4ZI6owDYWA2Jg6A2aZynxD2aA5N9AEABMxwYGh06IlPav7tIIR0BXsoKlnrOIwPrGfKQOr9fz1yGHK4zQybBrJx5tFhPGwTurKQfd3zTHia9RzGl8B2hiqLrSsXAcKsZyjy9Jxsf5JWbOuhO5J8OKVX4hvGwObryjaAzOmoWyuFEIQELhNU3dA0ZXH0mNQhvA7WuVO3ebpyAZhKbLahlIyBrol-aRJydjkh9SK2hZLrW1J1Td32p7KA001XQR5KxyKfRC7dAGrdHwd5EWDGxoBP_D3CC_c_sWvK-lWI_rXxcx17DdZhfhYutgWcocx-T-fy1R8IZ_G-CNahnjpx5j7aQSltSYrOSXcn9GVV5RdmfAqvQzeQgE3XmfWMUf656bKh6-ud53b4KbwO3UACNl3nTZMU2s9kKTK6pQlkYJwFXct-Aft3KNhzUTWD39s7EV-HcCAFm7DrjgnBBLutpyszc0zNwq7BvwDqzwJUpiGL0s8ZT-VXIX4eKjZ_Vw6IKTWnXnl0Vv_BxUvBhPnpA3wivgrvUAo2YVcm-B4tYomzc0Z0H-Gxb0A3lV-HcSAHm7ErG8yBNLqxCRIxTz7320WMKameCWP14BlmTOVXYRzKwWac2ymjy3W_5_WQmtgRZSZFMneKy-4pYoPD7C9fhwcLr6Nq9jz-1WHgozh9GeVuMMOogXBcnJcBsUyZOjOgyWhD8RbVi6j6o4ertuA6OL3napNzZjd7A4MxNR-L6IOvRa-pbIX47-gXBrbo6iznzN4m68xs9nWn3BQJMYHRovvKb6ubi54U5Qx38N7gEpgHcrEt4Mx-9nsaswAUZyG76gRhV5Gm29bft9RqKMt1YXpP0cYH7QrXSR-mBJjC3DdAgJnJqvB4tAgcw-90n922ECW_IWV_cu-c5g3uCejUxm0h_QG1yXwOr4MvEf83_wyTPpEgHZuobRZXgK5Wi-P7p-5DSXfD6scjyB7bemhOMBtlVofmNVEbGbYrnGmkxHB0QyY3TXL9DQWOJF8WCwIR6dAHyMTckUOMTKUM8fqkKR5e4ruhKWvCBT9-GOASnmG-QBrOO7QwApbRgP3lUapC_M3b_4NBlrY:1vNnsc:A97uezglXSc0c9SooQ51Ya2whoGFmD_LkLGlbrTqyKk 2025-12-09 07:54:30.634534+00
|
||||
9dk1ipkwk83y8dt19pgoqea4is9ysvl3 .eJzNmklz6ygQgP9KyucsWkCgd5z7HOc0nkqxxkpkSaMlrzKv8t8HUBLLBNsS0iv5YlyoaeBrlm7g1-aRdO3usWtE_ZjxzY9NuLkd5lHCXkShP_BnUjyV96ws2jqj91rk_uNrc_9nyUX-x4fskYIdaXaqtMAJS3BCE5GwNEEcU4JiAKWIExamMopTiGIMWRLTKEUqFSKOg5QkOBA8jKBWuhdF1yhdf__abgqyF9vNj5vtZrvtgAiJSmBEQpUkJNIJRBBtN7dKIlNt7mUlqW8kuWNZzXLRf9zrljf68ze1aciQSjCPuNYXQzhCX1fn_bcHwvdZ8UC7JitE0zxUXc0UDFHWXNQPvTDh_K9x8kryowytBeGs7vbU3WpfGO-3N7MBvP-jP4uM669hEETjlKpmJoFQCRIsWYxx1or9VM6mzLWwnoLFJh_bGaGjTiAkHTSfA50gEtB58-aYCqQy1R1jSehh2qYlrZB5-fPLYCV9Fqw9bdiTJaaY1Y-LE7EfC9t80KEbcqEHCY6QqSJmkW4tRMEszuZfRWpVU3tuoTpZYhXOvixszolD97BhKpFhojPjNJrFuapLptefEYA_RVch69t7myy6qBtHWFsNBTSdP4JHD9xroDq-5zZVPEm3anuIYp0ZQLDE6C3ULjBhBBvxK-M9hYlNP3XUhCJqZojacg_1Ypkwa11SmZTqBEovx9Iaxnn59LUE14Ipb2b0DHAUXcVGv4OcZbEwmFjvojbS_ybaZlDk6mzib4XQnkjAtaXHxtYqWNSzE1HTGQSCMRHCGe_0WG1CY2H6EMjZYUhVZ0WbFU9ff55LetrQTunxNvalcxG0LxHbxvHFmjAl0dY7sP7G70JgfUL-CoiP52Azdk4bGQRm4gK8IONcrUWjAR-E16HrScCmC-0M13EGRFJHHAgDszFxAMw2lY5xYc_G0EQfB0DADAeG-gU9GhOafzseIU3GHrNCltoP4x1rmTKQGv9vZ45HTpeZYFNPNs442o-HbUJXFHKse5oJL7Oewvga2E5QZbF1xSJAmPkMRRovyfYnqcWu7JozwYdTeiW-fgxsvq5oA8iU9rq1UgiB1_npAVXTVVWenT2gdgivg3Vq1y2ekSsWgLHEZhuKSe_oGu-XRj5nlwNSr2KXKbm2JkVTlXV7KQq4XHQV5L50bPKuw2gIoNb9eZAXAGZsDPhgvUd45v4n9lVevgnRvlXjlo6jAuswX4SLbQGnK3Nc01Jr9SfCSbyvgrXvSh05Yx-9QCltUYyWpLsX-vqqGOdmfAmvQ9eTgE3XGfX0Xv7SdFnXtOV-5Hb4JbwOXU8CNl3nTZMUep1JYmR0S-PIwDDxuhA_APu3y9hLVlTduNE7EF-HsCcFm7DrjgnBCLutpzMTc0zN_B4gHADqBxkq0pBZPm4xHsqvQnwZKjZ_VwyIKTWnXmmw6PrBxWvGhPk7BvhAfBXevhRswq5I8MNbxBInS3p0n-7xWIduKL8OY08ONmNXNJgCaXRj4yRiHn3tt7MYU1K8EMbKbqSbMZRfhbEvB4txHNghuGvp_ojrITW-I0pMiGTuFOfdU4QGh9lfDocHM6-jSvbS_2o38ElcvoxyF5hgVE84Ls7zgNi2dUZAg9b64s2KV1G0Zw9XbcF1cI7uq03OGd0cNQyG1DwW0Qdfs4aprIX47-wLA1t0dZZTem-TdUY2x7pjbpKIGMdo1n3lt9nNRUuyfMJy8FHgGph7crEt4Ix-jmvqowAUJj676gBhU5Cq2ZXft9Siy_N1YY7uoo0vsTNcJ32YEmASc98AAWYmqsL90SJwNL_RdTa7TOT8juTtxb1zGDe4O6BDG7eF9NN1E_mcngcHkfEjf4FOXwiQznXUNovLQVezxfH-qflU0tyx8ukMsqe67KoLzHqZ1aGN6qiNLLUznGGkxLBfhkxsGqX6DQUOJJ_nCwIRaNcHyMjckUOMTKb0WfVJlT2-hg9dlZeEC37-MMAlPMF8njScd2h-BCyjAfvlEVAu_ub9f9O_D4c:1vOXQZ:LJRQQAEQPL57SgmCuYg0323wuqRZN7iv5lWKX4fiM9E 2025-12-11 08:32:35.073302+00
|
||||
ntf30dd3of18gsyba1risn3yscoj0t6y .eJzNmktz4ygQgP9Kyuck1guB5rj3Pe5pPZXiaSuRJa0emcpO5b8vIMeRCbER8pZ8CQ40DXzNoxv0e_WE-2731Le8ecrZ6scqXN2P8wimL7xUBewZl9vqkVZl1-TkUYk8Hkrbxz8rxos_DrInCna43cnaHKU0RSlJeUqzFDJEMIwTIHic0jATUZwBGCNA05hEGZQp53EcZDhFAWdhBJTSPS_7Vur6-_dmVeI936x-3G1Wm02fcEFkAiIcyiRliUogDshmdS8lctnnQVbg5k7gB5o3tOBD4V71vFXFFrUh_lQLiMhkgmgaOqjtm2IoW2O2z8t12-GOi6L6tSZ9m5e8bSvyzGm3HsQxY3-51pCyh1qk4ZjRpt-T7wbgx-X9_u5aLN5_qmKeM1UaBkFk0Q0YB0pbBHUTMY1UbwEMZnHWv2rcyJY63rhwNmoswtmXhck5tuged0wmIkxVZpxFszjXTUXl5HQB_CG6CFnf0Ztkk4u6UYSU1WBAsvkz2Hni3gJV95GbVMEk3bLvIYxVZgCSa8zeUp4CE2awFr8x3lOYmPRTS0swInqFBMGo00ik1NiXZCYhKgECgPnzvai2xy244bRqmPMKsFRdxEb_BznTYnBiu1e1kfo10TajKjdnE38rIDMjtB3psba1dHrV6oREDwYmQTrPOz1Vm5KY6zEEQv2XBuo_yKlLI6a16yYvu7zcHn88V-R7Q1ul3W3sS-ciaF8ihknD4GJLMpaJdKbX2vrCT66Rc_7qN_I3QNydg8nYumxEEOiFm6ArMi7kXuQM-FN4GbqeBEy6kblLZbbGoFARB0SJPphYkuhjKnNxYc_G0Bgq7QnVHCgcNvTIJTQ3rUhwm9OnvBSV8sNYTzsqDSTn_9v3xjxTZ4JNPdlY42g_HqZNbVHIqe5pJrzMegrjW2A7QZXB1haLJFyvZ8Cz-Jpsf-GG76q-PRN8WKUX4uvHwORrizYSkZFBt1IKQOLjuIxQtX1dF_m5bd4mvAzWqUM3edpiARALpI-hGA-OrvZ-SeRzdzki9cp3uZTrGly2ddV0l6KAy1UXQe5LxySPbLoToHR_XORda6fg-7qo3rjbRnEUXoauJwGTrtVNUctDaotieE26e97QHS7dDrmj8DJ0PQkYdCNrXDP4mNemS_u2q_aOm_FReBm6ngRMuvaIhquNPo2h1i30MQrC1CeiGQH7p8_pS17WvdvsHYkvQ9iTgknY9pKECNExfxZcdf4y_ppTrn-6EB6JL0LYl4JJ2PaGdDgrkUDpKH6BaGY89-EcvNVuB9xYfhnGnhxMxrY4LkuE1o0SfTvJouN-P4sxweULprTqHY-5sfwijH05mIyB6VjYJvYhqgFE-y4w1Q6iflGZd0sbahx6f_sMnWZexlf0Zfir3JAtv3wVb68wwaiecGyc5wExbWuNfUa99cWbl6-87M5eLZmCy-B0HqtJzhq7nHQMhEQ_lauwf9Y0FQ3n_559XzVFF2c5ZfQmWWvccqo7ZjqJsA5DZ73WfFndjHc4LyZsB4cKt8Dck4thgdga25y2NHihMEx9TtURwrbEdburvh6pZV8Uy8J0HqKJzxa8ZCEdvMmIbbzfYQZuda_i5_bSO4xNeJE5OnnoJk_zASay3YAighOd6NtrkCCqoyQ0XFQlln63qs12l_OCPeCiu-iLQBCh8x6U-mLTbjn1QedaFX9vr08RdytdYdC2x37HgZp2sjnjcvexfE3TfihpH2i1PYNs21R9fYHZILM4NKeBmshM3zq2B40IDNu6_kwpytSLPAoEm_u2GChXMhGRfnEFCOpM4XOK4jp_eg3XfV1UmHEm8uKMv2ITnmA-TxrWFxk_AqbRoJmRvv9cvf8He7uCiw:1vLId5:n8_xdalAfSPyiCi7SJPZLr1CI1IGxTC-WpvvN_z9NHQ 2025-12-02 10:08:07.630045+00
|
||||
8g6qqs5ssg2zi3dlvoj8dmvskek2nues .eJzNmktz4ygQgP9Kyuck1guB5rj3Pe5pPZXiaSuRJa0emcpO5b8vIMeRCbER8pZ8CQ40DXzNoxv0e_WE-2731Le8ecrZ6scqXN2P8wimL7xUBewZl9vqkVZl1-TkUYk8Hkrbxz8rxos_DrInCna43cnaHKU0RSlJeUqzFDJEMIwTIHic0jATUZwBGCNA05hEGZQp53EcZDhFAWdhBJTSPS_7Vur6-_dmVeI936x-3G1Wm02fcEFkAiIcyiRliUogDshmdS8lctnnQVbg5k7gB5o3tOBD4V71vFXFFrUh_lQLiMhkgmgaOqjtm2IoW2O2z8t12-GOi6L6tSZ9m5e8bSvyzGm3HsQxY3-51pCyh1qk4ZjRpt-T7wbgx-X9_u5aLN5_qmKeM1UaBkFk0Q0YB0pbBHUTMY1UbwEMZnHWv2rcyJY63rhwNmoswtmXhck5tuged0wmIkxVZpxFszjXTUXl5HQB_CG6CFnf0Ztkk4u6UYSU1WBAsvkz2Hni3gJV95GbVMEk3bLvIYxVZgCSa8zeUp4CE2awFr8x3lOYmPRTS0swInqFBMGo00ik1NiXZCYhKgECgPnzvai2xy244bRqmPMKsFRdxEb_BznTYnBiu1e1kfo10TajKjdnE38rIDMjtB3psba1dHrV6oREDwYmQTrPOz1Vm5KY6zEEQv2XBuo_yKlLI6a16yYvu7zcHn88V-R7Q1ul3W3sS-ciaF8ihknD4GJLMpaJdKbX2vrCT66Rc_7qN_I3QNydg8nYumxEEOiFm6ArMi7kXuQM-FN4GbqeBEy6kblLZbbGoFARB0SJPphYkuhjKnNxYc_G0Bgq7QnVHCgcNvTIJTQ3rUhwm9OnvBSV8sNYTzsqDSTn_9v3xjxTZ4JNPdlY42g_HqZNbVHIqe5pJrzMegrjW2A7QZXB1haLJFyvZ8Cz-Jpsf-GG76q-PRN8WKUX4uvHwORrizYSkZFBt1IKQOLjuIxQtX1dF_m5bd4mvAzWqUM3edpiARALpI-hGA-OrvZ-SeRzdzki9cp3uZTrGly2ddV0l6KAy1UXQe5LxySPbLoToHR_XORda6fg-7qo3rjbRnEUXoauJwGTrtVNUctDaotieE26e97QHS7dDrmj8DJ0PQkYdCNrXDP4mNemS_u2q_aOm_FReBm6ngRMuvaIhquNPo2h1i30MQrC1CeiGQH7p8_pS17WvdvsHYkvQ9iTgknY9pKECNExfxZcdf4y_ppTrn-6EB6JL0LYl4JJ2PaGdDgrkUDpKH6BaGY89-EcvNVuB9xYfhnGnhxMxrY4LkuE1o0SfTvJouN-P4sxweULprTqHY-5sfwijH05mIyB6VjYJvYhqgFE-y4w1Q6iflGZd0sbahx6f_sMnWZexlf0Zfir3JAtv3wVb68wwaiecGyc5wExbWuNfUa99cWbl6-87M5eLZmCy-B0HqtJzhq7nHQMhEQ_lauwf9Y0FQ3n_559XzVFF2c5ZfQmWWvccqo7ZjqJsA5DZ73WfFndjHc4LyZsB4cKt8Dck4thgdga25y2NHihMEx9TtURwrbEdburvh6pZV8Uy8J0HqKJzxa8ZCEdvMmIbbzfYQZuda_i5_bSO4xNeJE5OnnoJk_zASay3YAighOd6NtrkCCqoyQ0XFQlln63qs12l_OCPeCiu-iLQBCh8x6U-mLTbjn1QedaFX9vr08RdytdYdC2x37HgZp2sjnjcvexfE3TfihpH2i1PYNs21R9fYHZILM4NKeBmshM3zq2B40IDNu6_kwpytSLPAoEm_u2GChXMhGRfnEFCOpM4XOK4jp_eg3XfV1UmHEm8uKMv2ITnmA-TxrWFxk_AqbRoJmRvv9cvf8He7uCiw:1vLYyA:AD0u5F-mNfMwOsOq-xELhPG46cKCAbcOeMXQfrwkvng 2025-12-03 03:34:58.965151+00
|
||||
\.
|
||||
@@ -2163,29 +2344,40 @@ ntf30dd3of18gsyba1risn3yscoj0t6y .eJzNmktz4ygQgP9Kyuck1guB5rj3Pe5pPZXiaSuRJa0emc
|
||||
-- Data for Name: printing_plateorder; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.printing_plateorder (id, created_at, updated_at, design_code, plate_type, plate_date, plate_method, plate_image, plate_notes, reprint_reason, urgency_level, area, default_address, is_mark_frame, drawing_rating, color_matching_rating, sample_rating, difficulty_rating, required_completion_date, completion_date, fabric, width, style_name, production_method, sample_meter, required_sample_meters, approval_result, is_ordered, customer_feedback, business_object_id, customer_id, merchandiser_id, salesperson_id, is_invalid, process, fabric_source, designer_id) FROM stdin;
|
||||
1 2025-11-18 09:55:35.334641+00 2025-11-18 09:55:35.334647+00 \N 首版 2025-11-17 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 1 3 \N \N f 1 \N \N
|
||||
2 2025-11-18 09:58:01.444122+00 2025-11-18 09:58:01.444129+00 123 首版 2025-11-17 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N 123123 \N \N \N \N \N \N f \N 2 3 \N \N f 1 \N \N
|
||||
3 2025-11-18 09:58:20.379338+00 2025-11-18 09:58:20.379345+00 123 复版 2025-11-17 16:00:00+00 \N 123 123 正常 213 \N f \N \N \N \N \N \N 123 123 \N \N \N \N \N f \N 3 3 \N 1 f 1 \N \N
|
||||
5 2025-11-18 10:10:04.816531+00 2025-11-18 10:10:04.816538+00 \N 首版 2025-11-17 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 6 3 \N \N f 2 \N \N
|
||||
6 2025-11-18 10:10:55.523408+00 2025-11-18 10:10:55.523419+00 \N 首版 2025-11-17 16:00:00+00 \N 123 \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 7 3 \N \N f 2 \N \N
|
||||
7 2025-11-18 10:35:25.877134+00 2025-11-18 10:35:25.877145+00 \N 首版 2025-11-17 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 9 4 \N \N f 2 \N \N
|
||||
8 2025-11-19 01:44:43.685246+00 2025-11-19 01:52:40.560325+00 \N 复版 2025-11-18 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 12 3 \N \N f 2 \N \N
|
||||
9 2025-11-19 02:00:49.484416+00 2025-11-19 02:00:49.484427+00 \N 首版 2025-11-18 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 13 3 \N \N f 2 \N \N
|
||||
4 2025-11-18 10:09:47.337719+00 2025-11-19 02:29:58.260464+00 \N 首版 2025-11-17 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 5 3 \N \N t 2 \N \N
|
||||
80004 2025-11-21 08:45:33.728595+00 2025-11-21 08:45:33.728604+00 \N 首版 2025-11-20 16:00:00+00 图片开版 \N \N 加急 中大 \N f 画图难度1 调色难度1 \N \N 2025-11-21 2025-11-21 16:00:00+00 四面弹 1.5米 \N 定位 \N \N \N f \N 27 3 1 1 f 2 仓库布 \N
|
||||
80005 2025-11-22 02:39:05.937858+00 2025-11-22 02:39:05.937868+00 \N 首版 2025-11-21 16:00:00+00 图片开版 \N \N 加急 \N f \N \N \N \N 2025-11-22 2025-11-22 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 28 3 \N \N f 2 仓库布 \N
|
||||
10 2025-11-19 02:00:59.58389+00 2025-11-19 03:54:21.995604+00 \N 复版 2025-11-18 16:00:00+00 \N plate_images/QQ浏览器截图20250609233935_XYYq9Lt.png \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 14 3 \N \N f 2 \N \N
|
||||
11 2025-11-19 02:04:48.932631+00 2025-11-19 09:27:56.024307+00 123123 首版 2025-11-18 16:00:00+00 样衣开版 plate_images/QQ浏览器截图20250609233935_ARgZcEm.png 123 123123 正常 123 213 f A A A \N 2025-11-19 \N 123 123 3123 32 \N 123.00 \N f \N 15 3 2 1 f 2 \N \N
|
||||
12 2025-11-19 09:28:09.459625+00 2025-11-19 09:28:09.459634+00 \N 复版 2025-11-18 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 19 3 \N \N f 2 \N \N
|
||||
80006 2025-11-23 13:11:02.777704+00 2025-11-23 13:11:02.777711+00 \N 修改单 2025-11-22 16:00:00+00 \N plate_images/5cab1e39605388041cf59ea15a2177c7.jpg 改颜色, \N 加急 中大 \N f \N \N \N \N \N 2025-11-23 16:00:00+00 四面弹 1.5米 \N 匹布 \N 3.00 \N f \N 29 5 2 2 f 2 仓库布 \N
|
||||
13 2025-11-19 09:28:40.840111+00 2025-11-19 09:42:26.990677+00 \N 首版 2025-11-18 16:00:00+00 样衣开版 plate_images/QQ浏览器截图20250609233935_y9vJ9LT.png 123 213 正常 312312 321 f A A A \N 2025-11-19 \N 3123 312 312 批布 \N 123.00 \N f \N 20 3 1 1 f 2 \N \N
|
||||
14 2025-11-20 01:52:49.284672+00 2025-11-20 01:52:49.284679+00 \N 首版 2025-11-19 16:00:00+00 \N \N \N 正常 123 f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 21 3 \N \N f 2 \N \N
|
||||
80000 2025-11-20 03:48:01.383018+00 2025-11-20 03:48:01.383027+00 \N 首版 2025-11-19 16:00:00+00 \N \N \N 正常 123 \N f \N \N \N \N \N \N 123 123 123 \N \N \N \N f \N 22 3 1 1 f 2 123 \N
|
||||
80001 2025-11-20 03:49:28.25281+00 2025-11-20 03:49:28.252817+00 \N 首版 2025-11-19 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 23 4 \N 1 f 2 \N \N
|
||||
80002 2025-11-20 03:49:37.949557+00 2025-11-20 03:49:37.949565+00 \N 首版 2025-11-19 16:00:00+00 \N \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 24 4 \N \N f 1 \N \N
|
||||
80003 2025-11-20 06:55:47.636958+00 2025-11-20 07:08:53.430169+00 80003 首版 2025-11-19 16:00:00+00 图片开版 plate_images/微信图片_2025-11-20_145534_377.png 123 123 加急 中大 \N f 画图难度1 调色难度1 套样难度1 难度1 2025-11-20 2025-11-19 16:00:00+00 四面弹 1.5米 \N 匹布 \N 123.00 待审批 f 123 25 3 1 1 f 2 仓库布 \N
|
||||
80007 2025-11-24 11:33:15.702111+00 2025-11-24 11:33:15.702118+00 \N 首版 2025-11-23 16:00:00+00 图片开版 \N \N 加急 周边 \N f \N \N \N \N 2025-11-24 2025-11-24 16:00:00+00 四面弹 1.5米 \N 匹布 \N 3.00 \N f \N 30 6 2 2 f 2 仓库布 \N
|
||||
COPY public.printing_plateorder (id, created_at, updated_at, design_code, plate_type, plate_date, plate_method, plate_image, plate_notes, reprint_reason, urgency_level, area, default_address, is_mark_frame, drawing_rating, color_matching_rating, sample_rating, difficulty_rating, required_completion_date, completion_date, fabric, width, style_name, production_method, sample_meter, required_sample_meters, approval_result, is_ordered, customer_feedback, business_object_id, customer_id, merchandiser_id, salesperson_id, is_invalid, process, fabric_source, designer_id, image_name, print_count) FROM stdin;
|
||||
80009 2025-11-27 08:08:49.408011+00 2025-11-27 08:08:49.408018+00 \N 首版 2025-11-26 16:00:00+00 图片开版 [] \N \N 加急 \N f \N \N \N \N 2025-11-27 00:00:00+00 2025-11-27 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 32 3 \N \N f 2 仓库布 \N image.png 0
|
||||
80008 2025-11-27 07:23:06.020811+00 2025-11-27 07:23:06.02082+00 \N 首版 2025-11-26 16:00:00+00 图片开版 [] \N \N 加急 \N f \N \N \N \N 2025-11-27 00:00:00+00 2025-11-27 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 31 3 \N \N f 2 仓库布 \N image.png 0
|
||||
80007 2025-11-24 11:33:15.702111+00 2025-11-24 11:33:15.702118+00 \N 首版 2025-11-23 16:00:00+00 图片开版 [] \N \N 加急 周边 \N f \N \N \N \N 2025-11-24 00:00:00+00 2025-11-24 16:00:00+00 四面弹 1.5米 \N 匹布 \N 3.00 \N f \N 30 6 2 2 f 2 仓库布 \N \N 0
|
||||
80006 2025-11-23 13:11:02.777704+00 2025-11-23 13:11:02.777711+00 \N 修改单 2025-11-22 16:00:00+00 \N [] 改颜色, \N 加急 中大 \N f \N \N \N \N \N 2025-11-23 16:00:00+00 四面弹 1.5米 \N 匹布 \N 3.00 \N f \N 29 5 2 2 f 2 仓库布 \N \N 0
|
||||
80005 2025-11-22 02:39:05.937858+00 2025-11-22 02:39:05.937868+00 \N 首版 2025-11-21 16:00:00+00 图片开版 [] \N \N 加急 \N f \N \N \N \N 2025-11-22 00:00:00+00 2025-11-22 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 28 3 \N \N f 2 仓库布 \N \N 0
|
||||
80004 2025-11-21 08:45:33.728595+00 2025-11-21 08:45:33.728604+00 \N 首版 2025-11-20 16:00:00+00 图片开版 [] \N \N 加急 中大 \N f 画图难度1 调色难度1 \N \N 2025-11-21 00:00:00+00 2025-11-21 16:00:00+00 四面弹 1.5米 \N 定位 \N \N \N f \N 27 3 1 1 f 2 仓库布 \N \N 0
|
||||
80003 2025-11-20 06:55:47.636958+00 2025-11-20 07:08:53.430169+00 80003 首版 2025-11-19 16:00:00+00 图片开版 [] 123 123 加急 中大 \N f 画图难度1 调色难度1 套样难度1 难度1 2025-11-20 00:00:00+00 2025-11-19 16:00:00+00 四面弹 1.5米 \N 匹布 \N 123.00 待审批 f 123 25 3 1 1 f 2 仓库布 \N \N 0
|
||||
80002 2025-11-20 03:49:37.949557+00 2025-11-20 03:49:37.949565+00 \N 首版 2025-11-19 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 24 4 \N \N f 1 \N \N \N 0
|
||||
80001 2025-11-20 03:49:28.25281+00 2025-11-20 03:49:28.252817+00 \N 首版 2025-11-19 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 23 4 \N 1 f 2 \N \N \N 0
|
||||
80000 2025-11-20 03:48:01.383018+00 2025-11-20 03:48:01.383027+00 \N 首版 2025-11-19 16:00:00+00 \N [] \N \N 正常 123 \N f \N \N \N \N \N \N 123 123 123 \N \N \N \N f \N 22 3 1 1 f 2 123 \N \N 0
|
||||
14 2025-11-20 01:52:49.284672+00 2025-11-20 01:52:49.284679+00 \N 首版 2025-11-19 16:00:00+00 \N [] \N \N 正常 123 f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 21 3 \N \N f 2 \N \N \N 0
|
||||
12 2025-11-19 09:28:09.459625+00 2025-11-19 09:28:09.459634+00 \N 复版 2025-11-18 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 19 3 \N \N f 2 \N \N \N 0
|
||||
11 2025-11-19 02:04:48.932631+00 2025-11-19 09:27:56.024307+00 123123 首版 2025-11-18 16:00:00+00 样衣开版 [] 123 123123 正常 123 213 f A A A \N 2025-11-19 00:00:00+00 \N 123 123 3123 32 \N 123.00 \N f \N 15 3 2 1 f 2 \N \N \N 0
|
||||
10 2025-11-19 02:00:59.58389+00 2025-11-19 03:54:21.995604+00 \N 复版 2025-11-18 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 14 3 \N \N f 2 \N \N \N 0
|
||||
9 2025-11-19 02:00:49.484416+00 2025-11-19 02:00:49.484427+00 \N 首版 2025-11-18 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 13 3 \N \N f 2 \N \N \N 0
|
||||
8 2025-11-19 01:44:43.685246+00 2025-11-19 01:52:40.560325+00 \N 复版 2025-11-18 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 12 3 \N \N f 2 \N \N \N 0
|
||||
7 2025-11-18 10:35:25.877134+00 2025-11-18 10:35:25.877145+00 \N 首版 2025-11-17 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 9 4 \N \N f 2 \N \N \N 0
|
||||
6 2025-11-18 10:10:55.523408+00 2025-11-18 10:10:55.523419+00 \N 首版 2025-11-17 16:00:00+00 \N [] 123 \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 7 3 \N \N f 2 \N \N \N 0
|
||||
5 2025-11-18 10:10:04.816531+00 2025-11-18 10:10:04.816538+00 \N 首版 2025-11-17 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 6 3 \N \N f 2 \N \N \N 0
|
||||
4 2025-11-18 10:09:47.337719+00 2025-11-19 02:29:58.260464+00 \N 首版 2025-11-17 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 5 3 \N \N t 2 \N \N \N 0
|
||||
3 2025-11-18 09:58:20.379338+00 2025-11-18 09:58:20.379345+00 123 复版 2025-11-17 16:00:00+00 \N [] 123 123 正常 213 \N f \N \N \N \N \N \N 123 123 \N \N \N \N \N f \N 3 3 \N 1 f 1 \N \N \N 0
|
||||
2 2025-11-18 09:58:01.444122+00 2025-11-18 09:58:01.444129+00 123 首版 2025-11-17 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N 123123 \N \N \N \N \N \N f \N 2 3 \N \N f 1 \N \N \N 0
|
||||
1 2025-11-18 09:55:35.334641+00 2025-11-18 09:55:35.334647+00 \N 首版 2025-11-17 16:00:00+00 \N [] \N \N 正常 \N f \N \N \N \N \N \N \N \N \N \N \N \N \N f \N 1 3 \N \N f 1 \N \N \N 0
|
||||
80012 2025-11-27 09:04:52.036529+00 2025-11-27 09:04:52.036539+00 \N 首版 2025-11-26 16:00:00+00 图片开版 [] \N \N 加急 \N f \N \N \N \N 2025-11-27 00:00:00+00 2025-11-27 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 35 4 \N \N f 2 仓库布 \N image.png 0
|
||||
80011 2025-11-27 09:01:25.141528+00 2025-11-27 09:01:25.141536+00 \N 首版 2025-11-26 16:00:00+00 图片开版 [] \N \N 加急 \N f \N \N \N \N 2025-11-27 00:00:00+00 2025-11-27 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 34 4 \N \N f 2 仓库布 \N image.png 0
|
||||
80010 2025-11-27 08:09:00.363084+00 2025-11-27 09:00:48.383782+00 80010 首版 2025-11-26 16:00:00+00 图片开版 [] \N \N 加急 \N f \N \N \N \N 2025-11-27 00:00:00+00 2025-11-27 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 33 4 \N \N f 2 仓库布 \N image.png 0
|
||||
13 2025-11-19 09:28:40.840111+00 2025-11-27 09:14:45.498603+00 13 首版 2025-11-18 16:00:00+00 样衣开版 [] 123 213 正常 312312 321 f A A A \N 2025-11-19 00:00:00+00 \N 3123 312 312 批布 \N 123.00 \N f \N 20 3 1 1 f 2 \N \N plate_image.png,image.png 0
|
||||
80013 2025-11-28 01:32:58.418934+00 2025-11-28 01:32:58.41894+00 \N 首版 2025-11-27 16:00:00+00 图片开版 [{"url": "http://t5510mjho.hn-bkt.clouddn.com/uploads/2025/11/28/669cb9d758ec4d1d812221634c0a8f0c.png", "name": "pasted_image_1764293575811.png", "path": "uploads/2025/11/28/669cb9d758ec4d1d812221634c0a8f0c.png", "size": 9562, "file_id": 3, "uploaded_at": "2025-11-28T01:32:56.717249+00:00", "content_type": "image/png"}] \N \N 加急 \N f \N \N \N \N 2025-11-28 00:00:00+00 2025-11-28 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 36 3 \N \N f 2 仓库布 \N image.png 0
|
||||
80014 2025-11-28 01:33:14.214559+00 2025-11-28 01:33:19.972906+00 80014 首版 2025-11-27 16:00:00+00 图片开版 [{"url": "http://t5510mjho.hn-bkt.clouddn.com/uploads/2025/11/28/7ceb226e55ff49f1ae19288bceb30043.png", "name": "pasted_image_1764293584299.png", "path": "uploads/2025/11/28/7ceb226e55ff49f1ae19288bceb30043.png", "size": 27946, "file_id": 4, "uploaded_at": "2025-11-28T01:33:05.201488+00:00", "content_type": "image/png"}, {"url": "http://t5510mjho.hn-bkt.clouddn.com/uploads/2025/11/28/d8476a09bc65428186de1fff70acc8cb.png", "name": "pasted_image_1764293586483.png", "path": "uploads/2025/11/28/d8476a09bc65428186de1fff70acc8cb.png", "size": 19360, "file_id": 5, "uploaded_at": "2025-11-28T01:33:07.382898+00:00", "content_type": "image/png"}, {"url": "http://t5510mjho.hn-bkt.clouddn.com/uploads/2025/11/28/114c7bdecf9547dd8d55c19d814fcbe5.png", "name": "pasted_image_1764293589617.png", "path": "uploads/2025/11/28/114c7bdecf9547dd8d55c19d814fcbe5.png", "size": 25338, "file_id": 6, "uploaded_at": "2025-11-28T01:33:10.519571+00:00", "content_type": "image/png"}] \N \N 加急 \N f \N \N \N \N 2025-11-28 00:00:00+00 2025-11-28 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 37 6 \N \N f 2 仓库布 \N image.png,image.png,image.png 0
|
||||
80015 2025-11-28 01:43:59.357314+00 2025-11-28 01:54:32.381904+00 80015 首版 2025-11-27 16:00:00+00 图片开版 [] 123123 1231 加急 中大 \N f 画图难度1 调色难度1 套样难度1 难度1 2025-11-28 00:00:00+00 2025-11-28 16:00:00+00 四面弹 1.5米 \N 匹布 \N 123.00 待审批 f 123 38 4 1 2 f 2 仓库布 \N 测试 0
|
||||
80016 2025-11-28 02:39:41.388679+00 2025-11-28 02:39:41.388688+00 \N 首版 2025-11-27 16:00:00+00 图片开版 [] \N \N 加急 \N f \N \N \N \N 2025-11-28 00:00:00+00 2025-11-28 16:00:00+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 39 4 \N \N f 2 仓库布 \N \N 0
|
||||
80017 2025-11-28 02:57:18.733054+00 2025-11-28 02:57:18.733063+00 \N 首版 2025-11-28 02:53:36+00 图片开版 [{"url": "http://t5510mjho.hn-bkt.clouddn.com/uploads/2025/11/28/ebb3a627a5ce4eaaabc6fc4d9cd15bc7.png", "name": "pasted_image_1764298419736.png", "path": "uploads/2025/11/28/ebb3a627a5ce4eaaabc6fc4d9cd15bc7.png", "size": 55897, "file_id": 9, "uploaded_at": "2025-11-28T02:53:41.308123+00:00", "content_type": "image/png"}] \N \N 加急 \N f \N \N \N \N 2025-11-28 00:00:00+00 2025-11-29 02:53:36+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 40 4 \N \N f 2 仓库布 \N image.png 0
|
||||
80018 2025-11-28 02:58:57.700327+00 2025-11-28 02:58:57.700336+00 \N 首版 2025-11-28 02:58:40+00 图片开版 [{"url": "http://t5510mjho.hn-bkt.clouddn.com/uploads/2025/11/28/afaea93815684e2c8c3ea17124a702ed.png", "name": "pasted_image_1764298729933.png", "path": "uploads/2025/11/28/afaea93815684e2c8c3ea17124a702ed.png", "size": 35906, "file_id": 10, "uploaded_at": "2025-11-28T02:58:51.677179+00:00", "content_type": "image/png"}] \N \N 加急 \N f \N \N \N \N 2025-11-28 02:58:40+00 2025-11-29 02:58:40+00 四面弹 1.5米 \N 匹布 \N \N \N f \N 41 3 2 1 f 2 仓库布 \N image.png 0
|
||||
\.
|
||||
|
||||
|
||||
@@ -2209,17 +2401,17 @@ COPY public.printing_printingjob (id, created_at, updated_at, quantity, unit, si
|
||||
-- Data for Name: printing_printingorder; Type: TABLE DATA; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COPY public.printing_printingorder (id, created_at, updated_at, fabric, width, is_urgent, area, address, fabric_source, is_fabric_received, craft, description, outgoing_date, curve, new_curve, "position", printing_warn, rolling_warn, production_warn, customer_id, is_invalid, process_id) FROM stdin;
|
||||
1 2025-11-18 10:03:03.512062+00 2025-11-18 10:03:03.512073+00 123214 123 f 中大 3123 f 4213 \N 2025-11-18 3213 321 123 312 312 321 3 f 1
|
||||
2 2025-11-18 10:15:58.06474+00 2025-11-18 10:15:58.064748+00 456 456 f 中大 456 f 456 \N 2025-11-18 645 645 6456 456 456 456 3 f 1
|
||||
3 2025-11-18 10:42:09.201812+00 2025-11-18 10:42:09.20182+00 123 3213123 f \N 123 f 23123 \N \N 123 213 123 123 123 213 3 f 1
|
||||
5 2025-11-18 10:42:48.781191+00 2025-11-18 16:29:16.270004+00 123 123 f \N \N t \N \N \N \N \N \N \N \N \N 4 f 1
|
||||
4 2025-11-18 10:42:09.764532+00 2025-11-18 16:29:38.034391+00 123 3213123 f \N 123 t 23123 \N \N 123 213 123 123 123 213 3 f 1
|
||||
6 2025-11-19 01:59:30.596405+00 2025-11-19 01:59:30.596416+00 123 23 f 中大 \N f \N \N 2025-11-19 \N \N \N \N \N \N 3 f 1
|
||||
7 2025-11-19 05:10:28.027246+00 2025-11-19 05:10:28.027259+00 四面弹 1.5米 f 中大 仓库布 f 批布 \N 2025-11-20 \N \N \N \N \N \N 3 f 1
|
||||
8 2025-11-19 06:16:00.11043+00 2025-11-19 06:16:00.110438+00 123 23 f 中大 \N f \N \N 2025-11-19 \N \N \N \N \N \N 3 f 1
|
||||
9 2025-11-19 08:21:50.42566+00 2025-11-19 08:21:50.425668+00 123 23 f 中大 \N f \N \N 2025-11-19 \N \N \N \N \N \N 3 f 1
|
||||
10 2025-11-21 03:10:14.707335+00 2025-11-21 03:10:14.707343+00 四面弹 1.5米 f 中大 仓库布 f 批布 \N 2025-11-22 测试 3124123 \N \N \N \N 3 f 1
|
||||
COPY public.printing_printingorder (id, created_at, updated_at, fabric, width, is_urgent, area, address, fabric_source, is_fabric_received, craft, description, outgoing_date, curve, new_curve, "position", printing_warn, rolling_warn, production_warn, customer_id, is_invalid, process_id, print_count) FROM stdin;
|
||||
1 2025-11-18 10:03:03.512062+00 2025-11-18 10:03:03.512073+00 123214 123 f 中大 3123 f 4213 \N 2025-11-18 3213 321 123 312 312 321 3 f 1 0
|
||||
2 2025-11-18 10:15:58.06474+00 2025-11-18 10:15:58.064748+00 456 456 f 中大 456 f 456 \N 2025-11-18 645 645 6456 456 456 456 3 f 1 0
|
||||
3 2025-11-18 10:42:09.201812+00 2025-11-18 10:42:09.20182+00 123 3213123 f \N 123 f 23123 \N \N 123 213 123 123 123 213 3 f 1 0
|
||||
5 2025-11-18 10:42:48.781191+00 2025-11-18 16:29:16.270004+00 123 123 f \N \N t \N \N \N \N \N \N \N \N \N 4 f 1 0
|
||||
4 2025-11-18 10:42:09.764532+00 2025-11-18 16:29:38.034391+00 123 3213123 f \N 123 t 23123 \N \N 123 213 123 123 123 213 3 f 1 0
|
||||
6 2025-11-19 01:59:30.596405+00 2025-11-19 01:59:30.596416+00 123 23 f 中大 \N f \N \N 2025-11-19 \N \N \N \N \N \N 3 f 1 0
|
||||
7 2025-11-19 05:10:28.027246+00 2025-11-19 05:10:28.027259+00 四面弹 1.5米 f 中大 仓库布 f 批布 \N 2025-11-20 \N \N \N \N \N \N 3 f 1 0
|
||||
8 2025-11-19 06:16:00.11043+00 2025-11-19 06:16:00.110438+00 123 23 f 中大 \N f \N \N 2025-11-19 \N \N \N \N \N \N 3 f 1 0
|
||||
9 2025-11-19 08:21:50.42566+00 2025-11-19 08:21:50.425668+00 123 23 f 中大 \N f \N \N 2025-11-19 \N \N \N \N \N \N 3 f 1 0
|
||||
10 2025-11-21 03:10:14.707335+00 2025-11-28 05:21:10.802605+00 四面弹 1.5米 f 中大 仓库布 t 批布 \N 2025-11-22 测试 3124123 \N \N \N \N 3 f 1 0
|
||||
\.
|
||||
|
||||
|
||||
@@ -2525,7 +2717,7 @@ COPY public.stateflow_stateparameter (id, created_at, updated_at, key, value, de
|
||||
COPY public.stock_inventory (created_at, updated_at, id, quantity, num_of_rolls, spec, description, merchant_id, product_id, warehouse_id) FROM stdin;
|
||||
2025-11-20 08:03:02.167396+00 2025-11-20 08:03:02.167403+00 2 25.00 1 \N \N 1 1 3
|
||||
2025-11-25 10:10:50.074909+00 2025-11-25 10:10:52.768415+00 3 -1602.00 -12 \N \N 1 2 2
|
||||
2025-11-20 07:54:45.864074+00 2025-11-25 10:13:09.762694+00 1 1712.00 10 \N \N 1 1 2
|
||||
2025-11-20 07:54:45.864074+00 2025-11-28 04:16:58.098231+00 1 1907.00 12 \N \N 1 1 2
|
||||
\.
|
||||
|
||||
|
||||
@@ -2561,6 +2753,14 @@ COPY public.stock_stockchangedetail (created_at, updated_at, id, quantity, unit,
|
||||
2025-11-25 09:21:02.981242+00 2025-11-25 09:21:02.981252+00 25 55.00 1 1 1 9 f \N
|
||||
2025-11-25 09:28:55.964056+00 2025-11-25 09:28:55.964062+00 26 22.00 1 1 1 10 f \N
|
||||
2025-11-25 10:12:41.752146+00 2025-11-25 10:12:59.642704+00 27 22.00 1 1 1 11 f 26
|
||||
2025-11-28 03:49:04.600109+00 2025-11-28 03:49:04.600114+00 30 97.00 1 1 1 14 f \N
|
||||
2025-11-28 03:49:04.600598+00 2025-11-28 03:49:04.600602+00 31 98.00 1 1 1 14 f \N
|
||||
2025-11-28 06:18:30.986094+00 2025-11-28 06:18:30.9861+00 32 97.00 1 1 1 15 f \N
|
||||
2025-11-28 06:18:30.986555+00 2025-11-28 06:18:30.986558+00 33 98.00 1 1 1 15 f \N
|
||||
2025-11-28 06:18:54.739337+00 2025-11-28 06:18:54.739342+00 34 97.00 1 1 1 16 f \N
|
||||
2025-11-28 06:18:54.739784+00 2025-11-28 06:18:54.739787+00 35 98.00 1 1 1 16 f \N
|
||||
2025-11-28 06:18:54.739967+00 2025-11-28 06:18:54.73997+00 36 97.00 1 1 1 16 f \N
|
||||
2025-11-28 06:18:54.740122+00 2025-11-28 06:18:54.740125+00 37 94.00 1 1 1 16 f \N
|
||||
\.
|
||||
|
||||
|
||||
@@ -2579,6 +2779,9 @@ COPY public.stock_stockchangerecord (created_at, updated_at, id, type, source_ty
|
||||
2025-11-25 08:49:45.154243+00 2025-11-25 10:10:50.088642+00 7 2 6 \N t 2025-11-25 10:10:50.087782+00 \N 2 1 2
|
||||
2025-11-25 08:49:42.446213+00 2025-11-25 10:10:52.770383+00 6 2 6 \N t 2025-11-25 10:10:52.769618+00 \N 2 1 2
|
||||
2025-11-25 10:12:01.37053+00 2025-11-25 10:13:09.76617+00 11 2 9 \N t 2025-11-25 10:13:09.765158+00 1 1 2
|
||||
2025-11-28 03:49:04.598896+00 2025-11-28 04:16:58.100963+00 14 1 1 9 t 2025-11-28 04:16:58.099857+00 \N 2 1 2
|
||||
2025-11-28 06:18:30.984995+00 2025-11-28 06:18:30.985004+00 15 1 1 10 f \N \N 2 1 3
|
||||
2025-11-28 06:18:54.73818+00 2025-11-28 06:18:54.73819+00 16 1 1 11 f \N \N 2 1 4
|
||||
\.
|
||||
|
||||
|
||||
@@ -2622,6 +2825,8 @@ COPY public.stock_stocksnapshot (created_at, updated_at, id, delta, quantity_bef
|
||||
2025-11-25 10:10:52.76742+00 2025-11-25 10:10:52.767424+00 25 -78.00 -1437.00 -1515.00 1 -12 \N \N f \N \N 1 2 6 2
|
||||
2025-11-25 10:10:52.769042+00 2025-11-25 10:10:52.769047+00 26 -87.00 -1515.00 -1602.00 1 -13 \N \N f \N \N 1 2 6 2
|
||||
2025-11-25 10:13:09.763497+00 2025-11-25 10:13:09.763502+00 27 -22.00 1734.00 1712.00 1 9 \N \N f \N \N 1 1 11 2
|
||||
2025-11-28 04:16:58.096138+00 2025-11-28 04:16:58.096144+00 28 97.00 1712.00 1809.00 1 12 \N \N f \N \N 1 1 14 2
|
||||
2025-11-28 04:16:58.099209+00 2025-11-28 04:16:58.099214+00 29 98.00 1809.00 1907.00 1 13 \N \N f \N \N 1 1 14 2
|
||||
\.
|
||||
|
||||
|
||||
@@ -2629,7 +2834,7 @@ COPY public.stock_stocksnapshot (created_at, updated_at, id, delta, quantity_bef
|
||||
-- Name: api_uploaded_file_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.api_uploaded_file_id_seq', 1, true);
|
||||
SELECT pg_catalog.setval('public.api_uploaded_file_id_seq', 10, true);
|
||||
|
||||
|
||||
--
|
||||
@@ -2650,7 +2855,7 @@ SELECT pg_catalog.setval('public.auth_group_permissions_id_seq', 1, false);
|
||||
-- Name: auth_permission_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.auth_permission_id_seq', 157, true);
|
||||
SELECT pg_catalog.setval('public.auth_permission_id_seq', 165, true);
|
||||
|
||||
|
||||
--
|
||||
@@ -2723,6 +2928,13 @@ SELECT pg_catalog.setval('public.basic_info_employeetype_id_seq', 7, true);
|
||||
SELECT pg_catalog.setval('public.basic_info_merchant_id_seq', 3, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_merchantsetting_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.basic_info_merchantsetting_id_seq', 1, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_product_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -2779,32 +2991,39 @@ SELECT pg_catalog.setval('public.basic_info_vehicletype_id_seq', 1, false);
|
||||
SELECT pg_catalog.setval('public.basic_info_warehouse_id_seq', 4, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorderitem_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.business_purchaseorderitem_id_seq', 8, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: django_admin_log_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.django_admin_log_id_seq', 99, true);
|
||||
SELECT pg_catalog.setval('public.django_admin_log_id_seq', 116, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: django_content_type_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.django_content_type_id_seq', 38, true);
|
||||
SELECT pg_catalog.setval('public.django_content_type_id_seq', 40, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: django_migrations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.django_migrations_id_seq', 76, true);
|
||||
SELECT pg_catalog.setval('public.django_migrations_id_seq', 91, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: printing_plateorder_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.printing_plateorder_id_seq', 80007, true);
|
||||
SELECT pg_catalog.setval('public.printing_plateorder_id_seq', 80018, true);
|
||||
|
||||
|
||||
--
|
||||
@@ -2832,7 +3051,7 @@ SELECT pg_catalog.setval('public.state_log_parameter_record_id_seq', 9, true);
|
||||
-- Name: stateflow_order_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.stateflow_order_id_seq', 30, true);
|
||||
SELECT pg_catalog.setval('public.stateflow_order_id_seq', 41, true);
|
||||
|
||||
|
||||
--
|
||||
@@ -2888,21 +3107,21 @@ SELECT pg_catalog.setval('public.stock_inventory_id_seq', 3, true);
|
||||
-- Name: stock_purchaseorder_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.stock_purchaseorder_id_seq', 1, false);
|
||||
SELECT pg_catalog.setval('public.stock_purchaseorder_id_seq', 12, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stock_stockchangedetail_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.stock_stockchangedetail_id_seq', 27, true);
|
||||
SELECT pg_catalog.setval('public.stock_stockchangedetail_id_seq', 37, true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: stock_stockchangerecord_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.stock_stockchangerecord_id_seq', 11, true);
|
||||
SELECT pg_catalog.setval('public.stock_stockchangerecord_id_seq', 16, true);
|
||||
|
||||
|
||||
--
|
||||
@@ -2916,7 +3135,7 @@ SELECT pg_catalog.setval('public.stock_stockfreeze_id_seq', 1, false);
|
||||
-- Name: stock_stocksnapshot_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
SELECT pg_catalog.setval('public.stock_stocksnapshot_id_seq', 27, true);
|
||||
SELECT pg_catalog.setval('public.stock_stocksnapshot_id_seq', 29, true);
|
||||
|
||||
|
||||
--
|
||||
@@ -3103,6 +3322,14 @@ ALTER TABLE ONLY public.basic_info_merchant
|
||||
ADD CONSTRAINT basic_info_merchant_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_merchantsetting basic_info_merchantsetting_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.basic_info_merchantsetting
|
||||
ADD CONSTRAINT basic_info_merchantsetting_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_product basic_info_product_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -3183,6 +3410,14 @@ ALTER TABLE ONLY public.basic_info_warehouse
|
||||
ADD CONSTRAINT basic_info_warehouse_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorderitem business_purchaseorderitem_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.business_purchaseorderitem
|
||||
ADD CONSTRAINT business_purchaseorderitem_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: django_admin_log django_admin_log_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -3548,6 +3783,13 @@ CREATE INDEX basic_info_employee_position_id_0f790d25 ON public.basic_info_emplo
|
||||
CREATE INDEX basic_info_employeetype_merchant_id_cf26fc93 ON public.basic_info_employeetype USING btree (merchant_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_merchantsetting_merchant_id_4ec5ec48; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE INDEX basic_info_merchantsetting_merchant_id_4ec5ec48 ON public.basic_info_merchantsetting USING btree (merchant_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_product_category_id_2bbbe6be; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -3618,6 +3860,34 @@ CREATE INDEX basic_info_warehouse_merchant_id_d5bb4b76 ON public.basic_info_ware
|
||||
CREATE INDEX business_object_content_type_id_94dccf29 ON public.business_object USING btree (content_type_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorder_operator_id_759fe141; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE INDEX business_purchaseorder_operator_id_759fe141 ON public.business_purchaseorder USING btree (operator_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorder_warehouse_id_7aad4bbe; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE INDEX business_purchaseorder_warehouse_id_7aad4bbe ON public.business_purchaseorder USING btree (warehouse_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorderitem_product_id_1cd637ce; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE INDEX business_purchaseorderitem_product_id_1cd637ce ON public.business_purchaseorderitem USING btree (product_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorderitem_purchase_order_id_329d998b; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE INDEX business_purchaseorderitem_purchase_order_id_329d998b ON public.business_purchaseorderitem USING btree (purchase_order_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: django_admin_log_content_type_id_c4bce8eb; Type: INDEX; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -4077,6 +4347,14 @@ ALTER TABLE ONLY public.basic_info_employeetype
|
||||
ADD CONSTRAINT basic_info_employeet_merchant_id_cf26fc93_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_merchantsetting basic_info_merchants_merchant_id_4ec5ec48_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.basic_info_merchantsetting
|
||||
ADD CONSTRAINT basic_info_merchants_merchant_id_4ec5ec48_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: basic_info_product basic_info_product_category_id_2bbbe6be_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -4165,6 +4443,38 @@ ALTER TABLE ONLY public.business_object
|
||||
ADD CONSTRAINT business_object_content_type_id_94dccf29_fk_django_co FOREIGN KEY (content_type_id) REFERENCES public.django_content_type(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorder business_purchaseord_operator_id_759fe141_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.business_purchaseorder
|
||||
ADD CONSTRAINT business_purchaseord_operator_id_759fe141_fk_basic_inf FOREIGN KEY (operator_id) REFERENCES public.basic_info_employee(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorderitem business_purchaseord_product_id_1cd637ce_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.business_purchaseorderitem
|
||||
ADD CONSTRAINT business_purchaseord_product_id_1cd637ce_fk_basic_inf FOREIGN KEY (product_id) REFERENCES public.basic_info_product(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorderitem business_purchaseord_purchase_order_id_329d998b_fk_business_; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.business_purchaseorderitem
|
||||
ADD CONSTRAINT business_purchaseord_purchase_order_id_329d998b_fk_business_ FOREIGN KEY (purchase_order_id) REFERENCES public.business_purchaseorder(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: business_purchaseorder business_purchaseord_warehouse_id_7aad4bbe_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY public.business_purchaseorder
|
||||
ADD CONSTRAINT business_purchaseord_warehouse_id_7aad4bbe_fk_basic_inf FOREIGN KEY (warehouse_id) REFERENCES public.basic_info_warehouse(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
|
||||
--
|
||||
-- Name: django_admin_log django_admin_log_content_type_id_c4bce8eb_fk_django_co; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
@@ -4521,5 +4831,5 @@ ALTER TABLE ONLY public.stock_stocksnapshot
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
\unrestrict lAaLqLBC97fkSperdxYl5fjzjf6qhnkvpVyPWEkZyQ37Mu4fIXbcWZBIFk58Sg7
|
||||
\unrestrict 7vxIoMQ6fViE6Ury13lyTyPcnDejMchx1YmoWE9b14f1V4IdfU6SzZ7uNzJes6K
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# PlateOrder 文件上传时返回 401 错误修复报告
|
||||
|
||||
> **更新(2025-11)**:`PlateOrder.plate_image` 现已改为 JSON 字段,仅接受通过 `/api/v1/upload/` 上传后的 `file_id` 引用,不再直接处理 multipart/form-data。以下内容保留旧问题的排查过程以供参考。
|
||||
|
||||
## 📋 问题描述
|
||||
|
||||
**症状:**
|
||||
|
||||
@@ -38,8 +38,8 @@ GET /api/v1/plate-orders/
|
||||
| style_name | string | 否 | 款式名称(模糊查询) |
|
||||
| plate_date_from | date | 否 | 开版日期起始(YYYY-MM-DD) |
|
||||
| plate_date_to | date | 否 | 开版日期结束(YYYY-MM-DD) |
|
||||
| required_completion_date_from | date | 否 | 要求完成日期起始 |
|
||||
| required_completion_date_to | date | 否 | 要求完成日期结束 |
|
||||
| required_completion_date_from | datetime | 否 | 要求完成时间起始(ISO8601,例:2025-11-20T08:00:00Z) |
|
||||
| required_completion_date_to | datetime | 否 | 要求完成时间结束(ISO8601) |
|
||||
| created_date_from | date | 否 | 创建日期起始 |
|
||||
| created_date_to | date | 否 | 创建日期结束 |
|
||||
| search | string | 否 | 全文搜索(搜索设计编号、款式名称、客户名称、面料) |
|
||||
@@ -75,7 +75,7 @@ GET /api/v1/plate-orders/
|
||||
"style_name": "花朵印染",
|
||||
"fabric": "棉布",
|
||||
"width": "150cm",
|
||||
"required_completion_date": "2025-11-20",
|
||||
"required_completion_date": "2025-11-20T18:00:00Z",
|
||||
"completion_date": null,
|
||||
"is_ordered": false,
|
||||
"process": 1,
|
||||
@@ -112,8 +112,24 @@ GET /api/v1/plate-orders/{id}/
|
||||
"plate_type": "圆网",
|
||||
"plate_date": "2025-11-10T10:00:00Z",
|
||||
"plate_method": "手工",
|
||||
"plate_image": "/media/plate_images/design001.jpg",
|
||||
"plate_image_url": "http://example.com/media/plate_images/design001.jpg",
|
||||
"plate_image": [
|
||||
{
|
||||
"file_id": 12,
|
||||
"name": "主图",
|
||||
"url": "https://example.com/media/uploads/2025/11/plate-a.jpg",
|
||||
"path": "uploads/2025/11/plate-a.jpg"
|
||||
},
|
||||
{
|
||||
"file_id": 13,
|
||||
"name": "细节图",
|
||||
"url": "https://example.com/media/uploads/2025/11/plate-b.jpg",
|
||||
"path": "uploads/2025/11/plate-b.jpg"
|
||||
}
|
||||
],
|
||||
"plate_image_url": [
|
||||
"https://example.com/media/uploads/2025/11/plate-a.jpg",
|
||||
"https://example.com/media/uploads/2025/11/plate-b.jpg"
|
||||
],
|
||||
"plate_notes": "注意色彩还原",
|
||||
"reprint_reason": null,
|
||||
"urgency_level": "加急",
|
||||
@@ -140,7 +156,7 @@ GET /api/v1/plate-orders/{id}/
|
||||
"difficulty_rating": "中等",
|
||||
"sample_meter": "5米",
|
||||
"required_sample_meters": 10.00,
|
||||
"required_completion_date": "2025-11-20",
|
||||
"required_completion_date": "2025-11-20T18:00:00Z",
|
||||
"completion_date": null,
|
||||
"approval_result": "通过",
|
||||
"is_ordered": false,
|
||||
@@ -181,7 +197,11 @@ Content-Type: application/json
|
||||
"salesperson": 3,
|
||||
"merchandiser": 4,
|
||||
"designer": 8,
|
||||
"required_completion_date": "2025-11-25",
|
||||
"plate_image": [
|
||||
{"file_id": 21, "name": "主图"},
|
||||
{"file_id": 22, "name": "细节图"}
|
||||
],
|
||||
"required_completion_date": "2025-11-25T06:30:00Z",
|
||||
"is_mark_frame": false,
|
||||
"plate_method": "机器",
|
||||
"production_method": "小批量",
|
||||
@@ -197,7 +217,7 @@ Content-Type: application/json
|
||||
- `plate_type`: 版型
|
||||
- `plate_date`: 开版日期
|
||||
- `plate_method`: 开版方式
|
||||
- `plate_image`: 开版图片(文件上传)
|
||||
- `plate_image`: 开版图片引用列表(数组,元素包含 `file_id` 与可选 `name`,需要先通过 `/api/v1/upload/` 上传文件)
|
||||
- `plate_notes`: 打版注意事项
|
||||
- `reprint_reason`: 复版原因
|
||||
- `urgency_level`: 紧急程度
|
||||
@@ -218,7 +238,7 @@ Content-Type: application/json
|
||||
- `difficulty_rating`: 难度评级
|
||||
- `sample_meter`: 米样
|
||||
- `required_sample_meters`: 所需样品米数
|
||||
- `required_completion_date`: 要求完成时间
|
||||
- `required_completion_date`: 要求完成时间(日期+具体时间,UTC 推荐)
|
||||
- `completion_date`: 完成时间
|
||||
- `approval_result`: 审批结果
|
||||
- `is_ordered`: 是否已下单
|
||||
@@ -576,7 +596,7 @@ GET /api/v1/plate-orders/{id}/timeline/
|
||||
| plate_type | string | 版型(首版/复版等) |
|
||||
| plate_date | datetime | 开版时间 |
|
||||
| plate_method | string | 开版方式 |
|
||||
| plate_image | file | 开版图片 |
|
||||
| plate_image | array<object> | 开版图片引用列表(通过 `UploadedFile` ID 关联) |
|
||||
| plate_notes | text | 打版注意事项 |
|
||||
| reprint_reason | text | 复版原因 |
|
||||
| urgency_level | string | 紧急程度 |
|
||||
@@ -599,7 +619,7 @@ GET /api/v1/plate-orders/{id}/timeline/
|
||||
| difficulty_rating | string | 难度评级 |
|
||||
| sample_meter | string | 米样 |
|
||||
| required_sample_meters | decimal | 所需样品米数 |
|
||||
| required_completion_date | date | 要求完成时间 |
|
||||
| required_completion_date | datetime | 要求完成时间(ISO8601 字符串) |
|
||||
| completion_date | datetime | 完成时间 |
|
||||
| approval_result | string | 审批结果 |
|
||||
| is_ordered | boolean | 是否已下单 |
|
||||
@@ -647,7 +667,7 @@ curl -X POST "http://api.example.com/api/v1/plate-orders/" \
|
||||
"style_name": "新款印染",
|
||||
"fabric": "纯棉",
|
||||
"urgency_level": "加急",
|
||||
"required_completion_date": "2025-11-20",
|
||||
"required_completion_date": "2025-11-20T18:00:00Z",
|
||||
"process": 1
|
||||
}'
|
||||
```
|
||||
@@ -709,7 +729,7 @@ curl -X GET "http://api.example.com/api/v1/plate-orders/1/timeline/" \
|
||||
2. **权限控制**: 作废和恢复操作需要特定权限
|
||||
3. **日期格式**: 所有日期字段使用 ISO 8601 格式(YYYY-MM-DD 或 YYYY-MM-DDTHH:MM:SSZ)
|
||||
4. **分页**: 列表接口默认支持分页,使用 `limit` 和 `offset` 参数控制
|
||||
5. **文件上传**: `plate_image` 字段需要使用 multipart/form-data 格式上传
|
||||
5. **文件引用**: 需先调用 `/api/v1/upload/` 上传文件并获取 `file_id`,再通过 `plate_image` 数组提交引用(无需 multipart)
|
||||
6. **自动创建流程实例**: 创建开版订单时,如果提供了 `process` 字段,系统会自动创建对应的 BusinessObject 流程实例。如果不提供则使用默认流程
|
||||
7. **流程验证**: 提供的 `process` ID 必须在系统中存在,否则会返回验证错误
|
||||
8. **流程操作**:
|
||||
|
||||
@@ -11,18 +11,17 @@
|
||||
```json
|
||||
{
|
||||
"supplier": 1,
|
||||
"warehouse": 2,
|
||||
"warehouse_id": 2,
|
||||
"order_date": "2025-11-26",
|
||||
"total_amount": "1200.50",
|
||||
"remarks": "测试采购单",
|
||||
"items": [
|
||||
{
|
||||
"product_id": 10,
|
||||
"quantities": ["10.5", "6.0"]
|
||||
},
|
||||
{
|
||||
"product_id": 18,
|
||||
"quantities": ["3.25"]
|
||||
"quantity": 120,
|
||||
"num_of_rolls": 3,
|
||||
"price": "12.50",
|
||||
"unit": "米",
|
||||
"empty_diff_percent": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -31,15 +30,23 @@
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| supplier | integer | ✅ | 供应商 ID,必须隶属于当前商户 |
|
||||
| warehouse | integer | ✅ | 入库仓库 ID |
|
||||
| warehouse / warehouse_id | integer | ✅ | 入库仓库 ID(两字段二选一,推荐 `warehouse_id`) |
|
||||
| order_date | string (date) | ✅ | 订单日期(`YYYY-MM-DD`) |
|
||||
| total_amount | string/number | ✅ | 采购总金额 |
|
||||
| remarks | string | 否 | 备注 |
|
||||
| items | array | ✅ | 入库明细,结构需满足 `StockFlowService` 的要求(目前仅支持严谨模式:product_id + quantities) |
|
||||
| items | array | ✅ | 入库明细,根据仓库模式提供不同字段 |
|
||||
|
||||
> `items` 内部字段会被直接传给 `StockFlowService`,因此:
|
||||
> - 严谨/严进模式:`{'product_id': 1, 'quantities': ['10', '5']}`
|
||||
> - 宽松模式/严进严出出库暂未开放,后续按需扩展。
|
||||
#### items 结构说明
|
||||
|
||||
- **宽进仓(UNRESTRICTED)**
|
||||
- 必填:`product_id`、`quantity`、`num_of_rolls`
|
||||
- `quantity_of_rolls` 会自动置空,并以 `{value, num_of_rolls}` 的形式传递给 `StockFlowService.stock_in` 的宽松模式。
|
||||
|
||||
- **严进仓(RESTRICT_IN / RESTRICT_IN_OUT)**
|
||||
- 必填:`product_id`、`numbers`(数组)
|
||||
- 后端会以数组长度设置 `num_of_rolls`,把所有数值拼为 `quantity_of_rolls="10,5,8"`,同时求和得到 `quantity`,并生成 `{'quantities': ['10','5','8']}` 传递给严谨模式。
|
||||
|
||||
公共可选字段:`price`、`unit`、`color`、`empty_diff_percent`、`batch_number`、`remarks`。缺省时默认使用 0 或产品单位。
|
||||
⚠️ 当仓库模式与 items 字段不匹配(例如严进仓缺少 `numbers`、宽进仓提供 `numbers`)时将返回 `400`,提示“仓库为 ×× 模式,items[n] 需要提供 …”。
|
||||
|
||||
### 响应
|
||||
|
||||
@@ -56,21 +63,20 @@
|
||||
|
||||
| 状态码 | 示例 | 说明 |
|
||||
|--------|------|------|
|
||||
| 400 | `{"error": "缺少供应商 ID"}` | 请求缺失关键字段 |
|
||||
| 400 | `{"error": "供应商 99 不存在"}` | 提供的供应商不属于当前商户 |
|
||||
| 400 | `{"error": "缺少仓库 ID"}` | 请求缺失关键字段 |
|
||||
| 400 | `{"error": "仓库为严进模式,items[0] 需要提供 numbers 数组"}` | 参数与仓库模式不匹配 |
|
||||
| 400 | `{"error": "供应商 99 不存在"}` | 供应商不属于当前商户 |
|
||||
| 403 | `{"error": "无权限访问"}` | 当前用户无员工信息 |
|
||||
|
||||
### 关联任务(business/tasks.py)
|
||||
|
||||
`create_purchase_order_stock_entries` 任务会接收 `purchase_order_id`、`warehouse_id`、`items` 等信息,并通过 `StockFlowService.stock_in` 创建入库记录。
|
||||
`create_purchase_order_stock_entries` 任务会接收 `purchase_order_id`、`warehouse_id`、已转换好的 `items` 信息,并通过 `StockFlowService.stock_in` 创建入库记录。
|
||||
|
||||
- 任务日志示例:`采购单 35 入库任务完成`
|
||||
- 返回 payload 包含 `stock_change_record_id`、`created_details_count`
|
||||
|
||||
### 测试
|
||||
|
||||
`api_v1/tests.py` 中新增 `PurchaseOrderAPITestCase`,通过 eager Celery 设置验证:
|
||||
|
||||
1. API 请求返回 201
|
||||
2. Celery 任务被成功调用(patch `create_purchase_order_stock_entries.delay` 断言参数)
|
||||
`api_v1/tests.py` 中的 `PurchaseOrderAPITestCase` 覆盖宽进/严进模式、模式不匹配和未登录场景;
|
||||
`business/tests.py` 中的 `PurchaseOrderServiceTestCase`、`PurchaseOrderStockTaskTestCase` 验证 service 层逻辑与 Celery 任务(通过 mock `StockFlowService`)。
|
||||
|
||||
|
||||
54
docs/print_count_delta.md
Normal file
54
docs/print_count_delta.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 打印次数增量 API
|
||||
|
||||
该接口用于为支持 `print_count` 字段的业务对象递增打印次数。目前支持:
|
||||
|
||||
- `printing_order` → `printing.models.PrintingOrder`
|
||||
- `plate_order` → `printing.models.PlateOrder`
|
||||
|
||||
未来可以在后端枚举中扩展更多对象类型(例如采购单等)。
|
||||
|
||||
## Endpoint
|
||||
|
||||
- **Method**: `POST`
|
||||
- **Path**: `/api/v1/print-count/delta/`
|
||||
- **Auth**: 需要登录(仅校验是否认证)
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
## 请求参数
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `object_type` | string | 是 | 对象类型,`printing_order` 或 `plate_order` |
|
||||
| `object_id` | integer | 是 | 目标对象的 ID |
|
||||
| `delta` | integer/string | 否 | 增量值,默认 1;若传入小于 1 或无法转换为整数,则自动回退为 1 |
|
||||
|
||||
## 响应
|
||||
|
||||
```json
|
||||
{
|
||||
"object_type": "printing_order",
|
||||
"object_id": 12,
|
||||
"delta": 3,
|
||||
"print_count": 5
|
||||
}
|
||||
```
|
||||
|
||||
字段说明:
|
||||
|
||||
- `delta`: 实际生效的增量(若请求非法会改为 1)
|
||||
- `print_count`: 更新后的打印次数
|
||||
|
||||
## 错误响应
|
||||
|
||||
| HTTP 状态码 | 场景 | 响应 |
|
||||
|-------------|------|------|
|
||||
| 400 | `object_type` 不受支持 | `{"detail": "不支持的对象类型"}` |
|
||||
| 404 | `object_id` 与 `object_type` 匹配的对象不存在 | `{"detail": "指定对象不存在"}` |
|
||||
| 401 | 未认证访问 | DRF 默认未认证响应 |
|
||||
|
||||
## 扩展说明
|
||||
|
||||
- `delta` 仅做加法,不提供回退逻辑。
|
||||
- 所有增量操作在数据库事务中执行,可避免并发更新冲突。
|
||||
- 新的对象类型只需在后端的 `PrintCountObjectType` 枚举和映射表中补充即可。
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| 调色评级 | ✅ | ❌ | **需添加** `color_matching_rating` | CharField(20) | 调色质量评级 |
|
||||
| 套样评级 | ✅ | ❌ | **需添加** `sample_rating` | CharField(20) | 套样质量评级 |
|
||||
| 难度评级 | ✅ | ❌ | **需添加** `difficulty_rating` | CharField(20) | 难度评级 |
|
||||
| 要求完成时间 | ✅ | ❌ | **需添加** `required_completion_date` | DateField | 要求完成日期 |
|
||||
| 要求完成时间 | ✅ | ❌ | **需添加** `required_completion_date` | DateTimeField | 要求完成日期+时间 |
|
||||
| 布料 | ✅ | ❌ | **需添加** `fabric` | CharField(100) | 布料信息 |
|
||||
| 幅宽 | ✅ | ❌ | **需添加** `width` | CharField(50) | 幅宽 |
|
||||
| 款号名称 | ✅ | ❌ | **需添加** `style_name` | CharField(100) | 款号名称 |
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-27 10:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0017_alter_plateorder_image_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='plateorder',
|
||||
name='plate_image',
|
||||
field=models.JSONField(blank=True, default=dict, help_text='存储多个开版图片的路径,格式: {"image_1": "/path/to/image1.jpg", "image_2": "/path/to/image2.jpg"}', verbose_name='开版图集合'),
|
||||
),
|
||||
]
|
||||
69
printing/migrations/0019_alter_plateorder_plate_image.py
Normal file
69
printing/migrations/0019_alter_plateorder_plate_image.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def _build_entry(value, name_hint=''):
|
||||
if not value:
|
||||
return None
|
||||
if isinstance(value, dict):
|
||||
entry = {}
|
||||
entry.update(value)
|
||||
# Ensure keys exist
|
||||
entry.setdefault('name', name_hint or entry.get('name') or '')
|
||||
if 'url' not in entry and 'path' in entry:
|
||||
entry['url'] = entry['path']
|
||||
elif 'path' not in entry and 'url' in entry:
|
||||
entry['path'] = entry['url']
|
||||
return entry
|
||||
if isinstance(value, str):
|
||||
return {
|
||||
'name': name_hint or value.split('/')[-1],
|
||||
'path': value,
|
||||
'url': value,
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
PlateOrder = apps.get_model('printing', 'PlateOrder')
|
||||
for obj in PlateOrder.objects.all():
|
||||
raw = obj.plate_image
|
||||
if not raw:
|
||||
obj.plate_image = []
|
||||
obj.save(update_fields=['plate_image'])
|
||||
continue
|
||||
|
||||
normalized = []
|
||||
if isinstance(raw, list):
|
||||
for entry in raw:
|
||||
built = _build_entry(entry)
|
||||
if built:
|
||||
normalized.append(built)
|
||||
elif isinstance(raw, dict):
|
||||
for key, value in raw.items():
|
||||
built = _build_entry(value, name_hint=str(key))
|
||||
if built:
|
||||
normalized.append(built)
|
||||
else:
|
||||
built = _build_entry(raw)
|
||||
if built:
|
||||
normalized.append(built)
|
||||
|
||||
obj.plate_image = normalized
|
||||
obj.save(update_fields=['plate_image'])
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0018_plateorder_plate_image_to_jsonfield'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='plateorder',
|
||||
name='plate_image',
|
||||
field=models.JSONField(blank=True, default=list, help_text='存储多个开版图片引用信息,例如 [{"file_id": 1, "name": "封面", "path": "/media/xxx"}]', verbose_name='开版图'),
|
||||
),
|
||||
migrations.RunPython(forwards, migrations.RunPython.noop),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-28 02:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0019_alter_plateorder_plate_image'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='plateorder',
|
||||
name='required_completion_date',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='要求完成时间'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-28 03:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('printing', '0020_alter_plateorder_required_completion_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='plateorder',
|
||||
name='print_count',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='打印次数'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='printingorder',
|
||||
name='print_count',
|
||||
field=models.PositiveIntegerField(default=0, verbose_name='打印次数'),
|
||||
),
|
||||
]
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from flower.common import ModelBase
|
||||
from basic_info import models as basic_models
|
||||
from stateflow import models as stateflow_models
|
||||
@@ -15,7 +15,12 @@ class PlateOrder(ModelBase):
|
||||
plate_type = models.CharField(max_length=20, blank=True, null=True, verbose_name='起版情况') # 首版/复版等
|
||||
plate_date = models.DateTimeField(null=True, blank=True, verbose_name='下版时间')
|
||||
plate_method = models.CharField(max_length=50, blank=True, null=True, verbose_name='开版方式')
|
||||
plate_image = models.FileField(upload_to='plate_images/', null=True, blank=True, verbose_name='开版图')
|
||||
plate_image = models.JSONField(
|
||||
default=list,
|
||||
blank=True,
|
||||
verbose_name='开版图',
|
||||
help_text='存储多个开版图片引用信息,例如 [{"file_id": 1, "name": "封面", "path": "/media/xxx"}]'
|
||||
)
|
||||
image_name = models.TextField(blank=True, null=True, verbose_name='图片名称')
|
||||
plate_notes = models.TextField(blank=True, null=True, verbose_name='打版注意事项')
|
||||
reprint_reason = models.TextField(blank=True, null=True, verbose_name='复版原因')
|
||||
@@ -68,7 +73,7 @@ class PlateOrder(ModelBase):
|
||||
difficulty_rating = models.CharField(max_length=20, blank=True, null=True, verbose_name='难度评级')
|
||||
|
||||
# 时间相关
|
||||
required_completion_date = models.DateField(null=True, blank=True, verbose_name='要求完成时间')
|
||||
required_completion_date = models.DateTimeField(null=True, blank=True, verbose_name='要求完成时间')
|
||||
completion_date = models.DateTimeField(null=True, blank=True, verbose_name='完成时间')
|
||||
|
||||
# 产品信息
|
||||
@@ -95,6 +100,7 @@ class PlateOrder(ModelBase):
|
||||
is_ordered = models.BooleanField(default=False, verbose_name='是否已下单')
|
||||
is_invalid = models.BooleanField(default=False, verbose_name='是否作废')
|
||||
customer_feedback = models.TextField(blank=True, null=True, verbose_name='客户修改意见')
|
||||
print_count = models.PositiveIntegerField(default=0, verbose_name='打印次数')
|
||||
|
||||
# 流程管理
|
||||
process = models.IntegerField(default=settings.PLATE_ORDER_DEFAULT_PROCESS_ID, verbose_name='关联流程')
|
||||
@@ -228,6 +234,7 @@ class PrintingOrder(ModelBase):
|
||||
printing_warn = models.TextField(blank=True, null=True, verbose_name='打印注意事项')
|
||||
rolling_warn = models.TextField(blank=True, null=True, verbose_name='滚筒注意事项')
|
||||
production_warn = models.TextField(blank=True, null=True, verbose_name='生产注意事项')
|
||||
print_count = models.PositiveIntegerField(default=0, verbose_name='打印次数')
|
||||
is_invalid = models.BooleanField(default=False, verbose_name='是否作废')
|
||||
process = models.ForeignKey(
|
||||
stateflow_models.Process,
|
||||
|
||||
@@ -414,12 +414,12 @@ paths:
|
||||
description: 开版日期结束。
|
||||
- in: query
|
||||
name: required_completion_date_from
|
||||
schema: {type: string, format: date}
|
||||
description: 要求完成日期起始。
|
||||
schema: {type: string, format: date-time}
|
||||
description: 要求完成时间起始(ISO8601)。
|
||||
- in: query
|
||||
name: required_completion_date_to
|
||||
schema: {type: string, format: date}
|
||||
description: 要求完成日期结束。
|
||||
schema: {type: string, format: date-time}
|
||||
description: 要求完成时间结束(ISO8601)。
|
||||
- in: query
|
||||
name: created_date_from
|
||||
schema: {type: string, format: date}
|
||||
@@ -713,10 +713,11 @@ components:
|
||||
type: string
|
||||
description: 开版方式。
|
||||
plate_image:
|
||||
type: string
|
||||
format: binary
|
||||
type: array
|
||||
nullable: true
|
||||
description: 开版图片(仅 multipart/form-data 时可上传)。
|
||||
description: 已上传开版图片引用列表(先调用 /api/v1/upload/ 获取 file_id)
|
||||
items:
|
||||
$ref: '#/components/schemas/PlateImageInput'
|
||||
image_name:
|
||||
type: string
|
||||
nullable: true
|
||||
@@ -784,9 +785,9 @@ components:
|
||||
description: 客户要求的米样米数。
|
||||
required_completion_date:
|
||||
type: string
|
||||
format: date
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: 要求完成日期。
|
||||
description: 要求完成时间(含具体时刻)。
|
||||
completion_date:
|
||||
type: string
|
||||
format: date-time
|
||||
@@ -806,6 +807,49 @@ components:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: 关联流程 ID,缺省时采用 settings.PLATE_ORDER_DEFAULT_PROCESS_ID。
|
||||
PlateImageInput:
|
||||
type: object
|
||||
required: [file_id]
|
||||
properties:
|
||||
file_id:
|
||||
type: integer
|
||||
description: 上传文件的 ID(来自 `/api/v1/upload/`)
|
||||
name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 可选名称,默认使用文件原始名称
|
||||
PlateImageItem:
|
||||
type: object
|
||||
properties:
|
||||
file_id:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: 上传文件 ID,若为外部链接可为空
|
||||
name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 图片名称或备注
|
||||
url:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 图片可访问 URL
|
||||
path:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 图片相对路径
|
||||
size:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: 文件大小(字节)
|
||||
content_type:
|
||||
type: string
|
||||
nullable: true
|
||||
description: 文件内容类型
|
||||
uploaded_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
description: 上传时间
|
||||
StateAdvanceRequest:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -102,7 +102,7 @@ class PlateOrderModelTestCase(TestCase):
|
||||
"""测试带时间信息的开版订单"""
|
||||
now = timezone.now()
|
||||
completion_date = now + timezone.timedelta(days=7)
|
||||
required_date = (now + timezone.timedelta(days=10)).date()
|
||||
required_date = now + timezone.timedelta(days=10)
|
||||
|
||||
plate_order = printing_models.PlateOrder.objects.create(
|
||||
design_code='DES004',
|
||||
|
||||
@@ -81,12 +81,18 @@ class StockChangeDetailAdmin(admin.ModelAdmin):
|
||||
'product',
|
||||
'quantity',
|
||||
'direction',
|
||||
'consume_with',
|
||||
'consumer',
|
||||
'unit',
|
||||
)
|
||||
search_fields = ('stock_change_record__id', 'product__name')
|
||||
list_filter = ('stock_change_record', 'stock_change_record__type')
|
||||
list_filter = ('stock_change_record', 'stock_change_record__type', 'is_consumed')
|
||||
ordering = ('-created_at',)
|
||||
|
||||
@admin.display(description='消耗者')
|
||||
def consumer(self, obj: models.StockChangeDetail):
|
||||
return obj.consumed_by_detail
|
||||
|
||||
@admin.display(description='出入方向')
|
||||
def direction(self, obj):
|
||||
return '入库' if obj.stock_change_record.is_incoming else '出库'
|
||||
|
||||
Reference in New Issue
Block a user