销售发货涉及

This commit is contained in:
caoqianming 2022-02-22 09:45:10 +08:00
parent d1a8be167c
commit 1d7aa9c279
12 changed files with 252 additions and 55 deletions

View File

@ -28,6 +28,5 @@ class IProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
update_time_end = filters.DateFilter(field_name="update_time", lookup_expr='lte')
class Meta:
model = IProduct
fields = ['material', 'warehouse', 'batch', 'order', 'material__type',
'is_saled', 'update_time_start', 'update_time_end',
fields = ['material', 'warehouse', 'batch', 'order', 'material__type', 'update_time_start', 'update_time_end',
'to_order', 'need_to_order']

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.9 on 2022-02-22 01:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sam', '0013_auto_20220222_0941'),
('inm', '0031_fifoitem_pu_order_item'),
]
operations = [
migrations.RemoveField(
model_name='iproduct',
name='is_saled',
),
migrations.AddField(
model_name='fifo',
name='sale',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sam.sale', verbose_name='关联销售记录'),
),
migrations.AddField(
model_name='iproduct',
name='state',
field=models.IntegerField(choices=[(10, '可销售'), (20, '已锁定'), (30, '已售出')], default=10, verbose_name='状态'),
),
]

View File

@ -3,6 +3,7 @@ from django.db.models.base import Model
import django.utils.timezone as timezone
from django.db.models.query import QuerySet
from apps.pum.models import PuOrder, PuOrderItem, Vendor
from apps.sam.models import Sale
from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File
from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords
@ -86,6 +87,8 @@ class FIFO(CommonADModel):
on_delete=models.CASCADE, null=True, blank=True)
pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单',
null=True, blank=True, on_delete=models.CASCADE)
sale = models.ForeignKey(Sale, verbose_name='关联销售记录',
null=True, blank=True, on_delete=models.CASCADE)
class FIFOItem(BaseModel):
@ -113,6 +116,14 @@ class IProduct(BaseModel):
"""
具体产品条目
"""
SALE_OK = 10
SALE_LOCK = 20
SALED = 30
state_choices = (
(SALE_OK, '可销售'),
(SALE_LOCK, '已锁定'),
(SALED, '已售出')
)
number = models.CharField('物品编号', unique=True, max_length=50)
material = models.ForeignKey(
Material, verbose_name='物料类型', on_delete=models.CASCADE)
@ -121,7 +132,7 @@ class IProduct(BaseModel):
batch = models.CharField('所属批次号', max_length=100, default='')
wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False,
null=True, blank=True, related_name='iproduct_wproduct')
is_saled = models.BooleanField('是否售出', default=False)
state = models.IntegerField('状态', default=SALE_OK, choices=state_choices)
class FIFOItemProduct(BaseModel):
@ -139,4 +150,4 @@ class FIFOItemProduct(BaseModel):
Material, verbose_name='物料类型', on_delete=models.CASCADE)
iproduct = models.ForeignKey(
IProduct, verbose_name='关联库存产品',
null=True, blank=True, on_delete=models.SET_NULL)
null=True, blank=True, on_delete=models.SET_NULL)

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.9 on 2022-02-21 06:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mtm', '0047_packitem'),
]
operations = [
migrations.RemoveField(
model_name='step',
name='need_test',
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.9 on 2022-02-22 01:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mtm', '0048_remove_step_need_test'),
]
operations = [
migrations.AddField(
model_name='packitem',
name='product',
field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, related_name='pack_product', to='mtm.material', verbose_name='装箱产品'),
preserve_default=False,
),
migrations.AlterField(
model_name='packitem',
name='material',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pack_material', to='mtm.material', verbose_name='装箱配件'),
),
]

View File

@ -23,7 +23,7 @@ class Material(CommonAModel):
(MA_TYPE_GOOD, '成品'),
(MA_TYPE_HALFGOOD, '半成品'),
(MA_TYPE_MAINSO, '主要原料'),
(MA_TYPE_HELPSO, '辅助材料') ,
(MA_TYPE_HELPSO, '辅助材料'),
(MA_TYPE_TOOL, '加工工具'),
(MA_TYPE_HELPTOOL, '辅助工装')
)
@ -54,8 +54,10 @@ class PackItem(CommonAModel):
"""
装箱项目
"""
material = models.ForeignKey(Material, verbose_name='关联成品',
on_delete=models.CASCADE)
product = models.ForeignKey(Material, verbose_name='装箱产品',
on_delete=models.CASCADE, related_name='pack_product')
material = models.ForeignKey(Material, verbose_name='装箱配件',
on_delete=models.CASCADE, null=True, blank=True, related_name='pack_material')
name = models.CharField('名称', max_length=100)
specification = models.CharField('型号', max_length=100, null=True, blank=True)
unit = models.CharField('单位', max_length=10)

View File

