forked from erp-dev/erp
feat: auto complete stock change
This commit is contained in:
@@ -4,6 +4,14 @@ from django.utils.safestring import mark_safe
|
||||
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)
|
||||
class PurchaseOrderAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'supplier', 'order_date', 'total_amount')
|
||||
@@ -13,7 +21,7 @@ class PurchaseOrderAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(models.Inventory)
|
||||
class InventoryAdmin(admin.ModelAdmin):
|
||||
class InventoryAdmin(StockAdminBase):
|
||||
list_display = (
|
||||
'id',
|
||||
'product',
|
||||
@@ -22,12 +30,20 @@ class InventoryAdmin(admin.ModelAdmin):
|
||||
'quantity',
|
||||
'num_of_rolls',
|
||||
'minimum_quantity',
|
||||
'frozen_quantity',
|
||||
'updated_at',
|
||||
)
|
||||
search_fields = ('product__name', 'warehouse__name')
|
||||
list_filter = ('warehouse',)
|
||||
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='颜色')
|
||||
def product_color(self, obj):
|
||||
return obj.product.color
|
||||
@@ -45,7 +61,7 @@ class StockSnapshotAdmin(admin.ModelAdmin):
|
||||
'stock_change_record',
|
||||
'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',)
|
||||
ordering = ('-created_at', )
|
||||
|
||||
@@ -73,12 +89,17 @@ class StockChangeDetailAdmin(admin.ModelAdmin):
|
||||
'stock_change_record',
|
||||
'product',
|
||||
'quantity',
|
||||
'direction',
|
||||
'unit',
|
||||
)
|
||||
search_fields = ('stock_change_record__id', 'product__name')
|
||||
list_filter = ('stock_change_record', 'stock_change_record__type')
|
||||
ordering = ('-created_at',)
|
||||
|
||||
@admin.display(description='出入方向')
|
||||
def direction(self, obj):
|
||||
return '入库' if obj.stock_change_record.is_incoming else '出库'
|
||||
|
||||
|
||||
@admin.register(models.StockChangeRecord)
|
||||
class StockChangeRecordAdmin(admin.ModelAdmin):
|
||||
@@ -131,4 +152,29 @@ class StockChangeRecordAdmin(admin.ModelAdmin):
|
||||
]
|
||||
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
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -9,30 +10,111 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('basic_info', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProductCategory',
|
||||
name='PurchaseOrder',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='类别名称')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='类别描述')),
|
||||
('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='备注')),
|
||||
('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(
|
||||
name='Product',
|
||||
name='StockChangeRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='产品名')),
|
||||
('human_id', models.CharField(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(decimal_places=2, max_digits=10, verbose_name='单价')),
|
||||
('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='变动来源')),
|
||||
('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='规格')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||
('unit', models.CharField(default='米', max_length=20, verbose_name='单位')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='stock.productcategory', verbose_name='产品类别')),
|
||||
('merchant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='inventories', to='basic_info.merchant', 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,27 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-03 05:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0009_alter_product_single_price'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ProdictUnit',
|
||||
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=20, verbose_name='单位名称')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='备注描述')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '产品单位',
|
||||
'verbose_name_plural': '产品单位',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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):
|
||||
"""库存变动来源类型"""
|
||||
|
||||
@@ -35,6 +42,12 @@ class PurchaseOrder(ModelBase):
|
||||
"""采购单模型"""
|
||||
|
||||
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='供应商')
|
||||
order_date = models.DateField(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)
|
||||
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='变动类型')
|
||||
warehouse = models.ForeignKey(
|
||||
basic_info_models.WareHouse,
|
||||
@@ -127,6 +146,12 @@ class StockChangeDetail(ModelBase):
|
||||
"""库存变动明细模型"""
|
||||
|
||||
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(
|
||||
basic_info_models.Product,
|
||||
on_delete=models.PROTECT,
|
||||
@@ -161,6 +186,12 @@ class Inventory(ModelBase):
|
||||
"""库存模型"""
|
||||
|
||||
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(
|
||||
basic_info_models.Product,
|
||||
on_delete=models.PROTECT,
|
||||
@@ -192,6 +223,12 @@ class StockSnapshot(ModelBase):
|
||||
"""库存变动快照模型"""
|
||||
|
||||
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(
|
||||
basic_info_models.Product,
|
||||
on_delete=models.PROTECT,
|
||||
@@ -252,3 +289,76 @@ class StockSnapshot(ModelBase):
|
||||
class Meta:
|
||||
verbose_name = '库存快照'
|
||||
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 django.db.models import Sum
|
||||
from django.utils import timezone
|
||||
import logging
|
||||
|
||||
@@ -30,6 +31,7 @@ def create_stock_snapshot(stock_change_detail: models.StockChangeDetail, invento
|
||||
|
||||
# 必须在库存变动发生后创建快照
|
||||
snapshot = models.StockSnapshot(
|
||||
merchant_id=stock_change_detail.merchant_id,
|
||||
product_id=inventory.product_id,
|
||||
warehouse_id=inventory.warehouse_id,
|
||||
delta=delta,
|
||||
@@ -92,6 +94,7 @@ def make_stock_change_completed(stock_change_record: models.StockChangeRecord) -
|
||||
else:
|
||||
# 创建新库存记录
|
||||
inventory_record = models.Inventory(
|
||||
merchant_id=stock_change_record.merchant_id,
|
||||
product_id=detail.product_id,
|
||||
warehouse_id=stock_change_record.warehouse_id,
|
||||
quantity=detail.quantity * positive,
|
||||
@@ -114,3 +117,13 @@ def make_stock_change_completed(stock_change_record: models.StockChangeRecord) -
|
||||
f'库存变动记录 {stock_change_record.id} 标记为已完成。'
|
||||
)
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
@@ -18,8 +18,18 @@ class StockServicesTestCase(TestCase):
|
||||
cls.unit_meter = ProductUnitEnum.METER
|
||||
cls.unit_kg = ProductUnitEnum.KG
|
||||
|
||||
# 创建测试商户
|
||||
cls.merchant = Merchant.objects.create(
|
||||
name="测试商户",
|
||||
type=MerchantTypeEnum.STORE,
|
||||
area="测试地区",
|
||||
mobile="13800138000",
|
||||
contact="测试联系人"
|
||||
)
|
||||
|
||||
# 创建产品类别
|
||||
cls.fabric_category = ProductCategory.objects.create(
|
||||
merchant=cls.merchant,
|
||||
name="布料类",
|
||||
description="各种布料产品",
|
||||
product_prefix="FAB"
|
||||
@@ -27,16 +37,19 @@ class StockServicesTestCase(TestCase):
|
||||
|
||||
# 创建仓库
|
||||
cls.warehouse_main = WareHouse.objects.create(
|
||||
merchant=cls.merchant,
|
||||
name="主仓库",
|
||||
location="主仓库地址"
|
||||
)
|
||||
cls.warehouse_backup = WareHouse.objects.create(
|
||||
merchant=cls.merchant,
|
||||
name="备用仓库",
|
||||
location="备用仓库地址"
|
||||
)
|
||||
|
||||
# 创建供应商
|
||||
cls.supplier = Supplier.objects.create(
|
||||
merchant=cls.merchant,
|
||||
name="测试供应商",
|
||||
contact="张三",
|
||||
mobile="13800138000"
|
||||
@@ -44,6 +57,7 @@ class StockServicesTestCase(TestCase):
|
||||
|
||||
# 创建产品
|
||||
cls.product_fabric_a = Product.objects.create(
|
||||
merchant=cls.merchant,
|
||||
category=cls.fabric_category,
|
||||
name="布料A",
|
||||
human_id="FABRIC-A-001",
|
||||
@@ -54,6 +68,7 @@ class StockServicesTestCase(TestCase):
|
||||
unit=cls.unit_meter
|
||||
)
|
||||
cls.product_fabric_b = Product.objects.create(
|
||||
merchant=cls.merchant,
|
||||
category=cls.fabric_category,
|
||||
name="布料B",
|
||||
human_id="FABRIC-B-001",
|
||||
@@ -68,6 +83,7 @@ class StockServicesTestCase(TestCase):
|
||||
"""每个测试方法执行前的设置"""
|
||||
# 创建采购单
|
||||
self.purchase_order = models.PurchaseOrder.objects.create(
|
||||
merchant=self.merchant,
|
||||
supplier=self.supplier,
|
||||
order_date=timezone.now().date(),
|
||||
total_amount=Decimal('1000.00'),
|
||||
@@ -76,6 +92,7 @@ class StockServicesTestCase(TestCase):
|
||||
|
||||
# 创建库存变动记录(入库)
|
||||
self.stock_change_in = models.StockChangeRecord.objects.create(
|
||||
merchant=self.merchant,
|
||||
type=models.StockChangeTypeEnum.ADD,
|
||||
source_type=models.StockChangeSourceEnum.PURCHASE,
|
||||
source_id=self.purchase_order.id,
|
||||
@@ -85,6 +102,7 @@ class StockServicesTestCase(TestCase):
|
||||
|
||||
# 创建库存变动记录(出库)
|
||||
self.stock_change_out = models.StockChangeRecord.objects.create(
|
||||
merchant=self.merchant,
|
||||
type=models.StockChangeTypeEnum.REMOVE,
|
||||
source_type=models.StockChangeSourceEnum.SALES,
|
||||
source_id=1,
|
||||
@@ -94,6 +112,7 @@ class StockServicesTestCase(TestCase):
|
||||
|
||||
# 创建库存变动明细
|
||||
self.stock_detail_in = models.StockChangeDetail.objects.create(
|
||||
merchant=self.merchant,
|
||||
stock_change_record=self.stock_change_in,
|
||||
product=self.product_fabric_a,
|
||||
quantity=Decimal('100'),
|
||||
@@ -101,6 +120,7 @@ class StockServicesTestCase(TestCase):
|
||||
)
|
||||
|
||||
self.stock_detail_out = models.StockChangeDetail.objects.create(
|
||||
merchant=self.merchant,
|
||||
stock_change_record=self.stock_change_out,
|
||||
product=self.product_fabric_a,
|
||||
quantity=Decimal('50'),
|
||||
@@ -115,6 +135,7 @@ class FindInventoryTestCase(StockServicesTestCase):
|
||||
"""测试查找已存在的库存记录"""
|
||||
# 先创建一个库存记录
|
||||
inventory = models.Inventory.objects.create(
|
||||
merchant=self.merchant,
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=50,
|
||||
@@ -140,11 +161,11 @@ class FindInventoryTestCase(StockServicesTestCase):
|
||||
)
|
||||
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_find_inventory_wrong_warehouse(self):
|
||||
"""测试在错误的仓库中查找库存"""
|
||||
# 在主仓库创建库存
|
||||
models.Inventory.objects.create(
|
||||
merchant=self.merchant,
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=50,
|
||||
@@ -162,11 +183,11 @@ class FindInventoryTestCase(StockServicesTestCase):
|
||||
|
||||
class CreateStockSnapshotTestCase(StockServicesTestCase):
|
||||
"""测试 create_stock_snapshot 函数"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# 创建库存记录
|
||||
self.inventory = models.Inventory.objects.create(
|
||||
merchant=self.merchant,
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=150,
|
||||
@@ -213,6 +234,7 @@ class CreateStockSnapshotTestCase(StockServicesTestCase):
|
||||
|
||||
# 测试未保存的明细记录
|
||||
unsaved_detail = models.StockChangeDetail(
|
||||
merchant=self.merchant,
|
||||
stock_change_record=self.stock_change_in,
|
||||
product=self.product_fabric_a,
|
||||
quantity=50
|
||||
@@ -231,6 +253,7 @@ class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
||||
|
||||
# 测试未保存的记录
|
||||
unsaved_record = models.StockChangeRecord(
|
||||
merchant=self.merchant,
|
||||
type=models.StockChangeTypeEnum.ADD,
|
||||
warehouse=self.warehouse_main
|
||||
)
|
||||
@@ -272,12 +295,12 @@ class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
||||
product_id=self.product_fabric_a.id,
|
||||
warehouse_id=self.warehouse_main.id
|
||||
)
|
||||
mock_snapshot.assert_called_once()
|
||||
|
||||
def test_complete_inbound_record_existing_inventory(self):
|
||||
"""测试完成入库记录(更新现有库存)"""
|
||||
# 创建现有库存
|
||||
existing_inventory = models.Inventory.objects.create(
|
||||
merchant=self.merchant,
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=50,
|
||||
@@ -304,6 +327,7 @@ class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
||||
"""测试完成出库记录"""
|
||||
# 创建现有库存
|
||||
existing_inventory = models.Inventory.objects.create(
|
||||
merchant=self.merchant,
|
||||
product=self.product_fabric_a,
|
||||
warehouse=self.warehouse_main,
|
||||
quantity=100,
|
||||
@@ -326,6 +350,7 @@ class MakeStockChangeCompletedTestCase(StockServicesTestCase):
|
||||
"""测试完成包含多个明细的记录"""
|
||||
# 添加另一个明细
|
||||
models.StockChangeDetail.objects.create(
|
||||
merchant=self.merchant,
|
||||
stock_change_record=self.stock_change_in,
|
||||
product=self.product_fabric_b,
|
||||
quantity=Decimal('200'),
|
||||
|
||||
Reference in New Issue
Block a user