diff --git a/hb_server/apps/inm/filters.py b/hb_server/apps/inm/filters.py index 7f16ac4..e4f6409 100644 --- a/hb_server/apps/inm/filters.py +++ b/hb_server/apps/inm/filters.py @@ -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'] diff --git a/hb_server/apps/inm/migrations/0032_auto_20220222_0941.py b/hb_server/apps/inm/migrations/0032_auto_20220222_0941.py new file mode 100644 index 0000000..98c8747 --- /dev/null +++ b/hb_server/apps/inm/migrations/0032_auto_20220222_0941.py @@ -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='状态'), + ), + ] diff --git a/hb_server/apps/inm/models.py b/hb_server/apps/inm/models.py index 64b4108..4053765 100644 --- a/hb_server/apps/inm/models.py +++ b/hb_server/apps/inm/models.py @@ -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) \ No newline at end of file diff --git a/hb_server/apps/mtm/migrations/0048_remove_step_need_test.py b/hb_server/apps/mtm/migrations/0048_remove_step_need_test.py new file mode 100644 index 0000000..0d05c19 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0048_remove_step_need_test.py @@ -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', + ), + ] diff --git a/hb_server/apps/mtm/migrations/0049_auto_20220222_0944.py b/hb_server/apps/mtm/migrations/0049_auto_20220222_0944.py new file mode 100644 index 0000000..e7ede50 --- /dev/null +++ b/hb_server/apps/mtm/migrations/0049_auto_20220222_0944.py @@ -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='装箱配件'), + ), + ] diff --git a/hb_server/apps/mtm/models.py b/hb_server/apps/mtm/models.py index d89dc16..9ea0abd 100644 --- a/hb_server/apps/mtm/models.py +++ b/hb_server/apps/mtm/models.py @@ -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) diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 603ff5c..c54af66 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -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: diff --git a/hb_server/apps/qm/migrations/0026_alter_testrecord_is_testok.py b/hb_server/apps/qm/migrations/0026_alter_testrecord_is_testok.py new file mode 100644 index 0000000..0d2ce86 --- /dev/null +++ b/hb_server/apps/qm/migrations/0026_alter_testrecord_is_testok.py @@ -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='是否合格'), + ), + ] diff --git a/hb_server/apps/sam/migrations/0013_auto_20220222_0941.py b/hb_server/apps/sam/migrations/0013_auto_20220222_0941.py new file mode 100644 index 0000000..70b8f3e --- /dev/null +++ b/hb_server/apps/sam/migrations/0013_auto_20220222_0941.py @@ -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='预计发货数量'), + ), + ] diff --git a/hb_server/apps/sam/models.py b/hb_server/apps/sam/models.py index ce2ef62..a922770 100644 --- a/hb_server/apps/sam/models.py +++ b/hb_server/apps/sam/models.py @@ -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): diff --git a/hb_server/apps/sam/serializers_sale.py b/hb_server/apps/sam/serializers_sale.py index 627c4f2..4acdbf2 100644 --- a/hb_server/apps/sam/serializers_sale.py +++ b/hb_server/apps/sam/serializers_sale.py @@ -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 \ No newline at end of file diff --git a/hb_server/apps/sam/views_sale.py b/hb_server/apps/sam/views_sale.py index 1eb6717..9997346 100644 --- a/hb_server/apps/sam/views_sale.py +++ b/hb_server/apps/sam/views_sale.py @@ -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() \ No newline at end of file