forked from erp-dev/erp
feat: purchase order item model
This commit is contained in:
@@ -5,7 +5,33 @@ from . import models
|
|||||||
|
|
||||||
@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', 'purchase_date', 'operator', 'created_at')
|
||||||
search_fields = ('supplier__name',)
|
search_fields = ('supplier__name',)
|
||||||
list_filter = ('order_date',)
|
list_filter = ('operator', 'warehouse', 'created_at')
|
||||||
ordering = ('-order_date',)
|
ordering = ('-created_at',)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.PurchaseOrderItem)
|
||||||
|
class PurchaseOrderItemAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'id', 'purchase_order', 'product',
|
||||||
|
'quantity', 'unit', 'price', 'batch_number',
|
||||||
|
'_total_amount', 'empty_diff_percent',
|
||||||
|
'_real_quantity', '_diff_quantity',
|
||||||
|
'num_of_rolls',
|
||||||
|
)
|
||||||
|
search_fields = ('purchase_order__id', 'product__name')
|
||||||
|
list_filter = ('purchase_order__operator', 'purchase_order__warehouse', 'created_at')
|
||||||
|
ordering = ('-created_at',)
|
||||||
|
|
||||||
|
@admin.display(description='实际数量')
|
||||||
|
def _real_quantity(self, obj: models.PurchaseOrderItem):
|
||||||
|
return obj.real_quantity()
|
||||||
|
|
||||||
|
@admin.display(description='差异数量')
|
||||||
|
def _diff_quantity(self, obj: models.PurchaseOrderItem):
|
||||||
|
return obj.diff_quantity()
|
||||||
|
|
||||||
|
@admin.display(description='总金额')
|
||||||
|
def _total_amount(self, obj: models.PurchaseOrderItem):
|
||||||
|
return obj.total_amount()
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-27 07:29
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0013_alter_customer_options'),
|
||||||
|
('business', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='order_date',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='kind',
|
||||||
|
field=models.IntegerField(choices=[(1, '大货'), (2, '样板')], default=1, verbose_name='采购单类型'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='operator',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='basic_info.employee', verbose_name='经办人'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='purchase_date',
|
||||||
|
field=models.DateField(default=datetime.datetime(2025, 11, 27, 15, 29, 17, 309981), verbose_name='采购日期'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
18
business/migrations/0003_purchaseorder_status.py
Normal file
18
business/migrations/0003_purchaseorder_status.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-27 07:34
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('business', '0002_remove_purchaseorder_order_date_purchaseorder_kind_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='status',
|
||||||
|
field=models.IntegerField(choices=[(1, '审批中'), (2, '审批通过'), (3, '作废')], default=1, verbose_name='状态'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-27 08:08
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0013_alter_customer_options'),
|
||||||
|
('business', '0003_purchaseorder_status'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='total_amount',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='warehouse',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='basic_info.warehouse', verbose_name='仓库'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PurchaseOrderItem',
|
||||||
|
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)),
|
||||||
|
('price', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='单价')),
|
||||||
|
('color', models.CharField(blank=True, max_length=50, null=True, verbose_name='颜色')),
|
||||||
|
('quantity', models.PositiveIntegerField(verbose_name='数量')),
|
||||||
|
('unit', models.CharField(max_length=50, verbose_name='单位')),
|
||||||
|
('empty_diff_percent', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='空差百分比')),
|
||||||
|
('quantity_of_rolls', models.CharField(blank=True, max_length=255, null=True, verbose_name='各条数数量')),
|
||||||
|
('num_of_rolls', models.PositiveIntegerField(default=1, verbose_name='条数')),
|
||||||
|
('batch_number', models.PositiveIntegerField(default=1, verbose_name='批次号')),
|
||||||
|
('total_amount', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='总金额')),
|
||||||
|
('remarks', models.TextField(blank=True, null=True, verbose_name='备注')),
|
||||||
|
('product', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='purchase_order_items', to='basic_info.product', verbose_name='产品')),
|
||||||
|
('purchase_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='business.purchaseorder', verbose_name='采购单')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': '采购单明细',
|
||||||
|
'verbose_name_plural': '采购单明细',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-27 08:10
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('basic_info', '0013_alter_customer_options'),
|
||||||
|
('business', '0004_remove_purchaseorder_total_amount_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='operator',
|
||||||
|
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='basic_info.employee', verbose_name='经办人'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='purchaseorder',
|
||||||
|
name='warehouse',
|
||||||
|
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.PROTECT, related_name='purchase_orders', to='basic_info.warehouse', verbose_name='仓库'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-27 08:18
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('business', '0005_alter_purchaseorder_operator_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='purchaseorderitem',
|
||||||
|
name='total_amount',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-27 08:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('business', '0006_remove_purchaseorderitem_total_amount'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='purchaseorderitem',
|
||||||
|
name='batch_number',
|
||||||
|
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='批次号'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -3,6 +3,19 @@ from flower.common import ModelBase
|
|||||||
from basic_info import models as basic_info_models
|
from basic_info import models as basic_info_models
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderKindEnum(models.IntegerChoices):
|
||||||
|
"""采购单类型"""
|
||||||
|
WHOLESALE = 1, '大货'
|
||||||
|
SAMPLE = 2, '样板'
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderStatusEnum(models.IntegerChoices):
|
||||||
|
"""采购单状态"""
|
||||||
|
PENDING = 1, '审批中'
|
||||||
|
APPROVED = 2, '审批通过'
|
||||||
|
CANCELLED = 3, '作废'
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrder(ModelBase):
|
class PurchaseOrder(ModelBase):
|
||||||
"""采购单模型(业务模块)"""
|
"""采购单模型(业务模块)"""
|
||||||
|
|
||||||
@@ -19,8 +32,29 @@ class PurchaseOrder(ModelBase):
|
|||||||
related_name='purchase_orders',
|
related_name='purchase_orders',
|
||||||
verbose_name='供应商',
|
verbose_name='供应商',
|
||||||
)
|
)
|
||||||
order_date = models.DateField(verbose_name='订单日期')
|
purchase_date = models.DateField(verbose_name='采购日期')
|
||||||
total_amount = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='总金额')
|
kind = models.IntegerField(
|
||||||
|
choices=PurchaseOrderKindEnum.choices,
|
||||||
|
default=PurchaseOrderKindEnum.WHOLESALE,
|
||||||
|
verbose_name='采购单类型',
|
||||||
|
)
|
||||||
|
operator = models.ForeignKey(
|
||||||
|
basic_info_models.Employee,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='purchase_orders',
|
||||||
|
verbose_name='经办人',
|
||||||
|
)
|
||||||
|
warehouse = models.ForeignKey(
|
||||||
|
basic_info_models.WareHouse,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='purchase_orders',
|
||||||
|
verbose_name='仓库',
|
||||||
|
)
|
||||||
|
status = models.IntegerField(
|
||||||
|
choices=PurchaseOrderStatusEnum.choices,
|
||||||
|
default=PurchaseOrderStatusEnum.PENDING,
|
||||||
|
verbose_name='状态',
|
||||||
|
)
|
||||||
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
|
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@@ -29,3 +63,53 @@ class PurchaseOrder(ModelBase):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = '采购单'
|
verbose_name = '采购单'
|
||||||
verbose_name_plural = '采购单'
|
verbose_name_plural = '采购单'
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseOrderItem(ModelBase):
|
||||||
|
"""采购单明细模型"""
|
||||||
|
|
||||||
|
id = models.BigAutoField(primary_key=True)
|
||||||
|
purchase_order = models.ForeignKey(
|
||||||
|
PurchaseOrder,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='items',
|
||||||
|
verbose_name='采购单',
|
||||||
|
)
|
||||||
|
product = models.ForeignKey(
|
||||||
|
basic_info_models.Product,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
related_name='purchase_order_items',
|
||||||
|
verbose_name='产品',
|
||||||
|
)
|
||||||
|
price = models.DecimalField(max_digits=15, decimal_places=2, verbose_name='单价')
|
||||||
|
color = models.CharField(max_length=50, null=True, blank=True, verbose_name='颜色')
|
||||||
|
quantity = models.PositiveIntegerField(verbose_name='数量')
|
||||||
|
unit = models.CharField(max_length=50, verbose_name='单位')
|
||||||
|
empty_diff_percent = models.DecimalField(
|
||||||
|
max_digits=15,
|
||||||
|
decimal_places=2,
|
||||||
|
verbose_name='空差百分比',
|
||||||
|
)
|
||||||
|
# if purchase_order.warehouse.mode != WarehouseModeEnum.UNRESTRICTED, then quantity_of_rolls is required
|
||||||
|
quantity_of_rolls = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name='各条数数量',
|
||||||
|
)
|
||||||
|
num_of_rolls = models.PositiveIntegerField(default=1, verbose_name='条数')
|
||||||
|
batch_number = models.CharField(null=True, blank=True, max_length=100, verbose_name='批次号')
|
||||||
|
remarks = models.TextField(blank=True, null=True, verbose_name='备注')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = '采购单明细'
|
||||||
|
verbose_name_plural = '采购单明细'
|
||||||
|
|
||||||
|
def real_quantity(self):
|
||||||
|
return round(self.quantity * (1 - self.empty_diff_percent / 100), 2)
|
||||||
|
|
||||||
|
def diff_quantity(self):
|
||||||
|
return round(self.quantity * (self.empty_diff_percent / 100), 2)
|
||||||
|
|
||||||
|
def total_amount(self):
|
||||||
|
return round(self.price * self.real_quantity(), 2)
|
||||||
|
|||||||
18
printing/migrations/0017_alter_plateorder_image_name.py
Normal file
18
printing/migrations/0017_alter_plateorder_image_name.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-27 07:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('printing', '0016_plateorder_image_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='plateorder',
|
||||||
|
name='image_name',
|
||||||
|
field=models.TextField(blank=True, null=True, verbose_name='图片名称'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -16,7 +16,7 @@ class PlateOrder(ModelBase):
|
|||||||
plate_date = models.DateTimeField(null=True, blank=True, verbose_name='下版时间')
|
plate_date = models.DateTimeField(null=True, blank=True, verbose_name='下版时间')
|
||||||
plate_method = models.CharField(max_length=50, blank=True, null=True, verbose_name='开版方式')
|
plate_method = models.CharField(max_length=50, blank=True, null=True, verbose_name='开版方式')
|
||||||
plate_image = models.FileField(upload_to='plate_images/', null=True, blank=True, verbose_name='开版图')
|
plate_image = models.FileField(upload_to='plate_images/', null=True, blank=True, verbose_name='开版图')
|
||||||
image_name = models.CharField(max_length=100, blank=True, null=True, verbose_name='图片名称')
|
image_name = models.TextField(blank=True, null=True, verbose_name='图片名称')
|
||||||
plate_notes = models.TextField(blank=True, null=True, verbose_name='打版注意事项')
|
plate_notes = models.TextField(blank=True, null=True, verbose_name='打版注意事项')
|
||||||
reprint_reason = models.TextField(blank=True, null=True, verbose_name='复版原因')
|
reprint_reason = models.TextField(blank=True, null=True, verbose_name='复版原因')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user