@ -32,7 +32,7 @@ class PackItemSerializer(serializers.ModelSerializer):
class PackItemCreateSerializer(serializers.ModelSerializer):
class Meta:
model = PackItem
fields = ['material', 'name', 'specification', 'unit', 'count', 'sort']
fields = ['create', 'material', 'name', 'specification', 'unit', 'count', 'sort']
class PackItemUpdateSerializer(serializers.ModelSerializer):
class Meta:

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2022-02-21 07:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qm', '0025_alter_testrecord_type'),
]
operations = [
migrations.AlterField(
model_name='testrecord',
name='is_testok',
field=models.BooleanField(blank=True, null=True, verbose_name='是否合格'),
),
]

View File

@ -0,0 +1,53 @@
# Generated by Django 3.2.9 on 2022-02-22 01:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sam', '0012_alter_order_delivery_date'),
]
operations = [
migrations.AddField(
model_name='sale',
name='count_real',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='实际发货数量'),
),
migrations.AddField(
model_name='sale',
name='edelivery_date',
field=models.DateField(blank=True, null=True, verbose_name='预计发货日期'),
),
migrations.AddField(
model_name='sale',
name='receiver',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='收货人'),
),
migrations.AddField(
model_name='sale',
name='receiver_address',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='收获地址'),
),
migrations.AddField(
model_name='sale',
name='receiver_phone',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='收货人联系电话'),
),
migrations.AddField(
model_name='sale',
name='remark',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='备注'),
),
migrations.AddField(
model_name='sale',
name='trans_mode',
field=models.IntegerField(blank=True, choices=[(10, '铁路快运'), (20, '铁路慢件'), (30, '铁路整车'), (40, '汽车运输'), (50, '空运')], null=True, verbose_name='运输方式'),
),
migrations.AlterField(
model_name='sale',
name='count',
field=models.PositiveIntegerField(default=0, verbose_name='预计发货数量'),
),
]

View File

@ -69,11 +69,30 @@ class Sale(CommonADModel):
"""
销售记录
"""
TRANS_RAIL_FAST = 10
TRANS_RAIL_SLOW = 20
TRANS_RAIL_WHOLE = 30
TRANS_LORRY = 40
TRANS_AIR = 50
trans_choices=(
(TRANS_RAIL_FAST, '铁路快运'),
(TRANS_RAIL_SLOW, '铁路慢件'),
(TRANS_RAIL_WHOLE, '铁路整车'),
(TRANS_LORRY, '汽车运输'),
(TRANS_AIR, '空运'),
)
customer = models.ForeignKey(Customer, verbose_name='客户', on_delete=models.CASCADE)
order = models.ForeignKey(Order, verbose_name='关联订单', on_delete=models.CASCADE, null=True, blank=True)
product = models.ForeignKey(Material, verbose_name='所需产品', on_delete=models.CASCADE)
count = models.PositiveIntegerField('交货数量', default=0)
count = models.PositiveIntegerField('预计发货数量', default=0)
count_real = models.PositiveIntegerField('实际发货数量', null=True, blank=True)
is_audited = models.BooleanField('是否审核', default=False)
edelivery_date = models.DateField('预计发货日期', null=True, blank=True)
trans_mode = models.IntegerField('运输方式', null=True, blank=True, choices=trans_choices)
receiver = models.CharField('收货人', null=True, blank=True, max_length=200)
receiver_phone = models.CharField('收货人联系电话', null=True, blank=True, max_length=20)
receiver_address = models.CharField('收获地址', null=True, blank=True, max_length=200)
remark = models.CharField('备注', null=True, blank=True, max_length=200)
class SaleProduct(BaseModel):

View File

@ -5,12 +5,16 @@ from apps.inm.serializers import IProductListSerializer
from apps.mtm.serializers import MaterialSimpleSerializer
from apps.sam.models import Sale, SaleProduct
from apps.sam.serializers import CustomerSimpleSerializer, OrderSimpleSerializer
from django.db import transaction
from rest_framework.exceptions import ValidationError
class SaleCreateSerializer(serializers.ModelSerializer):
iproducts = serializers.PrimaryKeyRelatedField(queryset=
IProduct.objects.all(), many=True)
class Meta:
model = Sale
fields = ['customer', 'order', 'product', 'iproducts']
fields = ['customer', 'order', 'product', 'iproducts',
'edelivery_date', 'trans_mode', 'receiver', 'receiver_phone'
, 'receiver_address', 'remark']
def validate(self, attrs):
order = attrs.get('order', None)
@ -18,9 +22,11 @@ class SaleCreateSerializer(serializers.ModelSerializer):
if order.customer:
attrs['customer'] = order.customer
attrs['product'] = order.product
for i in attrs['iproducts']:
if i.material != attrs['product']:
raise exceptions.APIException('产品选取错误')
ips = IProduct.objects.filter(id__in=[i.id for i in attrs['iproducts']])
if ips.exclude(state=IProduct.SALE_OK).exists():
raise exceptions.APIException('选取了非可用的产品')
if ips.count() != ips.filter(material=attrs['product']).count():
raise exceptions.APIException('产品选取错误')
return super().validate(attrs)
@ -44,8 +50,14 @@ class SaleProductCreateSerializer(serializers.ModelSerializer):
fields = ['sale', 'iproduct']
def create(self, validated_data):
validated_data['number'] = validated_data['iproduct'].number
instance = SaleProduct.objects.create(**validated_data)
instance.sale.count = SaleProduct.objects.filter(sale=instance.sale).count()
instance.sale.save()
with transaction.atomic():
validated_data['number'] = validated_data['iproduct'].number
instance = SaleProduct.objects.create(**validated_data)
sale = instance.sale
sale.count = SaleProduct.objects.filter(sale=instance.sale).count()
sale.save()
order = sale.order
if order:
if sale.count+order.delivered_count>order.count:
raise exceptions.APIException('超过订单所需数量')
return instance

