forked from erp-dev/erp
feat: auto complete stock change
This commit is contained in:
2
.env
2
.env
@@ -3,6 +3,6 @@ DEBUG=True
|
|||||||
ALLOWED_HOSTS=*,
|
ALLOWED_HOSTS=*,
|
||||||
DB_HOST=localhost
|
DB_HOST=localhost
|
||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_NAME=myapp
|
DB_NAME=flower
|
||||||
DB_USER=postgres
|
DB_USER=postgres
|
||||||
DB_PASSWORD=postgres
|
DB_PASSWORD=postgres
|
||||||
@@ -2,62 +2,105 @@ from rest_framework import serializers
|
|||||||
from basic_info import models as basic_models
|
from basic_info import models as basic_models
|
||||||
|
|
||||||
|
|
||||||
class ProductCategorySerializer(serializers.ModelSerializer):
|
class BaseSerializer(serializers.ModelSerializer):
|
||||||
|
merchant = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=basic_models.Merchant.objects.all(),
|
||||||
|
required=False # 创建时由视图层设置
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
try:
|
||||||
|
merchant = self.context['request'].user.employee.merchant
|
||||||
|
if instance.merchant != merchant:
|
||||||
|
# 数据保护手段
|
||||||
|
raise PermissionError("无权限访问该对象")
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
# TODO: log something?
|
||||||
|
raise PermissionError("无权限访问该对象")
|
||||||
|
|
||||||
|
result = super().to_representation(instance)
|
||||||
|
if self.context.get('expand_merchant', False):
|
||||||
|
result['merchant'] = {
|
||||||
|
'id': instance.merchant.id,
|
||||||
|
'name': instance.merchant.name
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class QuickInputSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = basic_models.QuickInput
|
||||||
|
fields = ['name', 'value', 'group']
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCategorySerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.ProductCategory
|
model = basic_models.ProductCategory
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class ProductSerializer(serializers.ModelSerializer):
|
class ProductSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.Product
|
model = basic_models.Product
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
depth = 1 # 展开外键关系
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
result = super().to_representation(instance)
|
||||||
|
result['category'] = {
|
||||||
|
'id': instance.category.id,
|
||||||
|
'name': instance.category.name
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class WareHouseSerializer(serializers.ModelSerializer):
|
class WareHouseSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.WareHouse
|
model = basic_models.WareHouse
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class SupplierSerializer(serializers.ModelSerializer):
|
class SupplierSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.Supplier
|
model = basic_models.Supplier
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class EmployeeSerializer(serializers.ModelSerializer):
|
class EmployeeSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.Employee
|
model = basic_models.Employee
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class CustomerSerializer(serializers.ModelSerializer):
|
class CustomerSerializer(BaseSerializer):
|
||||||
|
created_by = serializers.PrimaryKeyRelatedField(
|
||||||
|
queryset=basic_models.Employee.objects.all(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.Customer
|
model = basic_models.Customer
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class VehicleTypeSerializer(serializers.ModelSerializer):
|
class VehicleTypeSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.VehicleType
|
model = basic_models.VehicleType
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class BankAccountSerializer(serializers.ModelSerializer):
|
class BankAccountSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.BankAccount
|
model = basic_models.BankAccount
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class DeviceInfoSerializer(serializers.ModelSerializer):
|
class DeviceInfoSerializer(BaseSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = basic_models.DeviceInfo
|
model = basic_models.DeviceInfo
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class VehicleTransportRecordSerializer(serializers.ModelSerializer):
|
class VehicleTransportRecordSerializer(BaseSerializer):
|
||||||
vehicle_type_name = serializers.CharField(source='vehicle_type.name', read_only=True)
|
vehicle_type_name = serializers.CharField(source='vehicle_type.name', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from django.urls import path, include
|
|||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
quick_input_router = routers.DefaultRouter()
|
||||||
|
quick_input_router.register(prefix='', viewset=views.QuickInputViewSet)
|
||||||
|
|
||||||
product_router = routers.DefaultRouter()
|
product_router = routers.DefaultRouter()
|
||||||
product_router.register(prefix='', viewset=views.ProductViewSet)
|
product_router.register(prefix='', viewset=views.ProductViewSet)
|
||||||
@@ -35,6 +37,7 @@ vehicle_transport_record_router.register(prefix='', viewset=views.VehicleTranspo
|
|||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path('quick-inputs/', include(quick_input_router.urls)),
|
||||||
path('products/', include(product_router.urls)),
|
path('products/', include(product_router.urls)),
|
||||||
path('warehouses/', include(warehouse_router.urls)),
|
path('warehouses/', include(warehouse_router.urls)),
|
||||||
path('product-categories/', include(product_category_router.urls)),
|
path('product-categories/', include(product_category_router.urls)),
|
||||||
|
|||||||
@@ -1,9 +1,38 @@
|
|||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
from rest_framework.pagination import LimitOffsetPagination
|
||||||
from . import serializers
|
from . import serializers
|
||||||
|
|
||||||
|
|
||||||
class BaseViewSet(viewsets.ModelViewSet):
|
class BaseViewSet(viewsets.ModelViewSet):
|
||||||
permission_classes = [] # 允许所有访问
|
pagination_class = LimitOffsetPagination
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super().get_queryset()
|
||||||
|
# 如果是超级用户,返回所有数据
|
||||||
|
# TODO: 测试结束后要取注释
|
||||||
|
# if self.request.user.is_superuser:
|
||||||
|
# return qs
|
||||||
|
return qs.filter(merchant=self.request.user.employee.merchant)
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
try:
|
||||||
|
merchant = self.request.user.employee.merchant
|
||||||
|
serializer.save(merchant_id=merchant.id)
|
||||||
|
except AttributeError:
|
||||||
|
raise PermissionDenied("无权限创建该对象")
|
||||||
|
|
||||||
|
|
||||||
|
class QuickInputViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = serializers.basic_models.QuickInput.objects
|
||||||
|
serializer_class = serializers.QuickInputSerializer
|
||||||
|
|
||||||
|
def filter_queryset(self, queryset):
|
||||||
|
qs = super().filter_queryset(queryset)
|
||||||
|
group = self.request.query_params.get('group')
|
||||||
|
if group:
|
||||||
|
qs = qs.filter(group=group)
|
||||||
|
return qs
|
||||||
|
|
||||||
|
|
||||||
class ProductViewSet(BaseViewSet):
|
class ProductViewSet(BaseViewSet):
|
||||||
@@ -35,6 +64,17 @@ class CustomerViewSet(BaseViewSet):
|
|||||||
queryset = serializers.basic_models.Customer.objects
|
queryset = serializers.basic_models.Customer.objects
|
||||||
serializer_class = serializers.CustomerSerializer
|
serializer_class = serializers.CustomerSerializer
|
||||||
|
|
||||||
|
def perform_create(self, serializer):
|
||||||
|
merchant = self.request.user.employee.merchant
|
||||||
|
employee = self.request.user.employee
|
||||||
|
serializer.save(merchant=merchant, created_by=employee)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = super().get_queryset()
|
||||||
|
# 应用可见性过滤
|
||||||
|
from basic_info.services import CustomerVisibilityService
|
||||||
|
return CustomerVisibilityService.filter_customers_for_employee(qs, self.request.user)
|
||||||
|
|
||||||
|
|
||||||
class VehicleTypeViewSet(BaseViewSet):
|
class VehicleTypeViewSet(BaseViewSet):
|
||||||
queryset = serializers.basic_models.VehicleType.objects
|
queryset = serializers.basic_models.VehicleType.objects
|
||||||
|
|||||||
@@ -84,3 +84,10 @@ class CreateStockChangeResponseSerializer(serializers.Serializer):
|
|||||||
details = StockChangeDetailResponseSerializer(many=True)
|
details = StockChangeDetailResponseSerializer(many=True)
|
||||||
message = serializers.CharField()
|
message = serializers.CharField()
|
||||||
created_details_count = serializers.IntegerField()
|
created_details_count = serializers.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
class SetMerchantAutoCompleteStockChangeSerializer(serializers.Serializer):
|
||||||
|
"""设置商户自动确认库存变动序列化器"""
|
||||||
|
auto_complete = serializers.BooleanField(
|
||||||
|
help_text="是否自动确认库存变动"
|
||||||
|
)
|
||||||
@@ -4,4 +4,9 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# 库存变动相关API
|
# 库存变动相关API
|
||||||
path('stock-change/', views.create_full_stock_change, name='create_full_stock_change'),
|
path('stock-change/', views.create_full_stock_change, name='create_full_stock_change'),
|
||||||
|
path(
|
||||||
|
'set-merchant-auto-complete-stock-change/',
|
||||||
|
views.set_merchant_auto_complete_stock_change,
|
||||||
|
name='set_merchant_auto_complete_stock_change',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from basic_info.services import DataVisibilityService
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from typing import List, Dict, Any
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from stock import models as stock_models
|
from stock import models as stock_models
|
||||||
@@ -58,6 +57,20 @@ def create_full_stock_change(request):
|
|||||||
'error': '产品列表不能为空'
|
'error': '产品列表不能为空'
|
||||||
}, status=status.HTTP_400_BAD_REQUEST)
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
# 验证仓库是否本员工可见
|
||||||
|
if not DataVisibilityService.is_warehouse_visible_to_employee(record_data['warehouse'], request.user):
|
||||||
|
return Response({
|
||||||
|
'error': f'仓库ID {record_data["warehouse"]} 对当前用户不可见'
|
||||||
|
}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
# 验证产品是否本员工可见
|
||||||
|
for p in products_data:
|
||||||
|
product_id = p.get('product')
|
||||||
|
if not DataVisibilityService.is_product_visible_to_employee(product_id, request.user):
|
||||||
|
return Response({
|
||||||
|
'error': f'产品ID {product_id} 对当前用户不可见'
|
||||||
|
}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
# 验证产品数据
|
# 验证产品数据
|
||||||
product_serializer = serializers.CreateStockChangeSerializer(data={
|
product_serializer = serializers.CreateStockChangeSerializer(data={
|
||||||
'products': products_data
|
'products': products_data
|
||||||
@@ -83,7 +96,8 @@ def create_full_stock_change(request):
|
|||||||
warehouse=warehouse,
|
warehouse=warehouse,
|
||||||
source_type=record_data['source_type'],
|
source_type=record_data['source_type'],
|
||||||
source_id=record_data['source_id'],
|
source_id=record_data['source_id'],
|
||||||
# created_by=request.user
|
merchant=request.user.employee.merchant,
|
||||||
|
created_by=request.user
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"创建新库存变动记录 ID: {stock_change_record.id}")
|
logger.info(f"创建新库存变动记录 ID: {stock_change_record.id}")
|
||||||
@@ -108,11 +122,19 @@ def create_full_stock_change(request):
|
|||||||
stock_change_record=stock_change_record,
|
stock_change_record=stock_change_record,
|
||||||
product=product,
|
product=product,
|
||||||
quantity=quantity,
|
quantity=quantity,
|
||||||
|
merchant=request.user.employee.merchant,
|
||||||
unit=product.unit
|
unit=product.unit
|
||||||
)
|
)
|
||||||
created_details.append(detail)
|
created_details.append(detail)
|
||||||
created_count += 1
|
created_count += 1
|
||||||
|
|
||||||
|
|
||||||
|
if warehouse.merchant.auto_complete_stock_change:
|
||||||
|
# 自动确认库存变动
|
||||||
|
from stock import services as stock_services
|
||||||
|
stock_services.make_stock_change_completed(stock_change_record)
|
||||||
|
logger.info(f"自动确认库存变动记录 ID: {stock_change_record.id}")
|
||||||
|
|
||||||
# 3. 构建响应
|
# 3. 构建响应
|
||||||
response_serializer = serializers.CreateStockChangeResponseSerializer({
|
response_serializer = serializers.CreateStockChangeResponseSerializer({
|
||||||
'stock_change_record': stock_change_record,
|
'stock_change_record': stock_change_record,
|
||||||
@@ -134,3 +156,30 @@ def create_full_stock_change(request):
|
|||||||
'error': '创建库存变动记录失败',
|
'error': '创建库存变动记录失败',
|
||||||
'message': str(e)
|
'message': str(e)
|
||||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
@extend_schema(tags=['修改商户自动确认出入库设定'])
|
||||||
|
@api_view(['POST'])
|
||||||
|
def set_merchant_auto_complete_stock_change(request):
|
||||||
|
"""
|
||||||
|
设置商户自动确认库存变动设定
|
||||||
|
POST /api/v1/merchant/set-auto-complete-stock-change/
|
||||||
|
请求参数:
|
||||||
|
{
|
||||||
|
"auto_complete_stock_change": true // 是否自动确认库存变动
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
emp = request.user.employee
|
||||||
|
merchant = emp.merchant
|
||||||
|
serializer = serializers.SetMerchantAutoCompleteStockChangeSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
auto_complete = serializer.validated_data['auto_complete']
|
||||||
|
merchant.auto_complete_stock_change = auto_complete
|
||||||
|
merchant.save()
|
||||||
|
logger.info(f"用户 {request.user.username} 设置商户 {merchant.name} 自动确认库存变动为 {auto_complete}")
|
||||||
|
return Response({'status': 'success'})
|
||||||
|
return Response({'error': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
except AttributeError:
|
||||||
|
logger.error(f"用户 {request.user.username} 无权限设置商户自动确认库存变动", exc_info=True)
|
||||||
|
return Response({'error': '无权限操作'}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|||||||
@@ -1,21 +1,63 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from basic_info.services import CustomerVisibilityService
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
class AdminBase(admin.ModelAdmin):
|
||||||
|
"""管理员基础类,定义通用配置"""
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj = ...):
|
||||||
|
readonly_fields = list(super().get_readonly_fields(request, obj))
|
||||||
|
if request.user.is_superuser:
|
||||||
|
readonly_fields.append('merchant')
|
||||||
|
return readonly_fields
|
||||||
|
|
||||||
|
def get_list_display(self, request):
|
||||||
|
result = super().get_list_display(request)
|
||||||
|
result = list(result)
|
||||||
|
|
||||||
|
if 'id' not in result:
|
||||||
|
result = ['id'] + result
|
||||||
|
|
||||||
|
if request.user.is_superuser and 'merchant' not in result:
|
||||||
|
result = result + ['merchant']
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if not change:
|
||||||
|
if not obj.merchant_id:
|
||||||
|
obj.merchant_id = request.user.employee.merchant.id
|
||||||
|
return super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.QuickInput)
|
||||||
|
class QuickInputAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'value', 'group')
|
||||||
|
list_filter = ('group',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.Merchant)
|
||||||
|
class MerchantAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('name', 'type', 'email', 'mobile', 'auto_complete_stock_change')
|
||||||
|
search_fields = ('name', 'mobile')
|
||||||
|
list_filter = ('type', 'auto_complete_stock_change')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Supplier)
|
@admin.register(models.Supplier)
|
||||||
class SupplierAdmin(admin.ModelAdmin):
|
class SupplierAdmin(AdminBase):
|
||||||
list_display = ('name', 'area', 'email', 'mobile')
|
list_display = ('name', 'area', 'email', 'mobile')
|
||||||
search_fields = ('name', 'area', 'email', 'mobile')
|
search_fields = ('name', 'area', 'email', 'mobile')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.ProductCategory)
|
@admin.register(models.ProductCategory)
|
||||||
class ProductCategoryAdmin(admin.ModelAdmin):
|
class ProductCategoryAdmin(AdminBase):
|
||||||
list_display = ('name', 'product_prefix', 'description')
|
list_display = ('name', 'product_prefix', 'description')
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Product)
|
@admin.register(models.Product)
|
||||||
class ProductAdmin(admin.ModelAdmin):
|
class ProductAdmin(AdminBase):
|
||||||
list_display = (
|
list_display = (
|
||||||
'name',
|
'name',
|
||||||
'category',
|
'category',
|
||||||
@@ -31,29 +73,35 @@ class ProductAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(models.BankAccount)
|
@admin.register(models.BankAccount)
|
||||||
class BankAccountAdmin(admin.ModelAdmin):
|
class BankAccountAdmin(AdminBase):
|
||||||
list_display = ('name', 'auto_number')
|
list_display = ('name', 'auto_number')
|
||||||
search_fields = ('name', 'auto_number')
|
search_fields = ('name', 'auto_number')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.WareHouse)
|
@admin.register(models.WareHouse)
|
||||||
class WareHouseAdmin(admin.ModelAdmin):
|
class WareHouseAdmin(AdminBase):
|
||||||
list_display = ('name', 'location', 'area', 'mobile')
|
list_display = ('name', 'location', 'area', 'mobile', 'mode')
|
||||||
search_fields = ('name', 'area')
|
list_filter = ('area', 'mode')
|
||||||
|
search_fields = ('name', 'location', 'mobile')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Customer)
|
@admin.register(models.Customer)
|
||||||
class CustomerAdmin(admin.ModelAdmin):
|
class CustomerAdmin(AdminBase):
|
||||||
list_display = ('name', 'area', 'email', 'mobile', 'total_debt')
|
list_display = ('name', 'area', 'email', 'mobile', 'created_by', 'total_debt')
|
||||||
search_fields = ('name', 'area', 'email', 'mobile')
|
search_fields = ('name', 'area', 'email', 'mobile')
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
qs = super().get_queryset(request)
|
||||||
|
qs = CustomerVisibilityService.filter_customers_for_employee(qs, request.user)
|
||||||
|
return qs
|
||||||
|
|
||||||
@admin.display(boolean=False, description='累欠金额')
|
@admin.display(boolean=False, description='累欠金额')
|
||||||
def total_debt(self, obj):
|
def total_debt(self, obj):
|
||||||
return '0 (动态逻辑字段待实现)'
|
return '0 (动态逻辑字段待实现)'
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.Employee)
|
@admin.register(models.Employee)
|
||||||
class EmployeeAdmin(admin.ModelAdmin):
|
class EmployeeAdmin(AdminBase):
|
||||||
list_display = ('name', 'mobile', 'status', 'job_type', 'sys_user')
|
list_display = ('name', 'mobile', 'status', 'job_type', 'sys_user')
|
||||||
search_fields = ('name', 'mobile')
|
search_fields = ('name', 'mobile')
|
||||||
list_filter = (
|
list_filter = (
|
||||||
@@ -63,20 +111,20 @@ class EmployeeAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(models.DeviceInfo)
|
@admin.register(models.DeviceInfo)
|
||||||
class DeviceInfoAdmin(admin.ModelAdmin):
|
class DeviceInfoAdmin(AdminBase):
|
||||||
list_display = ('name', 'type', 'is_occupied', 'status')
|
list_display = ('name', 'type', 'is_occupied', 'status')
|
||||||
search_fields = ('name', 'start_working_at', 'stop_working_at')
|
search_fields = ('name', 'start_working_at', 'stop_working_at')
|
||||||
list_filter = ('type', 'status')
|
list_filter = ('type', 'status')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.VehicleType)
|
@admin.register(models.VehicleType)
|
||||||
class VehicleTypeAdmin(admin.ModelAdmin):
|
class VehicleTypeAdmin(AdminBase):
|
||||||
list_display = ('name', 'description')
|
list_display = ('name', 'description')
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.VehicleTransportRecord)
|
@admin.register(models.VehicleTransportRecord)
|
||||||
class VehicleTransportRecordAdmin(admin.ModelAdmin):
|
class VehicleTransportRecordAdmin(AdminBase):
|
||||||
list_display = ('driver_name', 'contact_number', 'delivery_date')
|
list_display = ('driver_name', 'contact_number', 'delivery_date')
|
||||||
search_fields = ('driver_name', 'contact_number')
|
search_fields = ('driver_name', 'contact_number')
|
||||||
list_filter = ('vehicle_type',)
|
list_filter = ('vehicle_type',)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:06
|
# Generated by Django 5.2.7 on 2025-11-06 05:33
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
@@ -9,9 +10,88 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Merchant',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='商户名称')),
|
||||||
|
('type', models.IntegerField(choices=[(1, '布行'), (2, '印花厂')], verbose_name='商户类型')),
|
||||||
|
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='电子邮箱')),
|
||||||
|
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
||||||
|
('contact', models.TextField(blank=True, null=True, verbose_name='联系人')),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '商户资料',
|
||||||
|
'verbose_name_plural': '商户资料',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Employee',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='员工姓名')),
|
||||||
|
('job_type', models.CharField(choices=[('打纸', '打纸'), ('滚筒', '滚筒'), ('仓库', '仓库'), ('送货', '送货')], default='打纸', max_length=20, verbose_name='职位')),
|
||||||
|
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
||||||
|
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
|
('status', models.CharField(choices=[('在职', '在职'), ('离职', '离职')], default='在职', max_length=20, verbose_name='员工状态')),
|
||||||
|
('sys_user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='employee', to=settings.AUTH_USER_MODEL, verbose_name='系统用户')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='employees', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '员工资料',
|
||||||
|
'verbose_name_plural': '员工资料',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DeviceInfo',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='设备名称')),
|
||||||
|
('type', models.CharField(choices=[('滚筒', '滚筒'), ('打印', '打印')], default='滚筒', max_length=20, verbose_name='设备类型')),
|
||||||
|
('status', models.CharField(choices=[('使用中', '使用中'), ('停用', '停用'), ('维修中', '维修中')], default='使用中', max_length=20, verbose_name='设备状态')),
|
||||||
|
('start_working_at', models.DateField(blank=True, null=True, verbose_name='开始服役日期')),
|
||||||
|
('stop_working_at', models.DateField(blank=True, null=True, verbose_name='停止服役日期')),
|
||||||
|
('is_occupied', models.BooleanField(default=False, verbose_name='是否被占用')),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '设备资料',
|
||||||
|
'verbose_name_plural': '设备资料',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Customer',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=100, verbose_name='客户名称')),
|
||||||
|
('mobile', models.CharField(max_length=20, verbose_name='手机')),
|
||||||
|
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='电子邮箱')),
|
||||||
|
('contact', models.CharField(blank=True, null=True, verbose_name='联系人')),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='customers', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '客户资料',
|
||||||
|
'verbose_name_plural': '客户资料',
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='BankAccount',
|
name='BankAccount',
|
||||||
fields=[
|
fields=[
|
||||||
@@ -22,6 +102,7 @@ class Migration(migrations.Migration):
|
|||||||
('auto_number', models.CharField(max_length=50, verbose_name='自动编号')),
|
('auto_number', models.CharField(max_length=50, verbose_name='自动编号')),
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
('attachment', models.FileField(blank=True, null=True, upload_to='bank_accounts/', verbose_name='附件')),
|
('attachment', models.FileField(blank=True, null=True, upload_to='bank_accounts/', verbose_name='附件')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='bank_accounts', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': '银行账户',
|
'verbose_name': '银行账户',
|
||||||
@@ -37,6 +118,7 @@ class Migration(migrations.Migration):
|
|||||||
('name', models.CharField(max_length=100, verbose_name='类别名称')),
|
('name', models.CharField(max_length=100, verbose_name='类别名称')),
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='类别描述')),
|
('description', models.TextField(blank=True, null=True, verbose_name='类别描述')),
|
||||||
('product_prefix', models.CharField(max_length=5, verbose_name='产品编号前缀')),
|
('product_prefix', models.CharField(max_length=5, verbose_name='产品编号前缀')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='product_categories', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': '产品类别',
|
'verbose_name': '产品类别',
|
||||||
@@ -44,17 +126,28 @@ class Migration(migrations.Migration):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProductUnit',
|
name='Product',
|
||||||
fields=[
|
fields=[
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
('name', models.CharField(max_length=20, verbose_name='单位名称')),
|
('name', models.CharField(max_length=100, verbose_name='产品名')),
|
||||||
|
('human_id', models.CharField(blank=True, max_length=100, verbose_name='产品编号')),
|
||||||
|
('color', models.CharField(blank=True, max_length=50, null=True, verbose_name='颜色')),
|
||||||
|
('width_size', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='宽幅')),
|
||||||
|
('single_price_in', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='参考进价')),
|
||||||
|
('single_price_out', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='参考售价')),
|
||||||
|
('unit', models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], default=1, verbose_name='单位')),
|
||||||
|
('spec', models.CharField(blank=True, max_length=100, null=True, verbose_name='规格')),
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
|
('image', models.ImageField(blank=True, null=True, upload_to='product_images/', verbose_name='产品图片')),
|
||||||
|
('transform_rate', models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='公斤转换率')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='basic_info.productcategory', verbose_name='产品类别')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': '产品单位',
|
'verbose_name': '产品资料',
|
||||||
'verbose_name_plural': '产品单位',
|
'verbose_name_plural': '产品资料',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@@ -69,12 +162,46 @@ class Migration(migrations.Migration):
|
|||||||
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
||||||
('contact', models.TextField(blank=True, null=True, verbose_name='联系人')),
|
('contact', models.TextField(blank=True, null=True, verbose_name='联系人')),
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='suppliers', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': '供应商',
|
'verbose_name': '供应商',
|
||||||
'verbose_name_plural': '供应商',
|
'verbose_name_plural': '供应商',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VehicleType',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=50, verbose_name='车辆类型名称')),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='vehicle_types', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '车辆类型',
|
||||||
|
'verbose_name_plural': '车辆类型',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VehicleTransportRecord',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('driver_name', models.CharField(max_length=100, verbose_name='司机姓名')),
|
||||||
|
('contact_number', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系电话')),
|
||||||
|
('license_plate', models.CharField(blank=True, max_length=20, null=True, verbose_name='车牌号码')),
|
||||||
|
('delivery_date', models.DateField(verbose_name='送货日期')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='vehicle_transport_records', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
('vehicle_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='vehicles', to='basic_info.vehicletype', verbose_name='车次')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '司机车次',
|
||||||
|
'verbose_name_plural': '司机车次',
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='WareHouse',
|
name='WareHouse',
|
||||||
fields=[
|
fields=[
|
||||||
@@ -87,47 +214,11 @@ class Migration(migrations.Migration):
|
|||||||
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
||||||
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='warehouses', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': '仓库资料',
|
'verbose_name': '仓库资料',
|
||||||
'verbose_name_plural': '仓库资料',
|
'verbose_name_plural': '仓库资料',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='Product',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='产品名')),
|
|
||||||
('human_id', models.CharField(blank=True, max_length=100, verbose_name='产品编号')),
|
|
||||||
('color', models.CharField(blank=True, max_length=50, null=True, verbose_name='颜色')),
|
|
||||||
('width_size', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='宽幅')),
|
|
||||||
('single_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='单价')),
|
|
||||||
('spec', models.CharField(blank=True, max_length=100, null=True, verbose_name='规格')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='basic_info.productcategory', verbose_name='产品类别')),
|
|
||||||
('unit', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='basic_info.productunit', verbose_name='单位')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '产品资料',
|
|
||||||
'verbose_name_plural': '产品资料',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PurchaseOrder',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('order_date', models.DateField(verbose_name='订单日期')),
|
|
||||||
('total_amount', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='总金额')),
|
|
||||||
('remarks', models.TextField(blank=True, null=True, verbose_name='备注')),
|
|
||||||
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='basic_info.supplier', verbose_name='供应商')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '采购单',
|
|
||||||
'verbose_name_plural': '采购单',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:11
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='PurchaseOrder',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 05:28
|
# Generated by Django 5.2.7 on 2025-11-06 07:48
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
@@ -6,22 +6,23 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('stock', '0009_alter_product_single_price'),
|
('basic_info', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProdictUnit',
|
name='QuickInput',
|
||||||
fields=[
|
fields=[
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
('name', models.CharField(max_length=20, verbose_name='单位名称')),
|
('name', models.CharField(max_length=100, verbose_name='名')),
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
('value', models.CharField(max_length=200, verbose_name='值')),
|
||||||
|
('group', models.CharField(verbose_name='组')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': '产品单位',
|
'verbose_name': '快捷输入',
|
||||||
'verbose_name_plural': '产品单位',
|
'verbose_name_plural': '快捷输入',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-06 07:55
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0002_quickinput'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='quickinput',
|
||||||
|
unique_together={('name', 'group')},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:14
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0002_delete_purchaseorder'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Customer',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='客户名称')),
|
|
||||||
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='电子邮箱')),
|
|
||||||
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
|
||||||
('contact', models.TextField(blank=True, null=True, verbose_name='联系人')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '客户资料',
|
|
||||||
'verbose_name_plural': '客户资料',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:15
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0003_customer'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='customer',
|
|
||||||
name='contact',
|
|
||||||
field=models.CharField(blank=True, null=True, verbose_name='联系人'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
18
basic_info/migrations/0004_customer_visible_employees.py
Normal file
18
basic_info/migrations/0004_customer_visible_employees.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-06 09:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0003_alter_quickinput_unique_together'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customer',
|
||||||
|
name='visible_employees',
|
||||||
|
field=models.ManyToManyField(blank=True, related_name='visible_customers', to='basic_info.employee', verbose_name='可见该客户的员工'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:16
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0004_alter_customer_contact'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='customer',
|
|
||||||
name='mobile',
|
|
||||||
field=models.CharField(max_length=20, verbose_name='手机'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
19
basic_info/migrations/0005_customer_created_by.py
Normal file
19
basic_info/migrations/0005_customer_created_by.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-06 09:36
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0004_customer_visible_employees'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customer',
|
||||||
|
name='created_by',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='created_customers', to='basic_info.employee', verbose_name='创建者'),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
basic_info/migrations/0006_alter_product_merchant.py
Normal file
19
basic_info/migrations/0006_alter_product_merchant.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-07 06:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0005_customer_created_by'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='product',
|
||||||
|
name='merchant',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='basic_info.merchant', verbose_name='所属商户'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:24
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0005_alter_customer_mobile'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Employee',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='员工姓名')),
|
|
||||||
('job_type', models.CharField(choices=[('打纸', '打纸'), ('滚筒', '滚筒'), ('仓库', '仓库'), ('送货', '送货')], default='打纸', max_length=20, verbose_name='职位')),
|
|
||||||
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
|
||||||
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='电子邮箱')),
|
|
||||||
('contact', models.CharField(blank=True, null=True, verbose_name='联系人')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
('status', models.CharField(choices=[('在职', '在职'), ('离职', '离职')], default='在职', max_length=20, verbose_name='员工状态')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '员工资料',
|
|
||||||
'verbose_name_plural': '员工资料',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:27
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0006_employee'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='employee',
|
|
||||||
name='contact',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
18
basic_info/migrations/0007_warehouse_mode.py
Normal file
18
basic_info/migrations/0007_warehouse_mode.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-07 08:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0006_alter_product_merchant'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='warehouse',
|
||||||
|
name='mode',
|
||||||
|
field=models.IntegerField(choices=[(1, '严进宽出'), (2, '严进严出'), (3, '宽进宽出')], default=1, verbose_name='出入库模式'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-07 08:38
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0007_warehouse_mode'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='merchant',
|
||||||
|
name='auto_complete_stock_change',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='自动确认库存变动'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:29
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0007_remove_employee_contact'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='employee',
|
|
||||||
name='email',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 07:05
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0008_remove_employee_email'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='employee',
|
|
||||||
name='sys_user',
|
|
||||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='employee', to=settings.AUTH_USER_MODEL, verbose_name='系统用户'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 07:14
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0009_employee_sys_user'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='DeviceInfo',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='设备名称')),
|
|
||||||
('type', models.CharField(choices=[('滚筒', '滚筒'), ('打印', '打印')], default='滚筒', max_length=20, verbose_name='设备类型')),
|
|
||||||
('status', models.CharField(choices=[('使用中', '使用中'), ('停用', '停用'), ('维修中', '维修中')], default='使用中', max_length=20, verbose_name='设备状态')),
|
|
||||||
('start_working_at', models.DateField(blank=True, null=True, verbose_name='开始服役日期')),
|
|
||||||
('stop_working_at', models.DateField(blank=True, null=True, verbose_name='停止服役日期')),
|
|
||||||
('is_occupied', models.BooleanField(default=False, verbose_name='是否被占用')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '设备信息',
|
|
||||||
'verbose_name_plural': '设备信息',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 07:26
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0010_deviceinfo'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='VehicleType',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=50, verbose_name='车辆类型名称')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '车辆类型',
|
|
||||||
'verbose_name_plural': '车辆类型',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='deviceinfo',
|
|
||||||
options={'verbose_name': '设备资料', 'verbose_name_plural': '设备资料'},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='VehicleTransportRecord',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('driver_name', models.CharField(max_length=100, verbose_name='司机姓名')),
|
|
||||||
('contact_number', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系电话')),
|
|
||||||
('license_plate', models.CharField(blank=True, max_length=20, null=True, verbose_name='车牌号码')),
|
|
||||||
('delivery_date', models.DateField(verbose_name='送货日期')),
|
|
||||||
('vehicle_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='vehicles', to='basic_info.vehicletype', verbose_name='车次')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '司机车次',
|
|
||||||
'verbose_name_plural': '司机车次',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 08:58
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0011_vehicletype_alter_deviceinfo_options_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='unit',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='basic_info.productunit', verbose_name='主单位'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 09:10
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0012_alter_product_unit'),
|
|
||||||
('stock', '0018_remove_inventory_unit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='product',
|
|
||||||
name='unit',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='main_unit',
|
|
||||||
field=models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], default=1, verbose_name='主单位'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='sub_unit',
|
|
||||||
field=models.IntegerField(blank=True, choices=[(1, '米'), (2, '码'), (3, '公斤')], null=True, verbose_name='辅单位'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='transform_rate',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='转换率(主单位->辅单位)'),
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='ProductUnit',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 09:30
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0013_remove_product_unit_product_main_unit_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='product',
|
|
||||||
name='main_unit',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='product',
|
|
||||||
name='sub_unit',
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='single_price',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='单价(米)'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='transform_rate',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True, verbose_name='公斤转换率'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 09:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0014_remove_product_main_unit_remove_product_sub_unit_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='product',
|
|
||||||
name='single_price',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='single_price_in',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='参考进价'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='single_price_out',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='参考售价'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 02:12
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0015_remove_product_single_price_product_single_price_in_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='unit',
|
|
||||||
field=models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], default=1, verbose_name='单位'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 05:07
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0016_product_unit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='image',
|
|
||||||
field=models.ImageField(blank=True, null=True, upload_to='product_images/', verbose_name='产品图片'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -12,11 +12,19 @@ YARD_TO_METER_RATE = 1.09361
|
|||||||
|
|
||||||
# ==================== 枚举类定义 ====================
|
# ==================== 枚举类定义 ====================
|
||||||
|
|
||||||
|
class WareHouseModeEnum(models.IntegerChoices):
|
||||||
|
"""仓库出入库模式枚举"""
|
||||||
|
RESTRICT_IN = 1, '严进宽出'
|
||||||
|
RESTRICT_IN_OUT = 2, '严进严出'
|
||||||
|
UNRESTRICTED = 3, '宽进宽出'
|
||||||
|
|
||||||
|
|
||||||
class MerchantTypeEnum(models.IntegerChoices):
|
class MerchantTypeEnum(models.IntegerChoices):
|
||||||
"""商户类型枚举"""
|
"""商户类型枚举"""
|
||||||
STORE = 1, '布行'
|
STORE = 1, '布行'
|
||||||
FACTORY = 2, '印花厂'
|
FACTORY = 2, '印花厂'
|
||||||
|
|
||||||
|
|
||||||
class ProductUnitEnum(models.IntegerChoices):
|
class ProductUnitEnum(models.IntegerChoices):
|
||||||
"""产品单位枚举"""
|
"""产品单位枚举"""
|
||||||
METER = 1, '米'
|
METER = 1, '米'
|
||||||
@@ -54,6 +62,21 @@ class DeviceTypeEnum(models.TextChoices):
|
|||||||
# ==================== 模型定义 ====================
|
# ==================== 模型定义 ====================
|
||||||
|
|
||||||
|
|
||||||
|
class QuickInput(ModelBase):
|
||||||
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
name = models.CharField(max_length=100, verbose_name='名')
|
||||||
|
value = models.CharField(max_length=200, verbose_name='值')
|
||||||
|
group = models.CharField(verbose_name='组')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.group}-{self.name}'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = '快捷输入'
|
||||||
|
verbose_name_plural = '快捷输入'
|
||||||
|
unique_together = ('name', 'group')
|
||||||
|
|
||||||
|
|
||||||
class Merchant(ModelBase):
|
class Merchant(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
name = models.CharField(max_length=100, verbose_name='商户名称')
|
name = models.CharField(max_length=100, verbose_name='商户名称')
|
||||||
@@ -62,6 +85,10 @@ class Merchant(ModelBase):
|
|||||||
email = models.EmailField(blank=True, null=True, verbose_name='电子邮箱')
|
email = models.EmailField(blank=True, null=True, verbose_name='电子邮箱')
|
||||||
mobile = models.CharField(max_length=20, blank=True, null=True, verbose_name='手机')
|
mobile = models.CharField(max_length=20, blank=True, null=True, verbose_name='手机')
|
||||||
contact = models.TextField(blank=True, null=True, verbose_name='联系人')
|
contact = models.TextField(blank=True, null=True, verbose_name='联系人')
|
||||||
|
auto_complete_stock_change = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name='自动确认库存变动',
|
||||||
|
)
|
||||||
description = models.TextField(blank=True, null=True, verbose_name='备注描述')
|
description = models.TextField(blank=True, null=True, verbose_name='备注描述')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -74,6 +101,7 @@ class Merchant(ModelBase):
|
|||||||
|
|
||||||
class VehicleType(ModelBase):
|
class VehicleType(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='vehicle_types', verbose_name='所属商户')
|
||||||
name = models.CharField(max_length=50, verbose_name='车辆类型名称')
|
name = models.CharField(max_length=50, verbose_name='车辆类型名称')
|
||||||
description = models.TextField(blank=True, null=True, verbose_name='备注描述')
|
description = models.TextField(blank=True, null=True, verbose_name='备注描述')
|
||||||
|
|
||||||
@@ -87,8 +115,14 @@ class VehicleType(ModelBase):
|
|||||||
|
|
||||||
class WareHouse(ModelBase):
|
class WareHouse(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='warehouses', verbose_name='所属商户')
|
||||||
name = models.CharField(max_length=100, verbose_name='仓库名称')
|
name = models.CharField(max_length=100, verbose_name='仓库名称')
|
||||||
location = models.CharField(max_length=200, blank=True, null=True, verbose_name='仓库地址')
|
location = models.CharField(max_length=200, blank=True, null=True, verbose_name='仓库地址')
|
||||||
|
mode = models.IntegerField(
|
||||||
|
choices=WareHouseModeEnum.choices,
|
||||||
|
default=WareHouseModeEnum.RESTRICT_IN,
|
||||||
|
verbose_name='出入库模式',
|
||||||
|
)
|
||||||
contact = models.CharField(max_length=100, blank=True, null=True, verbose_name='联系人')
|
contact = models.CharField(max_length=100, blank=True, null=True, verbose_name='联系人')
|
||||||
mobile = models.CharField(max_length=20, blank=True, null=True, verbose_name='手机')
|
mobile = models.CharField(max_length=20, blank=True, null=True, verbose_name='手机')
|
||||||
area = models.CharField(max_length=100, blank=True, null=True, verbose_name='地区')
|
area = models.CharField(max_length=100, blank=True, null=True, verbose_name='地区')
|
||||||
@@ -104,6 +138,7 @@ class WareHouse(ModelBase):
|
|||||||
|
|
||||||
class Supplier(ModelBase):
|
class Supplier(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='suppliers', verbose_name='所属商户')
|
||||||
name = models.CharField(max_length=100, verbose_name='供应商名称')
|
name = models.CharField(max_length=100, verbose_name='供应商名称')
|
||||||
area = models.CharField(max_length=100, blank=True, null=True, verbose_name='地区')
|
area = models.CharField(max_length=100, blank=True, null=True, verbose_name='地区')
|
||||||
email = models.EmailField(blank=True, null=True, verbose_name='电子邮箱')
|
email = models.EmailField(blank=True, null=True, verbose_name='电子邮箱')
|
||||||
@@ -121,6 +156,7 @@ class Supplier(ModelBase):
|
|||||||
|
|
||||||
class BankAccount(ModelBase):
|
class BankAccount(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='bank_accounts', verbose_name='所属商户')
|
||||||
name = models.CharField(max_length=100, verbose_name='名称')
|
name = models.CharField(max_length=100, verbose_name='名称')
|
||||||
auto_number = models.CharField(max_length=50, verbose_name='自动编号')
|
auto_number = models.CharField(max_length=50, verbose_name='自动编号')
|
||||||
description = models.TextField(blank=True, null=True, verbose_name='备注描述')
|
description = models.TextField(blank=True, null=True, verbose_name='备注描述')
|
||||||
@@ -136,6 +172,7 @@ class BankAccount(ModelBase):
|
|||||||
|
|
||||||
class ProductCategory(ModelBase):
|
class ProductCategory(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='product_categories', verbose_name='所属商户')
|
||||||
name = models.CharField(max_length=100, verbose_name='类别名称')
|
name = models.CharField(max_length=100, verbose_name='类别名称')
|
||||||
description = models.TextField(blank=True, null=True, verbose_name='类别描述')
|
description = models.TextField(blank=True, null=True, verbose_name='类别描述')
|
||||||
product_prefix = models.CharField(max_length=5, verbose_name='产品编号前缀')
|
product_prefix = models.CharField(max_length=5, verbose_name='产品编号前缀')
|
||||||
@@ -150,6 +187,7 @@ class ProductCategory(ModelBase):
|
|||||||
|
|
||||||
class Product(ModelBase):
|
class Product(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, verbose_name='所属商户')
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
ProductCategory,
|
ProductCategory,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@@ -231,7 +269,21 @@ class Product(ModelBase):
|
|||||||
|
|
||||||
class Customer(ModelBase):
|
class Customer(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='customers', verbose_name='所属商户')
|
||||||
name = models.CharField(max_length=100, verbose_name='客户名称')
|
name = models.CharField(max_length=100, verbose_name='客户名称')
|
||||||
|
visible_employees = models.ManyToManyField(
|
||||||
|
'Employee',
|
||||||
|
blank=True,
|
||||||
|
related_name='visible_customers',
|
||||||
|
verbose_name='可见该客户的员工',
|
||||||
|
)
|
||||||
|
created_by = models.ForeignKey(
|
||||||
|
'Employee',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='created_customers',
|
||||||
|
null=True,
|
||||||
|
verbose_name='创建者',
|
||||||
|
)
|
||||||
mobile = models.CharField(max_length=20, verbose_name='手机')
|
mobile = models.CharField(max_length=20, verbose_name='手机')
|
||||||
area = models.CharField(max_length=100, blank=True, null=True, verbose_name='地区')
|
area = models.CharField(max_length=100, blank=True, null=True, verbose_name='地区')
|
||||||
email = models.EmailField(blank=True, null=True, verbose_name='电子邮箱')
|
email = models.EmailField(blank=True, null=True, verbose_name='电子邮箱')
|
||||||
@@ -248,6 +300,7 @@ class Customer(ModelBase):
|
|||||||
|
|
||||||
class Employee(ModelBase):
|
class Employee(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='employees', verbose_name='所属商户')
|
||||||
sys_user = models.OneToOneField(
|
sys_user = models.OneToOneField(
|
||||||
User,
|
User,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@@ -283,6 +336,7 @@ class Employee(ModelBase):
|
|||||||
|
|
||||||
class DeviceInfo(ModelBase):
|
class DeviceInfo(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='devices', verbose_name='所属商户')
|
||||||
name = models.CharField(max_length=100, verbose_name='设备名称')
|
name = models.CharField(max_length=100, verbose_name='设备名称')
|
||||||
type = models.CharField(
|
type = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
@@ -311,6 +365,7 @@ class DeviceInfo(ModelBase):
|
|||||||
|
|
||||||
class VehicleTransportRecord(ModelBase):
|
class VehicleTransportRecord(ModelBase):
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey('Merchant', on_delete=models.PROTECT, related_name='vehicle_transport_records', verbose_name='所属商户')
|
||||||
vehicle_type = models.ForeignKey(
|
vehicle_type = models.ForeignKey(
|
||||||
VehicleType,
|
VehicleType,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
|||||||
75
basic_info/services.py
Normal file
75
basic_info/services.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from django.db.models import Q
|
||||||
|
from . import models as basic_models
|
||||||
|
|
||||||
|
|
||||||
|
class CustomerVisibilityService:
|
||||||
|
@staticmethod
|
||||||
|
def filter_customers_for_employee(customer_queryset, user):
|
||||||
|
"""
|
||||||
|
过滤客户查询集,使其仅包含对指定员工可见的客户。
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user.is_superuser:
|
||||||
|
return customer_queryset
|
||||||
|
|
||||||
|
emp = getattr(user, 'employee', None)
|
||||||
|
if emp is None:
|
||||||
|
return customer_queryset.none()
|
||||||
|
|
||||||
|
print(f'Filtering customers for employee: {emp.id}')
|
||||||
|
# 员工创建的客户可见 + 员工被设置为可见员工的客户可见
|
||||||
|
visibility_filter = Q(created_by=emp) | Q(visible_employees=emp)
|
||||||
|
return customer_queryset.filter(visibility_filter).distinct()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_customer_visible_to_employee(customer, user):
|
||||||
|
"""
|
||||||
|
检查特定客户是否对指定员工可见。
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
emp = getattr(user, 'employee', None)
|
||||||
|
if emp is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if customer.created_by == emp:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if emp in customer.visible_employees.all():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class DataVisibilityService:
|
||||||
|
@staticmethod
|
||||||
|
def is_product_visible_to_employee(product_id: int, user):
|
||||||
|
"""
|
||||||
|
检查特定产品是否对指定员工可见。
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
emp: basic_models.Employee = getattr(user, 'employee', None)
|
||||||
|
if emp is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return basic_models.Product.objects.filter(id=product_id).filter(merchant=emp.merchant).exists()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_warehouse_visible_to_employee(warehouse_id: int, user):
|
||||||
|
"""
|
||||||
|
检查特定仓库是否对指定员工可见。
|
||||||
|
"""
|
||||||
|
|
||||||
|
if user.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
emp: basic_models.Employee = getattr(user, 'employee', None)
|
||||||
|
if emp is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return basic_models.WareHouse.objects.filter(id=warehouse_id).filter(merchant=emp.merchant).exists()
|
||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: myapp
|
POSTGRES_DB: flower
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -49,6 +49,28 @@ DEBUG = env('DEBUG', default=False)
|
|||||||
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])
|
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])
|
||||||
|
|
||||||
|
|
||||||
|
# CORS 配置
|
||||||
|
CORS_ALLOW_ALL_ORIGINS = DEBUG # 开发环境允许所有源,生产环境需要配置白名单
|
||||||
|
CORS_ALLOWED_ORIGINS = env.list('CORS_ALLOWED_ORIGINS', default=[
|
||||||
|
'http://localhost:3000',
|
||||||
|
'http://localhost:5173',
|
||||||
|
'http://127.0.0.1:3000',
|
||||||
|
'http://127.0.0.1:5173',
|
||||||
|
])
|
||||||
|
CORS_ALLOW_CREDENTIALS = True # 允许携带凭证(如 Cookie、认证头)
|
||||||
|
CORS_ALLOW_HEADERS = [
|
||||||
|
'accept',
|
||||||
|
'accept-encoding',
|
||||||
|
'authorization',
|
||||||
|
'content-type',
|
||||||
|
'dnt',
|
||||||
|
'origin',
|
||||||
|
'user-agent',
|
||||||
|
'x-csrftoken',
|
||||||
|
'x-requested-with',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
@@ -59,6 +81,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles', # 使用七牛云存储,不需要staticfiles app
|
'django.contrib.staticfiles', # 使用七牛云存储,不需要staticfiles app
|
||||||
|
'corsheaders', # CORS 支持
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'drf_spectacular',
|
'drf_spectacular',
|
||||||
'basic_info',
|
'basic_info',
|
||||||
@@ -70,6 +93,7 @@ INSTALLED_APPS = [
|
|||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'corsheaders.middleware.CorsMiddleware', # CORS 中间件,必须放在 CommonMiddleware 之前
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
|||||||
@@ -17,16 +17,37 @@ Including another URLconf
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from stock.views import router
|
from stock.views import router
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||||
|
from sse.views import create_sse_event, push_sse_event
|
||||||
from rest_framework_simplejwt.views import (
|
from rest_framework_simplejwt.views import (
|
||||||
TokenObtainPairView,
|
TokenObtainPairView,
|
||||||
TokenRefreshView,
|
# TokenRefreshView,
|
||||||
)
|
)
|
||||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
|
||||||
|
class CustomTokenObtainPairView(TokenObtainPairView):
|
||||||
|
"""自定义登录视图"""
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
resp = super().post(request, *args, **kwargs)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
srz = self.get_serializer(data=request.data)
|
||||||
|
srz.is_valid()
|
||||||
|
user = srz.user
|
||||||
|
|
||||||
|
if not hasattr(user, 'employee'):
|
||||||
|
# 非员工用户,直接返回登录失败
|
||||||
|
return Response({'detail': '无绑定的员工身份'}, status=401)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
except Exception as e:
|
||||||
|
return Response({'detail': '无法登录'}, status=400)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
# JWT 登录
|
# JWT 登录
|
||||||
path('api/auth/login/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
|
path('api/auth/login/', CustomTokenObtainPairView.as_view(), name='token_obtain_pair'),
|
||||||
# path('api/auth/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
# path('api/auth/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
||||||
|
|
||||||
# API 文档
|
# API 文档
|
||||||
@@ -37,4 +58,6 @@ urlpatterns = [
|
|||||||
path('stock/', router.urls),
|
path('stock/', router.urls),
|
||||||
path('api/v1/', include('api_v1.urls')),
|
path('api/v1/', include('api_v1.urls')),
|
||||||
path('api/backend/', include('api_man.urls')),
|
path('api/backend/', include('api_man.urls')),
|
||||||
|
path('sse/', create_sse_event, name='sse_event'),
|
||||||
|
path('sse/push/', push_sse_event, name='push_sse_event'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ readme = "README.md"
|
|||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"django>=5.2.7",
|
"django>=5.2.7",
|
||||||
"django-cors>=0.1",
|
"django-cors-headers>=4.9.0",
|
||||||
"django-environ>=0.12.0",
|
"django-environ>=0.12.0",
|
||||||
"django-filter>=25.2",
|
"django-filter>=25.2",
|
||||||
"django-ninja>=1.4.5",
|
"django-ninja>=1.4.5",
|
||||||
|
|||||||
0
sse/__init__.py
Normal file
0
sse/__init__.py
Normal file
3
sse/admin.py
Normal file
3
sse/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
sse/apps.py
Normal file
6
sse/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class SseConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'sse'
|
||||||
0
sse/migrations/__init__.py
Normal file
0
sse/migrations/__init__.py
Normal file
3
sse/models.py
Normal file
3
sse/models.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
sse/tests.py
Normal file
3
sse/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
45
sse/views.py
Normal file
45
sse/views.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from django.http.response import StreamingHttpResponse
|
||||||
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from contextlib import suppress
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
_connections = set()
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([])
|
||||||
|
def create_sse_event(request):
|
||||||
|
"""
|
||||||
|
创建一个简单的SSE事件流响应,用于测试和演示目的。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def sse_stream():
|
||||||
|
queue = asyncio.Queue()
|
||||||
|
_connections.add(queue)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = queue.get()
|
||||||
|
yield f"data: {data}\n\n"
|
||||||
|
finally:
|
||||||
|
_connections.remove(queue)
|
||||||
|
|
||||||
|
response = StreamingHttpResponse(sse_stream(), content_type='text/event-stream')
|
||||||
|
response['Cache-Control'] = 'no-cache'
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
@permission_classes([])
|
||||||
|
def push_sse_event(request):
|
||||||
|
"""
|
||||||
|
向所有连接的客户端广播一个SSE事件。
|
||||||
|
请求体应包含一个 'message' 字段,表示要发送的消息内容。
|
||||||
|
"""
|
||||||
|
message = request.data.get('message', 'Hello, SSE!')
|
||||||
|
for q in list(_connections):
|
||||||
|
with suppress(asyncio.QueueFull):
|
||||||
|
q.put_nowait(message)
|
||||||
|
return Response({'status': 'message sent'})
|
||||||
@@ -4,6 +4,14 @@ from django.utils.safestring import mark_safe
|
|||||||
from . import models, services
|
from . import models, services
|
||||||
|
|
||||||
|
|
||||||
|
class StockAdminBase(admin.ModelAdmin):
|
||||||
|
"""库存管理基础类,定义通用配置"""
|
||||||
|
|
||||||
|
def get_list_filter(self, request):
|
||||||
|
result = super().get_list_filter(request)
|
||||||
|
result = list(result) + ['created_at']
|
||||||
|
return result
|
||||||
|
|
||||||
@admin.register(models.PurchaseOrder)
|
@admin.register(models.PurchaseOrder)
|
||||||
class PurchaseOrderAdmin(admin.ModelAdmin):
|
class PurchaseOrderAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'supplier', 'order_date', 'total_amount')
|
list_display = ('id', 'supplier', 'order_date', 'total_amount')
|
||||||
@@ -13,7 +21,7 @@ class PurchaseOrderAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
|
|
||||||
@admin.register(models.Inventory)
|
@admin.register(models.Inventory)
|
||||||
class InventoryAdmin(admin.ModelAdmin):
|
class InventoryAdmin(StockAdminBase):
|
||||||
list_display = (
|
list_display = (
|
||||||
'id',
|
'id',
|
||||||
'product',
|
'product',
|
||||||
@@ -22,12 +30,20 @@ class InventoryAdmin(admin.ModelAdmin):
|
|||||||
'quantity',
|
'quantity',
|
||||||
'num_of_rolls',
|
'num_of_rolls',
|
||||||
'minimum_quantity',
|
'minimum_quantity',
|
||||||
|
'frozen_quantity',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
)
|
)
|
||||||
search_fields = ('product__name', 'warehouse__name')
|
search_fields = ('product__name', 'warehouse__name')
|
||||||
list_filter = ('warehouse',)
|
list_filter = ('warehouse',)
|
||||||
ordering = ('-updated_at',)
|
ordering = ('-updated_at',)
|
||||||
|
|
||||||
|
@admin.display(description='冻结中数量')
|
||||||
|
def frozen_quantity(self, obj):
|
||||||
|
return services.get_frozen_stock_count(
|
||||||
|
product_id=obj.product_id,
|
||||||
|
warehouse_id=obj.warehouse_id
|
||||||
|
)
|
||||||
|
|
||||||
@admin.display(description='颜色')
|
@admin.display(description='颜色')
|
||||||
def product_color(self, obj):
|
def product_color(self, obj):
|
||||||
return obj.product.color
|
return obj.product.color
|
||||||
@@ -45,7 +61,7 @@ class StockSnapshotAdmin(admin.ModelAdmin):
|
|||||||
'stock_change_record',
|
'stock_change_record',
|
||||||
'created_at',
|
'created_at',
|
||||||
)
|
)
|
||||||
search_fields = ('product__name', 'warehouse__name', 'stock_change_record__id')
|
search_fields = ('product', 'warehouse', 'stock_change_record__id')
|
||||||
list_filter = ('stock_change_record__type',)
|
list_filter = ('stock_change_record__type',)
|
||||||
ordering = ('-created_at', )
|
ordering = ('-created_at', )
|
||||||
|
|
||||||
@@ -73,12 +89,17 @@ class StockChangeDetailAdmin(admin.ModelAdmin):
|
|||||||
'stock_change_record',
|
'stock_change_record',
|
||||||
'product',
|
'product',
|
||||||
'quantity',
|
'quantity',
|
||||||
|
'direction',
|
||||||
'unit',
|
'unit',
|
||||||
)
|
)
|
||||||
search_fields = ('stock_change_record__id', 'product__name')
|
search_fields = ('stock_change_record__id', 'product__name')
|
||||||
list_filter = ('stock_change_record', 'stock_change_record__type')
|
list_filter = ('stock_change_record', 'stock_change_record__type')
|
||||||
ordering = ('-created_at',)
|
ordering = ('-created_at',)
|
||||||
|
|
||||||
|
@admin.display(description='出入方向')
|
||||||
|
def direction(self, obj):
|
||||||
|
return '入库' if obj.stock_change_record.is_incoming else '出库'
|
||||||
|
|
||||||
|
|
||||||
@admin.register(models.StockChangeRecord)
|
@admin.register(models.StockChangeRecord)
|
||||||
class StockChangeRecordAdmin(admin.ModelAdmin):
|
class StockChangeRecordAdmin(admin.ModelAdmin):
|
||||||
@@ -132,3 +153,28 @@ class StockChangeRecordAdmin(admin.ModelAdmin):
|
|||||||
return mark_safe('<br>'.join(detail_list))
|
return mark_safe('<br>'.join(detail_list))
|
||||||
|
|
||||||
actions = [make_selected_stock_changes_completed]
|
actions = [make_selected_stock_changes_completed]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.StockFreeze)
|
||||||
|
class StockFreezeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'id',
|
||||||
|
'product',
|
||||||
|
'warehouse',
|
||||||
|
'quantity',
|
||||||
|
'reason',
|
||||||
|
'created_at',
|
||||||
|
)
|
||||||
|
readonly_fields = (
|
||||||
|
'frozen_by',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
'cancelled_at',
|
||||||
|
'cancelled_by',
|
||||||
|
'completed_at',
|
||||||
|
'completed_by',
|
||||||
|
'completed_with',
|
||||||
|
)
|
||||||
|
search_fields = ('product__name', 'warehouse__name', 'reason')
|
||||||
|
list_filter = ('warehouse', 'created_at')
|
||||||
|
ordering = ('-created_at',)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 04:08
|
# Generated by Django 5.2.7 on 2025-11-06 05:33
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
@@ -9,30 +10,111 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
('basic_info', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ProductCategory',
|
name='PurchaseOrder',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
('name', models.CharField(max_length=100, verbose_name='类别名称')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='类别描述')),
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('order_date', models.DateField(verbose_name='订单日期')),
|
||||||
|
('total_amount', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='总金额')),
|
||||||
|
('remarks', models.TextField(blank=True, null=True, verbose_name='备注')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='basic_info.supplier', verbose_name='供应商')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '采购单',
|
||||||
|
'verbose_name_plural': '采购单',
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Product',
|
name='StockChangeRecord',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
('name', models.CharField(max_length=100, verbose_name='产品名')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
('human_id', models.CharField(max_length=100, verbose_name='产品编号')),
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
('color', models.CharField(blank=True, max_length=50, null=True, verbose_name='颜色')),
|
('type', models.IntegerField(choices=[(1, '入库'), (2, '出库')], verbose_name='变动类型')),
|
||||||
('width_size', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='宽幅')),
|
('source_type', models.IntegerField(choices=[(1, '采购'), (2, '销退'), (3, '调入'), (4, '盘盈'), (5, '销售'), (6, '采购退货'), (7, '调出'), (8, '盘亏')], verbose_name='变动来源')),
|
||||||
('single_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='单价')),
|
('source_id', models.BigIntegerField(blank=True, null=True, verbose_name='来源单据ID')),
|
||||||
|
('is_finished', models.BooleanField(default=False, verbose_name='完成出入库')),
|
||||||
|
('finished_at', models.DateTimeField(blank=True, null=True, verbose_name='完成时间')),
|
||||||
|
('remarks', models.TextField(blank=True, null=True, verbose_name='备注')),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='操作人')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_change_records', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_change_records', to='basic_info.warehouse', verbose_name='仓库')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '出入库记录',
|
||||||
|
'verbose_name_plural': '出入库记录',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StockChangeDetail',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('quantity', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='数量')),
|
||||||
|
('unit', models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], verbose_name='单位')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_change_details', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_change_details', to='basic_info.product', verbose_name='产品')),
|
||||||
|
('stock_change_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='details', to='stock.stockchangerecord', verbose_name='库存变动记录')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '库存变动明细',
|
||||||
|
'verbose_name_plural': '库存变动明细',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StockSnapshot',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('delta', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='库存变动数量')),
|
||||||
|
('quantity_before', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='变动前库存数量')),
|
||||||
|
('quantity_after', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='变动后库存数量')),
|
||||||
|
('unit', models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], verbose_name='单位')),
|
||||||
|
('num_of_rolls', models.IntegerField(default=1, verbose_name='匹数')),
|
||||||
|
('offset_to', models.BigIntegerField(blank=True, null=True, verbose_name='冲抵目标ID')),
|
||||||
|
('offset_at', models.DateTimeField(blank=True, null=True, verbose_name='冲抵时间')),
|
||||||
|
('cancelled', models.BooleanField(default=False, verbose_name='已取消')),
|
||||||
|
('cancelled_at', models.DateTimeField(blank=True, null=True, verbose_name='取消时间')),
|
||||||
|
('offset_id', models.BigIntegerField(blank=True, null=True, verbose_name='冲抵记录ID')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_snapshots', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_snapshots', to='basic_info.product', verbose_name='产品')),
|
||||||
|
('stock_change_record', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_snapshots', to='stock.stockchangerecord', verbose_name='关联的库存变动记录')),
|
||||||
|
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_snapshots', to='basic_info.warehouse', verbose_name='仓库')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '库存快照',
|
||||||
|
'verbose_name_plural': '库存快照',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Inventory',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('quantity', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='库存数量')),
|
||||||
|
('num_of_rolls', models.IntegerField(default=1, verbose_name='匹数')),
|
||||||
|
('minimum_quantity', models.IntegerField(blank=True, null=True, verbose_name='最低库存量')),
|
||||||
('spec', models.CharField(blank=True, max_length=100, null=True, verbose_name='规格')),
|
('spec', models.CharField(blank=True, max_length=100, null=True, verbose_name='规格')),
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||||
('unit', models.CharField(default='米', max_length=20, verbose_name='单位')),
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inventories', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='stock.productcategory', verbose_name='产品类别')),
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inventories', to='basic_info.product', verbose_name='产品')),
|
||||||
|
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inventories', to='basic_info.warehouse', verbose_name='仓库')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '库存',
|
||||||
|
'verbose_name_plural': '库存',
|
||||||
|
'unique_together': {('product', 'warehouse')},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 04:10
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='product',
|
|
||||||
options={'verbose_name': '产品资料', 'verbose_name_plural': '产品资料'},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='productcategory',
|
|
||||||
options={'verbose_name': '产品类别', 'verbose_name_plural': '产品类别'},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
44
stock/migrations/0002_stockfreeze.py
Normal file
44
stock/migrations/0002_stockfreeze.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-07 05:30
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0005_customer_created_by'),
|
||||||
|
('stock', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='StockFreeze',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
||||||
|
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||||
|
('quantity', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='冻结数量')),
|
||||||
|
('unit', models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], verbose_name='单位')),
|
||||||
|
('status', models.IntegerField(choices=[(1, '冻结中'), (2, '已取消'), (3, '已成单')], default=1, verbose_name='冻结状态')),
|
||||||
|
('frozen_with', models.BigIntegerField(blank=True, null=True, verbose_name='冻结关联ID')),
|
||||||
|
('completed_at', models.DateTimeField(blank=True, null=True, verbose_name='成单时间')),
|
||||||
|
('completed_with', models.BigIntegerField(blank=True, null=True, verbose_name='成单关联ID')),
|
||||||
|
('cancelled_at', models.DateTimeField(blank=True, null=True, verbose_name='取消时间')),
|
||||||
|
('reason', models.TextField(blank=True, null=True, verbose_name='冻结原因')),
|
||||||
|
('cancelled_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='cancelled_stock_freezes', to=settings.AUTH_USER_MODEL, verbose_name='取消操作人')),
|
||||||
|
('completed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='completed_stock_freezes', to=settings.AUTH_USER_MODEL, verbose_name='成单操作人')),
|
||||||
|
('frozen_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='冻结操作人')),
|
||||||
|
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_freezes', to='basic_info.merchant', verbose_name='所属商户')),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_freezes', to='basic_info.product', verbose_name='产品')),
|
||||||
|
('stock_detail', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_freezes', to='stock.stockchangedetail', verbose_name='关联的库存变动明细')),
|
||||||
|
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_freezes', to='basic_info.warehouse', verbose_name='仓库')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '库存冻结',
|
||||||
|
'verbose_name_plural': '库存冻结',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 04:20
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0002_alter_product_options_alter_productcategory_options'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Supplier',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='供应商名称')),
|
|
||||||
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
|
||||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='电子邮箱')),
|
|
||||||
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
|
||||||
('contact', models.TextField(blank=True, null=True, verbose_name='联系人')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '供应商',
|
|
||||||
'verbose_name_plural': '供应商',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 04:24
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0003_supplier'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='id',
|
|
||||||
field=models.BigAutoField(primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='productcategory',
|
|
||||||
name='id',
|
|
||||||
field=models.BigAutoField(primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='id',
|
|
||||||
field=models.BigAutoField(primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PurchaseOrder',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('order_date', models.DateField(verbose_name='订单日期')),
|
|
||||||
('total_amount', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='总金额')),
|
|
||||||
('remarks', models.TextField(blank=True, null=True, verbose_name='备注')),
|
|
||||||
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='stock.supplier', verbose_name='供应商')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '采购单',
|
|
||||||
'verbose_name_plural': '采购单',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 04:29
|
|
||||||
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0004_alter_product_id_alter_productcategory_id_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='created_at',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='创建时间'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='product',
|
|
||||||
name='updated_at',
|
|
||||||
field=models.DateTimeField(auto_now=True, verbose_name='更新时间'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='productcategory',
|
|
||||||
name='created_at',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='创建时间'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='productcategory',
|
|
||||||
name='updated_at',
|
|
||||||
field=models.DateTimeField(auto_now=True, verbose_name='更新时间'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='purchaseorder',
|
|
||||||
name='created_at',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='创建时间'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='purchaseorder',
|
|
||||||
name='updated_at',
|
|
||||||
field=models.DateTimeField(auto_now=True, verbose_name='更新时间'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='created_at',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='创建时间'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='supplier',
|
|
||||||
name='updated_at',
|
|
||||||
field=models.DateTimeField(auto_now=True, verbose_name='更新时间'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 04:32
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0005_product_created_at_product_updated_at_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='BankAccount',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='名称')),
|
|
||||||
('auto_number', models.CharField(max_length=50, verbose_name='自动编号')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
('attachment', models.FileField(blank=True, null=True, upload_to='bank_accounts/', verbose_name='附件')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '银行账户',
|
|
||||||
'verbose_name_plural': '银行账户',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 05:07
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0006_bankaccount'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='productcategory',
|
|
||||||
name='product_prefix',
|
|
||||||
field=models.CharField(blank=True, max_length=5, null=True, verbose_name='产品编号前缀'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 05:13
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0007_productcategory_product_prefix'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='human_id',
|
|
||||||
field=models.CharField(blank=True, max_length=100, verbose_name='产品编号'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='productcategory',
|
|
||||||
name='product_prefix',
|
|
||||||
field=models.CharField(max_length=5, verbose_name='产品编号前缀'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 05:20
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0008_alter_product_human_id_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='single_price',
|
|
||||||
field=models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='单价'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 05:28
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0010_prodictunit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='product',
|
|
||||||
name='unit',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='products', to='stock.prodictunit', verbose_name='单位'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 05:34
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0011_alter_product_unit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameModel(
|
|
||||||
old_name='ProdictUnit',
|
|
||||||
new_name='ProductUnit',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 05:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0012_rename_prodictunit_productunit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='WareHouse',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='仓库名称')),
|
|
||||||
('location', models.CharField(blank=True, max_length=200, null=True, verbose_name='仓库地址')),
|
|
||||||
('contact', models.CharField(blank=True, max_length=100, null=True, verbose_name='联系人')),
|
|
||||||
('mobile', models.CharField(blank=True, max_length=20, null=True, verbose_name='手机')),
|
|
||||||
('area', models.CharField(blank=True, max_length=100, null=True, verbose_name='地区')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '仓库资料',
|
|
||||||
'verbose_name_plural': '仓库资料',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:06
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0013_warehouse'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='BankAccount',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='product',
|
|
||||||
name='category',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='product',
|
|
||||||
name='unit',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='purchaseorder',
|
|
||||||
name='supplier',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='WareHouse',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='ProductCategory',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Product',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='ProductUnit',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='PurchaseOrder',
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name='Supplier',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 06:11
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0002_delete_purchaseorder'),
|
|
||||||
('stock', '0014_delete_bankaccount_remove_product_category_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PurchaseOrder',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('order_date', models.DateField(verbose_name='订单日期')),
|
|
||||||
('total_amount', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='总金额')),
|
|
||||||
('remarks', models.TextField(blank=True, null=True, verbose_name='备注')),
|
|
||||||
('supplier', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='basic_info.supplier', verbose_name='供应商')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '采购单',
|
|
||||||
'verbose_name_plural': '采购单',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 07:47
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0011_vehicletype_alter_deviceinfo_options_and_more'),
|
|
||||||
('stock', '0015_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Inventory',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('quantity', models.IntegerField(verbose_name='数量')),
|
|
||||||
('minimum_quantity', models.IntegerField(blank=True, null=True, verbose_name='最低库存量')),
|
|
||||||
('spec', models.CharField(blank=True, max_length=100, null=True, verbose_name='规格')),
|
|
||||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
|
||||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inventories', to='basic_info.product', verbose_name='产品')),
|
|
||||||
('unit', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inventories', to='basic_info.productunit', verbose_name='单位')),
|
|
||||||
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inventories', to='basic_info.warehouse', verbose_name='仓库')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '库存',
|
|
||||||
'verbose_name_plural': '库存',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 08:01
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0016_inventory'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='num_of_rolls',
|
|
||||||
field=models.IntegerField(blank=True, null=True, verbose_name='匹数'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='quantity',
|
|
||||||
field=models.IntegerField(verbose_name='库存数量'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-03 09:10
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0017_inventory_num_of_rolls_alter_inventory_quantity'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='unit',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 05:18
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0017_product_image'),
|
|
||||||
('stock', '0018_remove_inventory_unit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='inventory',
|
|
||||||
unique_together={('product', 'warehouse')},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 05:51
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0019_alter_inventory_unique_together'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='StockChangeRecord',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('type', models.IntegerField(choices=[(1, '入库'), (2, '出库')], verbose_name='变动类型')),
|
|
||||||
('source_type', models.IntegerField(choices=[(1, '采购'), (2, '销退'), (3, '调入'), (4, '盘盈'), (5, '销售'), (6, '采购退货'), (7, '调出'), (8, '盘亏')], verbose_name='变动来源')),
|
|
||||||
('remarks', models.TextField(blank=True, null=True, verbose_name='备注')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '出入库记录',
|
|
||||||
'verbose_name_plural': '出入库记录',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 05:57
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0017_product_image'),
|
|
||||||
('stock', '0020_stockchangerecord'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='is_finished',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='完成出入库'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='product',
|
|
||||||
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='stock_change_records', to='basic_info.product', verbose_name='产品'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='source_id',
|
|
||||||
field=models.BigIntegerField(blank=True, null=True, verbose_name='来源单据ID'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='warehouse',
|
|
||||||
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='stock_change_records', to='basic_info.warehouse', verbose_name='仓库'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 06:05
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0021_stockchangerecord_is_finished_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='quantity',
|
|
||||||
field=models.IntegerField(default=1, verbose_name='数量'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='unit',
|
|
||||||
field=models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], default=1, verbose_name='单位'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 06:21
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0022_stockchangerecord_quantity_stockchangerecord_unit'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='num_of_rolls',
|
|
||||||
field=models.IntegerField(blank=True, null=True, verbose_name='匹数'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 06:35
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0023_stockchangerecord_num_of_rolls'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='created_by',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 06:35
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0024_stockchangerecord_created_by'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='created_by',
|
|
||||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='操作人'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 06:44
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0025_alter_stockchangerecord_created_by'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='finished_at',
|
|
||||||
field=models.DateTimeField(blank=True, null=True, verbose_name='完成时间'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 07:09
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0026_stockchangerecord_finished_at'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='num_of_rolls',
|
|
||||||
field=models.IntegerField(default=1, verbose_name='匹数'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='num_of_rolls',
|
|
||||||
field=models.IntegerField(default=1, verbose_name='匹数'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 07:53
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0017_product_image'),
|
|
||||||
('stock', '0027_alter_inventory_num_of_rolls_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='StockSnapshot',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('delta', models.IntegerField(verbose_name='库存数量')),
|
|
||||||
('quantity_before', models.IntegerField(verbose_name='变动前库存数量')),
|
|
||||||
('quantity_after', models.IntegerField(verbose_name='变动后库存数量')),
|
|
||||||
('unit', models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], verbose_name='单位')),
|
|
||||||
('num_of_rolls', models.IntegerField(default=1, verbose_name='匹数')),
|
|
||||||
('offset_to', models.BigIntegerField(blank=True, null=True, verbose_name='冲抵目标ID')),
|
|
||||||
('offset_at', models.DateTimeField(blank=True, null=True, verbose_name='冲抵时间')),
|
|
||||||
('cancelled', models.BooleanField(default=False, verbose_name='已取消')),
|
|
||||||
('cancelled_at', models.DateTimeField(blank=True, null=True, verbose_name='取消时间')),
|
|
||||||
('offset_id', models.BigIntegerField(blank=True, null=True, verbose_name='冲抵记录ID')),
|
|
||||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_snapshots', to='basic_info.product', verbose_name='产品')),
|
|
||||||
('stock_change_record', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_snapshots', to='stock.stockchangerecord', verbose_name='关联的库存变动记录')),
|
|
||||||
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_snapshots', to='basic_info.warehouse', verbose_name='仓库')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '库存快照',
|
|
||||||
'verbose_name_plural': '库存快照',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 08:53
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0028_stocksnapshot'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='inventory',
|
|
||||||
name='quantity',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='库存数量'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='quantity',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='数量'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='stocksnapshot',
|
|
||||||
name='delta',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='库存数量'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='stocksnapshot',
|
|
||||||
name='quantity_after',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='变动后库存数量'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='stocksnapshot',
|
|
||||||
name='quantity_before',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='变动前库存数量'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-04 09:50
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0029_alter_inventory_quantity_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='stocksnapshot',
|
|
||||||
name='delta',
|
|
||||||
field=models.DecimalField(decimal_places=2, max_digits=10, verbose_name='库存变动数量'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-05 05:34
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('basic_info', '0017_product_image'),
|
|
||||||
('stock', '0030_alter_stocksnapshot_delta'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='num_of_rolls',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='product',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='quantity',
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='StockChangeDetail',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
|
|
||||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
|
||||||
('quantity', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='数量')),
|
|
||||||
('unit', models.IntegerField(choices=[(1, '米'), (2, '码'), (3, '公斤')], verbose_name='单位')),
|
|
||||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_change_details', to='basic_info.product', verbose_name='产品')),
|
|
||||||
('stock_change_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='details', to='stock.stockchangerecord', verbose_name='库存变动记录')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': '库存变动明细',
|
|
||||||
'verbose_name_plural': '库存变动明细',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Generated by Django 5.2.7 on 2025-11-05 06:50
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('stock', '0031_remove_stockchangerecord_num_of_rolls_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='stockchangerecord',
|
|
||||||
name='unit',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
110
stock/models.py
110
stock/models.py
@@ -8,6 +8,13 @@ import decimal
|
|||||||
|
|
||||||
# ==================== 枚举类定义 ====================
|
# ==================== 枚举类定义 ====================
|
||||||
|
|
||||||
|
class StockFreezeStatusEnum(models.IntegerChoices):
|
||||||
|
"""库存冻结状态类型"""
|
||||||
|
|
||||||
|
FROZEN = 1, '冻结中'
|
||||||
|
CANCELLED = 2, '已取消'
|
||||||
|
COMPLETED = 3, '已成单'
|
||||||
|
|
||||||
class StockChangeSourceEnum(models.IntegerChoices):
|
class StockChangeSourceEnum(models.IntegerChoices):
|
||||||
"""库存变动来源类型"""
|
"""库存变动来源类型"""
|
||||||
|
|
||||||
@@ -35,6 +42,12 @@ class PurchaseOrder(ModelBase):
|
|||||||
"""采购单模型"""
|
"""采购单模型"""
|
||||||
|
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey(
|
||||||
|
basic_info_models.Merchant,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='purchase_orders',
|
||||||
|
verbose_name='所属商户'
|
||||||
|
)
|
||||||
supplier = models.ForeignKey(basic_info_models.Supplier, on_delete=models.PROTECT, related_name='purchase_orders', verbose_name='供应商')
|
supplier = models.ForeignKey(basic_info_models.Supplier, on_delete=models.PROTECT, related_name='purchase_orders', verbose_name='供应商')
|
||||||
order_date = models.DateField(verbose_name='订单日期')
|
order_date = models.DateField(verbose_name='订单日期')
|
||||||
total_amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='总金额')
|
total_amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='总金额')
|
||||||
@@ -52,6 +65,12 @@ class StockChangeRecord(ModelBase):
|
|||||||
"""库存变动记录模型"""
|
"""库存变动记录模型"""
|
||||||
|
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey(
|
||||||
|
basic_info_models.Merchant,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='stock_change_records',
|
||||||
|
verbose_name='所属商户'
|
||||||
|
)
|
||||||
type = models.IntegerField(choices=StockChangeTypeEnum.choices, verbose_name='变动类型')
|
type = models.IntegerField(choices=StockChangeTypeEnum.choices, verbose_name='变动类型')
|
||||||
warehouse = models.ForeignKey(
|
warehouse = models.ForeignKey(
|
||||||
basic_info_models.WareHouse,
|
basic_info_models.WareHouse,
|
||||||
@@ -127,6 +146,12 @@ class StockChangeDetail(ModelBase):
|
|||||||
"""库存变动明细模型"""
|
"""库存变动明细模型"""
|
||||||
|
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey(
|
||||||
|
basic_info_models.Merchant,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='stock_change_details',
|
||||||
|
verbose_name='所属商户'
|
||||||
|
)
|
||||||
product = models.ForeignKey(
|
product = models.ForeignKey(
|
||||||
basic_info_models.Product,
|
basic_info_models.Product,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@@ -161,6 +186,12 @@ class Inventory(ModelBase):
|
|||||||
"""库存模型"""
|
"""库存模型"""
|
||||||
|
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey(
|
||||||
|
basic_info_models.Merchant,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='inventories',
|
||||||
|
verbose_name='所属商户'
|
||||||
|
)
|
||||||
product = models.ForeignKey(
|
product = models.ForeignKey(
|
||||||
basic_info_models.Product,
|
basic_info_models.Product,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@@ -192,6 +223,12 @@ class StockSnapshot(ModelBase):
|
|||||||
"""库存变动快照模型"""
|
"""库存变动快照模型"""
|
||||||
|
|
||||||
id = models.BigAutoField(primary_key=True)
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey(
|
||||||
|
basic_info_models.Merchant,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='stock_snapshots',
|
||||||
|
verbose_name='所属商户'
|
||||||
|
)
|
||||||
product = models.ForeignKey(
|
product = models.ForeignKey(
|
||||||
basic_info_models.Product,
|
basic_info_models.Product,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
@@ -252,3 +289,76 @@ class StockSnapshot(ModelBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = '库存快照'
|
verbose_name = '库存快照'
|
||||||
verbose_name_plural = '库存快照'
|
verbose_name_plural = '库存快照'
|
||||||
|
|
||||||
|
|
||||||
|
class StockFreeze(ModelBase):
|
||||||
|
"""库存冻结模型"""
|
||||||
|
|
||||||
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
merchant = models.ForeignKey(
|
||||||
|
basic_info_models.Merchant,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='stock_freezes',
|
||||||
|
verbose_name='所属商户'
|
||||||
|
)
|
||||||
|
product = models.ForeignKey(
|
||||||
|
basic_info_models.Product,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='stock_freezes',
|
||||||
|
verbose_name='产品',
|
||||||
|
)
|
||||||
|
warehouse = models.ForeignKey(
|
||||||
|
basic_info_models.WareHouse,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='stock_freezes',
|
||||||
|
verbose_name='仓库',
|
||||||
|
)
|
||||||
|
stock_detail = models.ForeignKey(
|
||||||
|
StockChangeDetail,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='stock_freezes',
|
||||||
|
verbose_name='关联的库存变动明细',
|
||||||
|
)
|
||||||
|
quantity = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='冻结数量')
|
||||||
|
unit = models.IntegerField(
|
||||||
|
verbose_name='单位',
|
||||||
|
choices=basic_info_models.ProductUnitEnum.choices,
|
||||||
|
)
|
||||||
|
status = models.IntegerField(
|
||||||
|
choices=StockFreezeStatusEnum.choices,
|
||||||
|
default=StockFreezeStatusEnum.FROZEN,
|
||||||
|
verbose_name='冻结状态',
|
||||||
|
)
|
||||||
|
frozen_by = models.ForeignKey(
|
||||||
|
'auth.User',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
verbose_name='冻结操作人',
|
||||||
|
)
|
||||||
|
frozen_with = models.BigIntegerField(null=True, blank=True, verbose_name='冻结关联ID')
|
||||||
|
cancelled_by = models.ForeignKey(
|
||||||
|
'auth.User',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name='cancelled_stock_freezes',
|
||||||
|
verbose_name='取消操作人',
|
||||||
|
)
|
||||||
|
completed_by = models.ForeignKey(
|
||||||
|
'auth.User',
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name='completed_stock_freezes',
|
||||||
|
verbose_name='成单操作人',
|
||||||
|
)
|
||||||
|
completed_at = models.DateTimeField(null=True, blank=True, verbose_name='成单时间')
|
||||||
|
completed_with = models.BigIntegerField(null=True, blank=True, verbose_name='成单关联ID')
|
||||||
|
cancelled_at = models.DateTimeField(null=True, blank=True, verbose_name='取消时间')
|
||||||
|
reason = models.TextField(blank=True, null=True, verbose_name='冻结原因')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'库存冻结 {self.id} - 产品: {self.product.name}'
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = '库存冻结'
|
||||||
|
verbose_name_plural = '库存冻结'
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from . import models
|
from . import models
|
||||||
|
from django.db.models import Sum
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ def create_stock_snapshot(stock_change_detail: models.StockChangeDetail, invento
|
|||||||
|
|
||||||
# 必须在库存变动发生后创建快照
|
# 必须在库存变动发生后创建快照
|
||||||
snapshot = models.StockSnapshot(
|
snapshot = models.StockSnapshot(
|
||||||
|
merchant_id=stock_change_detail.merchant_id,
|
||||||
product_id=inventory.product_id,
|
product_id=inventory.product_id,
|
||||||
warehouse_id=inventory.warehouse_id,
|
warehouse_id=inventory.warehouse_id,
|
||||||
delta=delta,
|
delta=delta,
|
||||||
@@ -92,6 +94,7 @@ def make_stock_change_completed(stock_change_record: models.StockChangeRecord) -
|
|||||||
else:
|
else:
|
||||||
# 创建新库存记录
|
# 创建新库存记录
|
||||||
inventory_record = models.Inventory(
|
inventory_record = models.Inventory(
|
||||||
|
merchant_id=stock_change_record.merchant_id,
|
||||||
product_id=detail.product_id,
|
product_id=detail.product_id,
|
||||||
warehouse_id=stock_change_record.warehouse_id,
|
warehouse_id=stock_change_record.warehouse_id,
|
||||||
quantity=detail.quantity * positive,
|
quantity=detail.quantity * positive,
|
||||||
@@ -114,3 +117,13 @@ def make_stock_change_completed(stock_change_record: models.StockChangeRecord) -
|
|||||||
f'库存变动记录 {stock_change_record.id} 标记为已完成。'
|
f'库存变动记录 {stock_change_record.id} 标记为已完成。'
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_frozen_stock_count(product_id: int, warehouse_id: int) -> int:
|
||||||
|
"""获取指定产品在指定仓库的冻结库存数量"""
|
||||||
|
total_frozen = models.StockFreeze.objects.filter(
|
||||||
|
product_id=product_id,
|
||||||
|
warehouse_id=warehouse_id,
|
||||||
|
status=models.StockFreezeStatusEnum.FROZEN
|
||||||
|
).aggregate(total=Sum('quantity'))['total'] or 0
|
||||||
|
return total_frozen
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from decimal import Decimal
|
|||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from basic_info.models import Product, WareHouse, Supplier, ProductUnitEnum, ProductCategory
|
from basic_info.models import Product, WareHouse, Supplier, ProductUnitEnum, ProductCategory, Merchant, MerchantTypeEnum
|
||||||
from . import models, services
|
from . import models, services
|
||||||
|
|
||||||
|
|
||||||
@@ -18,8 +18,18 @@ class StockServicesTestCase(TestCase):
|
|||||||
cls.unit_meter = ProductUnitEnum.METER
|
cls.unit_meter = ProductUnitEnum.METER
|
||||||
cls.unit_kg = ProductUnitEnum.KG
|
cls.unit_kg = ProductUnitEnum.KG
|
||||||
|
|
||||||
|
# 创建测试商户
|
||||||
|
cls.merchant = Merchant.objects.create(
|
||||||
|
name="测试商户",
|
||||||
|
type=MerchantTypeEnum.STORE,
|
||||||
|
area="测试地区",
|
||||||
|
mobile="13800138000",
|
||||||
|
contact="测试联系人"
|
||||||
|
)
|
||||||
|
|
||||||
# 创建产品类别
|
# 创建产品类别
|
||||||
cls.fabric_category = ProductCategory.objects.create(
|
cls.fabric_category = ProductCategory.objects.create(
|
||||||
|
merchant=cls.merchant,
|
||||||
name="布料类",
|
name="布料类",
|
||||||
description="各种布料产品",
|
description="各种布料产品",
|
||||||
product_prefix="FAB"
|
product_prefix="FAB"
|
||||||
@@ -27,16 +37,19 @@ class StockServicesTestCase(TestCase):
|
|||||||
|
|
||||||
# 创建仓库
|
# 创建仓库
|
||||||
cls.warehouse_main = WareHouse.objects.create(
|
cls.warehouse_main = WareHouse.objects.create(
|
||||||
|
merchant=cls.merchant,
|
||||||
name="主仓库",
|
name="主仓库",
|
||||||
location="主仓库地址"
|
location="主仓库地址"
|
||||||
)
|
)
|
||||||
cls.warehouse_backup = WareHouse.objects.create(
|
cls.warehouse_backup = WareHouse.objects.create(
|
||||||
|
merchant=cls.merchant,
|
||||||
name="备用仓库",
|
name="备用仓库",
|
||||||
location="备用仓库地址"
|
location="备用仓库地址"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 创建供应商
|
# 创建供应商
|
||||||
cls.supplier = Supplier.objects.create(
|
cls.supplier = Supplier.objects.create(
|
||||||
|
merchant=cls.merchant,
|
||||||
name="测试供应商",
|
name="测试供应商",
|
||||||
contact="张三",
|
contact="张三",
|
||||||
mobile="13800138000"
|
mobile="13800138000"
|
||||||
@@ -44,6 +57,7 @@ class StockServicesTestCase(TestCase):
|
|||||||
|
|
||||||
# 创建产品
|
# 创建产品
|
||||||
cls.product_fabric_a = Product.objects.create(
|
cls.product_fabric_a = Product.objects.create(
|
||||||
|
merchant=cls.merchant,
|
||||||
category=cls.fabric_category,
|
category=cls.fabric_category,
|
||||||
name="布料A",
|
name="布料A",
|
||||||
human_id="FABRIC-A-001",
|
human_id="FABRIC-A-001",
|
||||||
@@ -54,6 +68,7 @@ class StockServicesTestCase(TestCase):
|
|||||||
unit=cls.unit_meter
|
unit=cls.unit_meter
|
||||||
)
|
)
|
||||||
cls.product_fabric_b = Product.objects.create(
|
cls.product_fabric_b = Product.objects.create(
|
||||||
|
merchant=cls.merchant,
|
||||||
category=cls.fabric_category,
|
category=cls.fabric_category,
|
||||||
name="布料B",
|
name="布料B",
|
||||||
human_id="FABRIC-B-001",
|
human_id="FABRIC-B-001",
|
||||||
@@ -68,6 +83,7 @@ class StockServicesTestCase(TestCase):
|
|||||||
"""每个测试方法执行前的设置"""
|
"""每个测试方法执行前的设置"""
|
||||||
# 创建采购单
|
# 创建采购单
|
||||||
self.purchase_order = models.PurchaseOrder.objects.create(
|
self.purchase_order = models.PurchaseOrder.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
supplier=self.supplier,
|
supplier=self.supplier,
|
||||||
order_date=timezone.now().date(),
|
order_date=timezone.now().date(),
|
||||||
total_amount=Decimal('1000.00'),
|
total_amount=Decimal('1000.00'),
|
||||||
@@ -76,6 +92,7 @@ class StockServicesTestCase(TestCase):
|
|||||||
|
|
||||||
# 创建库存变动记录(入库)
|
# 创建库存变动记录(入库)
|
||||||
self.stock_change_in = models.StockChangeRecord.objects.create(
|
self.stock_change_in = models.StockChangeRecord.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
type=models.StockChangeTypeEnum.ADD,
|
type=models.StockChangeTypeEnum.ADD,
|
||||||
source_type=models.StockChangeSourceEnum.PURCHASE,
|
source_type=models.StockChangeSourceEnum.PURCHASE,
|
||||||
source_id=self.purchase_order.id,
|
source_id=self.purchase_order.id,
|
||||||
@@ -85,6 +102,7 @@ class StockServicesTestCase(TestCase):
|
|||||||
|
|
||||||
# 创建库存变动记录(出库)
|
# 创建库存变动记录(出库)
|
||||||
self.stock_change_out = models.StockChangeRecord.objects.create(
|
self.stock_change_out = models.StockChangeRecord.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
type=models.StockChangeTypeEnum.REMOVE,
|
type=models.StockChangeTypeEnum.REMOVE,
|
||||||
source_type=models.StockChangeSourceEnum.SALES,
|
source_type=models.StockChangeSourceEnum.SALES,
|
||||||
source_id=1,
|
source_id=1,
|
||||||
@@ -94,6 +112,7 @@ class StockServicesTestCase(TestCase):
|
|||||||
|
|
||||||
# 创建库存变动明细
|
# 创建库存变动明细
|
||||||
self.stock_detail_in = models.StockChangeDetail.objects.create(
|
self.stock_detail_in = models.StockChangeDetail.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
stock_change_record=self.stock_change_in,
|
stock_change_record=self.stock_change_in,
|
||||||
product=self.product_fabric_a,
|
product=self.product_fabric_a,
|
||||||
quantity=Decimal('100'),
|
quantity=Decimal('100'),
|
||||||
@@ -101,6 +120,7 @@ class StockServicesTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.stock_detail_out = models.StockChangeDetail.objects.create(
|
self.stock_detail_out = models.StockChangeDetail.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
stock_change_record=self.stock_change_out,
|
stock_change_record=self.stock_change_out,
|
||||||
product=self.product_fabric_a,
|
product=self.product_fabric_a,
|
||||||
quantity=Decimal('50'),
|
quantity=Decimal('50'),
|
||||||
@@ -115,6 +135,7 @@ class FindInventoryTestCase(StockServicesTestCase):
|
|||||||
"""测试查找已存在的库存记录"""
|
"""测试查找已存在的库存记录"""
|
||||||
# 先创建一个库存记录
|
# 先创建一个库存记录
|
||||||
inventory = models.Inventory.objects.create(
|
inventory = models.Inventory.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
product=self.product_fabric_a,
|
product=self.product_fabric_a,
|
||||||
warehouse=self.warehouse_main,
|
warehouse=self.warehouse_main,
|
||||||
quantity=50,
|
quantity=50,
|
||||||
@@ -140,11 +161,11 @@ class FindInventoryTestCase(StockServicesTestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_find_inventory_wrong_warehouse(self):
|
def test_find_inventory_wrong_warehouse(self):
|
||||||
"""测试在错误的仓库中查找库存"""
|
"""测试在错误的仓库中查找库存"""
|
||||||
# 在主仓库创建库存
|
# 在主仓库创建库存
|
||||||
models.Inventory.objects.create(
|
models.Inventory.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
product=self.product_fabric_a,
|
product=self.product_fabric_a,
|
||||||
warehouse=self.warehouse_main,
|
warehouse=self.warehouse_main,
|
||||||
quantity=50,
|
quantity=50,
|
||||||
@@ -162,11 +183,11 @@ class FindInventoryTestCase(StockServicesTestCase):
|
|||||||
|
|
||||||
class CreateStockSnapshotTestCase(StockServicesTestCase):
|
class CreateStockSnapshotTestCase(StockServicesTestCase):
|
||||||
"""测试 create_stock_snapshot 函数"""
|
"""测试 create_stock_snapshot 函数"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
# 创建库存记录
|
# 创建库存记录
|
||||||
self.inventory = models.Inventory.objects.create(
|
self.inventory = models.Inventory.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
product=self.product_fabric_a,
|
product=self.product_fabric_a,
|
||||||
warehouse=self.warehouse_main,
|
warehouse=self.warehouse_main,
|
||||||
quantity=150,
|
quantity=150,
|
||||||
@@ -213,6 +234,7 @@ class CreateStockSnapshotTestCase(StockServicesTestCase):
|
|||||||
|
|
||||||
# 测试未保存的明细记录
|
# 测试未保存的明细记录
|
||||||
unsaved_detail = models.StockChangeDetail(
|
unsaved_detail = models.StockChangeDetail(
|
||||||
|
merchant=self.merchant,
|
||||||
stock_change_record=self.stock_change_in,
|
stock_change_record=self.stock_change_in,
|
||||||
product=self.product_fabric_a,
|
product=self.product_fabric_a,
|
||||||
quantity=50
|
quantity=50
|
||||||
@@ -231,6 +253,7 @@ class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
|||||||
|
|
||||||
# 测试未保存的记录
|
# 测试未保存的记录
|
||||||
unsaved_record = models.StockChangeRecord(
|
unsaved_record = models.StockChangeRecord(
|
||||||
|
merchant=self.merchant,
|
||||||
type=models.StockChangeTypeEnum.ADD,
|
type=models.StockChangeTypeEnum.ADD,
|
||||||
warehouse=self.warehouse_main
|
warehouse=self.warehouse_main
|
||||||
)
|
)
|
||||||
@@ -272,12 +295,12 @@ class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
|||||||
product_id=self.product_fabric_a.id,
|
product_id=self.product_fabric_a.id,
|
||||||
warehouse_id=self.warehouse_main.id
|
warehouse_id=self.warehouse_main.id
|
||||||
)
|
)
|
||||||
mock_snapshot.assert_called_once()
|
|
||||||
|
|
||||||
def test_complete_inbound_record_existing_inventory(self):
|
def test_complete_inbound_record_existing_inventory(self):
|
||||||
"""测试完成入库记录(更新现有库存)"""
|
"""测试完成入库记录(更新现有库存)"""
|
||||||
# 创建现有库存
|
# 创建现有库存
|
||||||
existing_inventory = models.Inventory.objects.create(
|
existing_inventory = models.Inventory.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
product=self.product_fabric_a,
|
product=self.product_fabric_a,
|
||||||
warehouse=self.warehouse_main,
|
warehouse=self.warehouse_main,
|
||||||
quantity=50,
|
quantity=50,
|
||||||
@@ -304,6 +327,7 @@ class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
|||||||
"""测试完成出库记录"""
|
"""测试完成出库记录"""
|
||||||
# 创建现有库存
|
# 创建现有库存
|
||||||
existing_inventory = models.Inventory.objects.create(
|
existing_inventory = models.Inventory.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
product=self.product_fabric_a,
|
product=self.product_fabric_a,
|
||||||
warehouse=self.warehouse_main,
|
warehouse=self.warehouse_main,
|
||||||
quantity=100,
|
quantity=100,
|
||||||
@@ -326,6 +350,7 @@ class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
|||||||
"""测试完成包含多个明细的记录"""
|
"""测试完成包含多个明细的记录"""
|
||||||
# 添加另一个明细
|
# 添加另一个明细
|
||||||
models.StockChangeDetail.objects.create(
|
models.StockChangeDetail.objects.create(
|
||||||
|
merchant=self.merchant,
|
||||||
stock_change_record=self.stock_change_in,
|
stock_change_record=self.stock_change_in,
|
||||||
product=self.product_fabric_b,
|
product=self.product_fabric_b,
|
||||||
quantity=Decimal('200'),
|
quantity=Decimal('200'),
|
||||||
|
|||||||
17
uv.lock
generated
17
uv.lock
generated
@@ -78,10 +78,17 @@ wheels = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-cors"
|
name = "django-cors-headers"
|
||||||
version = "0.1"
|
version = "4.9.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/94/59b5353380df1aacd28e1efc1e77ce9925f65ef9adab6c44238daa9cefa3/django-cors-0.1.tar.gz", hash = "sha256:ba31c9455ddb848e578b26bac3634049951149775b818a35476d2b50350640c3", size = 1066, upload-time = "2012-12-04T02:52:05.092Z" }
|
dependencies = [
|
||||||
|
{ name = "asgiref" },
|
||||||
|
{ name = "django" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/21/39/55822b15b7ec87410f34cd16ce04065ff390e50f9e29f31d6d116fc80456/django_cors_headers-4.9.0.tar.gz", hash = "sha256:fe5d7cb59fdc2c8c646ce84b727ac2bca8912a247e6e68e1fb507372178e59e8", size = 21458, upload-time = "2025-09-18T10:40:52.326Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/d8/19ed1e47badf477d17fb177c1c19b5a21da0fd2d9f093f23be3fb86c5fab/django_cors_headers-4.9.0-py3-none-any.whl", hash = "sha256:15c7f20727f90044dcee2216a9fd7303741a864865f0c3657e28b7056f61b449", size = 12809, upload-time = "2025-09-18T10:40:50.843Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-environ"
|
name = "django-environ"
|
||||||
@@ -186,7 +193,7 @@ version = "0.1.0"
|
|||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "django" },
|
{ name = "django" },
|
||||||
{ name = "django-cors" },
|
{ name = "django-cors-headers" },
|
||||||
{ name = "django-environ" },
|
{ name = "django-environ" },
|
||||||
{ name = "django-filter" },
|
{ name = "django-filter" },
|
||||||
{ name = "django-ninja" },
|
{ name = "django-ninja" },
|
||||||
@@ -207,7 +214,7 @@ dev = [
|
|||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "django", specifier = ">=5.2.7" },
|
{ name = "django", specifier = ">=5.2.7" },
|
||||||
{ name = "django-cors", specifier = ">=0.1" },
|
{ name = "django-cors-headers", specifier = ">=4.9.0" },
|
||||||
{ name = "django-environ", specifier = ">=0.12.0" },
|
{ name = "django-environ", specifier = ">=0.12.0" },
|
||||||
{ name = "django-filter", specifier = ">=25.2" },
|
{ name = "django-filter", specifier = ">=25.2" },
|
||||||
{ name = "django-ninja", specifier = ">=1.4.5" },
|
{ name = "django-ninja", specifier = ">=1.4.5" },
|
||||||
|
|||||||
Reference in New Issue
Block a user