From a9c75a13faaed8b9e8e9d7c32246644b6a7ef07a Mon Sep 17 00:00:00 2001 From: colaftc Date: Wed, 26 Nov 2025 21:49:42 +0800 Subject: [PATCH] feat: big version, added tasks for backup_database and stock change, added health check api, approve sse (support channel via merchant) --- Dockerfile | 2 +- api_v1/__init__.py | 2 + api_v1/tasks.py | 126 + api_v1/tests.py | 144 +- api_v1/urls.py | 13 +- api_v1/views/healthy.py | 42 + api_v1/views/purchase_order.py | 72 + api_v1/views/stock_change_views/mixins.py | 1 + .../test_stock_change_api.py | 284 +- business/services.py | 86 + business/tasks.py | 56 + business/tests.py | 123 + business/views.py | 3 - data-bak/db-backup-20251126-124636.sql | 4525 +++++++++++++++++ docs/business_purchase.md | 76 + docs/celery_testing.md | 108 + docs/sse.md | 217 + api_v1/views/upload/API.md => docs/upload.md | 0 flower/celery.py | 1 - sse/README.md | 154 - sse/auth_utils.py | 117 + sse/client_example.js | 187 + sse/services.py | 143 +- sse/test_sse.py | 409 ++ sse/tests.py | 409 +- sse/views.py | 74 +- 26 files changed, 7158 insertions(+), 216 deletions(-) create mode 100644 api_v1/tasks.py create mode 100644 api_v1/views/healthy.py create mode 100644 api_v1/views/purchase_order.py create mode 100644 business/services.py create mode 100644 business/tasks.py delete mode 100644 business/views.py create mode 100644 data-bak/db-backup-20251126-124636.sql create mode 100644 docs/business_purchase.md create mode 100644 docs/celery_testing.md create mode 100644 docs/sse.md rename api_v1/views/upload/API.md => docs/upload.md (100%) delete mode 100644 sse/README.md create mode 100644 sse/auth_utils.py create mode 100644 sse/client_example.js create mode 100644 sse/test_sse.py diff --git a/Dockerfile b/Dockerfile index bf6a9d5..8cd41af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ WORKDIR /app RUN apt-get update \ - && apt-get install -y --no-install-recommends build-essential curl \ + && apt-get install -y --no-install-recommends build-essential curl postgresql-client \ && rm -rf /var/lib/apt/lists/* COPY pyproject.toml /app/ diff --git a/api_v1/__init__.py b/api_v1/__init__.py index e69de29..89c393d 100644 --- a/api_v1/__init__.py +++ b/api_v1/__init__.py @@ -0,0 +1,2 @@ +# 保持空模块,避免在 Django 应用加载期间触发视图导入 + diff --git a/api_v1/tasks.py b/api_v1/tasks.py new file mode 100644 index 0000000..cee1f4f --- /dev/null +++ b/api_v1/tasks.py @@ -0,0 +1,126 @@ +import logging +import os +import shutil +import subprocess +from pathlib import Path + +from celery import shared_task +from django.conf import settings +from django.utils import timezone + +from basic_info import models as basic_models + + +logger = logging.getLogger(__name__) + + +@shared_task(bind=True) +def ping_task(self, message: str = 'ping'): + """ + 最简单的心跳任务,用于验证 Celery worker 是否能够 + 正确消费队列并返回结果。 + """ + payload = { + 'task_id': self.request.id, + 'message': message, + 'timestamp': timezone.now().isoformat(), + } + logger.info('Celery ping_task 执行成功: %s', payload) + return payload + + +@shared_task(bind=True) +def merchant_product_count(self, merchant_id: int): + """ + 计算指定商户下的产品数量,用于演示如何在任务中访问数据库。 + """ + count = basic_models.Product.objects.filter(merchant_id=merchant_id).count() + payload = { + 'task_id': self.request.id, + 'merchant_id': merchant_id, + 'product_count': count, + 'calculated_at': timezone.now().isoformat(), + } + logger.info( + 'Celery merchant_product_count 统计完成: merchant=%s count=%s task=%s', + merchant_id, + count, + self.request.id, + ) + return payload + + +def _ensure_backup_dir(output_dir: str | None) -> Path: + base_dir = Path(settings.BASE_DIR) + backup_dir = Path(output_dir) if output_dir else (base_dir / 'data-bak') + backup_dir.mkdir(parents=True, exist_ok=True) + return backup_dir + + +def _build_backup_path(backup_dir: Path, filename_prefix: str) -> Path: + timestamp = timezone.now().strftime('%Y%m%d-%H%M%S') + return backup_dir / f'{filename_prefix}-{timestamp}.sql' + + +def _run_pg_dump(backup_path: Path): + db_settings = settings.DATABASES['default'] + pg_dump = shutil.which('pg_dump') + if not pg_dump: + raise RuntimeError('pg_dump 不存在,请确认 PostgreSQL 客户端工具已安装') + + host = db_settings.get('HOST') or 'localhost' + port = db_settings.get('PORT') or '5432' + user = db_settings.get('USER') or '' + name = db_settings['NAME'] + password = db_settings.get('PASSWORD') or '' + + cmd = [ + pg_dump, + '-h', + host, + '-p', + str(port), + '-U', + user, + '-F', + 'p', + '-d', + name, + ] + + env = os.environ.copy() + if password: + env['PGPASSWORD'] = password + + with backup_path.open('wb') as stream: + subprocess.run(cmd, check=True, stdout=stream, env=env) + + +def _dump_database_to_sql(backup_path: Path): + engine = settings.DATABASES['default']['ENGINE'] + if 'postgresql' not in engine: + raise NotImplementedError('当前项目只支持 PostgreSQL 数据库备份,请检查 DATABASES 配置') + _run_pg_dump(backup_path) + + +@shared_task(bind=True) +def backup_database(self, output_dir: str | None = None, filename_prefix: str = 'db-backup'): + """ + 备份当前数据库为 .sql 文件(仅数据,不包含表结构 DDL),存放在项目根目录 data-bak 下。 + + 参数: + output_dir: 可选,指定备份目录(默认 BASE_DIR/data-bak) + filename_prefix: 备份文件名前缀 + """ + backup_dir = _ensure_backup_dir(output_dir) + backup_path = _build_backup_path(backup_dir, filename_prefix) + _dump_database_to_sql(backup_path) + + payload = { + 'task_id': self.request.id, + 'backup_path': str(backup_path), + 'created_at': timezone.now().isoformat(), + } + logger.info('数据库备份完成: %s', payload) + return payload + diff --git a/api_v1/tests.py b/api_v1/tests.py index 76b4010..d116a98 100644 --- a/api_v1/tests.py +++ b/api_v1/tests.py @@ -1,8 +1,25 @@ -from django.test import TestCase +import shutil +import tempfile +from pathlib import Path +from unittest.mock import patch + +from django.test import TestCase, override_settings from django.contrib.auth.models import User, Permission from rest_framework.test import APIClient from rest_framework import status -from basic_info.models import Merchant, UserProfile +from basic_info.models import ( + Employee, + Merchant, + MerchantTypeEnum, + Product, + ProductCategory, + ProductUnitEnum, + Supplier, + UserProfile, + WareHouse, + WareHouseModeEnum, +) +from api_v1 import tasks class UserCreationAPITestCase(TestCase): @@ -110,3 +127,126 @@ class UserCreationAPITestCase(TestCase): response = self.client.post('/api/v1/users/create/', data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('password', response.data) + + +@override_settings( + CELERY_TASK_ALWAYS_EAGER=True, + CELERY_TASK_EAGER_PROPAGATES=True, +) +class PurchaseOrderAPITestCase(TestCase): + """采购单 API 测试""" + + 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( + merchant=self.merchant, + name='主仓', + mode=WareHouseModeEnum.RESTRICT_IN, + ) + category = ProductCategory.objects.create( + merchant=self.merchant, + name='品类', + product_prefix='FAB', + ) + self.product = Product.objects.create( + merchant=self.merchant, + category=category, + name='产品1', + human_id='FAB-001', + unit=ProductUnitEnum.METER, + ) + self.user = User.objects.create_user(username='po_user', password='pass123') + self.employee = Employee.objects.create( + merchant=self.merchant, + sys_user=self.user, + name='仓管', + ) + self.client = APIClient() + self.client.force_authenticate(user=self.user) + self.payload = { + 'supplier': self.supplier.id, + 'warehouse': self.warehouse.id, + 'order_date': '2025-11-26', + 'total_amount': '1500.00', + 'items': [ + { + 'product_id': self.product.id, + 'quantities': ['10.0', '5.0'], + } + ], + 'remarks': '接口测试', + } + + def test_create_purchase_order_success(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') + + 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} + 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') + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + +@override_settings( + CELERY_TASK_ALWAYS_EAGER=True, + CELERY_TASK_EAGER_PROPAGATES=True, +) +class CeleryTasksTestCase(TestCase): + """验证 Celery 任务的执行结果""" + + def setUp(self): + self.merchant = Merchant.objects.create(name='Celery商户', type=MerchantTypeEnum.FACTORY) + category = ProductCategory.objects.create( + merchant=self.merchant, + name='Celery品类', + product_prefix='CLY', + ) + for idx in range(3): + Product.objects.create( + merchant=self.merchant, + category=category, + name=f'Celery产品{idx}', + human_id=f'CLY-{idx:03d}', + unit=ProductUnitEnum.METER, + ) + + def test_ping_task_returns_payload(self): + result = tasks.ping_task.delay('celery hello') + payload = result.get(timeout=5) + self.assertEqual(payload['message'], 'celery hello') + self.assertIn('timestamp', payload) + self.assertIn('task_id', payload) + + def test_merchant_product_count(self): + result = tasks.merchant_product_count.delay(self.merchant.id) + payload = result.get(timeout=5) + self.assertEqual(payload['merchant_id'], self.merchant.id) + self.assertEqual(payload['product_count'], 3) + + def test_backup_database_creates_file(self): + tmpdir = Path(tempfile.mkdtemp()) + self.addCleanup(lambda: shutil.rmtree(tmpdir, ignore_errors=True)) + with patch('api_v1.tasks._dump_database_to_sql') as mock_dump: + def fake_dump(path: Path): + path.write_text('-- dummy sql\n', encoding='utf-8') + + mock_dump.side_effect = fake_dump + result = tasks.backup_database.delay(output_dir=str(tmpdir), filename_prefix='test-backup') + payload = result.get(timeout=10) + backup_path = Path(payload['backup_path']) + self.assertTrue(backup_path.exists()) + self.assertTrue(backup_path.is_file()) + self.assertEqual(backup_path.parent.resolve(), tmpdir.resolve()) + self.assertEqual(backup_path.suffix, '.sql') + self.assertTrue(backup_path.read_text(encoding='utf-8').strip()) diff --git a/api_v1/urls.py b/api_v1/urls.py index 41e88ee..fc5eb29 100644 --- a/api_v1/urls.py +++ b/api_v1/urls.py @@ -1,6 +1,15 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from .views import stock_change_views, user_info, inventory, product_image, stateflow, users +from .views import ( + stock_change_views, + purchase_order, + healthy, + user_info, + inventory, + product_image, + stateflow, + users, +) from .views.stock_change_views.snapshot import StockSnapshotListView from .views.printing.views import PrintingOrderViewSet, PrintingJobViewSet, PlateOrderViewSet from .views.upload import UploadFileViewSet @@ -46,6 +55,8 @@ urlpatterns = [ # 库存查询 API 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'), # 产品图片上传 API path('products//image/', product_image.ProductImageUploadView.as_view(), name='product_image_upload'), diff --git a/api_v1/views/healthy.py b/api_v1/views/healthy.py new file mode 100644 index 0000000..6911f37 --- /dev/null +++ b/api_v1/views/healthy.py @@ -0,0 +1,42 @@ +import platform +from typing import Dict + +from django.db import connections, DatabaseError +from django.utils import timezone +from django.utils.version import get_version +from rest_framework.response import Response +from rest_framework.views import APIView + + +class HealthCheckView(APIView): + """ + 简单健康检查接口,返回当前服务的基础指标信息,包括: + - 应用版本 + - Python/Django 版本 + - 服务器时间 + - 数据库连接状态 + """ + + authentication_classes: list = [] + permission_classes: list = [] + + def get(self, request): + db_status: Dict[str, str] = {} + for alias in connections: + try: + connections[alias].cursor() + db_status[alias] = 'ok' + except DatabaseError as exc: + db_status[alias] = f'error: {exc.__class__.__name__}' + + payload = { + 'service': 'flower-api', + 'status': 'ok' if all(status == 'ok' for status in db_status.values()) else 'degraded', + 'server_time': timezone.now().isoformat(), + 'python_version': platform.python_version(), + 'django_version': get_version(), + 'platform': platform.platform(), + 'databases': db_status, + } + return Response(payload) + diff --git a/api_v1/views/purchase_order.py b/api_v1/views/purchase_order.py new file mode 100644 index 0000000..b8a20eb --- /dev/null +++ b/api_v1/views/purchase_order.py @@ -0,0 +1,72 @@ +from decimal import Decimal, InvalidOperation + +from rest_framework import status, views +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 .stock_change_views.mixins import StockChangeViewMixin + + +class PurchaseOrderView(StockChangeViewMixin, views.APIView): + """创建采购订单并触发入库任务""" + + permission_classes = [IsAuthenticated] + + def post(self, request): + if not self.check_employee_permission(request): + return self.permission_error_response('无权限访问') + + merchant = request.user.employee.merchant + data = request.data or {} + + supplier_id = data.get('supplier') + warehouse_id = data.get('warehouse') + order_date = data.get('order_date') + total_amount = data.get('total_amount') + items = data.get('items', []) + remarks = data.get('remarks', '') + + if not supplier_id: + return Response({'error': '缺少供应商 ID'}, status=status.HTTP_400_BAD_REQUEST) + if not warehouse_id: + return Response({'error': '缺少仓库 ID'}, status=status.HTTP_400_BAD_REQUEST) + + try: + supplier = basic_models.Supplier.objects.get(id=supplier_id, merchant=merchant) + except basic_models.Supplier.DoesNotExist: + return Response({'error': f'供应商 {supplier_id} 不存在'}, status=status.HTTP_400_BAD_REQUEST) + + try: + 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) + + 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, + items=items, + remarks=remarks, + created_by=request.user, + ) + except ValueError as exc: + return Response({'error': str(exc)}, status=status.HTTP_400_BAD_REQUEST) + + return Response( + { + 'id': purchase_order.id, + 'message': '采购单创建成功,入库任务已排队', + }, + status=status.HTTP_201_CREATED, + ) + diff --git a/api_v1/views/stock_change_views/mixins.py b/api_v1/views/stock_change_views/mixins.py index e5bc3f4..13ddbac 100644 --- a/api_v1/views/stock_change_views/mixins.py +++ b/api_v1/views/stock_change_views/mixins.py @@ -67,6 +67,7 @@ class StockChangeViewMixin: """构建明细数据""" return { 'id': detail.id, + 'stock_change_record': detail.stock_change_record_id, 'product': detail.product_id, 'product_name': detail.product.name, 'quantity': float(detail.quantity), diff --git a/api_v1/views/stock_change_views/test_stock_change_api.py b/api_v1/views/stock_change_views/test_stock_change_api.py index dba7a0b..aaff3d8 100644 --- a/api_v1/views/stock_change_views/test_stock_change_api.py +++ b/api_v1/views/stock_change_views/test_stock_change_api.py @@ -1,4 +1,5 @@ from decimal import Decimal +import logging from django.contrib.auth import get_user_model from django.test import TestCase @@ -12,6 +13,279 @@ from stock import models as stock_models, services as stock_services User = get_user_model() +class CreateStockChangeAPITestCase(TestCase): + """测试标准库存变动创建 API""" + + def setUp(self): + self.client = APIClient() + + self.merchant = basic_models.Merchant.objects.create( + name='测试商户', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + self.category = basic_models.ProductCategory.objects.create( + merchant=self.merchant, + name='布料', + product_prefix='FAB', + ) + self.product = basic_models.Product.objects.create( + merchant=self.merchant, + category=self.category, + name='测试布料', + human_id='FAB-001', + unit=basic_models.ProductUnitEnum.METER, + ) + self.warehouse = basic_models.WareHouse.objects.create( + merchant=self.merchant, + name='严谨仓库', + mode=basic_models.WareHouseModeEnum.RESTRICT_IN, + ) + self.other_merchant = basic_models.Merchant.objects.create( + name='其他商户', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + other_category = basic_models.ProductCategory.objects.create( + merchant=self.other_merchant, + name='其他布料', + product_prefix='FABO', + ) + self.other_product = basic_models.Product.objects.create( + merchant=self.other_merchant, + category=other_category, + name='外部布料', + human_id='FAB-999', + unit=basic_models.ProductUnitEnum.METER, + ) + + self.user = User.objects.create_user(username='strict_user', password='pass123') + self.employee = basic_models.Employee.objects.create( + merchant=self.merchant, + sys_user=self.user, + name='仓管员', + mobile='13800138002', + status=basic_models.EmployeeStatusEnum.ACTIVE, + ) + self.client.force_authenticate(user=self.user) + + def test_create_stock_change_success(self): + payload = { + 'type': stock_models.StockChangeTypeEnum.ADD, + 'warehouse': self.warehouse.id, + 'source_type': stock_models.StockChangeSourceEnum.PURCHASE, + 'source_id': 10, + 'products': [ + {'product': self.product.id, 'quantity': ['10.50', '5.25']}, + ] + } + + response = self.client.post('/api/v1/stock-change/', payload, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['created_details_count'], 2) + self.assertEqual(len(response.data['details']), 2) + self.assertEqual(response.data['stock_change_record']['warehouse'], self.warehouse.id) + + record_id = response.data['stock_change_record']['id'] + record = stock_models.StockChangeRecord.objects.get(id=record_id) + self.assertEqual(record.details.count(), 2) + + def test_create_stock_change_products_required(self): + payload = { + 'type': stock_models.StockChangeTypeEnum.ADD, + 'warehouse': self.warehouse.id, + 'source_type': stock_models.StockChangeSourceEnum.PURCHASE, + 'source_id': 11, + 'products': [], + } + + response = self.client.post('/api/v1/stock-change/', payload, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data['error'], '产品列表不能为空') + + def test_create_stock_change_product_not_visible(self): + payload = { + 'type': stock_models.StockChangeTypeEnum.ADD, + 'warehouse': self.warehouse.id, + 'source_type': stock_models.StockChangeSourceEnum.PURCHASE, + 'products': [ + {'product': self.other_product.id, 'quantity': ['5.00']}, + ] + } + + response = self.client.post('/api/v1/stock-change/', payload, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual( + response.data['error'], + f'产品ID {self.other_product.id} 对当前用户不可见', + ) + + def test_create_stock_change_no_employee_permission_denied(self): + class DummyUser: + def __init__(self, username): + self.username = username + self.is_authenticated = True + + dummy_user = DummyUser('no_emp_user') + self.client.force_authenticate(user=dummy_user) + payload = { + 'type': stock_models.StockChangeTypeEnum.ADD, + 'warehouse': self.warehouse.id, + 'source_type': stock_models.StockChangeSourceEnum.PURCHASE, + 'products': [ + {'product': self.product.id, 'quantity': ['5.00']}, + ] + } + + logging.disable(logging.NOTSET) + self.addCleanup(logging.disable, logging.CRITICAL) + with self.assertLogs('api_v1.views.stock_change_views.mixins', level='ERROR') as cm: + response = self.client.post('/api/v1/stock-change/', payload, format='json') + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.data['error'], '无权限访问') + self.assertTrue(any('无员工信息' in msg for msg in cm.output)) + + +class CreateStockChangeRelaxedAPITestCase(TestCase): + """测试宽松模式库存变动创建 API""" + + def setUp(self): + self.client = APIClient() + + self.merchant = basic_models.Merchant.objects.create( + name='宽松商户', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + self.category = basic_models.ProductCategory.objects.create( + merchant=self.merchant, + name='面料', + product_prefix='FAB', + ) + self.product = basic_models.Product.objects.create( + merchant=self.merchant, + category=self.category, + name='宽松布料', + human_id='FAB-100', + unit=basic_models.ProductUnitEnum.METER, + ) + self.relaxed_warehouse = basic_models.WareHouse.objects.create( + merchant=self.merchant, + name='宽进宽出仓', + mode=basic_models.WareHouseModeEnum.UNRESTRICTED, + ) + self.other_merchant = basic_models.Merchant.objects.create( + name='RelaxedOther', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + other_category = basic_models.ProductCategory.objects.create( + merchant=self.other_merchant, + name='外部面料', + product_prefix='REL', + ) + self.other_product = basic_models.Product.objects.create( + merchant=self.other_merchant, + category=other_category, + name='外部布料', + human_id='REL-002', + unit=basic_models.ProductUnitEnum.METER, + ) + + self.user = User.objects.create_user(username='relaxed_user', password='pass123') + self.employee = basic_models.Employee.objects.create( + merchant=self.merchant, + sys_user=self.user, + name='宽松仓管员', + mobile='13800138003', + status=basic_models.EmployeeStatusEnum.ACTIVE, + ) + self.client.force_authenticate(user=self.user) + + def test_create_stock_change_relaxed_success(self): + payload = { + 'type': stock_models.StockChangeTypeEnum.ADD, + 'warehouse': self.relaxed_warehouse.id, + 'source_type': stock_models.StockChangeSourceEnum.PURCHASE, + 'source_id': 20, + 'products': [ + { + 'product': self.product.id, + 'quantity': {'value': '12', 'unit_count': '5'} + } + ] + } + + response = self.client.post('/api/v1/stock-change/relaxed/', payload, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(response.data['created_details_count'], 3) + record_id = response.data['stock_change_record']['id'] + details = stock_models.StockChangeDetail.objects.filter(stock_change_record_id=record_id) + self.assertEqual(details.count(), 3) + + def test_create_stock_change_relaxed_missing_required(self): + payload = { + 'warehouse': self.relaxed_warehouse.id, + 'source_type': stock_models.StockChangeSourceEnum.PURCHASE, + 'products': [ + { + 'product': self.product.id, + 'quantity': {'value': '10', 'unit_count': '4'} + } + ] + } + + response = self.client.post('/api/v1/stock-change/relaxed/', payload, format='json') + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data['error'], '缺少必要参数') + + def test_create_stock_change_relaxed_product_not_visible(self): + payload = { + 'type': stock_models.StockChangeTypeEnum.ADD, + 'warehouse': self.relaxed_warehouse.id, + 'source_type': stock_models.StockChangeSourceEnum.PURCHASE, + 'products': [ + { + 'product': self.other_product.id, + 'quantity': {'value': '8', 'unit_count': '4'} + } + ] + } + + response = self.client.post('/api/v1/stock-change/relaxed/', payload, format='json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual( + response.data['error'], + f'产品ID {self.other_product.id} 对当前用户不可见', + ) + + def test_create_stock_change_relaxed_no_employee_permission_denied(self): + class DummyUser: + def __init__(self, username): + self.username = username + self.is_authenticated = True + + dummy_user = DummyUser('relaxed_no_emp') + self.client.force_authenticate(user=dummy_user) + payload = { + 'type': stock_models.StockChangeTypeEnum.ADD, + 'warehouse': self.relaxed_warehouse.id, + 'source_type': stock_models.StockChangeSourceEnum.PURCHASE, + 'products': [ + { + 'product': self.product.id, + 'quantity': {'value': '10', 'unit_count': '5'} + } + ] + } + + logging.disable(logging.NOTSET) + self.addCleanup(logging.disable, logging.CRITICAL) + with self.assertLogs('api_v1.views.stock_change_views.mixins', level='ERROR') as cm: + response = self.client.post('/api/v1/stock-change/relaxed/', payload, format='json') + + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response.data['error'], '无权限访问') + self.assertTrue(any('无员工信息' in msg for msg in cm.output)) + + class StockChangeRestrictAPITestCase(TestCase): """测试严进严出模式的 API""" @@ -132,7 +406,7 @@ class ListStockChangeDetailsAPITestCase(TestCase): self.warehouse = basic_models.WareHouse.objects.create( merchant=self.merchant, name='测试仓库', - mode=basic_models.WareHouseModeEnum.UNRESTRICTED, + mode=basic_models.WareHouseModeEnum.RESTRICT_IN, ) # 创建另一个产品和仓库用于测试过滤 @@ -146,7 +420,7 @@ class ListStockChangeDetailsAPITestCase(TestCase): self.warehouse2 = basic_models.WareHouse.objects.create( merchant=self.merchant, name='测试仓库2', - mode=basic_models.WareHouseModeEnum.UNRESTRICTED, + mode=basic_models.WareHouseModeEnum.RESTRICT_IN, ) self.user = User.objects.create_user(username='detail_user', password='pass123') @@ -209,9 +483,9 @@ class ListStockChangeDetailsAPITestCase(TestCase): ) self.assertEqual(response.status_code, status.HTTP_200_OK) - # 应该返回仓库1中产品1的所有明细(record1和record2的明细) - self.assertEqual(response.data['count'], 3) # record1有2条明细,record2有1条明细 - self.assertEqual(len(response.data['results']), 3) + # 默认 direction=inbound,因此仅包含仓库1中产品1的入库明细(record1 的 2 条) + self.assertEqual(response.data['count'], 2) + self.assertEqual(len(response.data['results']), 2) # 检查返回数据结构 result = response.data['results'][0] diff --git a/business/services.py b/business/services.py new file mode 100644 index 0000000..24d0950 --- /dev/null +++ b/business/services.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from typing import Any, Dict, List + +from django.db import transaction + +from datetime import date, datetime +from decimal import Decimal +from typing import Any, Dict, List + +from django.db import transaction + +from basic_info import models as basic_info_models + +from . import models +from .tasks import create_purchase_order_stock_entries + + +def _normalize_order_date(value) -> date: + if isinstance(value, date): + return value + if isinstance(value, datetime): + return value.date() + if isinstance(value, str): + try: + return date.fromisoformat(value) + except ValueError as exc: + raise ValueError('order_date 格式不正确,应为 YYYY-MM-DD') from exc + raise ValueError('order_date 格式不正确') + + +def create_purchase_order( + *, + merchant: basic_info_models.Merchant, + supplier: basic_info_models.Supplier, + order_date, + total_amount, + warehouse_id: int, + items: List[Dict[str, Any]], + remarks: str | None = '', + created_by=None, +) -> models.PurchaseOrder: + """ + 创建采购订单并触发异步创建入库单任务。 + + Args: + 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,需满足其模式要求。 + 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)) + + with transaction.atomic(): + purchase_order = models.PurchaseOrder.objects.create( + merchant=merchant, + supplier=supplier, + order_date=normalized_date, + total_amount=total_amount, + remarks=remarks, + ) + + 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, + ) + return purchase_order + diff --git a/business/tasks.py b/business/tasks.py new file mode 100644 index 0000000..39c1b17 --- /dev/null +++ b/business/tasks.py @@ -0,0 +1,56 @@ +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__) + + +@shared_task(bind=True) +def create_purchase_order_stock_entries( + self, + *, + purchase_order_id: int, + 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( + warehouse_id=warehouse_id, + source_type=stock_models.StockChangeSourceEnum.PURCHASE, + source_id=purchase_order.id, + items=items, + ) + + 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) + return payload + diff --git a/business/tests.py b/business/tests.py index 7ce503c..cd0db94 100644 --- a/business/tests.py +++ b/business/tests.py @@ -1,3 +1,126 @@ +from decimal import Decimal +from typing import List, Dict, Any + +from django.contrib.auth import get_user_model +from django.test import TestCase, override_settings +from django.utils import timezone +from unittest.mock import patch, MagicMock + +from basic_info import models as basic_models +from stock import models as stock_models +from business import models as business_models, services, tasks + + +def create_basic_fixtures(): + merchant = basic_models.Merchant.objects.create( + name='测试商户', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + supplier = basic_models.Supplier.objects.create( + merchant=merchant, + name='测试供应商', + ) + warehouse = basic_models.WareHouse.objects.create( + merchant=merchant, + name='主仓', + mode=basic_models.WareHouseModeEnum.RESTRICT_IN, + ) + category = basic_models.ProductCategory.objects.create( + merchant=merchant, + name='面料', + product_prefix='FAB', + ) + product = basic_models.Product.objects.create( + merchant=merchant, + category=category, + name='测试面料', + human_id='FAB-001', + unit=basic_models.ProductUnitEnum.METER, + ) + return merchant, supplier, warehouse, product + + +class PurchaseOrderServiceTestCase(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='creator', password='pass123') + self.items: List[Dict[str, Any]] = [ + {'product_id': self.product.id, 'quantities': ['10.00', '5.00']}, + ] + + def test_create_purchase_order_triggers_task(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, + remarks='自动化测试', + created_by=self.user, + ) + + self.assertIsInstance(purchase_order, business_models.PurchaseOrder) + mock_delay.assert_called_once_with( + purchase_order_id=purchase_order.id, + warehouse_id=self.warehouse.id, + items=self.items, + created_by_id=self.user.id, + ) + + def test_create_purchase_order_without_items_raises(self): + with self.assertRaises(ValueError): + services.create_purchase_order( + merchant=self.merchant, + supplier=self.supplier, + order_date=timezone.now().date(), + total_amount=Decimal('50.00'), + warehouse_id=self.warehouse.id, + items=[], + ) + + +@override_settings( + CELERY_TASK_ALWAYS_EAGER=True, + CELERY_TASK_EAGER_PROPAGATES=True, +) +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']}] + + 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) + + async_result = tasks.create_purchase_order_stock_entries.delay( + purchase_order_id=self.purchase_order.id, + warehouse_id=self.warehouse.id, + items=self.items, + created_by_id=self.user.id, + ) + 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, + items=self.items, + ) + self.assertEqual(payload['purchase_order_id'], self.purchase_order.id) + self.assertEqual(payload['stock_change_record_id'], 321) from django.test import TestCase # Create your tests here. diff --git a/business/views.py b/business/views.py deleted file mode 100644 index 91ea44a..0000000 --- a/business/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/data-bak/db-backup-20251126-124636.sql b/data-bak/db-backup-20251126-124636.sql new file mode 100644 index 0000000..2dcca32 --- /dev/null +++ b/data-bak/db-backup-20251126-124636.sql @@ -0,0 +1,4525 @@ +-- +-- PostgreSQL database dump +-- + +\restrict lAaLqLBC97fkSperdxYl5fjzjf6qhnkvpVyPWEkZyQ37Mu4fIXbcWZBIFk58Sg7 + +-- Dumped from database version 16.10 +-- Dumped by pg_dump version 17.6 (Debian 17.6-0+deb13u1) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: api_uploaded_file; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.api_uploaded_file ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + path character varying(500) NOT NULL, + is_deleted boolean NOT NULL, + original_filename character varying(255) NOT NULL, + file_size bigint, + content_type character varying(100) NOT NULL, + owner_id integer NOT NULL +); + + +ALTER TABLE public.api_uploaded_file OWNER TO postgres; + +-- +-- Name: api_uploaded_file_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.api_uploaded_file ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.api_uploaded_file_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: auth_group; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.auth_group ( + id integer NOT NULL, + name character varying(150) NOT NULL +); + + +ALTER TABLE public.auth_group OWNER TO postgres; + +-- +-- Name: auth_group_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.auth_group ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.auth_group_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: auth_group_permissions; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.auth_group_permissions ( + id bigint NOT NULL, + group_id integer NOT NULL, + permission_id integer NOT NULL +); + + +ALTER TABLE public.auth_group_permissions OWNER TO postgres; + +-- +-- Name: auth_group_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.auth_group_permissions ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.auth_group_permissions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: auth_permission; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.auth_permission ( + id integer NOT NULL, + name character varying(255) NOT NULL, + content_type_id integer NOT NULL, + codename character varying(100) NOT NULL +); + + +ALTER TABLE public.auth_permission OWNER TO postgres; + +-- +-- Name: auth_permission_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.auth_permission ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.auth_permission_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: auth_user; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.auth_user ( + id integer NOT NULL, + password character varying(128) NOT NULL, + last_login timestamp with time zone, + is_superuser boolean NOT NULL, + username character varying(150) NOT NULL, + first_name character varying(150) NOT NULL, + last_name character varying(150) NOT NULL, + email character varying(254) NOT NULL, + is_staff boolean NOT NULL, + is_active boolean NOT NULL, + date_joined timestamp with time zone NOT NULL +); + + +ALTER TABLE public.auth_user OWNER TO postgres; + +-- +-- Name: auth_user_groups; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.auth_user_groups ( + id bigint NOT NULL, + user_id integer NOT NULL, + group_id integer NOT NULL +); + + +ALTER TABLE public.auth_user_groups OWNER TO postgres; + +-- +-- Name: auth_user_groups_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.auth_user_groups ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.auth_user_groups_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: auth_user_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.auth_user ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.auth_user_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: auth_user_user_permissions; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.auth_user_user_permissions ( + id bigint NOT NULL, + user_id integer NOT NULL, + permission_id integer NOT NULL +); + + +ALTER TABLE public.auth_user_user_permissions OWNER TO postgres; + +-- +-- Name: auth_user_user_permissions_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.auth_user_user_permissions ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.auth_user_user_permissions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_bankaccount; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_bankaccount ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + auto_number character varying(50) NOT NULL, + description text, + attachment character varying(100), + merchant_id bigint NOT NULL +); + + +ALTER TABLE public.basic_info_bankaccount OWNER TO postgres; + +-- +-- Name: basic_info_bankaccount_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_bankaccount ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_bankaccount_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_customer; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_customer ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + mobile character varying(20) NOT NULL, + area character varying(100), + email character varying(254), + contact character varying, + description text, + merchant_id bigint NOT NULL, + created_by_id bigint +); + + +ALTER TABLE public.basic_info_customer OWNER TO postgres; + +-- +-- Name: basic_info_customer_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_customer ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_customer_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_customer_visible_employees; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_customer_visible_employees ( + id bigint NOT NULL, + customer_id bigint NOT NULL, + employee_id bigint NOT NULL +); + + +ALTER TABLE public.basic_info_customer_visible_employees OWNER TO postgres; + +-- +-- Name: basic_info_customer_visible_employees_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_customer_visible_employees ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_customer_visible_employees_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_deviceinfo; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_deviceinfo ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + type character varying(20) NOT NULL, + status character varying(20) NOT NULL, + start_working_at date, + stop_working_at date, + is_occupied boolean NOT NULL, + description text, + merchant_id bigint NOT NULL +); + + +ALTER TABLE public.basic_info_deviceinfo OWNER TO postgres; + +-- +-- Name: basic_info_deviceinfo_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_deviceinfo ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_deviceinfo_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_employee; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_employee ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + mobile character varying(20), + area character varying(100), + description text, + status character varying(20) NOT NULL, + sys_user_id integer, + merchant_id bigint NOT NULL, + position_id bigint +); + + +ALTER TABLE public.basic_info_employee OWNER TO postgres; + +-- +-- Name: basic_info_employee_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_employee ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_employee_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_employeetype; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_employeetype ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + title character varying(50) NOT NULL, + description text, + reverse character varying(100), + merchant_id bigint NOT NULL +); + + +ALTER TABLE public.basic_info_employeetype OWNER TO postgres; + +-- +-- Name: basic_info_employeetype_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_employeetype ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_employeetype_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_merchant; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_merchant ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + type integer NOT NULL, + area character varying(100), + email character varying(254), + mobile character varying(20), + contact text, + description text, + auto_complete_stock_change boolean NOT NULL +); + + +ALTER TABLE public.basic_info_merchant OWNER TO postgres; + +-- +-- Name: basic_info_merchant_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_merchant ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_merchant_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_product; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_product ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + human_id character varying(100) NOT NULL, + color character varying(50), + width_size numeric(10,2), + single_price_in numeric(10,2), + single_price_out numeric(10,2), + unit integer NOT NULL, + spec character varying(100), + description text, + image character varying(100), + transform_rate numeric(10,4), + merchant_id bigint NOT NULL, + category_id bigint NOT NULL, + minimum_quantity integer +); + + +ALTER TABLE public.basic_info_product OWNER TO postgres; + +-- +-- Name: basic_info_product_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_product ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_product_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_productcategory; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_productcategory ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + description text, + product_prefix character varying(5) NOT NULL, + merchant_id bigint NOT NULL +); + + +ALTER TABLE public.basic_info_productcategory OWNER TO postgres; + +-- +-- Name: basic_info_productcategory_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_productcategory ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_productcategory_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_quickinput; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_quickinput ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + value character varying(200) NOT NULL, + "group" character varying NOT NULL +); + + +ALTER TABLE public.basic_info_quickinput OWNER TO postgres; + +-- +-- Name: basic_info_quickinput_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_quickinput ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_quickinput_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_supplier; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_supplier ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + area character varying(100), + email character varying(254), + mobile character varying(20), + contact text, + description text, + merchant_id bigint NOT NULL +); + + +ALTER TABLE public.basic_info_supplier OWNER TO postgres; + +-- +-- Name: basic_info_supplier_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_supplier ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_supplier_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_userprofile; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_userprofile ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + description text, + merchant_id bigint NOT NULL, + user_id integer NOT NULL +); + + +ALTER TABLE public.basic_info_userprofile OWNER TO postgres; + +-- +-- Name: basic_info_userprofile_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_userprofile ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_userprofile_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_vehicletransportrecord; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_vehicletransportrecord ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + driver_name character varying(100) NOT NULL, + contact_number character varying(20), + license_plate character varying(20), + delivery_date date NOT NULL, + merchant_id bigint NOT NULL, + vehicle_type_id bigint NOT NULL +); + + +ALTER TABLE public.basic_info_vehicletransportrecord OWNER TO postgres; + +-- +-- Name: basic_info_vehicletransportrecord_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_vehicletransportrecord ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_vehicletransportrecord_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_vehicletype; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_vehicletype ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(50) NOT NULL, + description text, + merchant_id bigint NOT NULL +); + + +ALTER TABLE public.basic_info_vehicletype OWNER TO postgres; + +-- +-- Name: basic_info_vehicletype_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_vehicletype ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_vehicletype_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: basic_info_warehouse; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.basic_info_warehouse ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + name character varying(100) NOT NULL, + location character varying(200), + contact character varying(100), + mobile character varying(20), + area character varying(100), + description text, + merchant_id bigint NOT NULL, + mode integer NOT NULL, + type integer NOT NULL +); + + +ALTER TABLE public.basic_info_warehouse OWNER TO postgres; + +-- +-- Name: basic_info_warehouse_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.basic_info_warehouse ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.basic_info_warehouse_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: business_object; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.business_object ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + name character varying(100) NOT NULL, + description text NOT NULL, + process_id bigint NOT NULL, + content_type_id integer, + object_id integer, + CONSTRAINT business_object_object_id_check CHECK ((object_id >= 0)) +); + + +ALTER TABLE public.business_object OWNER TO postgres; + +-- +-- Name: business_purchaseorder; Type: TABLE; Schema: public; Owner: postgres +-- + +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 +); + + +ALTER TABLE public.business_purchaseorder OWNER TO postgres; + +-- +-- Name: django_admin_log; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.django_admin_log ( + id integer NOT NULL, + action_time timestamp with time zone NOT NULL, + object_id text, + object_repr character varying(200) NOT NULL, + action_flag smallint NOT NULL, + change_message text NOT NULL, + content_type_id integer, + user_id integer NOT NULL, + CONSTRAINT django_admin_log_action_flag_check CHECK ((action_flag >= 0)) +); + + +ALTER TABLE public.django_admin_log OWNER TO postgres; + +-- +-- Name: django_admin_log_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.django_admin_log ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.django_admin_log_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: django_content_type; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.django_content_type ( + id integer NOT NULL, + app_label character varying(100) NOT NULL, + model character varying(100) NOT NULL +); + + +ALTER TABLE public.django_content_type OWNER TO postgres; + +-- +-- Name: django_content_type_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.django_content_type ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.django_content_type_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: django_migrations; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.django_migrations ( + id bigint NOT NULL, + app character varying(255) NOT NULL, + name character varying(255) NOT NULL, + applied timestamp with time zone NOT NULL +); + + +ALTER TABLE public.django_migrations OWNER TO postgres; + +-- +-- Name: django_migrations_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.django_migrations ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.django_migrations_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: django_session; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.django_session ( + session_key character varying(40) NOT NULL, + session_data text NOT NULL, + expire_date timestamp with time zone NOT NULL +); + + +ALTER TABLE public.django_session OWNER TO postgres; + +-- +-- Name: printing_plateorder; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.printing_plateorder ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + design_code character varying(50), + plate_type character varying(20), + plate_date timestamp with time zone, + plate_method character varying(50), + plate_image character varying(100), + plate_notes text, + reprint_reason text, + urgency_level character varying(20) NOT NULL, + area character varying(255), + default_address character varying(500), + is_mark_frame boolean NOT NULL, + drawing_rating character varying(20), + color_matching_rating character varying(20), + sample_rating character varying(20), + difficulty_rating character varying(20), + required_completion_date date, + completion_date timestamp with time zone, + fabric character varying(100), + width character varying(50), + style_name character varying(100), + production_method character varying(50), + sample_meter character varying(100), + required_sample_meters numeric(10,2), + approval_result character varying(50), + is_ordered boolean NOT NULL, + customer_feedback text, + business_object_id bigint, + customer_id bigint NOT NULL, + merchandiser_id bigint, + salesperson_id bigint, + is_invalid boolean NOT NULL, + process integer NOT NULL, + fabric_source character varying(100), + designer_id bigint +); + + +ALTER TABLE public.printing_plateorder OWNER TO postgres; + +-- +-- Name: printing_plateorder_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.printing_plateorder ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.printing_plateorder_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: printing_printingjob; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.printing_printingjob ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + quantity integer NOT NULL, + unit character varying(50) NOT NULL, + size character varying(100), + pieces integer, + description text, + product_id bigint NOT NULL, + printing_order_id bigint NOT NULL, + business_object_id bigint, + CONSTRAINT printing_printingjob_pieces_check CHECK ((pieces >= 0)), + CONSTRAINT printing_printingjob_quantity_check CHECK ((quantity >= 0)) +); + + +ALTER TABLE public.printing_printingjob OWNER TO postgres; + +-- +-- Name: printing_printingjob_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.printing_printingjob ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.printing_printingjob_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: printing_printingorder; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.printing_printingorder ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + fabric character varying(100) NOT NULL, + width character varying(100) NOT NULL, + is_urgent boolean NOT NULL, + area character varying(100), + address character varying(200), + fabric_source character varying(100), + is_fabric_received boolean NOT NULL, + craft character varying(100), + description text, + outgoing_date date, + curve character varying(100), + new_curve character varying(100), + "position" text, + printing_warn text, + rolling_warn text, + production_warn text, + customer_id bigint NOT NULL, + is_invalid boolean NOT NULL, + process_id bigint +); + + +ALTER TABLE public.printing_printingorder OWNER TO postgres; + +-- +-- Name: printing_printingorder_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.printing_printingorder ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.printing_printingorder_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: state_flow_record; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.state_flow_record ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + completed_at timestamp with time zone NOT NULL, + completed_by_id integer, + business_object_id bigint NOT NULL, + state_id bigint NOT NULL, + cancelled_at timestamp with time zone, + is_cancelled boolean NOT NULL +); + + +ALTER TABLE public.state_flow_record OWNER TO postgres; + +-- +-- Name: state_log_parameter_record; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.state_log_parameter_record ( + id bigint NOT NULL, + updated_at timestamp with time zone NOT NULL, + parameters jsonb NOT NULL, + created_at timestamp with time zone NOT NULL, + remark text NOT NULL, + state_log_id bigint NOT NULL +); + + +ALTER TABLE public.state_log_parameter_record OWNER TO postgres; + +-- +-- Name: state_log_parameter_record_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.state_log_parameter_record ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.state_log_parameter_record_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stateflow_order_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.business_object ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stateflow_order_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stateflow_orderstatelog_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.state_flow_record ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stateflow_orderstatelog_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stateflow_process; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stateflow_process ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + name character varying(100) NOT NULL, + description text NOT NULL +); + + +ALTER TABLE public.stateflow_process OWNER TO postgres; + +-- +-- Name: stateflow_process_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stateflow_process ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stateflow_process_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stateflow_processnode; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stateflow_processnode ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + "order" integer NOT NULL, + process_id bigint NOT NULL, + state_id bigint NOT NULL, + CONSTRAINT stateflow_processnode_order_check CHECK (("order" >= 0)) +); + + +ALTER TABLE public.stateflow_processnode OWNER TO postgres; + +-- +-- Name: stateflow_processnode_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stateflow_processnode ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stateflow_processnode_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stateflow_state; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stateflow_state ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + name character varying(100) NOT NULL, + description character varying(200) NOT NULL +); + + +ALTER TABLE public.stateflow_state OWNER TO postgres; + +-- +-- Name: stateflow_state_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stateflow_state ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stateflow_state_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stateflow_state_parameters; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stateflow_state_parameters ( + id bigint NOT NULL, + state_id bigint NOT NULL, + stateparameter_id bigint NOT NULL +); + + +ALTER TABLE public.stateflow_state_parameters OWNER TO postgres; + +-- +-- Name: stateflow_state_parameters_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stateflow_state_parameters ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stateflow_state_parameters_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stateflow_stateparameter; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stateflow_stateparameter ( + id bigint NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + key character varying(100) NOT NULL, + value character varying(200), + description character varying(200) NOT NULL, + attachment character varying(100), + is_required boolean NOT NULL, + is_image_path boolean NOT NULL +); + + +ALTER TABLE public.stateflow_stateparameter OWNER TO postgres; + +-- +-- Name: stateflow_stateparameter_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stateflow_stateparameter ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stateflow_stateparameter_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stock_inventory; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stock_inventory ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + quantity numeric(10,2) NOT NULL, + num_of_rolls integer NOT NULL, + spec character varying(100), + description text, + merchant_id bigint NOT NULL, + product_id bigint NOT NULL, + warehouse_id bigint NOT NULL +); + + +ALTER TABLE public.stock_inventory OWNER TO postgres; + +-- +-- Name: stock_inventory_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stock_inventory ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stock_inventory_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stock_purchaseorder_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.business_purchaseorder ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stock_purchaseorder_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stock_stockchangedetail; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stock_stockchangedetail ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + quantity numeric(10,2) NOT NULL, + unit integer NOT NULL, + merchant_id bigint NOT NULL, + product_id bigint NOT NULL, + stock_change_record_id bigint NOT NULL, + is_consumed boolean NOT NULL, + consume_with_id bigint +); + + +ALTER TABLE public.stock_stockchangedetail OWNER TO postgres; + +-- +-- Name: stock_stockchangedetail_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stock_stockchangedetail ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stock_stockchangedetail_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stock_stockchangerecord; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stock_stockchangerecord ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + type integer NOT NULL, + source_type integer NOT NULL, + source_id bigint, + is_finished boolean NOT NULL, + finished_at timestamp with time zone, + remarks text, + created_by_id integer, + merchant_id bigint NOT NULL, + warehouse_id bigint NOT NULL +); + + +ALTER TABLE public.stock_stockchangerecord OWNER TO postgres; + +-- +-- Name: stock_stockchangerecord_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stock_stockchangerecord ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stock_stockchangerecord_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stock_stockfreeze; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stock_stockfreeze ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + quantity numeric(10,2) NOT NULL, + unit integer NOT NULL, + status integer NOT NULL, + frozen_with bigint, + completed_at timestamp with time zone, + completed_with bigint, + cancelled_at timestamp with time zone, + reason text, + cancelled_by_id integer, + completed_by_id integer, + frozen_by_id integer NOT NULL, + merchant_id bigint NOT NULL, + product_id bigint NOT NULL, + stock_detail_id bigint NOT NULL, + warehouse_id bigint NOT NULL +); + + +ALTER TABLE public.stock_stockfreeze OWNER TO postgres; + +-- +-- Name: stock_stockfreeze_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stock_stockfreeze ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stock_stockfreeze_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Name: stock_stocksnapshot; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.stock_stocksnapshot ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + id bigint NOT NULL, + delta numeric(10,2) NOT NULL, + quantity_before numeric(10,2) NOT NULL, + quantity_after numeric(10,2) NOT NULL, + unit integer NOT NULL, + num_of_rolls integer NOT NULL, + offset_to bigint, + offset_at timestamp with time zone, + cancelled boolean NOT NULL, + cancelled_at timestamp with time zone, + offset_id bigint, + merchant_id bigint NOT NULL, + product_id bigint NOT NULL, + stock_change_record_id bigint NOT NULL, + warehouse_id bigint NOT NULL +); + + +ALTER TABLE public.stock_stocksnapshot OWNER TO postgres; + +-- +-- Name: stock_stocksnapshot_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +ALTER TABLE public.stock_stocksnapshot ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY ( + SEQUENCE NAME public.stock_stocksnapshot_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + +-- +-- Data for Name: api_uploaded_file; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +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 +\. + + +-- +-- Data for Name: auth_group; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.auth_group (id, name) FROM stdin; +\. + + +-- +-- Data for Name: auth_group_permissions; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.auth_group_permissions (id, group_id, permission_id) FROM stdin; +\. + + +-- +-- Data for Name: auth_permission; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.auth_permission (id, name, content_type_id, codename) FROM stdin; +1 Can add log entry 1 add_logentry +2 Can change log entry 1 change_logentry +3 Can delete log entry 1 delete_logentry +4 Can view log entry 1 view_logentry +5 Can add permission 2 add_permission +6 Can change permission 2 change_permission +7 Can delete permission 2 delete_permission +8 Can view permission 2 view_permission +9 Can add group 3 add_group +10 Can change group 3 change_group +11 Can delete group 3 delete_group +12 Can view group 3 view_group +13 Can add user 4 add_user +14 Can change user 4 change_user +15 Can delete user 4 delete_user +16 Can view user 4 view_user +17 Can add content type 5 add_contenttype +18 Can change content type 5 change_contenttype +19 Can delete content type 5 delete_contenttype +20 Can view content type 5 view_contenttype +21 Can add session 6 add_session +22 Can change session 6 change_session +23 Can delete session 6 delete_session +24 Can view session 6 view_session +25 Can add 商户资料 7 add_merchant +26 Can change 商户资料 7 change_merchant +27 Can delete 商户资料 7 delete_merchant +28 Can view 商户资料 7 view_merchant +29 Can add 员工资料 8 add_employee +30 Can change 员工资料 8 change_employee +31 Can delete 员工资料 8 delete_employee +32 Can view 员工资料 8 view_employee +33 Can add 设备资料 9 add_deviceinfo +34 Can change 设备资料 9 change_deviceinfo +35 Can delete 设备资料 9 delete_deviceinfo +36 Can view 设备资料 9 view_deviceinfo +37 Can add 客户资料 10 add_customer +38 Can change 客户资料 10 change_customer +39 Can delete 客户资料 10 delete_customer +40 Can view 客户资料 10 view_customer +41 Can add 银行账户 11 add_bankaccount +42 Can change 银行账户 11 change_bankaccount +43 Can delete 银行账户 11 delete_bankaccount +44 Can view 银行账户 11 view_bankaccount +45 Can add 产品类别 12 add_productcategory +46 Can change 产品类别 12 change_productcategory +47 Can delete 产品类别 12 delete_productcategory +48 Can view 产品类别 12 view_productcategory +49 Can add 产品资料 13 add_product +50 Can change 产品资料 13 change_product +51 Can delete 产品资料 13 delete_product +52 Can view 产品资料 13 view_product +53 Can add 供应商 14 add_supplier +54 Can change 供应商 14 change_supplier +55 Can delete 供应商 14 delete_supplier +56 Can view 供应商 14 view_supplier +57 Can add 车辆类型 15 add_vehicletype +58 Can change 车辆类型 15 change_vehicletype +59 Can delete 车辆类型 15 delete_vehicletype +60 Can view 车辆类型 15 view_vehicletype +61 Can add 司机车次 16 add_vehicletransportrecord +62 Can change 司机车次 16 change_vehicletransportrecord +63 Can delete 司机车次 16 delete_vehicletransportrecord +64 Can view 司机车次 16 view_vehicletransportrecord +65 Can add 仓库资料 17 add_warehouse +66 Can change 仓库资料 17 change_warehouse +67 Can delete 仓库资料 17 delete_warehouse +68 Can view 仓库资料 17 view_warehouse +69 Can add 快捷输入 18 add_quickinput +70 Can change 快捷输入 18 change_quickinput +71 Can delete 快捷输入 18 delete_quickinput +72 Can view 快捷输入 18 view_quickinput +73 Can add 采购单 19 add_purchaseorder +74 Can change 采购单 19 change_purchaseorder +75 Can delete 采购单 19 delete_purchaseorder +76 Can view 采购单 19 view_purchaseorder +77 Can add 出入库记录 20 add_stockchangerecord +78 Can change 出入库记录 20 change_stockchangerecord +79 Can delete 出入库记录 20 delete_stockchangerecord +80 Can view 出入库记录 20 view_stockchangerecord +81 Can add 库存变动明细 21 add_stockchangedetail +82 Can change 库存变动明细 21 change_stockchangedetail +83 Can delete 库存变动明细 21 delete_stockchangedetail +84 Can view 库存变动明细 21 view_stockchangedetail +85 Can add 库存快照 22 add_stocksnapshot +86 Can change 库存快照 22 change_stocksnapshot +87 Can delete 库存快照 22 delete_stocksnapshot +88 Can view 库存快照 22 view_stocksnapshot +89 Can add 库存 23 add_inventory +90 Can change 库存 23 change_inventory +91 Can delete 库存 23 delete_inventory +92 Can view 库存 23 view_inventory +93 Can add 库存冻结 24 add_stockfreeze +94 Can change 库存冻结 24 change_stockfreeze +95 Can delete 库存冻结 24 delete_stockfreeze +96 Can view 库存冻结 24 view_stockfreeze +97 Can add 上传文件 25 add_uploadedfile +98 Can change 上传文件 25 change_uploadedfile +99 Can delete 上传文件 25 delete_uploadedfile +100 Can view 上传文件 25 view_uploadedfile +101 Can add 印染订单 26 add_printingorder +102 Can change 印染订单 26 change_printingorder +103 Can delete 印染订单 26 delete_printingorder +104 Can view 印染订单 26 view_printingorder +105 可以作废印染订单 26 can_invalidate_printingorder +106 可以恢复印染订单 26 can_activate_printingorder +107 Can add 印染款式明细 27 add_printingjob +108 Can change 印染款式明细 27 change_printingjob +109 Can delete 印染款式明细 27 delete_printingjob +110 Can view 印染款式明细 27 view_printingjob +111 Can add 开版订单 28 add_plateorder +112 Can change 开版订单 28 change_plateorder +113 Can delete 开版订单 28 delete_plateorder +114 Can view 开版订单 28 view_plateorder +115 可以作废开版订单 28 can_invalidate_plateorder +116 可以恢复开版订单 28 can_activate_plateorder +117 Can add 流程节点 29 add_state +118 Can change 流程节点 29 change_state +119 Can delete 流程节点 29 delete_state +120 Can view 流程节点 29 view_state +121 Can add 流程编排 30 add_process +122 Can change 流程编排 30 change_process +123 Can delete 流程编排 30 delete_process +124 Can view 流程编排 30 view_process +125 Can add 工艺参数 31 add_stateparameter +126 Can change 工艺参数 31 change_stateparameter +127 Can delete 工艺参数 31 delete_stateparameter +128 Can view 工艺参数 31 view_stateparameter +129 Can add 流程节点关联 32 add_processnode +130 Can change 流程节点关联 32 change_processnode +131 Can delete 流程节点关联 32 delete_processnode +132 Can view 流程节点关联 32 view_processnode +133 Can add 业务对象 33 add_businessobject +134 Can change 业务对象 33 change_businessobject +135 Can delete 业务对象 33 delete_businessobject +136 Can view 业务对象 33 view_businessobject +137 Can add 状态流转记录 34 add_stateflowrecord +138 Can change 状态流转记录 34 change_stateflowrecord +139 Can delete 状态流转记录 34 delete_stateflowrecord +140 Can view 状态流转记录 34 view_stateflowrecord +141 Can add 状态流转参数记录 35 add_statelogparameterrecord +142 Can change 状态流转参数记录 35 change_statelogparameterrecord +143 Can delete 状态流转参数记录 35 delete_statelogparameterrecord +144 Can view 状态流转参数记录 35 view_statelogparameterrecord +145 Can add 员工职位类型 36 add_employeetype +146 Can change 员工职位类型 36 change_employeetype +147 Can delete 员工职位类型 36 delete_employeetype +148 Can view 员工职位类型 36 view_employeetype +149 Can add 用户资料扩展 37 add_userprofile +150 Can change 用户资料扩展 37 change_userprofile +151 Can delete 用户资料扩展 37 delete_userprofile +152 Can view 用户资料扩展 37 view_userprofile +153 查看所有客户资料 10 view_all_customers +154 Can add 采购单 38 add_purchaseorder +155 Can change 采购单 38 change_purchaseorder +156 Can delete 采购单 38 delete_purchaseorder +157 Can view 采购单 38 view_purchaseorder +\. + + +-- +-- Data for Name: auth_user; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +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 +\. + + +-- +-- Data for Name: auth_user_groups; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.auth_user_groups (id, user_id, group_id) FROM stdin; +\. + + +-- +-- Data for Name: auth_user_user_permissions; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.auth_user_user_permissions (id, user_id, permission_id) FROM stdin; +1 2 101 +2 2 102 +3 2 103 +4 2 104 +5 2 105 +6 2 106 +7 2 107 +8 2 108 +9 2 109 +10 2 110 +11 2 111 +12 2 112 +13 2 113 +14 2 114 +15 2 115 +16 2 116 +17 3 101 +18 3 102 +19 3 103 +20 3 104 +21 3 105 +22 3 106 +23 3 107 +24 3 108 +25 3 109 +26 3 110 +27 3 111 +28 3 112 +29 3 113 +30 3 114 +31 3 115 +32 3 116 +33 2 16 +34 2 13 +35 2 153 +36 2 40 +37 2 37 +38 2 38 +39 2 39 +\. + + +-- +-- Data for Name: basic_info_bankaccount; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_bankaccount (created_at, updated_at, id, name, auto_number, description, attachment, merchant_id) FROM stdin; +\. + + +-- +-- Data for Name: basic_info_customer; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_customer (created_at, updated_at, id, name, mobile, area, email, contact, description, merchant_id, created_by_id) FROM stdin; +2025-11-18 08:20:30.133277+00 2025-11-18 08:20:30.133288+00 4 黄花鱼报 1235565 \N \N \N \N 1 1 +2025-11-23 13:08:03.63937+00 2025-11-23 13:08:03.639378+00 5 福兴 15361280510 \N \N \N \N 1 2 +2025-11-23 13:08:17.625385+00 2025-11-23 13:08:17.625392+00 6 张晓鹏 15361280510 \N \N \N \N 1 2 +2025-11-23 13:08:36.458555+00 2025-11-23 13:08:36.458562+00 7 王杰敏 15361280510 \N \N \N \N 1 2 +2025-11-18 08:20:18.652207+00 2025-11-25 06:54:53.355892+00 3 测试123 123 \N \N \N \N 1 1 +\. + + +-- +-- Data for Name: basic_info_customer_visible_employees; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_customer_visible_employees (id, customer_id, employee_id) FROM stdin; +1 3 6 +\. + + +-- +-- Data for Name: basic_info_deviceinfo; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_deviceinfo (created_at, updated_at, id, name, type, status, start_working_at, stop_working_at, is_occupied, description, merchant_id) FROM stdin; +\. + + +-- +-- Data for Name: basic_info_employee; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_employee (created_at, updated_at, id, name, mobile, area, description, status, sys_user_id, merchant_id, position_id) FROM stdin; +2025-11-18 06:00:44.202474+00 2025-11-20 06:10:09.963599+00 1 左威 \N \N 在职 2 1 2 +2025-11-19 08:14:37.879581+00 2025-11-20 06:10:14.371773+00 2 映雪 15361280510 \N 在职 3 1 2 +2025-11-24 09:25:06.187356+00 2025-11-24 09:25:06.187366+00 4 123 \N \N \N 在职 5 1 \N +2025-11-24 09:25:22.570921+00 2025-11-24 09:25:22.570928+00 5 312 \N \N \N 在职 7 1 2 +2025-11-24 09:25:34.682437+00 2025-11-24 09:25:34.682449+00 6 威左 \N \N \N 在职 6 1 \N +\. + + +-- +-- Data for Name: basic_info_employeetype; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_employeetype (created_at, updated_at, id, title, description, reverse, merchant_id) FROM stdin; +2025-11-20 06:10:02.467608+00 2025-11-20 06:10:02.467618+00 2 设计师 设计师 \N 1 +2025-11-20 06:39:12.672087+00 2025-11-20 06:39:12.672099+00 3 跟单员 \N \N 1 +2025-11-20 06:39:22.85184+00 2025-11-20 06:39:22.851853+00 4 运营员 \N \N 1 +2025-11-20 06:39:34.031356+00 2025-11-20 06:39:41.998153+00 5 打纸员 \N \N 1 +2025-11-20 06:39:48.043445+00 2025-11-20 06:39:48.043455+00 6 滚筒工 \N \N 1 +2025-11-20 06:39:54.142622+00 2025-11-20 06:39:54.14263+00 7 业务员 \N \N 1 +\. + + +-- +-- Data for Name: basic_info_merchant; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_merchant (created_at, updated_at, id, name, type, area, email, mobile, contact, description, auto_complete_stock_change) FROM stdin; +2025-11-18 06:00:31.465397+00 2025-11-18 06:00:31.465405+00 1 瑞彩印花 2 \N \N \N f +2025-11-20 06:02:58.795363+00 2025-11-20 06:02:58.795382+00 2 测试商户2 1 \N \N \N \N \N f +2025-11-24 05:45:09.309332+00 2025-11-24 05:45:09.309344+00 3 测试商户 1 \N \N \N \N \N f +\. + + +-- +-- Data for Name: basic_info_product; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_product (created_at, updated_at, id, name, human_id, color, width_size, single_price_in, single_price_out, unit, spec, description, image, transform_rate, merchant_id, category_id, minimum_quantity) FROM stdin; +2025-11-18 08:21:51.521625+00 2025-11-18 08:21:51.521633+00 1 Tj602#V领19号色-25码 RC00001 \N \N \N \N 1 \N \N product_images/Tj602V领19号色-25码LWQ15-幅宽151-双层四面弹-一段一件-151cmX274cm-印好不能低于147cmX269cm__佩_preview.jpg \N 1 1 \N +2025-11-18 08:22:17.694033+00 2025-11-18 08:22:17.694041+00 2 Tj602#V领19号色-24码 RC00002 \N \N \N \N 1 \N \N product_images/Tj602V领19号色-24码LWQ15-幅宽151-双层四面弹-一段一件-151cmX268cm-印好不能低于147cmX253cm__佩_preview.jpg \N 1 1 \N +\. + + +-- +-- Data for Name: basic_info_productcategory; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_productcategory (created_at, updated_at, id, name, description, product_prefix, merchant_id) FROM stdin; +2025-11-18 08:21:39.444624+00 2025-11-18 08:21:39.444631+00 1 印花 \N RC 1 +\. + + +-- +-- Data for Name: basic_info_quickinput; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_quickinput (created_at, updated_at, id, name, value, "group") FROM stdin; +2025-11-18 08:23:24.813225+00 2025-11-18 08:23:24.813234+00 1 四面弹 四面弹 布料 +2025-11-18 09:48:48.101462+00 2025-11-18 09:48:48.10147+00 2 中大 中大 地区 +2025-11-18 15:31:25.825228+00 2025-11-18 15:31:25.825236+00 3 牛奶丝 牛奶丝 布料 +2025-11-18 15:31:40.483308+00 2025-11-18 15:31:40.483315+00 4 1.5米 1.5米 幅宽 +2025-11-19 04:41:28.625384+00 2025-11-19 04:41:28.625402+00 5 批布 批布 工艺 +2025-11-19 04:46:07.968444+00 2025-11-19 04:46:07.968452+00 6 仓库布 仓库布 布源 +2025-11-19 06:09:13.561928+00 2025-11-19 06:09:13.561936+00 7 测试 测试 曲线 +2025-11-19 06:09:18.26355+00 2025-11-19 06:09:18.263558+00 8 3124123 3124123 新加曲线 +2025-11-19 08:25:56.851308+00 2025-11-19 08:25:56.851316+00 9 测试 测试 布料 +2025-11-20 06:13:06.17721+00 2025-11-20 06:13:06.177218+00 10 画图难度1 画图难度1 画图评级 +2025-11-20 06:13:20.783887+00 2025-11-20 06:13:20.783896+00 11 调色难度1 调色难度1 调色评级 +2025-11-20 06:13:34.886151+00 2025-11-20 06:13:34.886159+00 12 套样难度1 套样难度1 套样评级 +2025-11-20 06:13:57.754838+00 2025-11-20 06:13:57.75485+00 13 难度1 难度1 难度评级 +2025-11-20 06:17:51.458274+00 2025-11-20 06:17:51.458287+00 14 首版 首版 起版情况 +2025-11-20 06:18:08.574225+00 2025-11-20 06:18:08.574233+00 15 加急 加急 紧急程度 +2025-11-20 06:18:21.724272+00 2025-11-20 06:18:21.724285+00 16 匹布 匹布 做货方式 +2025-11-20 06:18:34.280577+00 2025-11-20 06:18:34.280585+00 17 图片开版 图片开版 开版方式 +2025-11-21 08:42:37.687031+00 2025-11-21 08:42:37.687043+00 18 样衣开版 样衣开版 开版方式 +2025-11-21 08:42:43.58409+00 2025-11-21 08:42:43.584103+00 19 文件开版 文件开版 开版方式 +2025-11-21 08:42:47.910144+00 2025-11-21 08:42:47.910152+00 20 定位 定位 做货方式 +2025-11-21 08:43:02.652536+00 2025-11-21 08:43:02.652544+00 21 紧急已下单 紧急已下单 紧急程度 +2025-11-23 13:09:39.526104+00 2025-11-23 13:09:39.526113+00 22 修改单 修改单 起版情况 +2025-11-24 11:29:21.156305+00 2025-11-24 11:29:21.156323+00 23 周边 周边 地区 +\. + + +-- +-- Data for Name: basic_info_supplier; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_supplier (created_at, updated_at, id, name, area, email, mobile, contact, description, merchant_id) FROM stdin; +2025-11-26 03:14:51.868499+00 2025-11-26 03:14:51.868508+00 1 测试 123 \N \N \N \N 1 +\. + + +-- +-- Data for Name: basic_info_userprofile; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_userprofile (created_at, updated_at, id, description, merchant_id, user_id) FROM stdin; +2025-11-24 03:12:45.2506+00 2025-11-24 03:12:45.250611+00 1 1 2 +2025-11-24 05:45:22.720006+00 2025-11-24 05:45:22.720012+00 3 测试用户资料 1 5 +2025-11-24 06:08:19.191142+00 2025-11-24 06:08:19.191149+00 4 supersuper111 1 6 +2025-11-24 06:16:49.714832+00 2025-11-24 06:16:49.714838+00 5 supersuper111 1 7 +\. + + +-- +-- Data for Name: basic_info_vehicletransportrecord; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_vehicletransportrecord (created_at, updated_at, id, driver_name, contact_number, license_plate, delivery_date, merchant_id, vehicle_type_id) FROM stdin; +\. + + +-- +-- Data for Name: basic_info_vehicletype; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_vehicletype (created_at, updated_at, id, name, description, merchant_id) FROM stdin; +\. + + +-- +-- Data for Name: basic_info_warehouse; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.basic_info_warehouse (created_at, updated_at, id, name, location, contact, mobile, area, description, merchant_id, mode, type) FROM stdin; +2025-11-20 06:43:01.759659+00 2025-11-20 06:43:01.75967+00 3 中大(散) 中大2号 老铁 13310001999 \N 1 1 2 +2025-11-25 09:11:29.252782+00 2025-11-25 09:11:29.252792+00 4 测试 \N \N \N \N \N 1 2 1 +2025-11-20 06:42:36.740923+00 2025-11-25 10:18:01.333513+00 2 中大 中大1号 老铁 13310001999 \N 1 2 1 +\. + + +-- +-- Data for Name: business_object; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.business_object (id, created_at, updated_at, name, description, process_id, content_type_id, object_id) FROM stdin; +1 2025-11-18 09:55:35.336835+00 2025-11-18 09:55:35.336842+00 1 \N \N +2 2025-11-18 09:58:01.451306+00 2025-11-18 09:58:01.451311+00 1 \N \N +3 2025-11-18 09:58:20.381316+00 2025-11-18 09:58:20.38132+00 1 \N \N +4 2025-11-18 10:03:03.629943+00 2025-11-18 10:03:03.629948+00 PrintingJob-1 印染任务 1 的流程实例 1 \N \N +5 2025-11-18 10:09:47.339798+00 2025-11-18 10:09:47.339803+00 2 \N \N +6 2025-11-18 10:10:04.823874+00 2025-11-18 10:10:04.823879+00 2 \N \N +7 2025-11-18 10:10:55.526526+00 2025-11-18 10:10:55.526533+00 2 \N \N +8 2025-11-18 10:15:58.290203+00 2025-11-18 10:15:58.290207+00 PrintingJob-2 印染任务 2 的流程实例 1 \N \N +9 2025-11-18 10:35:25.880305+00 2025-11-18 10:35:25.880312+00 2 \N \N +10 2025-11-18 10:42:48.817571+00 2025-11-18 10:42:48.817579+00 PrintingJob-3 印染任务 3 的流程实例 1 \N \N +11 2025-11-18 10:42:48.848235+00 2025-11-18 10:42:48.84824+00 PrintingJob-4 印染任务 4 的流程实例 1 \N \N +12 2025-11-19 01:44:43.687468+00 2025-11-19 01:44:43.687472+00 2 \N \N +13 2025-11-19 02:00:49.486776+00 2025-11-19 02:00:49.486782+00 2 \N \N +14 2025-11-19 02:00:59.586971+00 2025-11-19 02:00:59.586976+00 2 \N \N +15 2025-11-19 02:04:48.934697+00 2025-11-19 02:04:48.934703+00 2 \N \N +16 2025-11-19 05:10:28.186858+00 2025-11-19 05:10:28.186863+00 PrintingJob-5 印染任务 5 的流程实例 1 \N \N +17 2025-11-19 06:16:00.259932+00 2025-11-19 06:16:00.259937+00 PrintingJob-6 印染任务 6 的流程实例 1 \N \N +18 2025-11-19 08:21:50.459601+00 2025-11-19 08:21:50.459608+00 PrintingJob-7 印染任务 7 的流程实例 1 \N \N +19 2025-11-19 09:28:09.461746+00 2025-11-19 09:28:09.461752+00 2 \N \N +20 2025-11-19 09:28:40.842389+00 2025-11-19 09:28:40.842394+00 2 \N \N +21 2025-11-20 01:52:49.286921+00 2025-11-20 01:52:49.286927+00 2 \N \N +22 2025-11-20 03:48:01.385016+00 2025-11-20 03:48:01.385022+00 2 \N \N +23 2025-11-20 03:49:28.254688+00 2025-11-20 03:49:28.254694+00 2 \N \N +24 2025-11-20 03:49:37.951494+00 2025-11-20 03:49:37.951499+00 1 \N \N +25 2025-11-20 06:55:48.528541+00 2025-11-20 06:55:48.528548+00 2 \N \N +26 2025-11-21 03:10:15.020199+00 2025-11-21 03:10:15.020203+00 PrintingJob-8 印染任务 8 的流程实例 1 \N \N +27 2025-11-21 08:45:33.732267+00 2025-11-21 08:45:33.732274+00 2 \N \N +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 +\. + + +-- +-- 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; +\. + + +-- +-- Data for Name: django_admin_log; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.django_admin_log (id, action_time, object_id, object_repr, action_flag, change_message, content_type_id, user_id) FROM stdin; +1 2025-11-18 05:59:33.764516+00 2 jimi 1 [{"added": {}}] 4 1 +2 2025-11-18 05:59:53.386691+00 2 jimi 2 [{"changed": {"fields": ["User permissions"]}}] 4 1 +3 2025-11-18 06:00:31.466055+00 1 瑞彩印花 1 [{"added": {}}] 7 1 +4 2025-11-18 06:00:44.202936+00 1 左威 1 [{"added": {}}] 8 1 +5 2025-11-18 06:15:00.809879+00 1 (印染厂生产)生产记录 1 [{"added": {}}] 31 1 +6 2025-11-18 06:17:56.054095+00 2 (印染厂生产)生产订单详情 1 [{"added": {}}] 31 1 +7 2025-11-18 06:18:26.252531+00 3 (印染厂生产)打印米数 1 [{"added": {}}] 31 1 +8 2025-11-18 06:18:36.506619+00 4 (印染厂生产)设备 1 [{"added": {}}] 31 1 +9 2025-11-18 06:18:51.697773+00 5 (印染厂生产)日期 1 [{"added": {}}] 31 1 +10 2025-11-18 06:36:14.719914+00 6 (印染厂生产)提交人 1 [{"added": {}}] 31 1 +11 2025-11-18 06:37:41.608909+00 7 (印染厂滚筒)生产记录 1 [{"added": {}}] 31 1 +12 2025-11-18 06:37:53.386955+00 8 (印染厂滚筒)生产记录明细 1 [{"added": {}}] 31 1 +13 2025-11-18 06:38:45.857424+00 9 (印染厂滚筒)生产单上传 1 [{"added": {}}] 31 1 +14 2025-11-18 06:39:07.772669+00 10 (印染厂滚筒)温度转速 1 [{"added": {}}] 31 1 +15 2025-11-18 06:41:38.316225+00 11 (印染厂滚筒)设备 1 [{"added": {}}] 31 1 +16 2025-11-18 06:41:46.718822+00 12 (印染厂滚筒)日期 1 [{"added": {}}] 31 1 +17 2025-11-18 06:41:52.441714+00 13 (印染厂滚筒)提交人 1 [{"added": {}}] 31 1 +18 2025-11-18 06:42:24.13544+00 1 待打纸 1 [{"added": {}}] 29 1 +19 2025-11-18 06:42:40.876823+00 2 待滚筒 1 [{"added": {}}] 29 1 +20 2025-11-18 06:42:51.439077+00 3 待送货 1 [{"added": {}}] 29 1 +21 2025-11-18 06:43:03.132987+00 4 送货完成 1 [{"added": {}}] 29 1 +22 2025-11-18 06:43:09.832876+00 5 待开单 1 [{"added": {}}] 29 1 +23 2025-11-18 06:43:24.578356+00 6 订单完结 1 [{"added": {}}] 29 1 +24 2025-11-18 06:44:18.308781+00 1 工厂印染 1 [{"added": {}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5de5\\u5382\\u5370\\u67d3 -> \\u5f85\\u6253\\u7eb8 (1)"}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5de5\\u5382\\u5370\\u67d3 -> \\u5f85\\u6eda\\u7b52 (2)"}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5de5\\u5382\\u5370\\u67d3 -> \\u5f85\\u9001\\u8d27 (3)"}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5de5\\u5382\\u5370\\u67d3 -> \\u9001\\u8d27\\u5b8c\\u6210 (4)"}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5de5\\u5382\\u5370\\u67d3 -> \\u5f85\\u5f00\\u5355 (5)"}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5de5\\u5382\\u5370\\u67d3 -> \\u8ba2\\u5355\\u5b8c\\u7ed3 (6)"}}] 30 1 +25 2025-11-18 07:03:18.962392+00 14 (印染厂开版画图)开版编号 1 [{"added": {}}] 31 1 +26 2025-11-18 07:04:21.326366+00 15 (印染厂开版画图)时间 1 [{"added": {}}] 31 1 +27 2025-11-18 07:04:40.367528+00 16 (印染厂开版画图)状态 1 [{"added": {}}] 31 1 +28 2025-11-18 07:04:50.258605+00 17 设计师名称 1 [{"added": {}}] 31 1 +29 2025-11-18 07:05:16.159648+00 17 (印染厂开版画图)设计师名称 2 [{"changed": {"fields": ["\\u53c2\\u6570\\u952e", "\\u53c2\\u6570\\u503c"]}}] 31 1 +30 2025-11-18 07:06:26.838863+00 18 (印染厂开版画图)电脑位置 1 [{"added": {}}] 31 1 +31 2025-11-18 07:06:40.088982+00 19 (印染厂开版画图)描述 1 [{"added": {}}] 31 1 +32 2025-11-18 07:07:20.127062+00 20 (印染厂开版画图)附件 1 [{"added": {}}] 31 1 +33 2025-11-18 07:07:31.519147+00 21 (印染厂开版画图)完成时间 1 [{"added": {}}] 31 1 +34 2025-11-18 07:09:18.686085+00 15 (印染厂开版画图)开始时间 2 [{"changed": {"fields": ["\\u53c2\\u6570\\u952e", "\\u53c2\\u6570\\u503c"]}}] 31 1 +35 2025-11-18 07:10:11.825565+00 22 (印染厂开版画图)完成数量 1 [{"added": {}}] 31 1 +36 2025-11-18 07:33:51.375829+00 23 test_param_001 3 31 1 +37 2025-11-18 07:58:28.856338+00 24 test_param_002 3 31 1 +38 2025-11-18 08:05:17.209889+00 51 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)开版编号 3 31 1 +39 2025-11-18 08:05:17.20991+00 50 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)开始时间 3 31 1 +40 2025-11-18 08:05:17.209918+00 49 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)状态 3 31 1 +41 2025-11-18 08:05:17.209925+00 48 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)设计师名称 3 31 1 +42 2025-11-18 08:05:17.209931+00 47 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)电脑位置 3 31 1 +43 2025-11-18 08:05:17.209937+00 46 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)描述 3 31 1 +44 2025-11-18 08:05:17.209943+00 45 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)附件 3 31 1 +45 2025-11-18 08:05:17.209949+00 44 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)完成时间 3 31 1 +46 2025-11-18 08:05:17.209955+00 43 (印染厂开版套样, 印染厂开版改图, 印染厂开版配色)完成数量 3 31 1 +47 2025-11-18 08:08:01.156317+00 27 (印染厂开版调色)附件 2 [{"changed": {"fields": ["\\u662f\\u5426\\u56fe\\u7247\\u8def\\u5f84"]}}] 31 1 +48 2025-11-18 08:08:06.425919+00 36 (印染厂开版找图)附件 2 [{"changed": {"fields": ["\\u662f\\u5426\\u56fe\\u7247\\u8def\\u5f84"]}}] 31 1 +49 2025-11-18 08:08:10.055754+00 54 (印染厂开版套样)附件 2 [{"changed": {"fields": ["\\u662f\\u5426\\u56fe\\u7247\\u8def\\u5f84"]}}] 31 1 +50 2025-11-18 08:08:14.833679+00 63 (印染厂开版配色)附件 2 [{"changed": {"fields": ["\\u662f\\u5426\\u56fe\\u7247\\u8def\\u5f84"]}}] 31 1 +51 2025-11-18 08:08:18.605846+00 72 (印染厂开版改图)附件 2 [{"changed": {"fields": ["\\u662f\\u5426\\u56fe\\u7247\\u8def\\u5f84"]}}] 31 1 +52 2025-11-18 08:08:51.909492+00 81 (印染厂开版P图)附件 2 [{"changed": {"fields": ["\\u662f\\u5426\\u56fe\\u7247\\u8def\\u5f84"]}}] 31 1 +53 2025-11-18 08:12:38.122506+00 7 画图 1 [{"added": {}}] 29 1 +54 2025-11-18 08:12:47.851618+00 8 调色 1 [{"added": {}}] 29 1 +55 2025-11-18 08:13:08.01894+00 9 套样 1 [{"added": {}}] 29 1 +56 2025-11-18 08:13:23.681664+00 10 改图 1 [{"added": {}}] 29 1 +57 2025-11-18 08:13:31.884636+00 11 P图 1 [{"added": {}}] 29 1 +58 2025-11-18 08:13:40.578384+00 12 配色 1 [{"added": {}}] 29 1 +59 2025-11-18 08:13:49.183222+00 13 找图 1 [{"added": {}}] 29 1 +60 2025-11-18 08:14:45.362321+00 12 配色 2 [{"changed": {"fields": ["\\u5173\\u8054\\u53c2\\u6570"]}}] 29 1 +61 2025-11-18 08:15:12.813968+00 13 找图 2 [{"changed": {"fields": ["\\u5173\\u8054\\u53c2\\u6570"]}}] 29 1 +62 2025-11-18 10:09:07.215232+00 2 开版 1 [{"added": {}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5f00\\u7248 -> \\u753b\\u56fe (1)"}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5f00\\u7248 -> \\u8c03\\u8272 (2)"}}, {"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5f00\\u7248 -> \\u5957\\u6837 (3)"}}] 30 1 +63 2025-11-18 10:13:04.721271+00 2 开版 2 [] 30 1 +64 2025-11-18 10:13:23.903652+00 2 开版 2 [{"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5f00\\u7248 -> \\u627e\\u56fe (4)"}}] 30 1 +65 2025-11-19 01:45:36.045179+00 8 待调色 2 [{"changed": {"fields": ["\\u72b6\\u6001\\u540d\\u79f0"]}}] 29 1 +66 2025-11-19 01:45:39.684091+00 2 开版 2 [] 30 1 +67 2025-11-19 02:13:50.033205+00 14 任务完成 1 [{"added": {}}] 29 1 +68 2025-11-19 02:14:21.623907+00 2 开版 2 [{"added": {"name": "\\u6d41\\u7a0b\\u8282\\u70b9\\u5173\\u8054", "object": "\\u5f00\\u7248 -> \\u4efb\\u52a1\\u5b8c\\u6210 (5)"}}] 30 1 +69 2025-11-19 08:12:47.374521+00 3 映雪 1 [{"added": {}}] 4 1 +70 2025-11-19 08:14:04.719762+00 3 映雪 2 [{"changed": {"fields": ["User permissions"]}}] 4 1 +71 2025-11-19 08:14:37.879969+00 2 映雪 1 [{"added": {}}] 8 1 +72 2025-11-20 05:39:43.3355+00 1 设计师 1 [{"added": {}}] 3 1 +73 2025-11-20 06:06:12.752762+00 1 设计师 3 3 1 +74 2025-11-20 06:10:02.468122+00 2 设计师 1 [{"added": {}}] 36 1 +75 2025-11-20 06:10:09.964149+00 1 左威 2 [{"changed": {"fields": ["\\u804c\\u4f4d"]}}] 8 1 +76 2025-11-20 06:10:14.372307+00 2 映雪 2 [{"changed": {"fields": ["\\u804c\\u4f4d"]}}] 8 1 +77 2025-11-20 06:42:36.741573+00 2 中大 1 [{"added": {}}] 17 1 +78 2025-11-20 06:43:01.760474+00 3 中大(散) 1 [{"added": {}}] 17 1 +79 2025-11-20 07:53:27.081642+00 1 出入库记录 1 - 仓库:中大 1 [{"added": {}}] 20 1 +80 2025-11-20 07:53:51.401153+00 1 出入库记录 1 - 仓库:中大 3 20 1 +81 2025-11-20 07:54:03.431107+00 2 出入库记录 2 - 仓库:中大 1 [{"added": {}}] 20 1 +82 2025-11-20 07:54:28.235099+00 1 库存变动明细 1 - 记录ID: 2 1 [{"added": {}}] 21 1 +83 2025-11-20 08:00:58.976268+00 3 出入库记录 3 - 仓库:中大 1 [{"added": {}}] 20 1 +84 2025-11-20 08:01:23.823488+00 2 库存变动明细 2 - 记录ID: 3 1 [{"added": {}}] 21 1 +85 2025-11-20 08:02:18.095284+00 4 出入库记录 4 - 仓库:中大(散) 1 [{"added": {}}] 20 1 +86 2025-11-20 08:02:47.394849+00 3 库存变动明细 3 - 记录ID: 4 1 [{"added": {}}] 21 1 +87 2025-11-24 03:12:45.251366+00 1 jimi's Profile 1 [{"added": {}}] 37 1 +88 2025-11-24 03:13:33.181153+00 2 映雪's Profile 1 [{"added": {}}] 37 1 +89 2025-11-24 03:16:35.368619+00 2 映雪's Profile 3 37 1 +90 2025-11-24 06:15:31.40043+00 2 jimi 2 [{"changed": {"fields": ["User permissions", "Last login"]}}] 4 1 +91 2025-11-24 09:16:10.732374+00 2 jimi 2 [{"changed": {"fields": ["User permissions"]}}] 4 1 +92 2025-11-25 06:12:23.660069+00 2 jimi 2 [{"changed": {"fields": ["User permissions"]}}] 4 1 +93 2025-11-25 09:20:41.470473+00 9 出入库记录 9 - 仓库:中大 1 [{"added": {}}] 20 1 +94 2025-11-25 09:21:02.981897+00 25 库存变动明细 25 - 记录ID: 9 1 [{"added": {}}] 21 1 +95 2025-11-25 09:28:40.652361+00 10 出入库记录 10 - 仓库:中大 1 [{"added": {}}] 20 1 +96 2025-11-25 09:28:55.964661+00 26 库存变动明细 26 - 记录ID: 10 1 [{"added": {}}] 21 1 +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 +\. + + +-- +-- Data for Name: django_content_type; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.django_content_type (id, app_label, model) FROM stdin; +1 admin logentry +2 auth permission +3 auth group +4 auth user +5 contenttypes contenttype +6 sessions session +7 basic_info merchant +8 basic_info employee +9 basic_info deviceinfo +10 basic_info customer +11 basic_info bankaccount +12 basic_info productcategory +13 basic_info product +14 basic_info supplier +15 basic_info vehicletype +16 basic_info vehicletransportrecord +17 basic_info warehouse +18 basic_info quickinput +19 stock purchaseorder +20 stock stockchangerecord +21 stock stockchangedetail +22 stock stocksnapshot +23 stock inventory +24 stock stockfreeze +25 api_v1 uploadedfile +26 printing printingorder +27 printing printingjob +28 printing plateorder +29 stateflow state +30 stateflow process +31 stateflow stateparameter +32 stateflow processnode +33 stateflow businessobject +34 stateflow stateflowrecord +35 stateflow statelogparameterrecord +36 basic_info employeetype +37 basic_info userprofile +38 business purchaseorder +\. + + +-- +-- Data for Name: django_migrations; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.django_migrations (id, app, name, applied) FROM stdin; +1 contenttypes 0001_initial 2025-11-18 05:57:57.606096+00 +2 auth 0001_initial 2025-11-18 05:57:57.649887+00 +3 admin 0001_initial 2025-11-18 05:57:57.661912+00 +4 admin 0002_logentry_remove_auto_add 2025-11-18 05:57:57.665247+00 +5 admin 0003_logentry_add_action_flag_choices 2025-11-18 05:57:57.668422+00 +6 api_v1 0001_initial 2025-11-18 05:57:57.678425+00 +7 contenttypes 0002_remove_content_type_name 2025-11-18 05:57:57.685116+00 +8 auth 0002_alter_permission_name_max_length 2025-11-18 05:57:57.688996+00 +9 auth 0003_alter_user_email_max_length 2025-11-18 05:57:57.692915+00 +10 auth 0004_alter_user_username_opts 2025-11-18 05:57:57.696222+00 +11 auth 0005_alter_user_last_login_null 2025-11-18 05:57:57.700191+00 +12 auth 0006_require_contenttypes_0002 2025-11-18 05:57:57.701563+00 +13 auth 0007_alter_validators_add_error_messages 2025-11-18 05:57:57.705386+00 +14 auth 0008_alter_user_username_max_length 2025-11-18 05:57:57.713231+00 +15 auth 0009_alter_user_last_name_max_length 2025-11-18 05:57:57.718913+00 +16 auth 0010_alter_group_name_max_length 2025-11-18 05:57:57.724371+00 +17 auth 0011_update_proxy_permissions 2025-11-18 05:57:57.728075+00 +18 auth 0012_alter_user_first_name_max_length 2025-11-18 05:57:57.731791+00 +19 basic_info 0001_initial 2025-11-18 05:57:57.853129+00 +20 basic_info 0002_quickinput 2025-11-18 05:57:57.858206+00 +21 basic_info 0003_alter_quickinput_unique_together 2025-11-18 05:57:57.861506+00 +22 basic_info 0004_customer_visible_employees 2025-11-18 05:57:57.878024+00 +23 basic_info 0005_customer_created_by 2025-11-18 05:57:57.887911+00 +24 basic_info 0006_alter_product_merchant 2025-11-18 05:57:57.895034+00 +25 basic_info 0007_warehouse_mode 2025-11-18 05:57:57.901277+00 +26 basic_info 0008_merchant_auto_complete_stock_change 2025-11-18 05:57:57.90704+00 +27 basic_info 0009_product_minimum_quantity 2025-11-18 05:57:57.912464+00 +28 stateflow 0001_initial 2025-11-18 05:57:57.926089+00 +29 stateflow 0002_remove_state_data_stateparameter 2025-11-18 05:57:57.9356+00 +30 stateflow 0003_alter_process_options_alter_state_options_and_more 2025-11-18 05:57:57.951752+00 +31 stateflow 0004_remove_state_previous 2025-11-18 05:57:57.9565+00 +32 stateflow 0005_alter_state_description_and_more 2025-11-18 05:57:57.967543+00 +33 stateflow 0006_remove_process_state_nodes_processnode_and_more 2025-11-18 05:57:57.983497+00 +34 stateflow 0007_alter_process_current_state 2025-11-18 05:57:57.989058+00 +35 stateflow 0008_remove_process_current_state_order 2025-11-18 05:57:58.005641+00 +36 stateflow 0009_remove_order_current_state_orderstatelog 2025-11-18 05:57:58.032921+00 +37 stateflow 0010_remove_orderstatelog_notes_and_more 2025-11-18 05:57:58.069827+00 +38 stateflow 0011_rename_order_to_business_object 2025-11-18 05:57:58.150215+00 +39 stateflow 0012_alter_businessobject_content_type_and_more 2025-11-18 05:57:58.17801+00 +40 stateflow 0013_alter_stateflowrecord_unique_together 2025-11-18 05:57:58.187343+00 +41 stateflow 0014_alter_process_options 2025-11-18 05:57:58.190803+00 +42 stateflow 0015_stateparameter_attachment 2025-11-18 05:57:58.193874+00 +43 printing 0001_initial 2025-11-18 05:57:58.231485+00 +44 printing 0002_alter_printingorder_craft_and_more 2025-11-18 05:57:58.237799+00 +45 printing 0003_remove_printingjob_status 2025-11-18 05:57:58.248458+00 +46 printing 0004_printingorder_is_invalid 2025-11-18 05:57:58.252566+00 +47 printing 0005_alter_printingorder_options 2025-11-18 05:57:58.256683+00 +48 printing 0006_printingjob_business_object_printingorder_process 2025-11-18 05:57:58.282396+00 +49 printing 0007_alter_printingjob_description_and_more 2025-11-18 05:57:58.295621+00 +50 printing 0008_plateorder 2025-11-18 05:57:58.324365+00 +51 printing 0009_remove_plateorder_development_status 2025-11-18 05:57:58.331238+00 +52 printing 0010_remove_plateorder_plate_code 2025-11-18 05:57:58.338116+00 +53 printing 0011_alter_plateorder_options_plateorder_is_invalid 2025-11-18 05:57:58.350452+00 +54 printing 0012_plateorder_process 2025-11-18 05:57:58.357239+00 +55 sessions 0001_initial 2025-11-18 05:57:58.365556+00 +56 stateflow 0016_alter_stateparameter_key_alter_stateparameter_value 2025-11-18 05:57:58.370806+00 +57 stateflow 0017_state_parameter_many_to_many 2025-11-18 05:57:58.427689+00 +58 stateflow 0018_remove_stateparameter_name 2025-11-18 05:57:58.430207+00 +59 stateflow 0019_stateparameter_is_required 2025-11-18 05:57:58.433063+00 +60 stateflow 0020_stateparameter_is_image_path 2025-11-18 05:57:58.435565+00 +61 stateflow 0021_statelogparameterrecord 2025-11-18 05:57:58.454384+00 +62 stock 0001_initial 2025-11-18 05:57:58.565539+00 +63 stock 0002_stockfreeze 2025-11-18 05:57:58.605245+00 +64 stock 0003_remove_inventory_minimum_quantity 2025-11-18 05:57:58.614748+00 +65 printing 0013_plateorder_fabric_source_alter_plateorder_process 2025-11-20 03:24:40.935777+00 +66 printing 0014_auto_20251120_1144 2025-11-20 03:47:32.344205+00 +67 basic_info 0010_warehouse_type 2025-11-20 05:11:33.321558+00 +68 basic_info 0011_remove_employee_job_type_employeetype_and_more 2025-11-20 05:56:35.088569+00 +69 printing 0015_plateorder_designer 2025-11-20 06:48:57.234756+00 +70 api_v1 0002_alter_uploadedfile_path 2025-11-24 03:07:15.613802+00 +71 basic_info 0012_userprofile 2025-11-24 03:07:15.640816+00 +72 stock 0004_alter_stockchangerecord_source_type 2025-11-24 03:07:15.658993+00 +73 basic_info 0013_alter_customer_options 2025-11-24 09:06:44.27194+00 +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 +\. + + +-- +-- Data for Name: django_session; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.django_session (session_key, session_data, expire_date) FROM stdin; +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 +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 +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 +\. + + +-- +-- 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 +\. + + +-- +-- Data for Name: printing_printingjob; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.printing_printingjob (id, created_at, updated_at, quantity, unit, size, pieces, description, product_id, printing_order_id, business_object_id) FROM stdin; +1 2025-11-18 10:03:03.628899+00 2025-11-18 10:03:03.630299+00 123 米 123 0 123 1 1 4 +2 2025-11-18 10:15:58.28923+00 2025-11-18 10:15:58.290546+00 456 米 456 0 456 1 2 8 +3 2025-11-18 10:42:48.816111+00 2025-11-18 10:42:48.818357+00 4 米 3 23 23 1 5 10 +4 2025-11-18 10:42:48.847294+00 2025-11-18 10:42:48.848577+00 32 米 23 23 23 2 5 11 +5 2025-11-19 05:10:28.185845+00 2025-11-19 05:10:28.187238+00 46 米 0 \N 1 7 16 +6 2025-11-19 06:16:00.258989+00 2025-11-19 06:16:00.260276+00 23 米 0 \N 1 8 17 +7 2025-11-19 08:21:50.457892+00 2025-11-19 08:21:50.460214+00 53 米 0 \N 1 9 18 +8 2025-11-21 03:10:15.018605+00 2025-11-21 03:10:15.020737+00 312 米 32 0 23 1 10 26 +\. + + +-- +-- 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 +\. + + +-- +-- Data for Name: state_flow_record; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.state_flow_record (id, created_at, updated_at, completed_at, completed_by_id, business_object_id, state_id, cancelled_at, is_cancelled) FROM stdin; +1 2025-11-18 09:58:46.228909+00 2025-11-18 09:58:46.228916+00 2025-11-18 09:58:46.228922+00 2 3 1 \N f +2 2025-11-18 10:01:34.15756+00 2025-11-18 10:01:34.157567+00 2025-11-18 10:01:34.157573+00 2 3 2 \N f +3 2025-11-18 10:01:35.677197+00 2025-11-18 10:01:35.677205+00 2025-11-18 10:01:35.677211+00 2 3 3 \N f +4 2025-11-18 10:01:36.99464+00 2025-11-18 10:01:36.994647+00 2025-11-18 10:01:36.994653+00 2 3 4 \N f +5 2025-11-18 10:01:38.319629+00 2025-11-18 10:01:38.319637+00 2025-11-18 10:01:38.319642+00 2 3 5 \N f +6 2025-11-18 10:01:39.955294+00 2025-11-18 10:01:39.955302+00 2025-11-18 10:01:39.955307+00 2 3 6 \N f +7 2025-11-18 10:01:46.861557+00 2025-11-18 10:01:46.861564+00 2025-11-18 10:01:46.86157+00 2 2 1 \N f +8 2025-11-18 10:01:48.658461+00 2025-11-18 10:01:48.658469+00 2025-11-18 10:01:48.658475+00 2 2 2 \N f +9 2025-11-18 10:01:50.173885+00 2025-11-18 10:01:50.173893+00 2025-11-18 10:01:50.173899+00 2 2 3 \N f +10 2025-11-18 10:01:52.545328+00 2025-11-18 10:01:52.545336+00 2025-11-18 10:01:52.545342+00 2 2 4 \N f +11 2025-11-18 10:01:57.787351+00 2025-11-18 10:01:57.787358+00 2025-11-18 10:01:57.787364+00 2 1 1 \N f +12 2025-11-18 10:01:59.345255+00 2025-11-18 10:01:59.345264+00 2025-11-18 10:01:59.34527+00 2 1 2 \N f +13 2025-11-18 10:09:49.997167+00 2025-11-18 10:09:49.997174+00 2025-11-18 10:09:49.997181+00 2 5 7 \N f +14 2025-11-18 10:09:52.586971+00 2025-11-18 10:09:52.58698+00 2025-11-18 10:09:52.586989+00 2 5 8 \N f +15 2025-11-18 10:09:54.553201+00 2025-11-18 10:09:54.553209+00 2025-11-18 10:09:54.553215+00 2 5 9 \N f +16 2025-11-18 10:10:07.994049+00 2025-11-18 10:10:07.994057+00 2025-11-18 10:10:07.994062+00 2 6 7 \N f +17 2025-11-18 10:11:01.556394+00 2025-11-18 10:11:01.556401+00 2025-11-18 10:11:01.556407+00 2 7 7 2025-11-18 10:12:15.089061+00 t +18 2025-11-18 10:12:29.524144+00 2025-11-18 10:12:29.524151+00 2025-11-18 10:12:29.524157+00 2 7 7 2025-11-18 10:12:40.839687+00 t +19 2025-11-18 10:12:44.940612+00 2025-11-18 10:12:44.94062+00 2025-11-18 10:12:44.940628+00 1 7 7 2025-11-18 10:13:33.151024+00 t +21 2025-11-18 10:14:30.398411+00 2025-11-18 10:14:30.39842+00 2025-11-18 10:14:30.398426+00 2 4 1 \N f +22 2025-11-18 10:18:26.785459+00 2025-11-18 10:18:26.785471+00 2025-11-18 10:18:26.785482+00 1 8 1 2025-11-18 10:18:41.280544+00 t +20 2025-11-18 10:13:35.858114+00 2025-11-18 10:13:35.85812+00 2025-11-18 10:13:35.858126+00 1 7 7 2025-11-18 10:23:22.848587+00 t +23 2025-11-18 10:18:48.568608+00 2025-11-18 10:18:48.568615+00 2025-11-18 10:18:48.568622+00 1 8 1 2025-11-18 10:24:21.224924+00 t +24 2025-11-18 10:24:27.361927+00 2025-11-18 10:24:27.361935+00 2025-11-18 10:24:27.361944+00 1 8 1 2025-11-18 10:25:12.155197+00 t +26 2025-11-18 10:25:26.794891+00 2025-11-18 10:25:26.794897+00 2025-11-18 10:25:26.794904+00 1 8 1 2025-11-18 10:29:49.223123+00 t +25 2025-11-18 10:24:33.486794+00 2025-11-18 10:24:33.486805+00 2025-11-18 10:24:33.486816+00 2 4 2 2025-11-18 10:30:07.11929+00 t +29 2025-11-18 10:30:13.309787+00 2025-11-18 10:30:13.309793+00 2025-11-18 10:30:13.309799+00 1 7 8 2025-11-18 10:30:17.03601+00 t +27 2025-11-18 10:26:59.40634+00 2025-11-18 10:26:59.406347+00 2025-11-18 10:26:59.406353+00 1 7 7 2025-11-18 10:30:19.302114+00 t +30 2025-11-18 10:30:21.79143+00 2025-11-18 10:30:21.79144+00 2025-11-18 10:30:21.791451+00 1 7 7 2025-11-18 10:30:27.468441+00 t +31 2025-11-18 10:30:30.989324+00 2025-11-18 10:30:30.989331+00 2025-11-18 10:30:30.989337+00 1 7 7 2025-11-18 10:31:00.732261+00 t +32 2025-11-18 10:31:17.422396+00 2025-11-18 10:31:17.422403+00 2025-11-18 10:31:17.422409+00 1 7 7 2025-11-18 10:34:10.27213+00 t +33 2025-11-18 10:34:27.057723+00 2025-11-18 10:34:27.05773+00 2025-11-18 10:34:27.057736+00 1 7 7 2025-11-18 10:34:35.804028+00 t +34 2025-11-18 10:35:31.360908+00 2025-11-18 10:35:31.360915+00 2025-11-18 10:35:31.360921+00 2 9 7 2025-11-18 10:36:25.879966+00 t +35 2025-11-18 10:36:44.149491+00 2025-11-18 10:36:44.149497+00 2025-11-18 10:36:44.149503+00 1 9 7 2025-11-18 10:43:32.002901+00 t +39 2025-11-18 15:34:46.83697+00 2025-11-18 15:34:46.836979+00 2025-11-18 15:34:46.836986+00 2 10 1 \N f +41 2025-11-18 15:35:13.447338+00 2025-11-18 15:35:13.447345+00 2025-11-18 15:35:13.447351+00 2 10 2 \N f +40 2025-11-18 15:35:08.177599+00 2025-11-18 15:35:08.177606+00 2025-11-18 15:35:08.177612+00 2 11 2 2025-11-19 01:43:15.822758+00 t +38 2025-11-18 10:48:46.506927+00 2025-11-18 10:48:46.506934+00 2025-11-18 10:48:46.506942+00 1 11 1 2025-11-19 01:43:18.26932+00 t +42 2025-11-19 01:43:35.225802+00 2025-11-19 01:43:35.225811+00 2025-11-19 01:43:35.225822+00 2 11 1 \N f +43 2025-11-19 01:44:46.93038+00 2025-11-19 01:44:46.930387+00 2025-11-19 01:44:46.930394+00 2 12 7 \N f +37 2025-11-18 10:44:46.98388+00 2025-11-18 10:44:46.983888+00 2025-11-18 10:44:46.983894+00 1 7 7 2025-11-19 01:46:12.629407+00 t +28 2025-11-18 10:29:54.025783+00 2025-11-18 10:29:54.02579+00 2025-11-18 10:29:54.025798+00 1 8 1 2025-11-19 01:46:28.824246+00 t +36 2025-11-18 10:43:37.46066+00 2025-11-18 10:43:37.460671+00 2025-11-18 10:43:37.460676+00 1 9 7 2025-11-19 01:46:41.823225+00 t +44 2025-11-19 01:47:10.945666+00 2025-11-19 01:47:10.945674+00 2025-11-19 01:47:10.945679+00 2 9 7 \N f +45 2025-11-19 02:02:07.518988+00 2025-11-19 02:02:07.518996+00 2025-11-19 02:02:07.519001+00 2 14 7 \N f +46 2025-11-19 02:04:56.055193+00 2025-11-19 02:04:56.0552+00 2025-11-19 02:04:56.055207+00 2 15 7 2025-11-19 02:10:15.110689+00 t +47 2025-11-20 02:15:53.773349+00 2025-11-20 02:15:53.773357+00 2025-11-20 02:15:53.773364+00 2 21 7 \N f +49 2025-11-21 02:46:32.322897+00 2025-11-21 02:46:32.322906+00 2025-11-21 02:46:32.322915+00 2 25 8 2025-11-21 03:02:09.35063+00 t +48 2025-11-20 07:42:53.260205+00 2025-11-20 07:42:53.260217+00 2025-11-20 07:42:53.260228+00 3 25 7 2025-11-21 03:02:14.054159+00 t +50 2025-11-21 03:02:26.250283+00 2025-11-21 03:02:26.250291+00 2025-11-21 03:02:26.250297+00 2 25 7 2025-11-21 06:10:39.775117+00 t +51 2025-11-21 07:39:38.586689+00 2025-11-21 07:39:38.586696+00 2025-11-21 07:39:38.586702+00 2 25 7 \N f +\. + + +-- +-- Data for Name: state_log_parameter_record; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.state_log_parameter_record (id, updated_at, parameters, created_at, remark, state_log_id) FROM stdin; +1 2025-11-18 10:14:30.398807+00 {"(印染厂生产)日期": "3", "(印染厂生产)设备": "3", "(印染厂生产)提交人": "3", "(印染厂生产)打印米数": "2", "(印染厂生产)生产记录": "2", "(印染厂生产)生产订单详情": "2"} 2025-11-18 10:14:30.398822+00 21 +2 2025-11-18 10:24:33.487419+00 {"(印染厂滚筒)生产记录": "421"} 2025-11-18 10:24:33.487439+00 25 +3 2025-11-18 15:34:46.837353+00 {"(印染厂生产)日期": "123", "(印染厂生产)设备": "123", "(印染厂生产)提交人": "123", "(印染厂生产)打印米数": "123", "(印染厂生产)生产记录": "123", "(印染厂生产)生产订单详情": "213"} 2025-11-18 15:34:46.837366+00 39 +4 2025-11-18 15:35:08.177999+00 {"(印染厂滚筒)生产记录": "123", "(印染厂滚筒)生产记录明细": "123"} 2025-11-18 15:35:08.178013+00 40 +5 2025-11-19 01:43:35.22624+00 {"(印染厂生产)日期": "3213", "(印染厂生产)设备": "123", "(印染厂生产)提交人": "421", "(印染厂生产)打印米数": "123", "(印染厂生产)生产记录": "123", "(印染厂生产)生产订单详情": "213"} 2025-11-19 01:43:35.226252+00 42 +6 2025-11-20 07:42:53.260881+00 {"(印染厂开版画图)描述": "123", "(印染厂开版画图)状态": "123", "(印染厂开版画图)完成数量": "123", "(印染厂开版画图)完成时间": "123", "(印染厂开版画图)开始时间": "123", "(印染厂开版画图)开版编号": "123", "(印染厂开版画图)电脑位置": "123", "(印染厂开版画图)设计师名称": "123"} 2025-11-20 07:42:53.260897+00 48 +7 2025-11-21 02:46:32.323845+00 {"(印染厂开版调色)附件": "http://t5510mjho.hn-bkt.clouddn.com/uploads/2025/11/21/21aed50ea13146c68d20b4ae3e1b3079.png"} 2025-11-21 02:46:32.323859+00 49 +8 2025-11-21 03:02:26.25063+00 {"(印染厂开版画图)描述": "123", "(印染厂开版画图)状态": "123", "(印染厂开版画图)开版编号": "12123", "(印染厂开版画图)电脑位置": "123", "(印染厂开版画图)设计师名称": "123"} 2025-11-21 03:02:26.250641+00 50 +9 2025-11-21 07:39:38.587038+00 {"(印染厂开版画图)状态": "123", "(印染厂开版画图)开版编号": "123", "(印染厂开版画图)电脑位置": "123", "(印染厂开版画图)设计师名称": "123"} 2025-11-21 07:39:38.587048+00 51 +\. + + +-- +-- Data for Name: stateflow_process; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stateflow_process (id, created_at, updated_at, name, description) FROM stdin; +1 2025-11-18 06:44:18.307114+00 2025-11-18 06:44:18.307121+00 工厂印染 工厂印染 +2 2025-11-18 10:09:07.214001+00 2025-11-19 02:14:21.623166+00 开版 开版 +\. + + +-- +-- Data for Name: stateflow_processnode; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stateflow_processnode (id, created_at, updated_at, "order", process_id, state_id) FROM stdin; +1 2025-11-18 06:44:18.3076+00 2025-11-18 06:44:18.307604+00 1 1 1 +2 2025-11-18 06:44:18.308102+00 2025-11-18 06:44:18.308105+00 2 1 2 +3 2025-11-18 06:44:18.308263+00 2025-11-18 06:44:18.308265+00 3 1 3 +4 2025-11-18 06:44:18.308391+00 2025-11-18 06:44:18.308393+00 4 1 4 +5 2025-11-18 06:44:18.308509+00 2025-11-18 06:44:18.308511+00 5 1 5 +6 2025-11-18 06:44:18.308622+00 2025-11-18 06:44:18.308624+00 6 1 6 +7 2025-11-18 10:09:07.214444+00 2025-11-18 10:09:07.214448+00 1 2 7 +8 2025-11-18 10:09:07.214896+00 2025-11-18 10:09:07.214899+00 2 2 8 +9 2025-11-18 10:09:07.215051+00 2025-11-18 10:09:07.215054+00 3 2 9 +10 2025-11-18 10:13:23.903321+00 2025-11-18 10:13:23.903325+00 4 2 13 +11 2025-11-19 02:14:21.623562+00 2025-11-19 02:14:21.623566+00 5 2 14 +\. + + +-- +-- Data for Name: stateflow_state; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stateflow_state (id, created_at, updated_at, name, description) FROM stdin; +1 2025-11-18 06:42:24.133555+00 2025-11-18 06:42:24.133562+00 待打纸 待打纸 +2 2025-11-18 06:42:40.875192+00 2025-11-18 06:42:40.875199+00 待滚筒 +3 2025-11-18 06:42:51.437497+00 2025-11-18 06:42:51.437505+00 待送货 待送货 +4 2025-11-18 06:43:03.131203+00 2025-11-18 06:43:03.13121+00 送货完成 送货完成 +5 2025-11-18 06:43:09.83042+00 2025-11-18 06:43:09.830426+00 待开单 待开单 +6 2025-11-18 06:43:24.576632+00 2025-11-18 06:43:24.576639+00 订单完结 +7 2025-11-18 08:12:38.120754+00 2025-11-18 08:12:38.12076+00 画图 画图 +9 2025-11-18 08:13:08.015847+00 2025-11-18 08:13:08.015857+00 套样 套样 +10 2025-11-18 08:13:23.679914+00 2025-11-18 08:13:23.679922+00 改图 +11 2025-11-18 08:13:31.882922+00 2025-11-18 08:13:31.882929+00 P图 +12 2025-11-18 08:13:40.576658+00 2025-11-18 08:14:45.360859+00 配色 +13 2025-11-18 08:13:49.180348+00 2025-11-18 08:15:12.812601+00 找图 +8 2025-11-18 08:12:47.849927+00 2025-11-19 01:45:36.044362+00 待调色 +14 2025-11-19 02:13:50.031495+00 2025-11-19 02:13:50.031504+00 任务完成 +\. + + +-- +-- Data for Name: stateflow_state_parameters; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stateflow_state_parameters (id, state_id, stateparameter_id) FROM stdin; +1 1 1 +2 1 2 +3 1 3 +4 1 4 +5 1 5 +6 1 6 +7 2 7 +8 2 8 +9 2 9 +10 2 10 +11 2 11 +12 2 12 +13 2 13 +14 7 14 +15 7 15 +16 7 16 +17 7 17 +18 7 18 +19 7 19 +20 7 20 +21 7 21 +22 7 22 +23 8 32 +24 8 33 +25 8 25 +26 8 26 +27 8 27 +28 8 28 +29 8 29 +30 8 30 +31 8 31 +32 9 52 +33 9 53 +34 9 54 +35 9 55 +36 9 56 +37 9 57 +38 9 58 +39 9 59 +40 9 60 +41 10 70 +42 10 71 +43 10 72 +44 10 73 +45 10 74 +46 10 75 +47 10 76 +48 10 77 +49 10 78 +50 11 79 +51 11 80 +52 11 81 +53 11 82 +54 11 83 +55 11 84 +56 11 85 +57 11 86 +58 11 87 +59 12 64 +60 12 65 +61 12 66 +62 12 67 +63 12 68 +64 12 69 +65 12 61 +66 12 62 +67 12 63 +68 13 34 +69 13 35 +70 13 36 +71 13 37 +72 13 38 +73 13 39 +74 13 40 +75 13 41 +76 13 42 +\. + + +-- +-- Data for Name: stateflow_stateparameter; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stateflow_stateparameter (id, created_at, updated_at, key, value, description, attachment, is_required, is_image_path) FROM stdin; +1 2025-11-18 06:15:00.809393+00 2025-11-18 06:15:00.8094+00 (印染厂生产)生产记录 (印染厂生产)生产记录 f f +2 2025-11-18 06:17:56.053737+00 2025-11-18 06:17:56.053743+00 (印染厂生产)生产订单详情 (印染厂生产)生产订单详情 f f +3 2025-11-18 06:18:26.25185+00 2025-11-18 06:18:26.251858+00 (印染厂生产)打印米数 (印染厂生产)打印米数 f f +4 2025-11-18 06:18:36.506182+00 2025-11-18 06:18:36.506189+00 (印染厂生产)设备 (印染厂生产)设备 f f +5 2025-11-18 06:18:51.697164+00 2025-11-18 06:18:51.697173+00 (印染厂生产)日期 (印染厂生产)日期 f f +6 2025-11-18 06:36:14.719497+00 2025-11-18 06:36:14.719504+00 (印染厂生产)提交人 (印染厂生产)提交人 f f +7 2025-11-18 06:37:41.608509+00 2025-11-18 06:37:41.608516+00 (印染厂滚筒)生产记录 (印染厂滚筒)生产记录 f f +8 2025-11-18 06:37:53.386596+00 2025-11-18 06:37:53.386602+00 (印染厂滚筒)生产记录明细 (印染厂滚筒)生产记录明细 f f +9 2025-11-18 06:38:45.85707+00 2025-11-18 06:38:45.857077+00 (印染厂滚筒)生产单上传 (印染厂滚筒)生产单上传 f t +10 2025-11-18 06:39:07.772314+00 2025-11-18 06:39:07.772321+00 (印染厂滚筒)温度转速 (印染厂滚筒)温度转速 f f +11 2025-11-18 06:41:38.315857+00 2025-11-18 06:41:38.315863+00 (印染厂滚筒)设备 (印染厂滚筒)设备 f f +12 2025-11-18 06:41:46.718423+00 2025-11-18 06:41:46.71843+00 (印染厂滚筒)日期 (印染厂滚筒)日期 f f +13 2025-11-18 06:41:52.441374+00 2025-11-18 06:41:52.44138+00 (印染厂滚筒)提交人 (印染厂滚筒)提交人 f f +14 2025-11-18 07:03:18.962028+00 2025-11-18 07:03:18.962034+00 (印染厂开版画图)开版编号 (印染厂开版画图)开版编号 f f +16 2025-11-18 07:04:40.367137+00 2025-11-18 07:04:40.367144+00 (印染厂开版画图)状态 (印染厂开版画图)状态 f f +17 2025-11-18 07:04:50.258244+00 2025-11-18 07:05:16.15894+00 (印染厂开版画图)设计师名称 (印染厂开版画图)设计师名称 f f +18 2025-11-18 07:06:26.838519+00 2025-11-18 07:06:26.838526+00 (印染厂开版画图)电脑位置 (印染厂开版画图)电脑位置 f f +19 2025-11-18 07:06:40.088596+00 2025-11-18 07:06:40.088602+00 (印染厂开版画图)描述 (印染厂开版画图)描述 f f +20 2025-11-18 07:07:20.126468+00 2025-11-18 07:07:20.126479+00 (印染厂开版画图)附件 (印染厂开版画图)附件 f t +21 2025-11-18 07:07:31.518786+00 2025-11-18 07:07:31.518792+00 (印染厂开版画图)完成时间 (印染厂开版画图)完成时间 f f +15 2025-11-18 07:04:21.325932+00 2025-11-18 07:09:18.685697+00 (印染厂开版画图)开始时间 (印染厂开版画图)开始时间 f f +22 2025-11-18 07:10:11.825216+00 2025-11-18 07:10:11.825223+00 (印染厂开版画图)完成数量 (印染厂开版画图)完成数量 f f +25 2025-11-18 08:04:06.826847+00 2025-11-18 08:04:06.826855+00 (印染厂开版调色)完成数量 \N (印染厂开版调色)完成数量 f f +26 2025-11-18 08:04:06.893984+00 2025-11-18 08:04:06.893993+00 (印染厂开版调色)完成时间 \N (印染厂开版调色)完成时间 f f +28 2025-11-18 08:04:07.043351+00 2025-11-18 08:04:07.043359+00 (印染厂开版调色)描述 \N (印染厂开版调色)描述 f f +29 2025-11-18 08:04:07.123119+00 2025-11-18 08:04:07.123127+00 (印染厂开版调色)电脑位置 \N (印染厂开版调色)电脑位置 f f +30 2025-11-18 08:04:07.200354+00 2025-11-18 08:04:07.200362+00 (印染厂开版调色)设计师名称 \N (印染厂开版调色)设计师名称 f f +31 2025-11-18 08:04:07.27682+00 2025-11-18 08:04:07.276829+00 (印染厂开版调色)状态 \N (印染厂开版调色)状态 f f +32 2025-11-18 08:04:07.351926+00 2025-11-18 08:04:07.351933+00 (印染厂开版调色)开始时间 \N (印染厂开版调色)开始时间 f f +33 2025-11-18 08:04:07.431118+00 2025-11-18 08:04:07.431127+00 (印染厂开版调色)开版编号 \N (印染厂开版调色)开版编号 f f +34 2025-11-18 08:04:07.758474+00 2025-11-18 08:04:07.758484+00 (印染厂开版找图)完成数量 \N (印染厂开版找图)完成数量 f f +35 2025-11-18 08:04:07.836584+00 2025-11-18 08:04:07.836593+00 (印染厂开版找图)完成时间 \N (印染厂开版找图)完成时间 f f +37 2025-11-18 08:04:07.988476+00 2025-11-18 08:04:07.988484+00 (印染厂开版找图)描述 \N (印染厂开版找图)描述 f f +38 2025-11-18 08:04:08.070932+00 2025-11-18 08:04:08.070943+00 (印染厂开版找图)电脑位置 \N (印染厂开版找图)电脑位置 f f +39 2025-11-18 08:04:08.14465+00 2025-11-18 08:04:08.14466+00 (印染厂开版找图)设计师名称 \N (印染厂开版找图)设计师名称 f f +40 2025-11-18 08:04:08.227005+00 2025-11-18 08:04:08.227019+00 (印染厂开版找图)状态 \N (印染厂开版找图)状态 f f +41 2025-11-18 08:04:08.301492+00 2025-11-18 08:04:08.301503+00 (印染厂开版找图)开始时间 \N (印染厂开版找图)开始时间 f f +42 2025-11-18 08:04:08.378264+00 2025-11-18 08:04:08.378271+00 (印染厂开版找图)开版编号 \N (印染厂开版找图)开版编号 f f +52 2025-11-18 08:06:23.545251+00 2025-11-18 08:06:23.545259+00 (印染厂开版套样)完成数量 \N (印染厂开版套样)完成数量 f f +53 2025-11-18 08:06:23.622055+00 2025-11-18 08:06:23.622062+00 (印染厂开版套样)完成时间 \N (印染厂开版套样)完成时间 f f +55 2025-11-18 08:06:23.775737+00 2025-11-18 08:06:23.775748+00 (印染厂开版套样)描述 \N (印染厂开版套样)描述 f f +56 2025-11-18 08:06:23.848947+00 2025-11-18 08:06:23.848956+00 (印染厂开版套样)电脑位置 \N (印染厂开版套样)电脑位置 f f +57 2025-11-18 08:06:23.929306+00 2025-11-18 08:06:23.929317+00 (印染厂开版套样)设计师名称 \N (印染厂开版套样)设计师名称 f f +58 2025-11-18 08:06:24.004115+00 2025-11-18 08:06:24.004123+00 (印染厂开版套样)状态 \N (印染厂开版套样)状态 f f +59 2025-11-18 08:06:24.078659+00 2025-11-18 08:06:24.078666+00 (印染厂开版套样)开始时间 \N (印染厂开版套样)开始时间 f f +60 2025-11-18 08:06:24.157041+00 2025-11-18 08:06:24.15705+00 (印染厂开版套样)开版编号 \N (印染厂开版套样)开版编号 f f +61 2025-11-18 08:07:09.998372+00 2025-11-18 08:07:09.99838+00 (印染厂开版配色)完成数量 \N (印染厂开版配色)完成数量 f f +62 2025-11-18 08:07:10.083424+00 2025-11-18 08:07:10.083434+00 (印染厂开版配色)完成时间 \N (印染厂开版配色)完成时间 f f +64 2025-11-18 08:07:10.232919+00 2025-11-18 08:07:10.232927+00 (印染厂开版配色)描述 \N (印染厂开版配色)描述 f f +65 2025-11-18 08:07:10.311549+00 2025-11-18 08:07:10.311561+00 (印染厂开版配色)电脑位置 \N (印染厂开版配色)电脑位置 f f +66 2025-11-18 08:07:10.387778+00 2025-11-18 08:07:10.387787+00 (印染厂开版配色)设计师名称 \N (印染厂开版配色)设计师名称 f f +67 2025-11-18 08:07:10.462353+00 2025-11-18 08:07:10.462361+00 (印染厂开版配色)状态 \N (印染厂开版配色)状态 f f +68 2025-11-18 08:07:10.541327+00 2025-11-18 08:07:10.541337+00 (印染厂开版配色)开始时间 \N (印染厂开版配色)开始时间 f f +69 2025-11-18 08:07:10.614664+00 2025-11-18 08:07:10.614671+00 (印染厂开版配色)开版编号 \N (印染厂开版配色)开版编号 f f +70 2025-11-18 08:07:24.736408+00 2025-11-18 08:07:24.736416+00 (印染厂开版改图)完成数量 \N (印染厂开版改图)完成数量 f f +71 2025-11-18 08:07:24.814028+00 2025-11-18 08:07:24.814036+00 (印染厂开版改图)完成时间 \N (印染厂开版改图)完成时间 f f +36 2025-11-18 08:04:07.913709+00 2025-11-18 08:08:06.425557+00 (印染厂开版找图)附件 \N (印染厂开版找图)附件 f t +54 2025-11-18 08:06:23.6976+00 2025-11-18 08:08:10.055142+00 (印染厂开版套样)附件 \N (印染厂开版套样)附件 f t +63 2025-11-18 08:07:10.156413+00 2025-11-18 08:08:14.833088+00 (印染厂开版配色)附件 \N (印染厂开版配色)附件 f t +73 2025-11-18 08:07:24.970837+00 2025-11-18 08:07:24.970848+00 (印染厂开版改图)描述 \N (印染厂开版改图)描述 f f +74 2025-11-18 08:07:25.045797+00 2025-11-18 08:07:25.045806+00 (印染厂开版改图)电脑位置 \N (印染厂开版改图)电脑位置 f f +75 2025-11-18 08:07:25.119207+00 2025-11-18 08:07:25.119215+00 (印染厂开版改图)设计师名称 \N (印染厂开版改图)设计师名称 f f +76 2025-11-18 08:07:25.197472+00 2025-11-18 08:07:25.197481+00 (印染厂开版改图)状态 \N (印染厂开版改图)状态 f f +77 2025-11-18 08:07:25.280098+00 2025-11-18 08:07:25.280106+00 (印染厂开版改图)开始时间 \N (印染厂开版改图)开始时间 f f +78 2025-11-18 08:07:25.350011+00 2025-11-18 08:07:25.350018+00 (印染厂开版改图)开版编号 \N (印染厂开版改图)开版编号 f f +27 2025-11-18 08:04:06.967852+00 2025-11-18 08:08:01.155911+00 (印染厂开版调色)附件 \N (印染厂开版调色)附件 f t +72 2025-11-18 08:07:24.892672+00 2025-11-18 08:08:18.605401+00 (印染厂开版改图)附件 \N (印染厂开版改图)附件 f t +79 2025-11-18 08:08:38.664863+00 2025-11-18 08:08:38.664879+00 (印染厂开版P图)完成数量 \N (印染厂开版P图)完成数量 f f +80 2025-11-18 08:08:38.743771+00 2025-11-18 08:08:38.74378+00 (印染厂开版P图)完成时间 \N (印染厂开版P图)完成时间 f f +82 2025-11-18 08:08:38.902216+00 2025-11-18 08:08:38.902227+00 (印染厂开版P图)描述 \N (印染厂开版P图)描述 f f +83 2025-11-18 08:08:38.972609+00 2025-11-18 08:08:38.972615+00 (印染厂开版P图)电脑位置 \N (印染厂开版P图)电脑位置 f f +84 2025-11-18 08:08:39.047021+00 2025-11-18 08:08:39.047034+00 (印染厂开版P图)设计师名称 \N (印染厂开版P图)设计师名称 f f +85 2025-11-18 08:08:39.122499+00 2025-11-18 08:08:39.122507+00 (印染厂开版P图)状态 \N (印染厂开版P图)状态 f f +86 2025-11-18 08:08:39.191187+00 2025-11-18 08:08:39.191194+00 (印染厂开版P图)开始时间 \N (印染厂开版P图)开始时间 f f +87 2025-11-18 08:08:39.267321+00 2025-11-18 08:08:39.267329+00 (印染厂开版P图)开版编号 \N (印染厂开版P图)开版编号 f f +81 2025-11-18 08:08:38.820486+00 2025-11-18 08:08:51.9089+00 (印染厂开版P图)附件 \N (印染厂开版P图)附件 f t +\. + + +-- +-- Data for Name: stock_inventory; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +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 +\. + + +-- +-- Data for Name: stock_stockchangedetail; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stock_stockchangedetail (created_at, updated_at, id, quantity, unit, merchant_id, product_id, stock_change_record_id, is_consumed, consume_with_id) FROM stdin; +2025-11-20 07:54:28.234159+00 2025-11-20 07:54:28.234167+00 1 50.00 1 1 1 2 f \N +2025-11-20 08:01:23.822874+00 2025-11-20 08:01:23.822882+00 2 25.00 1 1 1 3 f \N +2025-11-20 08:02:47.394261+00 2025-11-20 08:02:47.394267+00 3 25.00 1 1 1 4 f \N +2025-11-25 08:23:25.704772+00 2025-11-25 08:23:25.704776+00 4 45.00 1 1 1 5 f \N +2025-11-25 08:23:25.706869+00 2025-11-25 08:23:25.706873+00 5 45.00 1 1 1 5 f \N +2025-11-25 08:23:25.707105+00 2025-11-25 08:23:25.707108+00 6 54.00 1 1 1 5 f \N +2025-11-25 08:23:25.707312+00 2025-11-25 08:23:25.707315+00 7 46.00 1 1 1 5 f \N +2025-11-25 08:23:25.707497+00 2025-11-25 08:23:25.7075+00 8 64.00 1 1 1 5 f \N +2025-11-25 08:23:25.70768+00 2025-11-25 08:23:25.707683+00 9 46.00 1 1 1 5 f \N +2025-11-25 08:49:42.447165+00 2025-11-25 08:49:42.44717+00 10 45.00 1 1 2 6 f \N +2025-11-25 08:49:42.447659+00 2025-11-25 08:49:42.447663+00 11 456.00 1 1 2 6 f \N +2025-11-25 08:49:42.447844+00 2025-11-25 08:49:42.447848+00 12 57.00 1 1 2 6 f \N +2025-11-25 08:49:42.448005+00 2025-11-25 08:49:42.448008+00 13 78.00 1 1 2 6 f \N +2025-11-25 08:49:42.448169+00 2025-11-25 08:49:42.448173+00 14 78.00 1 1 2 6 f \N +2025-11-25 08:49:42.448333+00 2025-11-25 08:49:42.448336+00 15 87.00 1 1 2 6 f \N +2025-11-25 08:49:45.15512+00 2025-11-25 08:49:45.155124+00 16 45.00 1 1 2 7 f \N +2025-11-25 08:49:45.155571+00 2025-11-25 08:49:45.155574+00 17 456.00 1 1 2 7 f \N +2025-11-25 08:49:45.155746+00 2025-11-25 08:49:45.15575+00 18 57.00 1 1 2 7 f \N +2025-11-25 08:49:45.155905+00 2025-11-25 08:49:45.155908+00 19 78.00 1 1 2 7 f \N +2025-11-25 08:49:45.156062+00 2025-11-25 08:49:45.156065+00 20 78.00 1 1 2 7 f \N +2025-11-25 08:49:45.156227+00 2025-11-25 08:49:45.15623+00 21 87.00 1 1 2 7 f \N +2025-11-25 09:10:28.070164+00 2025-11-25 09:10:28.070168+00 22 465.00 1 1 1 8 f \N +2025-11-25 09:10:28.070615+00 2025-11-25 09:10:28.070619+00 23 78.00 1 1 1 8 f \N +2025-11-25 09:10:28.070802+00 2025-11-25 09:10:28.070806+00 24 789.00 1 1 1 8 f \N +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 +\. + + +-- +-- Data for Name: stock_stockchangerecord; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stock_stockchangerecord (created_at, updated_at, id, type, source_type, source_id, is_finished, finished_at, remarks, created_by_id, merchant_id, warehouse_id) FROM stdin; +2025-11-20 07:54:03.430455+00 2025-11-20 07:54:45.867681+00 2 1 4 \N t 2025-11-20 07:54:45.866469+00 1 1 2 +2025-11-20 08:00:58.975687+00 2025-11-20 08:02:59.822363+00 3 2 10 \N t 2025-11-20 08:02:59.82082+00 1 1 2 +2025-11-20 08:02:18.094174+00 2025-11-20 08:03:02.172741+00 4 1 3 \N t 2025-11-20 08:03:02.171627+00 1 1 3 +2025-11-25 08:23:25.703137+00 2025-11-25 08:26:59.598906+00 5 1 1 \N t 2025-11-25 08:26:59.598104+00 \N 2 1 2 +2025-11-25 09:28:40.651817+00 2025-11-25 10:10:43.514322+00 10 1 3 \N t 2025-11-25 10:10:43.513447+00 1 1 2 +2025-11-25 09:20:41.469745+00 2025-11-25 10:10:46.644773+00 9 1 4 \N t 2025-11-25 10:10:46.643786+00 1 1 2 +2025-11-25 09:10:28.069309+00 2025-11-25 10:10:48.427132+00 8 1 1 \N t 2025-11-25 10:10:48.426187+00 \N 2 1 2 +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 +\. + + +-- +-- Data for Name: stock_stockfreeze; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stock_stockfreeze (created_at, updated_at, id, quantity, unit, status, frozen_with, completed_at, completed_with, cancelled_at, reason, cancelled_by_id, completed_by_id, frozen_by_id, merchant_id, product_id, stock_detail_id, warehouse_id) FROM stdin; +\. + + +-- +-- Data for Name: stock_stocksnapshot; Type: TABLE DATA; Schema: public; Owner: postgres +-- + +COPY public.stock_stocksnapshot (created_at, updated_at, id, delta, quantity_before, quantity_after, unit, num_of_rolls, offset_to, offset_at, cancelled, cancelled_at, offset_id, merchant_id, product_id, stock_change_record_id, warehouse_id) FROM stdin; +2025-11-20 07:54:45.865438+00 2025-11-20 07:54:45.865443+00 1 50.00 0.00 50.00 1 2 \N \N f \N \N 1 1 2 2 +2025-11-20 08:02:59.81919+00 2025-11-20 08:02:59.819199+00 2 -25.00 50.00 25.00 1 -1 \N \N f \N \N 1 1 3 2 +2025-11-20 08:03:02.170296+00 2025-11-20 08:03:02.170306+00 3 25.00 0.00 25.00 1 2 \N \N f \N \N 1 1 4 3 +2025-11-25 08:26:59.585488+00 2025-11-25 08:26:59.585496+00 4 45.00 25.00 70.00 1 2 \N \N f \N \N 1 1 5 2 +2025-11-25 08:26:59.589092+00 2025-11-25 08:26:59.589097+00 5 45.00 70.00 115.00 1 3 \N \N f \N \N 1 1 5 2 +2025-11-25 08:26:59.59076+00 2025-11-25 08:26:59.590764+00 6 54.00 115.00 169.00 1 4 \N \N f \N \N 1 1 5 2 +2025-11-25 08:26:59.592872+00 2025-11-25 08:26:59.592876+00 7 46.00 169.00 215.00 1 5 \N \N f \N \N 1 1 5 2 +2025-11-25 08:26:59.595099+00 2025-11-25 08:26:59.595104+00 8 64.00 215.00 279.00 1 6 \N \N f \N \N 1 1 5 2 +2025-11-25 08:26:59.597258+00 2025-11-25 08:26:59.597262+00 9 46.00 279.00 325.00 1 7 \N \N f \N \N 1 1 5 2 +2025-11-25 10:10:43.512086+00 2025-11-25 10:10:43.512093+00 10 22.00 325.00 347.00 1 8 \N \N f \N \N 1 1 10 2 +2025-11-25 10:10:46.642532+00 2025-11-25 10:10:46.642538+00 11 55.00 347.00 402.00 1 9 \N \N f \N \N 1 1 9 2 +2025-11-25 10:10:48.421374+00 2025-11-25 10:10:48.421379+00 12 465.00 402.00 867.00 1 10 \N \N f \N \N 1 1 8 2 +2025-11-25 10:10:48.423686+00 2025-11-25 10:10:48.42369+00 13 78.00 867.00 945.00 1 11 \N \N f \N \N 1 1 8 2 +2025-11-25 10:10:48.425271+00 2025-11-25 10:10:48.425277+00 14 789.00 945.00 1734.00 1 12 \N \N f \N \N 1 1 8 2 +2025-11-25 10:10:50.076648+00 2025-11-25 10:10:50.076654+00 15 -45.00 0.00 -45.00 1 -2 \N \N f \N \N 1 2 7 2 +2025-11-25 10:10:50.079262+00 2025-11-25 10:10:50.079266+00 16 -456.00 -45.00 -501.00 1 -3 \N \N f \N \N 1 2 7 2 +2025-11-25 10:10:50.081387+00 2025-11-25 10:10:50.081391+00 17 -57.00 -501.00 -558.00 1 -4 \N \N f \N \N 1 2 7 2 +2025-11-25 10:10:50.083463+00 2025-11-25 10:10:50.083469+00 18 -78.00 -558.00 -636.00 1 -5 \N \N f \N \N 1 2 7 2 +2025-11-25 10:10:50.085389+00 2025-11-25 10:10:50.085393+00 19 -78.00 -636.00 -714.00 1 -6 \N \N f \N \N 1 2 7 2 +2025-11-25 10:10:50.087157+00 2025-11-25 10:10:50.087163+00 20 -87.00 -714.00 -801.00 1 -7 \N \N f \N \N 1 2 7 2 +2025-11-25 10:10:52.760974+00 2025-11-25 10:10:52.760978+00 21 -45.00 -801.00 -846.00 1 -8 \N \N f \N \N 1 2 6 2 +2025-11-25 10:10:52.763084+00 2025-11-25 10:10:52.763088+00 22 -456.00 -846.00 -1302.00 1 -9 \N \N f \N \N 1 2 6 2 +2025-11-25 10:10:52.764538+00 2025-11-25 10:10:52.764542+00 23 -57.00 -1302.00 -1359.00 1 -10 \N \N f \N \N 1 2 6 2 +2025-11-25 10:10:52.765994+00 2025-11-25 10:10:52.765998+00 24 -78.00 -1359.00 -1437.00 1 -11 \N \N f \N \N 1 2 6 2 +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 +\. + + +-- +-- 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); + + +-- +-- Name: auth_group_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.auth_group_id_seq', 1, true); + + +-- +-- Name: auth_group_permissions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +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); + + +-- +-- Name: auth_user_groups_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.auth_user_groups_id_seq', 1, false); + + +-- +-- Name: auth_user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.auth_user_id_seq', 7, true); + + +-- +-- Name: auth_user_user_permissions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.auth_user_user_permissions_id_seq', 39, true); + + +-- +-- Name: basic_info_bankaccount_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_bankaccount_id_seq', 1, false); + + +-- +-- Name: basic_info_customer_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_customer_id_seq', 7, true); + + +-- +-- Name: basic_info_customer_visible_employees_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_customer_visible_employees_id_seq', 2, true); + + +-- +-- Name: basic_info_deviceinfo_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_deviceinfo_id_seq', 1, false); + + +-- +-- Name: basic_info_employee_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_employee_id_seq', 6, true); + + +-- +-- Name: basic_info_employeetype_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_employeetype_id_seq', 7, true); + + +-- +-- Name: basic_info_merchant_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_merchant_id_seq', 3, true); + + +-- +-- Name: basic_info_product_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_product_id_seq', 2, true); + + +-- +-- Name: basic_info_productcategory_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_productcategory_id_seq', 1, true); + + +-- +-- Name: basic_info_quickinput_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_quickinput_id_seq', 23, true); + + +-- +-- Name: basic_info_supplier_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_supplier_id_seq', 1, true); + + +-- +-- Name: basic_info_userprofile_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_userprofile_id_seq', 5, true); + + +-- +-- Name: basic_info_vehicletransportrecord_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_vehicletransportrecord_id_seq', 1, false); + + +-- +-- Name: basic_info_vehicletype_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_vehicletype_id_seq', 1, false); + + +-- +-- Name: basic_info_warehouse_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.basic_info_warehouse_id_seq', 4, 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); + + +-- +-- 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); + + +-- +-- Name: django_migrations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.django_migrations_id_seq', 76, true); + + +-- +-- Name: printing_plateorder_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.printing_plateorder_id_seq', 80007, true); + + +-- +-- Name: printing_printingjob_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.printing_printingjob_id_seq', 8, true); + + +-- +-- Name: printing_printingorder_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.printing_printingorder_id_seq', 10, true); + + +-- +-- Name: state_log_parameter_record_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +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); + + +-- +-- Name: stateflow_orderstatelog_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.stateflow_orderstatelog_id_seq', 51, true); + + +-- +-- Name: stateflow_process_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.stateflow_process_id_seq', 2, true); + + +-- +-- Name: stateflow_processnode_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.stateflow_processnode_id_seq', 11, true); + + +-- +-- Name: stateflow_state_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.stateflow_state_id_seq', 14, true); + + +-- +-- Name: stateflow_state_parameters_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.stateflow_state_parameters_id_seq', 76, true); + + +-- +-- Name: stateflow_stateparameter_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.stateflow_stateparameter_id_seq', 87, true); + + +-- +-- Name: stock_inventory_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +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); + + +-- +-- Name: stock_stockchangedetail_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.stock_stockchangedetail_id_seq', 27, true); + + +-- +-- Name: stock_stockchangerecord_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.stock_stockchangerecord_id_seq', 11, true); + + +-- +-- Name: stock_stockfreeze_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +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); + + +-- +-- Name: api_uploaded_file api_uploaded_file_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.api_uploaded_file + ADD CONSTRAINT api_uploaded_file_pkey PRIMARY KEY (id); + + +-- +-- Name: auth_group auth_group_name_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_group + ADD CONSTRAINT auth_group_name_key UNIQUE (name); + + +-- +-- Name: auth_group_permissions auth_group_permissions_group_id_permission_id_0cd325b0_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_group_permissions + ADD CONSTRAINT auth_group_permissions_group_id_permission_id_0cd325b0_uniq UNIQUE (group_id, permission_id); + + +-- +-- Name: auth_group_permissions auth_group_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_group_permissions + ADD CONSTRAINT auth_group_permissions_pkey PRIMARY KEY (id); + + +-- +-- Name: auth_group auth_group_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_group + ADD CONSTRAINT auth_group_pkey PRIMARY KEY (id); + + +-- +-- Name: auth_permission auth_permission_content_type_id_codename_01ab375a_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_permission + ADD CONSTRAINT auth_permission_content_type_id_codename_01ab375a_uniq UNIQUE (content_type_id, codename); + + +-- +-- Name: auth_permission auth_permission_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_permission + ADD CONSTRAINT auth_permission_pkey PRIMARY KEY (id); + + +-- +-- Name: auth_user_groups auth_user_groups_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user_groups + ADD CONSTRAINT auth_user_groups_pkey PRIMARY KEY (id); + + +-- +-- Name: auth_user_groups auth_user_groups_user_id_group_id_94350c0c_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user_groups + ADD CONSTRAINT auth_user_groups_user_id_group_id_94350c0c_uniq UNIQUE (user_id, group_id); + + +-- +-- Name: auth_user auth_user_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user + ADD CONSTRAINT auth_user_pkey PRIMARY KEY (id); + + +-- +-- Name: auth_user_user_permissions auth_user_user_permissions_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user_user_permissions + ADD CONSTRAINT auth_user_user_permissions_pkey PRIMARY KEY (id); + + +-- +-- Name: auth_user_user_permissions auth_user_user_permissions_user_id_permission_id_14a6b632_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user_user_permissions + ADD CONSTRAINT auth_user_user_permissions_user_id_permission_id_14a6b632_uniq UNIQUE (user_id, permission_id); + + +-- +-- Name: auth_user auth_user_username_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user + ADD CONSTRAINT auth_user_username_key UNIQUE (username); + + +-- +-- Name: basic_info_bankaccount basic_info_bankaccount_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_bankaccount + ADD CONSTRAINT basic_info_bankaccount_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_customer basic_info_customer_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_customer + ADD CONSTRAINT basic_info_customer_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_customer_visible_employees basic_info_customer_visi_customer_id_employee_id_7be5046b_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_customer_visible_employees + ADD CONSTRAINT basic_info_customer_visi_customer_id_employee_id_7be5046b_uniq UNIQUE (customer_id, employee_id); + + +-- +-- Name: basic_info_customer_visible_employees basic_info_customer_visible_employees_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_customer_visible_employees + ADD CONSTRAINT basic_info_customer_visible_employees_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_deviceinfo basic_info_deviceinfo_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_deviceinfo + ADD CONSTRAINT basic_info_deviceinfo_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_employee basic_info_employee_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_employee + ADD CONSTRAINT basic_info_employee_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_employee basic_info_employee_sys_user_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_employee + ADD CONSTRAINT basic_info_employee_sys_user_id_key UNIQUE (sys_user_id); + + +-- +-- Name: basic_info_employeetype basic_info_employeetype_merchant_id_title_cdb313e8_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_employeetype + ADD CONSTRAINT basic_info_employeetype_merchant_id_title_cdb313e8_uniq UNIQUE (merchant_id, title); + + +-- +-- Name: basic_info_employeetype basic_info_employeetype_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_employeetype + ADD CONSTRAINT basic_info_employeetype_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_merchant basic_info_merchant_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_merchant + ADD CONSTRAINT basic_info_merchant_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_product basic_info_product_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_product + ADD CONSTRAINT basic_info_product_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_productcategory basic_info_productcategory_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_productcategory + ADD CONSTRAINT basic_info_productcategory_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_quickinput basic_info_quickinput_name_group_68f4787a_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_quickinput + ADD CONSTRAINT basic_info_quickinput_name_group_68f4787a_uniq UNIQUE (name, "group"); + + +-- +-- Name: basic_info_quickinput basic_info_quickinput_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_quickinput + ADD CONSTRAINT basic_info_quickinput_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_supplier basic_info_supplier_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_supplier + ADD CONSTRAINT basic_info_supplier_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_userprofile basic_info_userprofile_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_userprofile + ADD CONSTRAINT basic_info_userprofile_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_userprofile basic_info_userprofile_user_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_userprofile + ADD CONSTRAINT basic_info_userprofile_user_id_key UNIQUE (user_id); + + +-- +-- Name: basic_info_vehicletransportrecord basic_info_vehicletransportrecord_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_vehicletransportrecord + ADD CONSTRAINT basic_info_vehicletransportrecord_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_vehicletype basic_info_vehicletype_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_vehicletype + ADD CONSTRAINT basic_info_vehicletype_pkey PRIMARY KEY (id); + + +-- +-- Name: basic_info_warehouse basic_info_warehouse_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_warehouse + ADD CONSTRAINT basic_info_warehouse_pkey PRIMARY KEY (id); + + +-- +-- Name: django_admin_log django_admin_log_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.django_admin_log + ADD CONSTRAINT django_admin_log_pkey PRIMARY KEY (id); + + +-- +-- Name: django_content_type django_content_type_app_label_model_76bd3d3b_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.django_content_type + ADD CONSTRAINT django_content_type_app_label_model_76bd3d3b_uniq UNIQUE (app_label, model); + + +-- +-- Name: django_content_type django_content_type_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.django_content_type + ADD CONSTRAINT django_content_type_pkey PRIMARY KEY (id); + + +-- +-- Name: django_migrations django_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.django_migrations + ADD CONSTRAINT django_migrations_pkey PRIMARY KEY (id); + + +-- +-- Name: django_session django_session_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.django_session + ADD CONSTRAINT django_session_pkey PRIMARY KEY (session_key); + + +-- +-- Name: printing_plateorder printing_plateorder_business_object_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_plateorder + ADD CONSTRAINT printing_plateorder_business_object_id_key UNIQUE (business_object_id); + + +-- +-- Name: printing_plateorder printing_plateorder_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_plateorder + ADD CONSTRAINT printing_plateorder_pkey PRIMARY KEY (id); + + +-- +-- Name: printing_printingjob printing_printingjob_business_object_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_printingjob + ADD CONSTRAINT printing_printingjob_business_object_id_key UNIQUE (business_object_id); + + +-- +-- Name: printing_printingjob printing_printingjob_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_printingjob + ADD CONSTRAINT printing_printingjob_pkey PRIMARY KEY (id); + + +-- +-- Name: printing_printingorder printing_printingorder_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_printingorder + ADD CONSTRAINT printing_printingorder_pkey PRIMARY KEY (id); + + +-- +-- Name: state_log_parameter_record state_log_parameter_record_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.state_log_parameter_record + ADD CONSTRAINT state_log_parameter_record_pkey PRIMARY KEY (id); + + +-- +-- Name: business_object stateflow_order_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.business_object + ADD CONSTRAINT stateflow_order_pkey PRIMARY KEY (id); + + +-- +-- Name: state_flow_record stateflow_orderstatelog_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.state_flow_record + ADD CONSTRAINT stateflow_orderstatelog_pkey PRIMARY KEY (id); + + +-- +-- Name: stateflow_process stateflow_process_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_process + ADD CONSTRAINT stateflow_process_pkey PRIMARY KEY (id); + + +-- +-- Name: stateflow_processnode stateflow_processnode_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_processnode + ADD CONSTRAINT stateflow_processnode_pkey PRIMARY KEY (id); + + +-- +-- Name: stateflow_processnode stateflow_processnode_process_id_order_d27bc733_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_processnode + ADD CONSTRAINT stateflow_processnode_process_id_order_d27bc733_uniq UNIQUE (process_id, "order"); + + +-- +-- Name: stateflow_state_parameters stateflow_state_paramete_state_id_stateparameter__91e23688_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_state_parameters + ADD CONSTRAINT stateflow_state_paramete_state_id_stateparameter__91e23688_uniq UNIQUE (state_id, stateparameter_id); + + +-- +-- Name: stateflow_state_parameters stateflow_state_parameters_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_state_parameters + ADD CONSTRAINT stateflow_state_parameters_pkey PRIMARY KEY (id); + + +-- +-- Name: stateflow_state stateflow_state_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_state + ADD CONSTRAINT stateflow_state_pkey PRIMARY KEY (id); + + +-- +-- Name: stateflow_stateparameter stateflow_stateparameter_key_2174542b_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_stateparameter + ADD CONSTRAINT stateflow_stateparameter_key_2174542b_uniq UNIQUE (key); + + +-- +-- Name: stateflow_stateparameter stateflow_stateparameter_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_stateparameter + ADD CONSTRAINT stateflow_stateparameter_pkey PRIMARY KEY (id); + + +-- +-- Name: stock_inventory stock_inventory_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_inventory + ADD CONSTRAINT stock_inventory_pkey PRIMARY KEY (id); + + +-- +-- Name: stock_inventory stock_inventory_product_id_warehouse_id_b146267d_uniq; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_inventory + ADD CONSTRAINT stock_inventory_product_id_warehouse_id_b146267d_uniq UNIQUE (product_id, warehouse_id); + + +-- +-- Name: business_purchaseorder stock_purchaseorder_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.business_purchaseorder + ADD CONSTRAINT stock_purchaseorder_pkey PRIMARY KEY (id); + + +-- +-- Name: stock_stockchangedetail stock_stockchangedetail_consume_with_id_key; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangedetail + ADD CONSTRAINT stock_stockchangedetail_consume_with_id_key UNIQUE (consume_with_id); + + +-- +-- Name: stock_stockchangedetail stock_stockchangedetail_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangedetail + ADD CONSTRAINT stock_stockchangedetail_pkey PRIMARY KEY (id); + + +-- +-- Name: stock_stockchangerecord stock_stockchangerecord_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangerecord + ADD CONSTRAINT stock_stockchangerecord_pkey PRIMARY KEY (id); + + +-- +-- Name: stock_stockfreeze stock_stockfreeze_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockfreeze + ADD CONSTRAINT stock_stockfreeze_pkey PRIMARY KEY (id); + + +-- +-- Name: stock_stocksnapshot stock_stocksnapshot_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stocksnapshot + ADD CONSTRAINT stock_stocksnapshot_pkey PRIMARY KEY (id); + + +-- +-- Name: api_uploaded_file_owner_id_f2e6fe5c; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX api_uploaded_file_owner_id_f2e6fe5c ON public.api_uploaded_file USING btree (owner_id); + + +-- +-- Name: auth_group_name_a6ea08ec_like; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_group_name_a6ea08ec_like ON public.auth_group USING btree (name varchar_pattern_ops); + + +-- +-- Name: auth_group_permissions_group_id_b120cbf9; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_group_permissions_group_id_b120cbf9 ON public.auth_group_permissions USING btree (group_id); + + +-- +-- Name: auth_group_permissions_permission_id_84c5c92e; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_group_permissions_permission_id_84c5c92e ON public.auth_group_permissions USING btree (permission_id); + + +-- +-- Name: auth_permission_content_type_id_2f476e4b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_permission_content_type_id_2f476e4b ON public.auth_permission USING btree (content_type_id); + + +-- +-- Name: auth_user_groups_group_id_97559544; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_user_groups_group_id_97559544 ON public.auth_user_groups USING btree (group_id); + + +-- +-- Name: auth_user_groups_user_id_6a12ed8b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_user_groups_user_id_6a12ed8b ON public.auth_user_groups USING btree (user_id); + + +-- +-- Name: auth_user_user_permissions_permission_id_1fbb5f2c; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_user_user_permissions_permission_id_1fbb5f2c ON public.auth_user_user_permissions USING btree (permission_id); + + +-- +-- Name: auth_user_user_permissions_user_id_a95ead1b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_user_user_permissions_user_id_a95ead1b ON public.auth_user_user_permissions USING btree (user_id); + + +-- +-- Name: auth_user_username_6821ab7c_like; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX auth_user_username_6821ab7c_like ON public.auth_user USING btree (username varchar_pattern_ops); + + +-- +-- Name: basic_info_bankaccount_merchant_id_0dc91094; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_bankaccount_merchant_id_0dc91094 ON public.basic_info_bankaccount USING btree (merchant_id); + + +-- +-- Name: basic_info_customer_created_by_id_e4898d6a; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_customer_created_by_id_e4898d6a ON public.basic_info_customer USING btree (created_by_id); + + +-- +-- Name: basic_info_customer_merchant_id_c342572c; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_customer_merchant_id_c342572c ON public.basic_info_customer USING btree (merchant_id); + + +-- +-- Name: basic_info_customer_visible_employees_customer_id_a05ec7e2; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_customer_visible_employees_customer_id_a05ec7e2 ON public.basic_info_customer_visible_employees USING btree (customer_id); + + +-- +-- Name: basic_info_customer_visible_employees_employee_id_fb51a1e0; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_customer_visible_employees_employee_id_fb51a1e0 ON public.basic_info_customer_visible_employees USING btree (employee_id); + + +-- +-- Name: basic_info_deviceinfo_merchant_id_8ba16bd2; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_deviceinfo_merchant_id_8ba16bd2 ON public.basic_info_deviceinfo USING btree (merchant_id); + + +-- +-- Name: basic_info_employee_merchant_id_508e9a14; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_employee_merchant_id_508e9a14 ON public.basic_info_employee USING btree (merchant_id); + + +-- +-- Name: basic_info_employee_position_id_0f790d25; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_employee_position_id_0f790d25 ON public.basic_info_employee USING btree (position_id); + + +-- +-- Name: basic_info_employeetype_merchant_id_cf26fc93; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_employeetype_merchant_id_cf26fc93 ON public.basic_info_employeetype USING btree (merchant_id); + + +-- +-- Name: basic_info_product_category_id_2bbbe6be; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_product_category_id_2bbbe6be ON public.basic_info_product USING btree (category_id); + + +-- +-- Name: basic_info_product_merchant_id_16465f25; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_product_merchant_id_16465f25 ON public.basic_info_product USING btree (merchant_id); + + +-- +-- Name: basic_info_productcategory_merchant_id_026ead5b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_productcategory_merchant_id_026ead5b ON public.basic_info_productcategory USING btree (merchant_id); + + +-- +-- Name: basic_info_supplier_merchant_id_7b0fafc0; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_supplier_merchant_id_7b0fafc0 ON public.basic_info_supplier USING btree (merchant_id); + + +-- +-- Name: basic_info_userprofile_merchant_id_4c25789b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_userprofile_merchant_id_4c25789b ON public.basic_info_userprofile USING btree (merchant_id); + + +-- +-- Name: basic_info_vehicletransportrecord_merchant_id_da543289; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_vehicletransportrecord_merchant_id_da543289 ON public.basic_info_vehicletransportrecord USING btree (merchant_id); + + +-- +-- Name: basic_info_vehicletransportrecord_vehicle_type_id_ca9c2c9a; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_vehicletransportrecord_vehicle_type_id_ca9c2c9a ON public.basic_info_vehicletransportrecord USING btree (vehicle_type_id); + + +-- +-- Name: basic_info_vehicletype_merchant_id_834e9500; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_vehicletype_merchant_id_834e9500 ON public.basic_info_vehicletype USING btree (merchant_id); + + +-- +-- Name: basic_info_warehouse_merchant_id_d5bb4b76; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX basic_info_warehouse_merchant_id_d5bb4b76 ON public.basic_info_warehouse USING btree (merchant_id); + + +-- +-- Name: business_object_content_type_id_94dccf29; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX business_object_content_type_id_94dccf29 ON public.business_object USING btree (content_type_id); + + +-- +-- Name: django_admin_log_content_type_id_c4bce8eb; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX django_admin_log_content_type_id_c4bce8eb ON public.django_admin_log USING btree (content_type_id); + + +-- +-- Name: django_admin_log_user_id_c564eba6; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX django_admin_log_user_id_c564eba6 ON public.django_admin_log USING btree (user_id); + + +-- +-- Name: django_session_expire_date_a5c62663; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX django_session_expire_date_a5c62663 ON public.django_session USING btree (expire_date); + + +-- +-- Name: django_session_session_key_c0390e0f_like; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX django_session_session_key_c0390e0f_like ON public.django_session USING btree (session_key varchar_pattern_ops); + + +-- +-- Name: printing_plateorder_customer_id_920c92fc; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX printing_plateorder_customer_id_920c92fc ON public.printing_plateorder USING btree (customer_id); + + +-- +-- Name: printing_plateorder_designer_id_4f94e8c1; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX printing_plateorder_designer_id_4f94e8c1 ON public.printing_plateorder USING btree (designer_id); + + +-- +-- Name: printing_plateorder_merchandiser_id_f85f2095; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX printing_plateorder_merchandiser_id_f85f2095 ON public.printing_plateorder USING btree (merchandiser_id); + + +-- +-- Name: printing_plateorder_salesperson_id_0d75b741; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX printing_plateorder_salesperson_id_0d75b741 ON public.printing_plateorder USING btree (salesperson_id); + + +-- +-- Name: printing_printingjob_printing_order_id_ca5569e7; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX printing_printingjob_printing_order_id_ca5569e7 ON public.printing_printingjob USING btree (printing_order_id); + + +-- +-- Name: printing_printingjob_product_id_6c4bda82; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX printing_printingjob_product_id_6c4bda82 ON public.printing_printingjob USING btree (product_id); + + +-- +-- Name: printing_printingorder_customer_id_2f5affa4; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX printing_printingorder_customer_id_2f5affa4 ON public.printing_printingorder USING btree (customer_id); + + +-- +-- Name: printing_printingorder_process_id_087e5007; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX printing_printingorder_process_id_087e5007 ON public.printing_printingorder USING btree (process_id); + + +-- +-- Name: state_log_p_state_l_8cc99f_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX state_log_p_state_l_8cc99f_idx ON public.state_log_parameter_record USING btree (state_log_id, created_at); + + +-- +-- Name: state_log_parameter_record_state_log_id_f233799b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX state_log_parameter_record_state_log_id_f233799b ON public.state_log_parameter_record USING btree (state_log_id); + + +-- +-- Name: stateflow_order_process_id_8c0bb6c0; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_order_process_id_8c0bb6c0 ON public.business_object USING btree (process_id); + + +-- +-- Name: stateflow_orderstatelog_completed_by_id_2f822f5b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_orderstatelog_completed_by_id_2f822f5b ON public.state_flow_record USING btree (completed_by_id); + + +-- +-- Name: stateflow_orderstatelog_order_id_4d729f58; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_orderstatelog_order_id_4d729f58 ON public.state_flow_record USING btree (business_object_id); + + +-- +-- Name: stateflow_orderstatelog_state_id_70d1101d; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_orderstatelog_state_id_70d1101d ON public.state_flow_record USING btree (state_id); + + +-- +-- Name: stateflow_processnode_process_id_146d7cc7; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_processnode_process_id_146d7cc7 ON public.stateflow_processnode USING btree (process_id); + + +-- +-- Name: stateflow_processnode_state_id_18e2868e; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_processnode_state_id_18e2868e ON public.stateflow_processnode USING btree (state_id); + + +-- +-- Name: stateflow_state_parameters_state_id_1888c236; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_state_parameters_state_id_1888c236 ON public.stateflow_state_parameters USING btree (state_id); + + +-- +-- Name: stateflow_state_parameters_stateparameter_id_2a4745a3; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_state_parameters_stateparameter_id_2a4745a3 ON public.stateflow_state_parameters USING btree (stateparameter_id); + + +-- +-- Name: stateflow_stateparameter_key_2174542b_like; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stateflow_stateparameter_key_2174542b_like ON public.stateflow_stateparameter USING btree (key varchar_pattern_ops); + + +-- +-- Name: stock_inventory_merchant_id_474e786a; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_inventory_merchant_id_474e786a ON public.stock_inventory USING btree (merchant_id); + + +-- +-- Name: stock_inventory_product_id_87e1d4a4; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_inventory_product_id_87e1d4a4 ON public.stock_inventory USING btree (product_id); + + +-- +-- Name: stock_inventory_warehouse_id_38e88ef3; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_inventory_warehouse_id_38e88ef3 ON public.stock_inventory USING btree (warehouse_id); + + +-- +-- Name: stock_purchaseorder_merchant_id_5175381b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_purchaseorder_merchant_id_5175381b ON public.business_purchaseorder USING btree (merchant_id); + + +-- +-- Name: stock_purchaseorder_supplier_id_bc7a300c; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_purchaseorder_supplier_id_bc7a300c ON public.business_purchaseorder USING btree (supplier_id); + + +-- +-- Name: stock_stockchangedetail_merchant_id_222521fc; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockchangedetail_merchant_id_222521fc ON public.stock_stockchangedetail USING btree (merchant_id); + + +-- +-- Name: stock_stockchangedetail_product_id_a013d432; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockchangedetail_product_id_a013d432 ON public.stock_stockchangedetail USING btree (product_id); + + +-- +-- Name: stock_stockchangedetail_stock_change_record_id_b388d2f0; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockchangedetail_stock_change_record_id_b388d2f0 ON public.stock_stockchangedetail USING btree (stock_change_record_id); + + +-- +-- Name: stock_stockchangerecord_created_by_id_7317d383; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockchangerecord_created_by_id_7317d383 ON public.stock_stockchangerecord USING btree (created_by_id); + + +-- +-- Name: stock_stockchangerecord_merchant_id_777ab07d; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockchangerecord_merchant_id_777ab07d ON public.stock_stockchangerecord USING btree (merchant_id); + + +-- +-- Name: stock_stockchangerecord_warehouse_id_c7a706de; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockchangerecord_warehouse_id_c7a706de ON public.stock_stockchangerecord USING btree (warehouse_id); + + +-- +-- Name: stock_stockfreeze_cancelled_by_id_f36ed57b; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockfreeze_cancelled_by_id_f36ed57b ON public.stock_stockfreeze USING btree (cancelled_by_id); + + +-- +-- Name: stock_stockfreeze_completed_by_id_feb5742a; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockfreeze_completed_by_id_feb5742a ON public.stock_stockfreeze USING btree (completed_by_id); + + +-- +-- Name: stock_stockfreeze_frozen_by_id_747bed1c; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockfreeze_frozen_by_id_747bed1c ON public.stock_stockfreeze USING btree (frozen_by_id); + + +-- +-- Name: stock_stockfreeze_merchant_id_778ecada; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockfreeze_merchant_id_778ecada ON public.stock_stockfreeze USING btree (merchant_id); + + +-- +-- Name: stock_stockfreeze_product_id_0915f126; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockfreeze_product_id_0915f126 ON public.stock_stockfreeze USING btree (product_id); + + +-- +-- Name: stock_stockfreeze_stock_detail_id_3c1f49b0; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockfreeze_stock_detail_id_3c1f49b0 ON public.stock_stockfreeze USING btree (stock_detail_id); + + +-- +-- Name: stock_stockfreeze_warehouse_id_7cd7f629; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stockfreeze_warehouse_id_7cd7f629 ON public.stock_stockfreeze USING btree (warehouse_id); + + +-- +-- Name: stock_stocksnapshot_merchant_id_345af02e; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stocksnapshot_merchant_id_345af02e ON public.stock_stocksnapshot USING btree (merchant_id); + + +-- +-- Name: stock_stocksnapshot_product_id_f6e948d7; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stocksnapshot_product_id_f6e948d7 ON public.stock_stocksnapshot USING btree (product_id); + + +-- +-- Name: stock_stocksnapshot_stock_change_record_id_a12e8dc3; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stocksnapshot_stock_change_record_id_a12e8dc3 ON public.stock_stocksnapshot USING btree (stock_change_record_id); + + +-- +-- Name: stock_stocksnapshot_warehouse_id_469ad069; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX stock_stocksnapshot_warehouse_id_469ad069 ON public.stock_stocksnapshot USING btree (warehouse_id); + + +-- +-- Name: api_uploaded_file api_uploaded_file_owner_id_f2e6fe5c_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.api_uploaded_file + ADD CONSTRAINT api_uploaded_file_owner_id_f2e6fe5c_fk_auth_user_id FOREIGN KEY (owner_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: auth_group_permissions auth_group_permissio_permission_id_84c5c92e_fk_auth_perm; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_group_permissions + ADD CONSTRAINT auth_group_permissio_permission_id_84c5c92e_fk_auth_perm FOREIGN KEY (permission_id) REFERENCES public.auth_permission(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: auth_group_permissions auth_group_permissions_group_id_b120cbf9_fk_auth_group_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_group_permissions + ADD CONSTRAINT auth_group_permissions_group_id_b120cbf9_fk_auth_group_id FOREIGN KEY (group_id) REFERENCES public.auth_group(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: auth_permission auth_permission_content_type_id_2f476e4b_fk_django_co; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_permission + ADD CONSTRAINT auth_permission_content_type_id_2f476e4b_fk_django_co FOREIGN KEY (content_type_id) REFERENCES public.django_content_type(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: auth_user_groups auth_user_groups_group_id_97559544_fk_auth_group_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user_groups + ADD CONSTRAINT auth_user_groups_group_id_97559544_fk_auth_group_id FOREIGN KEY (group_id) REFERENCES public.auth_group(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: auth_user_groups auth_user_groups_user_id_6a12ed8b_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user_groups + ADD CONSTRAINT auth_user_groups_user_id_6a12ed8b_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: auth_user_user_permissions auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user_user_permissions + ADD CONSTRAINT auth_user_user_permi_permission_id_1fbb5f2c_fk_auth_perm FOREIGN KEY (permission_id) REFERENCES public.auth_permission(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: auth_user_user_permissions auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.auth_user_user_permissions + ADD CONSTRAINT auth_user_user_permissions_user_id_a95ead1b_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_bankaccount basic_info_bankaccou_merchant_id_0dc91094_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_bankaccount + ADD CONSTRAINT basic_info_bankaccou_merchant_id_0dc91094_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_customer_visible_employees basic_info_customer__customer_id_a05ec7e2_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_customer_visible_employees + ADD CONSTRAINT basic_info_customer__customer_id_a05ec7e2_fk_basic_inf FOREIGN KEY (customer_id) REFERENCES public.basic_info_customer(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_customer_visible_employees basic_info_customer__employee_id_fb51a1e0_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_customer_visible_employees + ADD CONSTRAINT basic_info_customer__employee_id_fb51a1e0_fk_basic_inf FOREIGN KEY (employee_id) REFERENCES public.basic_info_employee(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_customer basic_info_customer_created_by_id_e4898d6a_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_customer + ADD CONSTRAINT basic_info_customer_created_by_id_e4898d6a_fk_basic_inf FOREIGN KEY (created_by_id) REFERENCES public.basic_info_employee(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_customer basic_info_customer_merchant_id_c342572c_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_customer + ADD CONSTRAINT basic_info_customer_merchant_id_c342572c_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_deviceinfo basic_info_deviceinf_merchant_id_8ba16bd2_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_deviceinfo + ADD CONSTRAINT basic_info_deviceinf_merchant_id_8ba16bd2_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_employee basic_info_employee_merchant_id_508e9a14_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_employee + ADD CONSTRAINT basic_info_employee_merchant_id_508e9a14_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_employee basic_info_employee_position_id_0f790d25_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_employee + ADD CONSTRAINT basic_info_employee_position_id_0f790d25_fk_basic_inf FOREIGN KEY (position_id) REFERENCES public.basic_info_employeetype(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_employee basic_info_employee_sys_user_id_ea7739c5_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_employee + ADD CONSTRAINT basic_info_employee_sys_user_id_ea7739c5_fk_auth_user_id FOREIGN KEY (sys_user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_employeetype basic_info_employeet_merchant_id_cf26fc93_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +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_product basic_info_product_category_id_2bbbe6be_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_product + ADD CONSTRAINT basic_info_product_category_id_2bbbe6be_fk_basic_inf FOREIGN KEY (category_id) REFERENCES public.basic_info_productcategory(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_product basic_info_product_merchant_id_16465f25_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_product + ADD CONSTRAINT basic_info_product_merchant_id_16465f25_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_productcategory basic_info_productca_merchant_id_026ead5b_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_productcategory + ADD CONSTRAINT basic_info_productca_merchant_id_026ead5b_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_supplier basic_info_supplier_merchant_id_7b0fafc0_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_supplier + ADD CONSTRAINT basic_info_supplier_merchant_id_7b0fafc0_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_userprofile basic_info_userprofi_merchant_id_4c25789b_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_userprofile + ADD CONSTRAINT basic_info_userprofi_merchant_id_4c25789b_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_userprofile basic_info_userprofile_user_id_ed5ab8a5_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_userprofile + ADD CONSTRAINT basic_info_userprofile_user_id_ed5ab8a5_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_vehicletransportrecord basic_info_vehicletr_merchant_id_da543289_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_vehicletransportrecord + ADD CONSTRAINT basic_info_vehicletr_merchant_id_da543289_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_vehicletransportrecord basic_info_vehicletr_vehicle_type_id_ca9c2c9a_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_vehicletransportrecord + ADD CONSTRAINT basic_info_vehicletr_vehicle_type_id_ca9c2c9a_fk_basic_inf FOREIGN KEY (vehicle_type_id) REFERENCES public.basic_info_vehicletype(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_vehicletype basic_info_vehiclety_merchant_id_834e9500_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_vehicletype + ADD CONSTRAINT basic_info_vehiclety_merchant_id_834e9500_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: basic_info_warehouse basic_info_warehouse_merchant_id_d5bb4b76_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.basic_info_warehouse + ADD CONSTRAINT basic_info_warehouse_merchant_id_d5bb4b76_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: business_object business_object_content_type_id_94dccf29_fk_django_co; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +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: django_admin_log django_admin_log_content_type_id_c4bce8eb_fk_django_co; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.django_admin_log + ADD CONSTRAINT django_admin_log_content_type_id_c4bce8eb_fk_django_co FOREIGN KEY (content_type_id) REFERENCES public.django_content_type(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: django_admin_log django_admin_log_user_id_c564eba6_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.django_admin_log + ADD CONSTRAINT django_admin_log_user_id_c564eba6_fk_auth_user_id FOREIGN KEY (user_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: state_flow_record order_state_log_business_object_id_f642bfbd_fk_business_; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.state_flow_record + ADD CONSTRAINT order_state_log_business_object_id_f642bfbd_fk_business_ FOREIGN KEY (business_object_id) REFERENCES public.business_object(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_plateorder printing_plateorder_business_object_id_303b8c81_fk_business_; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_plateorder + ADD CONSTRAINT printing_plateorder_business_object_id_303b8c81_fk_business_ FOREIGN KEY (business_object_id) REFERENCES public.business_object(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_plateorder printing_plateorder_customer_id_920c92fc_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_plateorder + ADD CONSTRAINT printing_plateorder_customer_id_920c92fc_fk_basic_inf FOREIGN KEY (customer_id) REFERENCES public.basic_info_customer(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_plateorder printing_plateorder_designer_id_4f94e8c1_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_plateorder + ADD CONSTRAINT printing_plateorder_designer_id_4f94e8c1_fk_basic_inf FOREIGN KEY (designer_id) REFERENCES public.basic_info_employee(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_plateorder printing_plateorder_merchandiser_id_f85f2095_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_plateorder + ADD CONSTRAINT printing_plateorder_merchandiser_id_f85f2095_fk_basic_inf FOREIGN KEY (merchandiser_id) REFERENCES public.basic_info_employee(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_plateorder printing_plateorder_salesperson_id_0d75b741_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_plateorder + ADD CONSTRAINT printing_plateorder_salesperson_id_0d75b741_fk_basic_inf FOREIGN KEY (salesperson_id) REFERENCES public.basic_info_employee(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_printingjob printing_printingjob_business_object_id_76a7d2fd_fk_business_; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_printingjob + ADD CONSTRAINT printing_printingjob_business_object_id_76a7d2fd_fk_business_ FOREIGN KEY (business_object_id) REFERENCES public.business_object(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_printingjob printing_printingjob_printing_order_id_ca5569e7_fk_printing_; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_printingjob + ADD CONSTRAINT printing_printingjob_printing_order_id_ca5569e7_fk_printing_ FOREIGN KEY (printing_order_id) REFERENCES public.printing_printingorder(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_printingjob printing_printingjob_product_id_6c4bda82_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_printingjob + ADD CONSTRAINT printing_printingjob_product_id_6c4bda82_fk_basic_inf FOREIGN KEY (product_id) REFERENCES public.basic_info_product(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_printingorder printing_printingord_customer_id_2f5affa4_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_printingorder + ADD CONSTRAINT printing_printingord_customer_id_2f5affa4_fk_basic_inf FOREIGN KEY (customer_id) REFERENCES public.basic_info_customer(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: printing_printingorder printing_printingord_process_id_087e5007_fk_stateflow; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.printing_printingorder + ADD CONSTRAINT printing_printingord_process_id_087e5007_fk_stateflow FOREIGN KEY (process_id) REFERENCES public.stateflow_process(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: state_log_parameter_record state_log_parameter__state_log_id_f233799b_fk_state_flo; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.state_log_parameter_record + ADD CONSTRAINT state_log_parameter__state_log_id_f233799b_fk_state_flo FOREIGN KEY (state_log_id) REFERENCES public.state_flow_record(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: business_object stateflow_order_process_id_8c0bb6c0_fk_stateflow_process_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.business_object + ADD CONSTRAINT stateflow_order_process_id_8c0bb6c0_fk_stateflow_process_id FOREIGN KEY (process_id) REFERENCES public.stateflow_process(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: state_flow_record stateflow_orderstate_completed_by_id_2f822f5b_fk_auth_user; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.state_flow_record + ADD CONSTRAINT stateflow_orderstate_completed_by_id_2f822f5b_fk_auth_user FOREIGN KEY (completed_by_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: state_flow_record stateflow_orderstatelog_state_id_70d1101d_fk_stateflow_state_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.state_flow_record + ADD CONSTRAINT stateflow_orderstatelog_state_id_70d1101d_fk_stateflow_state_id FOREIGN KEY (state_id) REFERENCES public.stateflow_state(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stateflow_processnode stateflow_processnod_process_id_146d7cc7_fk_stateflow; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_processnode + ADD CONSTRAINT stateflow_processnod_process_id_146d7cc7_fk_stateflow FOREIGN KEY (process_id) REFERENCES public.stateflow_process(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stateflow_processnode stateflow_processnode_state_id_18e2868e_fk_stateflow_state_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_processnode + ADD CONSTRAINT stateflow_processnode_state_id_18e2868e_fk_stateflow_state_id FOREIGN KEY (state_id) REFERENCES public.stateflow_state(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stateflow_state_parameters stateflow_state_para_state_id_1888c236_fk_stateflow; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_state_parameters + ADD CONSTRAINT stateflow_state_para_state_id_1888c236_fk_stateflow FOREIGN KEY (state_id) REFERENCES public.stateflow_state(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stateflow_state_parameters stateflow_state_para_stateparameter_id_2a4745a3_fk_stateflow; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stateflow_state_parameters + ADD CONSTRAINT stateflow_state_para_stateparameter_id_2a4745a3_fk_stateflow FOREIGN KEY (stateparameter_id) REFERENCES public.stateflow_stateparameter(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_inventory stock_inventory_merchant_id_474e786a_fk_basic_info_merchant_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_inventory + ADD CONSTRAINT stock_inventory_merchant_id_474e786a_fk_basic_info_merchant_id FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_inventory stock_inventory_product_id_87e1d4a4_fk_basic_info_product_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_inventory + ADD CONSTRAINT stock_inventory_product_id_87e1d4a4_fk_basic_info_product_id FOREIGN KEY (product_id) REFERENCES public.basic_info_product(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_inventory stock_inventory_warehouse_id_38e88ef3_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_inventory + ADD CONSTRAINT stock_inventory_warehouse_id_38e88ef3_fk_basic_inf FOREIGN KEY (warehouse_id) REFERENCES public.basic_info_warehouse(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: business_purchaseorder stock_purchaseorder_merchant_id_5175381b_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.business_purchaseorder + ADD CONSTRAINT stock_purchaseorder_merchant_id_5175381b_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: business_purchaseorder stock_purchaseorder_supplier_id_bc7a300c_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.business_purchaseorder + ADD CONSTRAINT stock_purchaseorder_supplier_id_bc7a300c_fk_basic_inf FOREIGN KEY (supplier_id) REFERENCES public.basic_info_supplier(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockchangedetail stock_stockchangedet_consume_with_id_3844510b_fk_stock_sto; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangedetail + ADD CONSTRAINT stock_stockchangedet_consume_with_id_3844510b_fk_stock_sto FOREIGN KEY (consume_with_id) REFERENCES public.stock_stockchangedetail(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockchangedetail stock_stockchangedet_merchant_id_222521fc_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangedetail + ADD CONSTRAINT stock_stockchangedet_merchant_id_222521fc_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockchangedetail stock_stockchangedet_product_id_a013d432_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangedetail + ADD CONSTRAINT stock_stockchangedet_product_id_a013d432_fk_basic_inf FOREIGN KEY (product_id) REFERENCES public.basic_info_product(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockchangedetail stock_stockchangedet_stock_change_record__b388d2f0_fk_stock_sto; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangedetail + ADD CONSTRAINT stock_stockchangedet_stock_change_record__b388d2f0_fk_stock_sto FOREIGN KEY (stock_change_record_id) REFERENCES public.stock_stockchangerecord(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockchangerecord stock_stockchangerec_merchant_id_777ab07d_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangerecord + ADD CONSTRAINT stock_stockchangerec_merchant_id_777ab07d_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockchangerecord stock_stockchangerec_warehouse_id_c7a706de_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangerecord + ADD CONSTRAINT stock_stockchangerec_warehouse_id_c7a706de_fk_basic_inf FOREIGN KEY (warehouse_id) REFERENCES public.basic_info_warehouse(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockchangerecord stock_stockchangerecord_created_by_id_7317d383_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockchangerecord + ADD CONSTRAINT stock_stockchangerecord_created_by_id_7317d383_fk_auth_user_id FOREIGN KEY (created_by_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockfreeze stock_stockfreeze_cancelled_by_id_f36ed57b_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockfreeze + ADD CONSTRAINT stock_stockfreeze_cancelled_by_id_f36ed57b_fk_auth_user_id FOREIGN KEY (cancelled_by_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockfreeze stock_stockfreeze_completed_by_id_feb5742a_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockfreeze + ADD CONSTRAINT stock_stockfreeze_completed_by_id_feb5742a_fk_auth_user_id FOREIGN KEY (completed_by_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockfreeze stock_stockfreeze_frozen_by_id_747bed1c_fk_auth_user_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockfreeze + ADD CONSTRAINT stock_stockfreeze_frozen_by_id_747bed1c_fk_auth_user_id FOREIGN KEY (frozen_by_id) REFERENCES public.auth_user(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockfreeze stock_stockfreeze_merchant_id_778ecada_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockfreeze + ADD CONSTRAINT stock_stockfreeze_merchant_id_778ecada_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockfreeze stock_stockfreeze_product_id_0915f126_fk_basic_info_product_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockfreeze + ADD CONSTRAINT stock_stockfreeze_product_id_0915f126_fk_basic_info_product_id FOREIGN KEY (product_id) REFERENCES public.basic_info_product(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockfreeze stock_stockfreeze_stock_detail_id_3c1f49b0_fk_stock_sto; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockfreeze + ADD CONSTRAINT stock_stockfreeze_stock_detail_id_3c1f49b0_fk_stock_sto FOREIGN KEY (stock_detail_id) REFERENCES public.stock_stockchangedetail(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stockfreeze stock_stockfreeze_warehouse_id_7cd7f629_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stockfreeze + ADD CONSTRAINT stock_stockfreeze_warehouse_id_7cd7f629_fk_basic_inf FOREIGN KEY (warehouse_id) REFERENCES public.basic_info_warehouse(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stocksnapshot stock_stocksnapshot_merchant_id_345af02e_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stocksnapshot + ADD CONSTRAINT stock_stocksnapshot_merchant_id_345af02e_fk_basic_inf FOREIGN KEY (merchant_id) REFERENCES public.basic_info_merchant(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stocksnapshot stock_stocksnapshot_product_id_f6e948d7_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stocksnapshot + ADD CONSTRAINT stock_stocksnapshot_product_id_f6e948d7_fk_basic_inf FOREIGN KEY (product_id) REFERENCES public.basic_info_product(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stocksnapshot stock_stocksnapshot_stock_change_record__a12e8dc3_fk_stock_sto; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stocksnapshot + ADD CONSTRAINT stock_stocksnapshot_stock_change_record__a12e8dc3_fk_stock_sto FOREIGN KEY (stock_change_record_id) REFERENCES public.stock_stockchangerecord(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- Name: stock_stocksnapshot stock_stocksnapshot_warehouse_id_469ad069_fk_basic_inf; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stock_stocksnapshot + ADD CONSTRAINT stock_stocksnapshot_warehouse_id_469ad069_fk_basic_inf FOREIGN KEY (warehouse_id) REFERENCES public.basic_info_warehouse(id) DEFERRABLE INITIALLY DEFERRED; + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict lAaLqLBC97fkSperdxYl5fjzjf6qhnkvpVyPWEkZyQ37Mu4fIXbcWZBIFk58Sg7 + diff --git a/docs/business_purchase.md b/docs/business_purchase.md new file mode 100644 index 0000000..27f0131 --- /dev/null +++ b/docs/business_purchase.md @@ -0,0 +1,76 @@ +# Business 模块采购单 API + +## 创建采购单并触发入库 + +- **URL**: `POST /api/v1/purchase-orders/` +- **权限**: 需要登录且具备员工身份 +- **描述**: 创建业务模块的 `PurchaseOrder`,并异步触发库存入库任务(通过 `stock.services.StockFlowService.stock_in` 实现)。 + +### 请求体 + +```json +{ + "supplier": 1, + "warehouse": 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"] + } + ] +} +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| supplier | integer | ✅ | 供应商 ID,必须隶属于当前商户 | +| warehouse | integer | ✅ | 入库仓库 ID | +| order_date | string (date) | ✅ | 订单日期(`YYYY-MM-DD`) | +| total_amount | string/number | ✅ | 采购总金额 | +| remarks | string | 否 | 备注 | +| items | array | ✅ | 入库明细,结构需满足 `StockFlowService` 的要求(目前仅支持严谨模式:product_id + quantities) | + +> `items` 内部字段会被直接传给 `StockFlowService`,因此: +> - 严谨/严进模式:`{'product_id': 1, 'quantities': ['10', '5']}` +> - 宽松模式/严进严出出库暂未开放,后续按需扩展。 + +### 响应 + +```json +{ + "id": 35, + "message": "采购单创建成功,入库任务已排队" +} +``` + +创建成功即刻返回,实际入库明细由后台 Celery 任务生成,可在日志或 `stock_change` 记录中查看。 + +### 错误示例 + +| 状态码 | 示例 | 说明 | +|--------|------|------| +| 400 | `{"error": "缺少供应商 ID"}` | 请求缺失关键字段 | +| 400 | `{"error": "供应商 99 不存在"}` | 提供的供应商不属于当前商户 | +| 403 | `{"error": "无权限访问"}` | 当前用户无员工信息 | + +### 关联任务(business/tasks.py) + +`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` 断言参数) + diff --git a/docs/celery_testing.md b/docs/celery_testing.md new file mode 100644 index 0000000..b65b36e --- /dev/null +++ b/docs/celery_testing.md @@ -0,0 +1,108 @@ +# Celery 任务测试指南 + +## 1. 自动化测试(单元测试/CI) + +项目新增了三个演示型任务(`api_v1/tasks.py`): + +| 任务 | 功能 | +|------|------| +| `ping_task(message='ping')` | 打印并返回时间戳,用于快速验证 worker 是否可接收/返回结果 | +| `merchant_product_count(merchant_id)` | 统计某商户下产品数量,示范如何在任务中访问数据库 | +| `backup_database(output_dir=None, filename_prefix='db-backup')` | 将当前数据库导出为 `.sql` 文件(数据备份),默认保存到 `BASE_DIR/data-bak/` | + +对应的单元测试位于 `api_v1/tests.py`(类 `CeleryTasksTestCase`),通过 +`@override_settings(CELERY_TASK_ALWAYS_EAGER=True)` 让任务在测试进程内同步执行。 + +运行方式: + +```bash +uv run manage.py test api_v1.tests.CeleryTasksTestCase +``` + +若要在其他测试中调用任务,只需在测试类上使用同样的 `override_settings` +即可保证 Celery 在没有 worker 的情况下仍能同步执行。 + +--- + +## 2. 本地验证真实 worker(RabbitMQ + Celery) + +### 2.1 启动依赖服务 + +```bash +# 启动基础设施(Postgres/Redis/Rabbit/Celery worker/web) +docker compose up -d postgres redis rabbitmq + +# 启动 web(Django) +docker compose up -d web + +# 启动 Celery worker(如已运行,可跳过) +docker compose up -d celery_worker +# 或者手动:uv run celery -A flower worker -l info +``` + +> 若 worker 容器异常,可通过 `docker compose restart celery_worker` 重启。 + +### 2.2 发送测试任务 + +在另一个终端进入容器或宿主项目目录执行: + +```bash +uv run python manage.py shell +``` + +```python +from api_v1.tasks import ping_task, merchant_product_count, backup_database +from basic_info.models import Merchant + +# 发送心跳任务 +async_result = ping_task.delay('hello celery') +print(async_result.get(timeout=10)) + +# 发送统计任务(示例:使用 ID=1 的商户) +merchant = Merchant.objects.first() +result = merchant_product_count.delay(merchant.id) +print(result.get(timeout=10)) + +# 触发数据库备份(输出到默认 data-bak) +backup = backup_database.delay(filename_prefix='manual-backup') +print(backup.get(timeout=30)) # payload 中包含 backup_path,文件为 .sql +``` + +### 2.3 观察执行结果 + +1. **Worker 日志**:`docker compose logs -f celery_worker` \ + 可查看任务被消费、日志输出等信息。 +2. **RabbitMQ 控制台**:访问 `http://localhost:15672`(默认账号 guest/guest), + 观察队列长度是否回落到 0。 +3. **Task Result**:上面的 `result.get()` 会在任务完成时返回 payload, + 若超时或无法连接则会抛出异常,帮助定位问题。 + +--- + +## 3. 失败排查 Checklist + +1. **环境变量**:`CELERY_BROKER_URL` 和 `CELERY_RESULT_BACKEND` 是否指向 + 正在运行的 RabbitMQ/Redis? +2. **Worker 进程**:`docker compose ps` 确认 `celery_worker` 状态为 Up。 +3. **队列阻塞**:RabbitMQ 控制台查看是否有大量消息处于 `Unacked`。 +4. **日志级别**:测试中需要捕获日志时,可使用 + `with self.assertLogs('api_v1.tasks', level='INFO')`. +5. **自动化测试**:若任务在测试里需要真实队列,请去掉 + `CELERY_TASK_ALWAYS_EAGER` 覆盖;否则保持默认值即可同步执行。 +6. **数据库备份依赖**:`backup_database` 在 PostgreSQL 场景下需要系统可执行 `pg_dump`, + 请确保容器/宿主机已安装 PostgreSQL 客户端工具;SQLite 则会使用内置 `iterdump` 生成 `.sql`。 + +--- + +## 4. 常用命令速查 + +| 操作 | 命令 | +|------|------| +| 启动 worker | `docker compose up -d celery_worker` | +| 查看 worker 日志 | `docker compose logs -f celery_worker` | +| 重启 worker | `docker compose restart celery_worker` | +| 清空 RabbitMQ 队列 | `docker compose exec rabbitmq rabbitmqctl purge_queue flower`(示例) | +| 运行单个任务测试 | `uv run python manage.py shell` -> `ping_task.delay()` | + +通过以上步骤,即可确认 Celery + RabbitMQ 在本地能够成功执行任务,并在自动化测试里保持覆盖。若需要新增业务任务,可参考 `api_v1/tasks.py` 的写法:使用 `@shared_task`,在任务内进行必要的日志记录,便于问题排查。*** + diff --git a/docs/sse.md b/docs/sse.md new file mode 100644 index 0000000..cc7a7a4 --- /dev/null +++ b/docs/sse.md @@ -0,0 +1,217 @@ +# SSE (Server-Sent Events) 模块 + +基于 Django 和 Django REST Framework 的服务器推送事件实现,支持多商户隔离。 + +## 功能特性 + +- ✅ 使用 DRF 处理请求和响应(非流式端点) +- ✅ 支持多种请求格式(JSON、Form Data、Multipart) +- ✅ 自动数据验证和序列化 +- ✅ 异步支持,高并发处理 +- ✅ 心跳机制保持连接活跃 +- ✅ 自动清理断开的连接 +- ✅ 连接状态监控 +- ✅ 多商户消息隔离 +- ✅ JWT认证和商户验证 + +## API 端点 + +### 1. 订阅 SSE 事件流 + +**端点**: `GET /sse/` + +客户端连接此端点保持长连接,接收服务器推送的事件。 + +**认证要求**: 需要JWT认证,且用户必须有关联的商户 + +**示例**: +```bash +# 需要提供JWT token +curl -N -H "Authorization: Bearer YOUR_JWT_TOKEN" http://localhost:8000/sse/ +``` + +**JavaScript 示例**: +```javascript +// 需要在连接时提供认证头 +const eventSource = new EventSource('/sse/', { + headers: { + 'Authorization': `Bearer ${yourJwtToken}` + } +}); + +eventSource.onmessage = function(event) { + const data = JSON.parse(event.data); + console.log('收到消息:', data); + console.log('商户ID:', data.merchant_id); +}; +``` + +**注意**: 浏览器原生的EventSource不支持自定义请求头,因此推荐使用EventSource polyfill或fetch实现。 + +--- + +### 2. 推送事件到当前商户的客户端 + +**端点**: `POST /sse/push/` + +向当前用户所属商户的所有已连接客户端广播消息。 + +**认证要求**: 需要JWT认证,且用户必须有关联的商户 + +**请求参数**: +- 自动使用当前用户的商户ID +- 固定发送测试消息:'订单已支付' +- 固定事件类型:'order_paid' +- 固定对象ID:12345 + +**请求示例**: +```bash +curl -X POST http://localhost:8000/sse/push/ \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +**响应示例**: +```json +{ + "status": "ok", + "message": "Test event broadcasted to your merchant", + "merchant_id": 1, + "clients": 3 +} +``` + +--- + +### 3. 获取连接状态 + +**端点**: `GET /sse/status/` + +查询当前 SSE 服务器的状态和连接数。 + +**认证要求**: 需要JWT认证,且用户必须有关联的商户 + +**示例**: +```bash +curl -H "Authorization: Bearer YOUR_JWT_TOKEN" http://localhost:8000/sse/status/ +``` + +**响应示例**: +```json +{ + "status": "running", + "total_clients": 10, + "merchant_clients": 3, + "merchant_id": 1, + "message": "SSE server is running with 10 total connections, 3 for your merchant" +} +``` + +### 4. 关闭商户连接 + +**端点**: `POST /sse/shutdown/` + +关闭当前用户所属商户的所有SSE连接。 + +**认证要求**: 需要JWT认证,且用户必须有关联的商户 + +**示例**: +```bash +curl -X POST http://localhost:8000/sse/shutdown/ \ + -H "Authorization: Bearer YOUR_JWT_TOKEN" +``` + +**响应示例**: +```json +{ + "status": "ok", + "message": "Shutdown signal sent to your merchant's SSE connections", + "merchant_id": 1, + "clients": 3 +} +``` + +## 启动服务器 + +使用 Uvicorn (ASGI 服务器) 启动: + +```bash +# 开发环境 +uvicorn flower.asgi:application --reload --host 0.0.0.0 --port 8000 + +# 生产环境 +uvicorn flower.asgi:application --host 0.0.0.0 --port 8000 --workers 4 +``` + +## 测试 + +打开 `sse_test.html` 在浏览器中测试: + +1. 点击"连接 SSE"建立连接 +2. 输入消息 +3. 点击"发送 (JSON)"或"发送 (Form Data)"测试不同格式 +4. 点击"获取连接状态"查看当前连接数 +5. 打开多个浏览器标签测试广播功能 + +## Python 客户端示例 + +```python +import requests +import sseclient # pip install sseclient-py + +# 订阅事件 +response = requests.get('http://localhost:8000/sse/', stream=True) +client = sseclient.SSEClient(response) + +for event in client.events(): + print(f'收到消息: {event.data}') +``` + +## 技术实现 + +- **流式响应**: 使用 `StreamingHttpResponse` 实现 SSE 连接 +- **队列机制**: 每个连接对应一个 `queue.Queue`,按商户组织 +- **心跳**: 30 秒超时,自动发送心跳保持连接 +- **DRF 集成**: 非流式端点使用 DRF 的 `@api_view` 和序列化器 +- **多格式支持**: 自动解析 JSON、Form Data、Multipart 等格式 +- **商户隔离**: 所有消息按商户隔离,确保数据安全 +- **JWT认证**: 使用 DRF Simple JWT 进行身份验证 + +## 消息结构 + +### 连接成功消息 +```json +{ + "type": "connected", + "message": "SSE connection established", + "merchant_id": 1 +} +``` + +### 业务事件消息 +```json +{ + "mode": "simple_message", + "type": "order_paid", + "message": "订单已支付", + "object_id": 12345, + "merchant_id": 1 +} +``` + +### 服务器关闭消息 +```json +{ + "type": "server_shutdown", + "message": "Server shutting down your connections, please reconnect later" +} +``` + +## 注意事项 + +1. 必须使用 ASGI 服务器(如 Uvicorn、Daphne)运行 +2. 不支持使用传统的 WSGI 服务器(如 Gunicorn + WSGI) +3. 如果使用 Nginx,需要禁用缓冲:`X-Accel-Buffering: no` +4. 所有SSE端点都需要JWT认证,且用户必须有关联的商户 +5. 浏览器原生的EventSource不支持自定义请求头,推荐使用polyfill或fetch实现 +6. SSE 使用 GET 请求,注意 CORS 配置 diff --git a/api_v1/views/upload/API.md b/docs/upload.md similarity index 100% rename from api_v1/views/upload/API.md rename to docs/upload.md diff --git a/flower/celery.py b/flower/celery.py index c6c630b..60a5a20 100644 --- a/flower/celery.py +++ b/flower/celery.py @@ -12,4 +12,3 @@ app.autodiscover_tasks() @app.task(bind=True) def debug_task(self): print(f'Celery debug task - request: {self.request!r}') - diff --git a/sse/README.md b/sse/README.md deleted file mode 100644 index ce2c38b..0000000 --- a/sse/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# SSE (Server-Sent Events) 模块 - -基于 Django REST Framework 的服务器推送事件实现。 - -## 功能特性 - -- ✅ 使用 DRF 处理请求和响应 -- ✅ 支持多种请求格式(JSON、Form Data、Multipart) -- ✅ 自动数据验证和序列化 -- ✅ 异步支持,高并发处理 -- ✅ 心跳机制保持连接活跃 -- ✅ 自动清理断开的连接 -- ✅ 连接状态监控 - -## API 端点 - -### 1. 订阅 SSE 事件流 - -**端点**: `GET /sse/` - -客户端连接此端点保持长连接,接收服务器推送的事件。 - -**示例**: -```bash -curl -N http://localhost:8000/sse/ -``` - -**JavaScript 示例**: -```javascript -const eventSource = new EventSource('http://localhost:8000/sse/'); - -eventSource.onmessage = function(event) { - const data = JSON.parse(event.data); - console.log('收到消息:', data); -}; -``` - ---- - -### 2. 推送事件到所有客户端 - -**端点**: `POST /sse/push/` - -向所有已连接的客户端广播消息。 - -**请求参数**: -- `message` (必填): 消息内容 -- `type` (可选): 事件类型,默认为 'message' - -**支持的请求格式**: - -#### JSON 格式 -```bash -curl -X POST http://localhost:8000/sse/push/ \ - -H "Content-Type: application/json" \ - -d '{"message": "Hello, SSE!", "type": "notification"}' -``` - -#### Form Data 格式 -```bash -curl -X POST http://localhost:8000/sse/push/ \ - -d "message=Hello, SSE!" \ - -d "type=notification" -``` - -#### Multipart Form Data -```bash -curl -X POST http://localhost:8000/sse/push/ \ - -F "message=Hello, SSE!" \ - -F "type=notification" -``` - -**响应示例**: -```json -{ - "status": "success", - "message": "Event sent to 3 client(s)", - "clients": 3, - "sent": 3 -} -``` - ---- - -### 3. 获取连接状态 - -**端点**: `GET /sse/status/` - -查询当前 SSE 服务器的状态和连接数。 - -**示例**: -```bash -curl http://localhost:8000/sse/status/ -``` - -**响应示例**: -```json -{ - "status": "running", - "clients": 3, - "message": "SSE server is running with 3 active connection(s)" -} -``` - -## 启动服务器 - -使用 Uvicorn (ASGI 服务器) 启动: - -```bash -# 开发环境 -uvicorn flower.asgi:application --reload --host 0.0.0.0 --port 8000 - -# 生产环境 -uvicorn flower.asgi:application --host 0.0.0.0 --port 8000 --workers 4 -``` - -## 测试 - -打开 `sse_test.html` 在浏览器中测试: - -1. 点击"连接 SSE"建立连接 -2. 输入消息 -3. 点击"发送 (JSON)"或"发送 (Form Data)"测试不同格式 -4. 点击"获取连接状态"查看当前连接数 -5. 打开多个浏览器标签测试广播功能 - -## Python 客户端示例 - -```python -import requests -import sseclient # pip install sseclient-py - -# 订阅事件 -response = requests.get('http://localhost:8000/sse/', stream=True) -client = sseclient.SSEClient(response) - -for event in client.events(): - print(f'收到消息: {event.data}') -``` - -## 技术实现 - -- **异步视图**: 使用 `async def` 实现异步处理 -- **队列机制**: 每个连接对应一个 `asyncio.Queue` -- **心跳**: 30 秒超时,自动发送心跳保持连接 -- **DRF 集成**: 使用 DRF 的 `@api_view` 和序列化器 -- **多格式支持**: 自动解析 JSON、Form Data、Multipart 等格式 - -## 注意事项 - -1. 必须使用 ASGI 服务器(如 Uvicorn、Daphne)运行 -2. 不支持使用传统的 WSGI 服务器(如 Gunicorn + WSGI) -3. 如果使用 Nginx,需要禁用缓冲:`X-Accel-Buffering: no` -4. SSE 使用 GET 请求,注意 CORS 配置 diff --git a/sse/auth_utils.py b/sse/auth_utils.py new file mode 100644 index 0000000..8325d40 --- /dev/null +++ b/sse/auth_utils.py @@ -0,0 +1,117 @@ +""" +SSE认证工具模块 + +提供统一的认证和商户验证功能,用于SSE连接端点和其他需要认证的地方 +""" + +from django.http import HttpResponseForbidden +from rest_framework.authentication import get_authorization_header +from rest_framework_simplejwt.authentication import JWTAuthentication +from rest_framework_simplejwt.exceptions import InvalidToken +from rest_framework.exceptions import AuthenticationFailed +import logging + +logger = logging.getLogger(__name__) + + +def get_user_merchant_id(request): + """获取用户所属商户ID""" + try: + # 检查用户是否已认证 + if not request.user.is_authenticated: + return None + # 获取商户ID + return request.user.employee.merchant_id + except (AttributeError, AttributeError): + return None + + +def authenticate_sse_request(request): + """ + 对SSE连接请求进行认证 + + 参数: + - request: Django HTTP请求对象 + + 返回: + - tuple: (user, merchant_id) 认证成功返回用户和商户ID + - None: 认证失败返回None + """ + jwt_authenticator = JWTAuthentication() + + try: + # 获取Authorization头 + auth_header = get_authorization_header(request).split() + if not auth_header or auth_header[0].lower() != b'bearer': + logger.warning("SSE认证失败: 缺少Bearer token") + return None + + if len(auth_header) < 2: + logger.warning("SSE认证失败: Bearer token格式错误") + return None + + token = auth_header[1].decode('utf-8') + + # 验证token + validated_token = jwt_authenticator.get_validated_token(token) + user = jwt_authenticator.get_user(validated_token) + + # 检查用户是否有关联的员工和商户 + try: + merchant_id = user.employee.merchant_id + if not merchant_id: + logger.warning(f"用户 {user.username} 无关联商户") + return None + return user, merchant_id + except AttributeError: + logger.warning(f"用户 {user.username} 无关联员工") + return None + + except (IndexError, InvalidToken, AuthenticationFailed) as e: + logger.warning(f"SSE认证失败: {str(e)}") + return None + + +def require_sse_authentication(view_func): + """ + 装饰器:要求SSE连接认证 + + 用于SSE连接端点的认证,如果认证失败则返回403响应 + OPTIONS请求跳过认证 + """ + def wrapper(request, *args, **kwargs): + # OPTIONS请求跳过认证 + if request.method == 'OPTIONS': + return view_func(request, *args, **kwargs) + + auth_result = authenticate_sse_request(request) + if not auth_result: + return HttpResponseForbidden("Authentication failed or user has no associated merchant") + + user, merchant_id = auth_result + request.user = user + request.merchant_id = merchant_id + + return view_func(request, *args, **kwargs) + + return wrapper + + +def validate_sse_request(request): + """ + 验证SSE请求的认证状态 + + 返回: + - tuple: (is_valid, error_response) + - is_valid: bool 表示请求是否有效 + - error_response: HttpResponse 如果无效则返回错误响应,否则为None + """ + auth_result = authenticate_sse_request(request) + if not auth_result: + return False, HttpResponseForbidden("认证失败或用户无关联商户") + + user, merchant_id = auth_result + request.user = user + request.merchant_id = merchant_id + + return True, None \ No newline at end of file diff --git a/sse/client_example.js b/sse/client_example.js new file mode 100644 index 0000000..cba9d73 --- /dev/null +++ b/sse/client_example.js @@ -0,0 +1,187 @@ +/** + * SSE客户端示例 + * + * 由于浏览器原生的EventSource不支持自定义请求头, + * 这里提供了两种实现方式: + * 1. 使用fetch实现(推荐) + * 2. 使用EventSource polyfill(备选) + */ + +/** + * 方式1:使用fetch实现SSE客户端(推荐) + * + * @param {string} url - SSE端点URL + * @param {string} token - JWT认证token + * @param {function} onMessage - 接收到消息时的回调函数 + * @param {function} onError - 连接错误时的回调函数 + * @param {function} onClose - 连接关闭时的回调函数 + */ +function createSSEConnectionWithFetch(url, token, onMessage, onError, onClose) { + const controller = new AbortController(); + const signal = controller.signal; + + // 启动一个长时间运行的fetch请求 + fetch(url, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'text/event-stream', + 'Cache-Control': 'no-cache', + }, + signal: signal + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + function processBuffer() { + const lines = buffer.split('\n'); + buffer = lines.pop(); // 保留最后一个不完整的行 + + for (const line of lines) { + if (line.trim() === '') continue; // 空行表示事件结束 + + if (line.startsWith('data: ')) { + const data = line.substring(6); // 去掉 'data: ' 前缀 + try { + const event = JSON.parse(data); + if (onMessage) onMessage(event); + } catch (e) { + console.error('Error parsing SSE data:', e); + } + } + } + } + + function read() { + return reader.read().then(({ done, value }) => { + if (done) { + if (onClose) onClose(); + return; + } + + buffer += decoder.decode(value, { stream: true }); + processBuffer(); + + // 继续读取 + return read(); + }); + } + + return read(); + }) + .catch(error => { + if (onError) onError(error); + }); + + // 返回一个对象,包含关闭连接的方法 + return { + close: () => controller.abort() + }; +} + +/** + * 方式2:使用EventSource Polyfill实现 + * + * 需要先安装EventSource polyfill: + * npm install event-source-polyfill + * + * 在应用入口处导入: + * import 'event-source-polyfill'; + */ +function createSSEConnectionWithPolyfill(url, token, onMessage, onError, onClose) { + // 创建带有认证的URL + const urlWithAuth = `${url}?token=${encodeURIComponent(token)}`; + + const eventSource = new EventSource(urlWithAuth); + + eventSource.onmessage = function(event) { + try { + const data = JSON.parse(event.data); + if (onMessage) onMessage(data); + } catch (e) { + console.error('Error parsing SSE data:', e); + } + }; + + eventSource.onerror = function(error) { + if (onError) onError(error); + }; + + eventSource.onclose = function() { + if (onClose) onClose(); + }; + + return { + close: () => eventSource.close() + }; +} + +/** + * 使用示例 + */ +const JWT_TOKEN = 'your_jwt_token_here'; +const SSE_URL = '/sse/'; + +// 使用fetch方式(推荐) +const sseConnection = createSSEConnectionWithFetch( + SSE_URL, + JWT_TOKEN, + (event) => { + console.log('收到SSE事件:', event); + + // 根据事件类型处理不同业务逻辑 + switch (event.type) { + case 'connected': + console.log(`SSE连接成功,商户ID: ${event.merchant_id}`); + break; + + case 'order_paid': + console.log(`订单已支付: ${event.object_id}`); + // 刷新订单列表或显示通知 + break; + + case 'stock_change_record': + console.log(`库存变动记录: ${event.object_id}`); + // 刷新库存数据 + break; + + case 'server_shutdown': + console.log('服务器即将关闭连接'); + // 可以提示用户重新连接 + break; + } + }, + (error) => { + console.error('SSE连接错误:', error); + // 可以在这里实现重连逻辑 + setTimeout(() => { + console.log('尝试重新连接...'); + // 重新创建连接 + }, 5000); + }, + () => { + console.log('SSE连接已关闭'); + } +); + +// 当需要关闭连接时(例如用户退出登录) +// sseConnection.close(); + +/** + * 如果需要使用polyfill方式,确保在应用入口处导入polyfill + */ +// import 'event-source-polyfill'; +// +// const sseConnection = createSSEConnectionWithPolyfill( +// SSE_URL, +// JWT_TOKEN, +// (event) => { /* 处理事件 */ }, +// (error) => { /* 处理错误 */ }, +// () => { /* 处理关闭 */ } +// ); \ No newline at end of file diff --git a/sse/services.py b/sse/services.py index 0666215..b2e0915 100644 --- a/sse/services.py +++ b/sse/services.py @@ -3,50 +3,81 @@ import logging logger = logging.getLogger(__name__) -# 存储所有活动的 SSE 连接队列(同步队列) -_connections = set() +# 存储所有活动的 SSE 连接队列(按商户ID组织) +_connections = {} # {merchant_id: set([conn_queue1, conn_queue2, ...])} -def get_active_connections(): - """获取当前所有活动的 SSE 连接队列""" - return _connections +def get_all_connections_count(): + """获取所有商户的连接总数""" + total = 0 + for merchant_id, connections in _connections.items(): + total += len(connections) + return total -def push_connection(conn_queue): +def get_merchant_connections(merchant_id): + """获取指定商户的所有连接队列""" + return _connections.get(merchant_id, set()) + + +def get_all_merchant_ids(): + """获取所有有活跃连接的商户ID列表""" + return list(_connections.keys()) + + +def push_connection(merchant_id, conn_queue): """ - 将一个新的连接队列添加到活动连接集合中 + 将一个新的连接队列添加到指定商户的连接集合中 参数: + - merchant_id: 商户ID - conn_queue: queue.Queue 实例 """ - _connections.add(conn_queue) - logger.info(f"新的 SSE 连接建立,当前连接数: {len(_connections)}") + if merchant_id not in _connections: + _connections[merchant_id] = set() + + _connections[merchant_id].add(conn_queue) + total_connections = sum(len(conns) for conns in _connections.values()) + logger.info(f"商户 {merchant_id} 的新 SSE 连接建立,该商户连接数: {len(_connections[merchant_id])},总连接数: {total_connections}") -def remove_connection(conn_queue): +def remove_connection(merchant_id, conn_queue): """ - 从活动连接集合中移除一个连接队列 + 从指定商户的连接集合中移除一个连接队列 参数: + - merchant_id: 商户ID - conn_queue: queue.Queue 实例 """ - _connections.discard(conn_queue) - logger.info(f"SSE 连接断开,当前连接数: {len(_connections)}") + if merchant_id in _connections: + _connections[merchant_id].discard(conn_queue) + + # 如果该商户没有其他连接,则移除整个商户记录 + if not _connections[merchant_id]: + del _connections[merchant_id] + + total_connections = sum(len(conns) for conns in _connections.values()) + logger.info(f"商户 {merchant_id} 的 SSE 连接断开,该商户连接数: {len(_connections.get(merchant_id, set()))},总连接数: {total_connections}") + else: + logger.warning(f"尝试从未注册的商户 {merchant_id} 移除连接") def cleanup_all_connections(): """ 清理所有连接(用于服务器关闭时) """ - connections_list = list(_connections) - for conn_queue in connections_list: - try: - # 发送关闭信号到队列 - if hasattr(conn_queue, 'put_nowait'): - conn_queue.put_nowait({'type': 'server_shutdown', 'message': 'Server shutting down'}) - logger.debug(f'shutdown connection {conn_queue}') - except Exception: - pass # 忽略错误,因为连接可能已经断开 + all_connections = {} + for merchant_id, connections in list(_connections.items()): + all_connections[merchant_id] = list(connections) + for conn_queue in connections: + try: + # 发送关闭信号到队列 + if hasattr(conn_queue, 'put_nowait'): + conn_queue.put_nowait({'type': 'server_shutdown', 'message': 'Server shutting down'}) + logger.debug(f'shutdown connection {conn_queue} for merchant {merchant_id}') + except Exception: + pass # 忽略错误,因为连接可能已经断开 + _connections.clear() logger.info("所有SSE连接已清理") @@ -58,23 +89,60 @@ def push_sse_event_to_all(event_data: dict): 参数: - event_data: 要发送的事件数据(字典) """ + disconnected = {} + + for merchant_id, connections in list(_connections.items()): + disconnected[merchant_id] = [] + + for conn_queue in connections: + try: + # 使用put_nowait非阻塞发送消息 + conn_queue.put_nowait(event_data) + except queue.Full: + # 队列满了,说明客户端处理消息太慢,跳过这条消息 + logger.warning(f"商户 {merchant_id} 队列已满,跳过消息: {event_data.get('type', 'unknown')}") + except Exception as e: + # 其他异常可能表示连接已断开 + logger.error(f"向商户 {merchant_id} 的队列发送消息失败: {e}") + disconnected[merchant_id].append(conn_queue) + + # 清理断开的连接 + for merchant_id, conn_queues in disconnected.items(): + for conn_queue in conn_queues: + remove_connection(merchant_id, conn_queue) + + +def push_sse_event_to_merchant(merchant_id, event_data: dict): + """ + 向指定商户的所有连接客户端广播一个 SSE 事件(同步队列版本) + + 参数: + - merchant_id: 目标商户ID + - event_data: 要发送的事件数据(字典) + """ + if merchant_id not in _connections: + logger.info(f"商户 {merchant_id} 没有活跃连接,跳过消息发送") + return + disconnected = [] - for conn_queue in list(_connections): + for conn_queue in list(_connections[merchant_id]): try: # 使用put_nowait非阻塞发送消息 conn_queue.put_nowait(event_data) except queue.Full: # 队列满了,说明客户端处理消息太慢,跳过这条消息 - logger.warning(f"队列已满,跳过消息: {event_data.get('type', 'unknown')}") + logger.warning(f"商户 {merchant_id} 队列已满,跳过消息: {event_data.get('type', 'unknown')}") except Exception as e: # 其他异常可能表示连接已断开 - logger.error(f"向队列发送消息失败: {e}") + logger.error(f"向商户 {merchant_id} 的队列发送消息失败: {e}") disconnected.append(conn_queue) # 清理断开的连接 for conn_queue in disconnected: - remove_connection(conn_queue) + remove_connection(merchant_id, conn_queue) + + logger.info(f"向商户 {merchant_id} 广播 SSE 事件: type={event_data.get('type', 'unknown')}, 接收客户端数={len(_connections.get(merchant_id, set()))}") def push_simple_message_with_object_id(event_type: str, message: str, object_id): @@ -93,4 +161,25 @@ def push_simple_message_with_object_id(event_type: str, message: str, object_id) 'object_id': object_id, } push_sse_event_to_all(event_data) - logger.info(f"广播 SSE 事件: type={event_type}, object_id={object_id}, 接收客户端数={len(_connections)}") + total_connections = sum(len(conns) for conns in _connections.values()) + logger.info(f"广播 SSE 事件: type={event_type}, object_id={object_id}, 接收客户端数={total_connections}") + + +def push_simple_message_with_object_id_to_merchant(merchant_id, event_type: str, message: str, object_id): + """ + 向指定商户的所有连接客户端广播一个简单消息事件,包含关联对象ID + + 参数: + - merchant_id: 目标商户ID + - event_type: 事件类型字符串 + - message: 消息内容字符串 + - object_id: 关联对象的ID(整数或字符串) + """ + event_data = { + 'mode': 'simple_message', + 'type': event_type, + 'message': message, + 'object_id': object_id, + 'merchant_id': merchant_id, # 添加商户ID,便于客户端验证 + } + push_sse_event_to_merchant(merchant_id, event_data) diff --git a/sse/test_sse.py b/sse/test_sse.py new file mode 100644 index 0000000..cf3fbcb --- /dev/null +++ b/sse/test_sse.py @@ -0,0 +1,409 @@ +import json +import time +import threading +from queue import Queue +from unittest.mock import patch, MagicMock + +from django.test import TestCase +from django.contrib.auth import get_user_model +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APIClient +from rest_framework_simplejwt.tokens import RefreshToken + +from basic_info import models as basic_models +from . import services + +User = get_user_model() + + +class SSEAPITestCase(TestCase): + """SSE API测试用例""" + + def setUp(self): + """设置测试数据""" + # 创建测试商户 + self.merchant1 = basic_models.Merchant.objects.create( + name='Test Merchant 1', + type=basic_models.MerchantTypeEnum.STORE, + ) + self.merchant2 = basic_models.Merchant.objects.create( + name='Test Merchant 2', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + + # 创建测试用户 + self.user1 = User.objects.create_user(username='user1', password='testpass') + self.user2 = User.objects.create_user(username='user2', password='testpass') + self.user_no_employee = User.objects.create_user(username='no_employee', password='testpass') + + # 创建员工并关联商户 + self.employee1 = basic_models.Employee.objects.create( + merchant=self.merchant1, + sys_user=self.user1, + name='Employee 1', + mobile='13800138001', + ) + self.employee2 = basic_models.Employee.objects.create( + merchant=self.merchant2, + sys_user=self.user2, + name='Employee 2', + mobile='13800138002', + ) + + # 生成JWT token + refresh1 = RefreshToken.for_user(self.user1) + self.token1 = str(refresh1.access_token) + + refresh2 = RefreshToken.for_user(self.user2) + self.token2 = str(refresh2.access_token) + + refresh_no_employee = RefreshToken.for_user(self.user_no_employee) + self.token_no_employee = str(refresh_no_employee.access_token) + + # 设置客户端 + self.client = APIClient() + + # 清理所有连接 + services._connections.clear() + + def test_sse_connection_without_token(self): + """Test SSE connection without JWT token""" + response = self.client.get('/sse/') + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'Authentication failed or user has no associated merchant') + + # 检查连接未被添加到服务中 + self.assertEqual(len(services._connections), 0) + + def test_sse_connection_with_invalid_token(self): + """Test SSE connection with invalid JWT token""" + response = self.client.get( + '/sse/', + HTTP_AUTHORIZATION='Bearer invalid_token' + ) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'Authentication failed or user has no associated merchant') + + # 检查连接未被添加到服务中 + self.assertEqual(len(services._connections), 0) + + def test_sse_connection_user_no_employee(self): + """Test SSE connection with user without associated employee""" + response = self.client.get( + '/sse/', + HTTP_AUTHORIZATION=f'Bearer {self.token_no_employee}' + ) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'Authentication failed or user has no associated merchant') + + # 检查连接未被添加到服务中 + self.assertEqual(len(services._connections), 0) + + def test_push_test_event_to_merchant(self): + """Test pushing test event to a specific merchant""" + # 手动建立连接 + queue1 = Queue() + services.push_connection(self.merchant1.id, queue1) + + # 推送事件 + response = self.client.post( + '/sse/push/', + HTTP_AUTHORIZATION=f'Bearer {self.token1}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['status'], 'ok') + self.assertEqual(data['message'], 'Test event broadcasted to your merchant') + self.assertEqual(data['merchant_id'], self.merchant1.id) + self.assertEqual(data['clients'], 1) + + # 清理连接 + services.remove_connection(self.merchant1.id, queue1) + + def test_push_test_event_unauthenticated(self): + """Test pushing test event without authentication""" + response = self.client.post('/sse/push/') + + self.assertEqual(response.status_code, 401) + + def test_push_test_event_user_no_employee(self): + """Test pushing test event with user without associated employee""" + response = self.client.post( + '/sse/push/', + HTTP_AUTHORIZATION=f'Bearer {self.token_no_employee}' + ) + + self.assertEqual(response.status_code, 403) + data = response.json() + self.assertEqual(data['error'], 'User has no associated merchant') + + def test_get_sse_status(self): + """Test getting SSE status""" + # 手动建立两个商户的连接 + queue1 = Queue() + queue2 = Queue() + services.push_connection(self.merchant1.id, queue1) + services.push_connection(self.merchant2.id, queue2) + + # 获取状态 + response = self.client.get( + '/sse/status/', + HTTP_AUTHORIZATION=f'Bearer {self.token1}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['status'], 'running') + self.assertEqual(data['total_clients'], 2) # 所有连接数 + self.assertEqual(data['merchant_clients'], 1) # 当前商户的连接数 + self.assertEqual(data['merchant_id'], self.merchant1.id) + + # 清理连接 + services.remove_connection(self.merchant1.id, queue1) + services.remove_connection(self.merchant2.id, queue2) + + def test_get_sse_status_unauthenticated(self): + """Test getting SSE status without authentication""" + response = self.client.get('/sse/status/') + + self.assertEqual(response.status_code, 401) + + # def test_shutdown_merchant_connections(self): + """Test shutting down merchant connections""" + # 手动建立两个商户的连接 + queue1 = Queue() + queue2 = Queue() + services.push_connection(self.merchant1.id, queue1) + services.push_connection(self.merchant2.id, queue2) + + # 关闭商户1的连接 + response = self.client.post( + '/sse/shutdown/', + HTTP_AUTHORIZATION=f'Bearer {self.token1}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['status'], 'ok') + self.assertEqual(data['message'], 'Shutdown signal sent to your merchant\'s SSE connections') + self.assertEqual(data['merchant_id'], self.merchant1.id) + self.assertEqual(data['clients'], 1) + + # 检查连接状态 - 商户1的连接应该已被关闭 + merchant1_connections = services.get_merchant_connections(self.merchant1.id) + merchant2_connections = services.get_merchant_connections(self.merchant2.id) + # 如果测试失败,打印调试信息 + if len(merchant1_connections) != 0 or len(merchant2_connections) != 1: + print(f"Debug: merchant1_connections={len(merchant1_connections)}, merchant2_connections={len(merchant2_connections)}") + print(f"Debug: all connections={services._connections}") + self.assertEqual(len(merchant1_connections), 0) + self.assertEqual(len(merchant2_connections), 1) + + def test_shutdown_merchant_connections_unauthenticated(self): + """Test shutting down merchant connections without authentication""" + response = self.client.post('/sse/shutdown/') + + self.assertEqual(response.status_code, 401) + + def test_merchant_isolation(self): + """Test merchant isolation""" + # 为两个商户分别建立连接 + queue1 = Queue() + queue2 = Queue() + queue3 = Queue() + queue4 = Queue() + + services.push_connection(self.merchant1.id, queue1) + services.push_connection(self.merchant1.id, queue2) + services.push_connection(self.merchant2.id, queue3) + services.push_connection(self.merchant2.id, queue4) + + # 向商户1推送事件 + response = self.client.post( + '/sse/push/', + HTTP_AUTHORIZATION=f'Bearer {self.token1}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['merchant_id'], self.merchant1.id) + self.assertEqual(data['clients'], 2) # 商户1有2个客户端 + + # 向商户2推送事件 + response = self.client.post( + '/sse/push/', + HTTP_AUTHORIZATION=f'Bearer {self.token2}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['merchant_id'], self.merchant2.id) + self.assertEqual(data['clients'], 2) # 商户2有2个客户端 + + # 确认商户隔离 + merchant1_connections = services.get_merchant_connections(self.merchant1.id) + merchant2_connections = services.get_merchant_connections(self.merchant2.id) + self.assertEqual(len(merchant1_connections), 2) + self.assertEqual(len(merchant2_connections), 2) + + # 清理连接 + services.remove_connection(self.merchant1.id, queue1) + services.remove_connection(self.merchant1.id, queue2) + services.remove_connection(self.merchant2.id, queue3) + services.remove_connection(self.merchant2.id, queue4) + + def test_sse_connection_with_options_request(self): + """Test SSE connection with OPTIONS request""" + response = self.client.options('/sse/') + + # OPTIONS请求应该成功,返回CORS头 + self.assertEqual(response.status_code, 200) + + # 检查CORS头 - OPTIONS请求会返回CORS头 + # 在Django测试环境中,CORS头可能由中间件处理 + # 我们主要检查响应状态码是否正确 + self.assertEqual(response.status_code, 200) + + +class SSEServicesTestCase(TestCase): + """SSE服务层测试用例""" + + def setUp(self): + """设置测试数据""" + self.merchant1_id = 1 + self.merchant2_id = 2 + + # 清理所有连接 + services._connections.clear() + + def test_push_connection(self): + """测试添加连接""" + queue1 = Queue() + queue2 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + services.push_connection(self.merchant1_id, queue2) + services.push_connection(self.merchant2_id, Queue()) + + # 检查连接 + merchant1_connections = services.get_merchant_connections(self.merchant1_id) + merchant2_connections = services.get_merchant_connections(self.merchant2_id) + + self.assertEqual(len(merchant1_connections), 2) + self.assertEqual(len(merchant2_connections), 1) + self.assertIn(queue1, merchant1_connections) + self.assertIn(queue2, merchant1_connections) + + def test_remove_connection(self): + """测试移除连接""" + queue1 = Queue() + queue2 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + services.push_connection(self.merchant1_id, queue2) + + # 移除一个连接 + services.remove_connection(self.merchant1_id, queue1) + + # 检查连接 + merchant1_connections = services.get_merchant_connections(self.merchant1_id) + self.assertEqual(len(merchant1_connections), 1) + self.assertNotIn(queue1, merchant1_connections) + self.assertIn(queue2, merchant1_connections) + + def test_remove_all_merchant_connections(self): + """测试移除商户所有连接""" + queue1 = Queue() + queue2 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + services.push_connection(self.merchant1_id, queue2) + + # 移除所有连接 + services.remove_connection(self.merchant1_id, queue1) + services.remove_connection(self.merchant1_id, queue2) + + # 检查连接 + merchant1_connections = services.get_merchant_connections(self.merchant1_id) + self.assertEqual(len(merchant1_connections), 0) + + # 商户记录应该被移除 + self.assertNotIn(self.merchant1_id, services._connections) + + def test_get_all_connections_count(self): + """测试获取所有连接数""" + # 添加连接 + services.push_connection(self.merchant1_id, Queue()) + services.push_connection(self.merchant1_id, Queue()) + services.push_connection(self.merchant2_id, Queue()) + + # 检查总连接数 + total = services.get_all_connections_count() + self.assertEqual(total, 3) + + def test_push_event_to_merchant(self): + """测试向特定商户推送事件""" + queue1 = Queue() + queue2 = Queue() + queue3 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + services.push_connection(self.merchant1_id, queue2) + services.push_connection(self.merchant2_id, queue3) + + # 向商户1推送事件 + event_data = {'type': 'test', 'message': 'test message'} + services.push_sse_event_to_merchant(self.merchant1_id, event_data) + + # 检查消息 + self.assertEqual(queue1.qsize(), 1) + self.assertEqual(queue2.qsize(), 1) + self.assertEqual(queue3.qsize(), 0) # 商户2不应该收到消息 + + # 检查消息内容 + self.assertEqual(queue1.get_nowait(), event_data) + self.assertEqual(queue2.get_nowait(), event_data) + + def test_push_event_to_nonexistent_merchant(self): + """测试向不存在的商户推送事件""" + # 向不存在的商户推送事件 + event_data = {'type': 'test', 'message': 'test message'} + services.push_sse_event_to_merchant(999, event_data) + + # 不应该抛出异常,也不会有连接受到影响 + self.assertEqual(len(services._connections), 0) + + def test_push_simple_message_to_merchant(self): + """测试向特定商户推送简单消息""" + queue1 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + + # 推送简单消息 + services.push_simple_message_with_object_id_to_merchant( + self.merchant1_id, + 'order_paid', + 'Order paid', + 12345 + ) + + # 检查消息 + self.assertEqual(queue1.qsize(), 1) + + # 检查消息内容 + message = queue1.get_nowait() + self.assertEqual(message['mode'], 'simple_message') + self.assertEqual(message['type'], 'order_paid') + self.assertEqual(message['message'], 'Order paid') + self.assertEqual(message['object_id'], 12345) + self.assertEqual(message['merchant_id'], self.merchant1_id) \ No newline at end of file diff --git a/sse/tests.py b/sse/tests.py index 7ce503c..84ea8c4 100644 --- a/sse/tests.py +++ b/sse/tests.py @@ -1,3 +1,408 @@ -from django.test import TestCase +import json +import time +import threading +from queue import Queue +from unittest.mock import patch, MagicMock -# Create your tests here. +from django.test import TestCase +from django.contrib.auth import get_user_model +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APIClient +from rest_framework_simplejwt.tokens import RefreshToken + +from basic_info import models as basic_models +from . import services + +User = get_user_model() + + +class SSEAPITestCase(TestCase): + """SSE API测试用例""" + + def setUp(self): + """设置测试数据""" + # 创建测试商户 + self.merchant1 = basic_models.Merchant.objects.create( + name='测试商户1', + type=basic_models.MerchantTypeEnum.STORE, + ) + self.merchant2 = basic_models.Merchant.objects.create( + name='测试商户2', + type=basic_models.MerchantTypeEnum.FACTORY, + ) + + # 创建测试用户 + self.user1 = User.objects.create_user(username='user1', password='testpass') + self.user2 = User.objects.create_user(username='user2', password='testpass') + self.user_no_employee = User.objects.create_user(username='no_employee', password='testpass') + + # 创建员工并关联商户 + self.employee1 = basic_models.Employee.objects.create( + merchant=self.merchant1, + sys_user=self.user1, + name='员工1', + mobile='13800138001', + ) + self.employee2 = basic_models.Employee.objects.create( + merchant=self.merchant2, + sys_user=self.user2, + name='员工2', + mobile='13800138002', + ) + + # 生成JWT token + refresh1 = RefreshToken.for_user(self.user1) + self.token1 = str(refresh1.access_token) + + refresh2 = RefreshToken.for_user(self.user2) + self.token2 = str(refresh2.access_token) + + refresh_no_employee = RefreshToken.for_user(self.user_no_employee) + self.token_no_employee = str(refresh_no_employee.access_token) + + # 设置客户端 + self.client = APIClient() + + # 清理所有连接 + services._connections.clear() + + def test_sse_connection_without_token(self): + """测试没有JWT token的SSE连接""" + response = self.client.get('/sse/') + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'\u8ba4\u8bc1\u5931\u8d25\u6216\u6216\u7528\u6237\u6237') + + # 检查连接未被添加到服务中 + self.assertEqual(len(services._connections), 0) + + def test_sse_connection_with_invalid_token(self): + """测试无效JWT token的SSE连接""" + response = self.client.get( + '/sse/', + HTTP_AUTHORIZATION='Bearer invalid_token' + ) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'\u8ba4\u8bc1\u5931\u8d25\u6216\u6216\u7528\u6237\u6237') + + # 检查连接未被添加到服务中 + self.assertEqual(len(services._connections), 0) + + def test_sse_connection_user_no_employee(self): + """测试用户无关联员工的情况""" + response = self.client.get( + '/sse/', + HTTP_AUTHORIZATION=f'Bearer {self.token_no_employee}' + ) + + self.assertEqual(response.status_code, 403) + self.assertEqual(response.content, b'\u8ba4\u8bc1\u5931\u8d25\u6216\u6216\u7528\u6237\u6237') + + # 检查连接未被添加到服务中 + self.assertEqual(len(services._connections), 0) + + def test_push_test_event_to_merchant(self): + """测试向特定商户推送测试事件""" + # 手动建立连接 + queue1 = Queue() + services.push_connection(self.merchant1.id, queue1) + + # 推送事件 + response = self.client.post( + '/sse/push/', + HTTP_AUTHORIZATION=f'Bearer {self.token1}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['status'], 'ok') + self.assertEqual(data['message'], 'Test event broadcasted to your merchant') + self.assertEqual(data['merchant_id'], self.merchant1.id) + self.assertEqual(data['clients'], 1) + + # 清理连接 + services.remove_connection(self.merchant1.id, queue1) + + def test_push_test_event_unauthenticated(self): + """测试未认证用户推送事件""" + response = self.client.post('/sse/push/') + + self.assertEqual(response.status_code, 401) + + def test_push_test_event_user_no_employee(self): + """测试无关联员工用户推送事件""" + response = self.client.post( + '/sse/push/', + HTTP_AUTHORIZATION=f'Bearer {self.token_no_employee}' + ) + + self.assertEqual(response.status_code, 403) + data = response.json() + self.assertEqual(data['error'], '用户无关联商户') + + def test_get_sse_status(self): + """测试获取SSE状态""" + # 手动建立两个商户的连接 + queue1 = Queue() + queue2 = Queue() + services.push_connection(self.merchant1.id, queue1) + services.push_connection(self.merchant2.id, queue2) + + # 获取状态 + response = self.client.get( + '/sse/status/', + HTTP_AUTHORIZATION=f'Bearer {self.token1}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['status'], 'running') + self.assertEqual(data['total_clients'], 2) # 所有连接数 + self.assertEqual(data['merchant_clients'], 1) # 当前商户的连接数 + self.assertEqual(data['merchant_id'], self.merchant1.id) + + # 清理连接 + services.remove_connection(self.merchant1.id, queue1) + services.remove_connection(self.merchant2.id, queue2) + + def test_get_sse_status_unauthenticated(self): + """测试未认证用户获取状态""" + response = self.client.get('/sse/status/') + + self.assertEqual(response.status_code, 401) + + def test_shutdown_merchant_connections(self): + """测试关闭商户连接""" + # 手动建立两个商户的连接 + queue1 = Queue() + queue2 = Queue() + services.push_connection(self.merchant1.id, queue1) + services.push_connection(self.merchant2.id, queue2) + + # 关闭商户1的连接 + response = self.client.post( + '/sse/shutdown/', + HTTP_AUTHORIZATION=f'Bearer {self.token1}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['status'], 'ok') + self.assertEqual(data['message'], 'Shutdown signal sent to your merchant\'s SSE connections') + self.assertEqual(data['merchant_id'], self.merchant1.id) + self.assertEqual(data['clients'], 1) + + # 检查连接状态 + merchant1_connections = services.get_merchant_connections(self.merchant1.id) + merchant2_connections = services.get_merchant_connections(self.merchant2.id) + self.assertEqual(len(merchant1_connections), 0) + self.assertEqual(len(merchant2_connections), 1) + + # 清理连接 + services.remove_connection(self.merchant2.id, queue2) + + def test_shutdown_merchant_connections_unauthenticated(self): + """测试未认证用户关闭连接""" + response = self.client.post('/sse/shutdown/') + + self.assertEqual(response.status_code, 401) + + def test_merchant_isolation(self): + """测试商户隔离""" + # 为两个商户分别建立连接 + queue1 = Queue() + queue2 = Queue() + queue3 = Queue() + queue4 = Queue() + + services.push_connection(self.merchant1.id, queue1) + services.push_connection(self.merchant1.id, queue2) + services.push_connection(self.merchant2.id, queue3) + services.push_connection(self.merchant2.id, queue4) + + # 向商户1推送事件 + response = self.client.post( + '/sse/push/', + HTTP_AUTHORIZATION=f'Bearer {self.token1}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['merchant_id'], self.merchant1.id) + self.assertEqual(data['clients'], 2) # 商户1有2个客户端 + + # 向商户2推送事件 + response = self.client.post( + '/sse/push/', + HTTP_AUTHORIZATION=f'Bearer {self.token2}' + ) + + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertEqual(data['merchant_id'], self.merchant2.id) + self.assertEqual(data['clients'], 2) # 商户2有2个客户端 + + # 确认商户隔离 + merchant1_connections = services.get_merchant_connections(self.merchant1.id) + merchant2_connections = services.get_merchant_connections(self.merchant2.id) + self.assertEqual(len(merchant1_connections), 2) + self.assertEqual(len(merchant2_connections), 2) + + # 清理连接 + services.remove_connection(self.merchant1.id, queue1) + services.remove_connection(self.merchant1.id, queue2) + services.remove_connection(self.merchant2.id, queue3) + services.remove_connection(self.merchant2.id, queue4) + + def test_sse_connection_with_options_request(self): + """测试OPTIONS预检请求""" + response = self.client.options('/sse/') + + # OPTIONS请求应该成功,返回CORS头 + self.assertEqual(response.status_code, 200) + + # 检查CORS头 + self.assertIn('Access-Control-Allow-Origin', response) + self.assertIn('Access-Control-Allow-Methods', response) + self.assertIn('Access-Control-Allow-Headers', response) + + +class SSEServicesTestCase(TestCase): + """SSE服务层测试用例""" + + def setUp(self): + """设置测试数据""" + self.merchant1_id = 1 + self.merchant2_id = 2 + + # 清理所有连接 + services._connections.clear() + + def test_push_connection(self): + """测试添加连接""" + queue1 = Queue() + queue2 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + services.push_connection(self.merchant1_id, queue2) + services.push_connection(self.merchant2_id, Queue()) + + # 检查连接 + merchant1_connections = services.get_merchant_connections(self.merchant1_id) + merchant2_connections = services.get_merchant_connections(self.merchant2_id) + + self.assertEqual(len(merchant1_connections), 2) + self.assertEqual(len(merchant2_connections), 1) + self.assertIn(queue1, merchant1_connections) + self.assertIn(queue2, merchant1_connections) + + def test_remove_connection(self): + """测试移除连接""" + queue1 = Queue() + queue2 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + services.push_connection(self.merchant1_id, queue2) + + # 移除一个连接 + services.remove_connection(self.merchant1_id, queue1) + + # 检查连接 + merchant1_connections = services.get_merchant_connections(self.merchant1_id) + self.assertEqual(len(merchant1_connections), 1) + self.assertNotIn(queue1, merchant1_connections) + self.assertIn(queue2, merchant1_connections) + + def test_remove_all_merchant_connections(self): + """测试移除商户所有连接""" + queue1 = Queue() + queue2 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + services.push_connection(self.merchant1_id, queue2) + + # 移除所有连接 + services.remove_connection(self.merchant1_id, queue1) + services.remove_connection(self.merchant1_id, queue2) + + # 检查连接 + merchant1_connections = services.get_merchant_connections(self.merchant1_id) + self.assertEqual(len(merchant1_connections), 0) + + # 商户记录应该被移除 + self.assertNotIn(self.merchant1_id, services._connections) + + def test_get_all_connections_count(self): + """测试获取所有连接数""" + # 添加连接 + services.push_connection(self.merchant1_id, Queue()) + services.push_connection(self.merchant1_id, Queue()) + services.push_connection(self.merchant2_id, Queue()) + + # 检查总连接数 + total = services.get_all_connections_count() + self.assertEqual(total, 3) + + def test_push_event_to_merchant(self): + """测试向特定商户推送事件""" + queue1 = Queue() + queue2 = Queue() + queue3 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + services.push_connection(self.merchant1_id, queue2) + services.push_connection(self.merchant2_id, queue3) + + # 向商户1推送事件 + event_data = {'type': 'test', 'message': 'test message'} + services.push_sse_event_to_merchant(self.merchant1_id, event_data) + + # 检查消息 + self.assertEqual(queue1.qsize(), 1) + self.assertEqual(queue2.qsize(), 1) + self.assertEqual(queue3.qsize(), 0) # 商户2不应该收到消息 + + # 检查消息内容 + self.assertEqual(queue1.get_nowait(), event_data) + self.assertEqual(queue2.get_nowait(), event_data) + + def test_push_event_to_nonexistent_merchant(self): + """测试向不存在的商户推送事件""" + # 向不存在的商户推送事件 + event_data = {'type': 'test', 'message': 'test message'} + services.push_sse_event_to_merchant(999, event_data) + + # 不应该抛出异常,也不会有连接受到影响 + self.assertEqual(len(services._connections), 0) + + def test_push_simple_message_to_merchant(self): + """测试向特定商户推送简单消息""" + queue1 = Queue() + + # 添加连接 + services.push_connection(self.merchant1_id, queue1) + + # 推送简单消息 + services.push_simple_message_with_object_id_to_merchant( + self.merchant1_id, + 'order_paid', + '订单已支付', + 12345 + ) + + # 检查消息 + self.assertEqual(queue1.qsize(), 1) + + # 检查消息内容 + message = queue1.get_nowait() + self.assertEqual(message['mode'], 'simple_message') + self.assertEqual(message['type'], 'order_paid') + self.assertEqual(message['message'], '订单已支付') + self.assertEqual(message['object_id'], 12345) + self.assertEqual(message['merchant_id'], self.merchant1_id) \ No newline at end of file diff --git a/sse/views.py b/sse/views.py index d310fff..283de2a 100644 --- a/sse/views.py +++ b/sse/views.py @@ -2,16 +2,18 @@ from django.http import StreamingHttpResponse, HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from rest_framework.decorators import api_view, permission_classes -from rest_framework.permissions import AllowAny +from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status from . import services +from .auth_utils import require_sse_authentication, get_user_merchant_id import queue import json @csrf_exempt @require_http_methods(["GET", "OPTIONS"]) +@require_sse_authentication def create_sse_event(request): """ 创建一个 SSE 事件流响应。 @@ -24,6 +26,7 @@ def create_sse_event(request): 注意: - 使用纯 Django 视图,不使用 DRF,避免内容协商导致的 406 错误 - SSE 需要特殊的 CORS 配置 + - 需要JWT认证且用户必须有关联的商户 """ # 处理 OPTIONS 预检请求 if request.method == 'OPTIONS': @@ -38,13 +41,16 @@ def create_sse_event(request): return response def event_stream(): + # 获取当前商户ID + merchant_id = request.merchant_id + # 创建一个同步队列用于接收消息 conn_queue = queue.Queue(maxsize=100) - services.push_connection(conn_queue) + services.push_connection(merchant_id, conn_queue) try: # 发送初始连接成功消息 - yield f"data: {json.dumps({'type': 'connected', 'message': 'SSE connection established'})}\n\n" + yield f"data: {json.dumps({'type': 'connected', 'message': 'SSE connection established', 'merchant_id': merchant_id})}\n\n" # 持续从队列中获取消息并发送给客户端 while True: @@ -60,7 +66,7 @@ def create_sse_event(request): break finally: # 清理:从连接集合中移除此队列 - services.remove_connection(conn_queue) + services.remove_connection(merchant_id, conn_queue) response = StreamingHttpResponse( event_stream(), @@ -82,50 +88,78 @@ def create_sse_event(request): @api_view(['POST']) -@permission_classes([AllowAny]) +@permission_classes([IsAuthenticated]) def push_test_event(request): """ - 向所有连接的客户端广播一个 SSE 测试事件。 + 向当前用户所属商户的所有连接客户端广播一个 SSE 测试事件。 """ - services.push_simple_message_with_object_id('order_paid', '订单已支付', 12345) + # 获取当前用户的商户ID + merchant_id = get_user_merchant_id(request) + if not merchant_id: + return Response({'error': 'User has no associated merchant'}, status=status.HTTP_403_FORBIDDEN) + + services.push_simple_message_with_object_id_to_merchant( + merchant_id, 'order_paid', '订单已支付', 12345 + ) + + merchant_connections = services.get_merchant_connections(merchant_id) return Response({ 'status': 'ok', - 'message': 'Test event broadcasted', - 'clients': len(services.get_active_connections()) + 'message': 'Test event broadcasted to your merchant', + 'merchant_id': merchant_id, + 'clients': len(merchant_connections) }) @api_view(['GET']) -@permission_classes([AllowAny]) +@permission_classes([IsAuthenticated]) def get_sse_status(request): """ 获取 SSE 连接状态信息。 返回: - - clients: 当前连接的客户端数量 + - total_clients: 所有商户的客户端总数 + - merchant_clients: 当前商户的客户端数量 - status: 服务状态 """ - active_connections = services.get_active_connections() + merchant_id = get_user_merchant_id(request) + if not merchant_id: + return Response({'error': 'User has no associated merchant'}, status=status.HTTP_403_FORBIDDEN) + + # 获取所有连接数 + all_connections = services.get_all_connections_count() + + # 获取当前商户的连接数 + merchant_connections = services.get_merchant_connections(merchant_id) + return Response({ 'status': 'running', - 'clients': len(active_connections), - 'message': f'SSE server is running with {len(active_connections)} active connection(s)', + 'total_clients': all_connections, + 'merchant_clients': len(merchant_connections), + 'merchant_id': merchant_id, + 'message': f'SSE server is running with {all_connections} total connections, {len(merchant_connections)} for your merchant', }) @api_view(['POST']) -@permission_classes([AllowAny]) +@permission_classes([IsAuthenticated]) def shutdown_sse(request): """ - 优雅关闭所有SSE连接的端点 + 关闭当前用户所属商户的所有SSE连接 """ - services.push_sse_event_to_all({ + merchant_id = get_user_merchant_id(request) + if not merchant_id: + return Response({'error': 'User has no associated merchant'}, status=status.HTTP_403_FORBIDDEN) + + services.push_sse_event_to_merchant(merchant_id, { 'type': 'server_shutdown', - 'message': 'Server is shutting down, please reconnect later' + 'message': 'Server shutting down your connections, please reconnect later' }) + merchant_connections = services.get_merchant_connections(merchant_id) return Response({ 'status': 'ok', - 'message': 'Shutdown signal sent to all SSE connections', - 'clients': len(services.get_active_connections()) + 'message': 'Shutdown signal sent to your merchant\'s SSE connections', + 'merchant_id': merchant_id, + 'clients': len(merchant_connections) })