View File

@ -35,6 +35,13 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
elif self.action == 'retrieve':
return SaleListSerializer
return super().get_serializer_class()
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
if obj.is_audited:
raise exceptions.APIException('该销售记录已审核,不可删除')
obj.delete()
IProduct.objects.filter(sale_iproduct__sale=obj).update()
def create(self, request, *args, **kwargs):
data = request.data
@ -49,6 +56,8 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
sale = Sale.objects.create(**vdata)
i_l = []
for i in iproducts:
i.state = IProduct.SALE_LOCK
i.save()
i_d ={}
i_d['sale'] = sale
i_d['number'] = i.number
@ -66,56 +75,59 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
obj = self.get_object()
if obj.is_audited:
raise exceptions.APIException('已审核通过')
if obj.order:
if obj.count + obj.order.delivered_count > obj.order.count:
raise exceptions.APIException('超过订单所需数量')
# 创建出库记录
fifo = FIFO()
fifo.sale = obj
fifo.type = FIFO.FIFO_TYPE_SALE_OUT
fifo.is_audited = True
fifo.is_audited = False
fifo.auditor = request.user
fifo.inout_date = timezone.now()
fifo.create_by = request.user
fifo.number = 'CK' + ranstr(7)
fifo.save()
# 创建出库条目
ips = IProduct.objects.filter(sale_iproduct__sale=obj)
items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id'))
for i in items:
warehouse = WareHouse.objects.get(id=i['warehouse'])
material = Material.objects.get(id=i['material'])
fifoitem = FIFOItem()
fifoitem.need_test = False
fifoitem.warehouse = warehouse
fifoitem.material = material
fifoitem.count = i['total']
fifoitem.batch = i['batch']
fifoitem.fifo = fifo
fifoitem.save()
items_p = ips.filter(warehouse=warehouse, batch=i['batch'])
ipxs = []
for i in items_p:
# 创建出库明细半成品
ip = {}
ip['fifoitem'] = fifoitem
ip['number'] = i.number
ip['material'] = i.material
ip['iproduct'] = i
ipxs.append(FIFOItemProduct(**ip))
FIFOItemProduct.objects.bulk_create(ipxs)
# 更新成品库情况
ips.update(is_saled=True)
# ips = IProduct.objects.filter(sale_iproduct__sale=obj)
# items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id'))
# for i in items:
# warehouse = WareHouse.objects.get(id=i['warehouse'])
# material = Material.objects.get(id=i['material'])
# fifoitem = FIFOItem()
# fifoitem.need_test = False
# fifoitem.warehouse = warehouse
# fifoitem.material = material
# fifoitem.count = i['total']
# fifoitem.batch = i['batch']
# fifoitem.fifo = fifo
# fifoitem.save()
# items_p = ips.filter(warehouse=warehouse, batch=i['batch'])
# ipxs = []
# for i in items_p:
# # 创建出库明细半成品
# ip = {}
# ip['fifoitem'] = fifoitem
# ip['number'] = i.number
# ip['material'] = i.material
# ip['iproduct'] = i
# ipxs.append(FIFOItemProduct(**ip))
# FIFOItemProduct.objects.bulk_create(ipxs)
# 更新动态产品表情况
from apps.wpm.models import WProduct
WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update(
act_state=WProduct.WPR_ACT_STATE_SELLED)
# from apps.wpm.models import WProduct
# WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update(
# act_state=WProduct.WPR_ACT_STATE_SELLED)
# 更新库存
InmService.update_inm(fifo)
# 变更审核状态
# InmService.update_inm(fifo)
# 变更销售提货审核状态
obj.is_audited = True
obj.save()
# 变更订单状态
if obj.order:
order = obj.order
order.delivered_count = order.delivered_count + obj.count
order.save()
# if obj.order:
# order = obj.order
# order.delivered_count = order.delivered_count + obj.count
# order.save()
return Response()
@ -136,13 +148,13 @@ class SaleProductViewSet(ListModelMixin, DestroyModelMixin, CreateModelMixin, Ge
if self.action == 'create':
return SaleProductCreateSerializer
return super().get_serializer_class()
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
sale = obj.sale
if sale.is_audited:
raise exceptions.APIException('该销售记录已审核,不可删除产品')
obj.delete()
sale.count = SaleProduct.objects.filter(sale=obj.sale).count()
sale.save()
obj.delete()
return Response()