diff --git a/hb_client/src/components/customForm/index.vue b/hb_client/src/components/customForm/index.vue index 7fcfd50..416696f 100644 --- a/hb_client/src/components/customForm/index.vue +++ b/hb_client/src/components/customForm/index.vue @@ -19,7 +19,7 @@ @@ -68,7 +68,7 @@ placeholder="选择日期" value-format="yyyy-MM-dd HH:mm:ss" style="width: 100%" - @change="keyChange(item.field_key)" + @change="keyChange($index,item.field_key)" /> @@ -80,7 +80,7 @@ v-model="checkForm[item.field_key]" style="width: 100%" placeholder="请选择" - @change="keyChange(item.field_key)" + @change="keyChange($index,item.field_key)" > @@ -135,11 +137,22 @@ -
- - 合格验证 - -
+ + + + + 检查合格 + 检查不合格 + + + + @@ -159,6 +172,10 @@ type:Number, default:0 }, + wproduct: { + type:Number, + default:0 + }, hasPicture:{ type:Boolean, default:false @@ -167,24 +184,28 @@ mounted() { let that = this; this.form = this.formID; - // debugger; - // console.log(this.results); - this.formData=this.results; - this.formData=[...this.formData]; + let formData=this.results; + that.wproductId=this.wproduct; + formData.forEach(item => { + let obj = new Object(); + obj = item; + obj.is_testok = true;//是否合格 + that.formData.push(obj) + }); + that.formData=[...that.formData]; + for(let i=0;i { return item.need_judge === true; }); listJudge.forEach(item => { let obj = new Object(); obj = item; - obj.judge = false; that.judgeList.push(obj) }); setTimeout(function(){ @@ -193,8 +214,16 @@ }, data(){ return{ + field:[], checkForm:{}, + testrecord:{ + form:this.formID, + record_data:[], + is_testok:true, + wproduct:null, + }, canvas:null, + wproductId:null, ctx:null, myCanvas_rect:null, Txt:null, @@ -207,6 +236,9 @@ canvasImg:'', formData:[],//表单数组 judgeList:[],//判定数组 + is_testok:true, + testokTrue:true, + testokFalse:false, } }, methods:{ @@ -276,15 +308,63 @@ return true; } }, - keyChange(key){ - key; - this.$forceUpdate(); - /* debugger; - let y = this.checkForm[key]; - this.$set(this.checkForm,key,y);*/ - // this.filterBlock(); + keyChange(index,key){ + let that = this; + let reg = /\{(.+?)\}/g; + that.$forceUpdate(); + that.$nextTick(()=>{ + if(that.formData[index].need_judge===true){ + let arr = [],str = ''; + let item = that.formData[index].rule_expression.replace(/`/g,''); + if(item.indexOf('||')>-1){ + arr = item.split('||'); + }else if(item.indexOf('&&')>-1){ + arr = item.split('&&'); + }else{ + arr.push(item); + } + //对每个条件进行判定,如果符合, + for (let i = 0;i-1){ + if(str.indexOf('false')>-1){ + that.formData[index].is_testok = true; + }else{ + that.formData[index].is_testok = false; + } + }else{ + if(str.indexOf('true')>-1){ + that.formData[index].is_testok = false; + }else{ + that.formData[index].is_testok = true; + } + } + }else{ + that.formData[index].is_testok = true; + } + that.judgeList = that.formData.filter(item => { + return item.need_judge === true; + }); + that.judgeForm(); + }) }, - /* base64ToFile */ + /* 图片保存 */ + //初始化canvas canvasInit(){ let that = this; preDrawAry = []; @@ -299,6 +379,7 @@ that.draw(); },500); }, + //画布添加背景模板图 draw(){ let canvasImg = document.getElementById("canvasImg"); canvasImg.style.width = '500px'; @@ -360,7 +441,6 @@ } } }, - // 文字 word1(){ let canvas3 = document.getElementById('canvas'); @@ -462,6 +542,7 @@ // console.log(res); }) }, + //base64ToFile base64ToFile(baseUrl) { let arr = baseUrl.split(','); // let type = arr[0].match(/:(.*?);/)[1]; // 解锁图片类型 @@ -474,7 +555,8 @@ // let fileOfBlob = new File([bufferArray], new Date()+'.jpg'); return new File([bufferArray ],'draw.jpg'); }, - /* base64ToFile */ + /* 图片保存 */ + //最终表格判定 judgeForm(){ let that = this , reg = /\{(.+?)\}/g, @@ -508,21 +590,56 @@ } } } - if(str.indexOf('true')>-1){ - that.judgeList[i].judge = true; + if(item.indexOf('&&')>-1){ + if(str.indexOf('false')>-1){ + that.judgeList[i].is_testok = true; + }else{ + that.judgeList[i].is_testok = false; + } }else{ - that.judgeList[i].judge = false; + if(str.indexOf('true')>-1){ + that.judgeList[i].is_testok = false; + }else{ + that.judgeList[i].is_testok = true; + } } } let real = that.judgeList.filter(item=>{ - return item.judge==true; + return item.is_testok===false; }); if(real.length>0){ + that.is_testok = false; alert("检验不合格!") }else{ + that.is_testok = true; alert("检验合格!") } }, + //提交检查项目 + submitfield() { + let that = this; + debugger; + console.log(that.checkForm); + debugger; + that.field = []; //检查项目 + that.formData.forEach((item) => { + debugger; + console.log(that.checkForm[item.field_key]); + debugger; + that.field.push({ + form_field: item.id, + field_value: that.checkForm[item.field_key], + is_testok: item.is_testok//单项检查结果 + }); + }); + console.log(that.field); + debugger; + that.testrecord.form = that.formID;//检查表 + that.testrecord.record_data = that.field;//检查项列表 + that.testrecord.is_testok = that.is_testok;//检查表检查结果 + that.testrecord.wproduct = that.wproductId;//半成品ID + this.$emit('formFunc',that.testrecord); + }, } } diff --git a/hb_client/src/views/wpm/need.vue b/hb_client/src/views/wpm/need.vue index 1f6f670..4ed28d8 100644 --- a/hb_client/src/views/wpm/need.vue +++ b/hb_client/src/views/wpm/need.vue @@ -1,601 +1,511 @@ diff --git a/hb_server/apps/inm/filters.py b/hb_server/apps/inm/filters.py index c71c4fb..3f550a3 100644 --- a/hb_server/apps/inm/filters.py +++ b/hb_server/apps/inm/filters.py @@ -1,9 +1,17 @@ from django_filters import rest_framework as filters from apps.mtm.models import Material -from .models import MaterialBatch +from .models import IProduct, MaterialBatch class MbFilterSet(filters.FilterSet): material = filters.ModelMultipleChoiceFilter(field_name="material", queryset=Material.objects.all()) class Meta: model = MaterialBatch - fields = ['material', 'warehouse'] \ No newline at end of file + fields = ['material', 'warehouse'] + + +class IProductFilterSet(filters.FilterSet): + + order = filters.NumberFilter(field_name="wproduct__subproduction_plan__production_plan__order") + class Meta: + model = IProduct + fields = ['material', 'warehouse', 'batch', 'order'] \ No newline at end of file diff --git a/hb_server/apps/inm/migrations/0020_iproduct_is_saled.py b/hb_server/apps/inm/migrations/0020_iproduct_is_saled.py new file mode 100644 index 0000000..5ca02e7 --- /dev/null +++ b/hb_server/apps/inm/migrations/0020_iproduct_is_saled.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-12-06 01:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('inm', '0019_auto_20211201_1011'), + ] + + operations = [ + migrations.AddField( + model_name='iproduct', + name='is_saled', + field=models.BooleanField(default=False, verbose_name='是否售出'), + ), + ] diff --git a/hb_server/apps/inm/migrations/0021_fifoitemproduct_iproduct.py b/hb_server/apps/inm/migrations/0021_fifoitemproduct_iproduct.py new file mode 100644 index 0000000..9d9ea22 --- /dev/null +++ b/hb_server/apps/inm/migrations/0021_fifoitemproduct_iproduct.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.9 on 2021-12-07 00:42 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('inm', '0020_iproduct_is_saled'), + ] + + operations = [ + migrations.AddField( + model_name='fifoitemproduct', + name='iproduct', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='inm.iproduct', verbose_name='关联库存产品'), + ), + ] diff --git a/hb_server/apps/inm/models.py b/hb_server/apps/inm/models.py index 20d87dd..9059a91 100644 --- a/hb_server/apps/inm/models.py +++ b/hb_server/apps/inm/models.py @@ -84,15 +84,6 @@ class FIFOItem(BaseModel): fifo = models.ForeignKey(FIFO, verbose_name='关联出入库', on_delete=models.CASCADE) subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE, null=True, blank=True) -class FIFOItemProduct(BaseModel): - """ - 出入库产品 - """ - fifoitem = models.ForeignKey(FIFOItem, verbose_name='关联出入库具体产品', on_delete=models.CASCADE) - wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False, null=True, blank=True) - number = models.CharField('物品编号', max_length=50) - material = models.ForeignKey(Material, verbose_name='物料类型', on_delete=models.CASCADE) - class IProduct(BaseModel): """ 具体产品条目 @@ -102,5 +93,18 @@ class IProduct(BaseModel): warehouse = models.ForeignKey(WareHouse, on_delete=models.CASCADE, verbose_name='所在仓库') 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) + is_saled = models.BooleanField('是否售出', default=False) + +class FIFOItemProduct(BaseModel): + """ + 出入库产品 + """ + fifoitem = models.ForeignKey(FIFOItem, verbose_name='关联出入库具体产品', on_delete=models.CASCADE) + wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False, null=True, blank=True) + number = models.CharField('物品编号', max_length=50) + material = models.ForeignKey(Material, verbose_name='物料类型', on_delete=models.CASCADE) + iproduct = models.ForeignKey(IProduct, verbose_name='关联库存产品', null=True, blank=True, on_delete=models.SET_NULL) + + diff --git a/hb_server/apps/inm/signals.py b/hb_server/apps/inm/signals.py index 79d5a19..e659c0c 100644 --- a/hb_server/apps/inm/signals.py +++ b/hb_server/apps/inm/signals.py @@ -36,7 +36,7 @@ def update_inm(instance:FIFO, type:int=1): ips2.append(IProduct(**ip)) IProduct.objects.bulk_create(ips2) - elif instance.type in [FIFO.FIFO_TYPE_DO_OUT]: # 生产领料 + elif instance.type in [FIFO.FIFO_TYPE_DO_OUT, FIFO.FIFO_TYPE_SALE_OUT]: # 生产领料 销售出库 # 更新相关表 for i in FIFOItem.objects.filter(fifo=instance): material = i.material @@ -49,8 +49,9 @@ def update_inm(instance:FIFO, type:int=1): o2.save() material.count = material.count - i.count material.save() - - # 删除IProduct - numbers = FIFOItemProduct.objects.filter(fifoitem=i).values_list('number', flat=True) - IProduct.objects.filter(number__in=numbers).delete() + + # 删除IProduct + if instance.type == FIFO.FIFO_TYPE_DO_OUT: + numbers = FIFOItemProduct.objects.filter(fifoitem=i).values_list('number', flat=True) + IProduct.objects.filter(number__in=numbers).delete() diff --git a/hb_server/apps/inm/views.py b/hb_server/apps/inm/views.py index 6454c40..8f455e0 100644 --- a/hb_server/apps/inm/views.py +++ b/hb_server/apps/inm/views.py @@ -3,7 +3,7 @@ from rest_framework import serializers from rest_framework.exceptions import APIException from rest_framework.mixins import DestroyModelMixin, ListModelMixin, RetrieveModelMixin from rest_framework.viewsets import GenericViewSet, ModelViewSet -from apps.inm.filters import MbFilterSet +from apps.inm.filters import IProductFilterSet, MbFilterSet from apps.inm.models import FIFO, FIFOItem, IProduct, MaterialBatch, WareHouse,Inventory from apps.inm.serializers import FIFOItemSerializer, FIFOInPurSerializer, FIFOListSerializer, IProductListSerializer, InmTestRecordCreateSerializer, MaterialBatchQuerySerializer, MaterialBatchSerializer, WareHouseSerializer, WareHouseCreateUpdateSerializer,InventorySerializer @@ -167,9 +167,9 @@ class IProductViewSet(ListModelMixin, GenericViewSet): 半成品库存表 """ perms_map = {'*': '*'} - queryset = IProduct.objects.select_related('material', 'warehouse').all() + queryset = IProduct.objects.select_related('material', 'warehouse', 'wproduct__subproduction_plan__production_plan__order').filter(is_saled=False) serializer_class = IProductListSerializer - filterset_fields = ['material', 'warehouse', 'batch'] + filterset_class = IProductFilterSet search_fields = [] ordering_fields = ['create_time'] ordering = ['-create_time'] \ No newline at end of file diff --git a/hb_server/apps/mtm/serializers.py b/hb_server/apps/mtm/serializers.py index 153971f..f00b95d 100644 --- a/hb_server/apps/mtm/serializers.py +++ b/hb_server/apps/mtm/serializers.py @@ -209,13 +209,13 @@ class RecordFormDetailSerializer(serializers.ModelSerializer): def get_form_fields(self, obj): serializer = RecordFormFieldSerializer(instance=RecordFormField.objects.filter(form=obj, is_deleted=False), many=True) - vdata = serializer.data + data = serializer.data if obj.type == RecordForm.RF_TYPE_TEST: - for i in vdata: + for i in data: if i['need_judge']: i['is_testok'] = False i['is_teskok_robot'] = False - return serializer.data + return data class RecordFormFieldCreateSerializer(serializers.ModelSerializer): diff --git a/hb_server/apps/sam/migrations/0005_auto_20211203_1501.py b/hb_server/apps/sam/migrations/0005_auto_20211203_1501.py new file mode 100644 index 0000000..c819554 --- /dev/null +++ b/hb_server/apps/sam/migrations/0005_auto_20211203_1501.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.9 on 2021-12-03 07:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0004_order_planed_count'), + ] + + operations = [ + migrations.AddField( + model_name='order', + name='delivered_count', + field=models.PositiveIntegerField(default=0, verbose_name='交货数量'), + ), + migrations.AlterField( + model_name='order', + name='count', + field=models.PositiveIntegerField(default=0, verbose_name='所需数量'), + ), + migrations.AlterField( + model_name='order', + name='planed_count', + field=models.PositiveIntegerField(default=0, verbose_name='已排数量'), + ), + ] diff --git a/hb_server/apps/sam/migrations/0006_auto_20211206_0958.py b/hb_server/apps/sam/migrations/0006_auto_20211206_0958.py new file mode 100644 index 0000000..9b99454 --- /dev/null +++ b/hb_server/apps/sam/migrations/0006_auto_20211206_0958.py @@ -0,0 +1,65 @@ +# Generated by Django 3.2.9 on 2021-12-06 01:58 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('inm', '0020_iproduct_is_saled'), + ('mtm', '0041_alter_material_type'), + ('sam', '0005_auto_20211203_1501'), + ] + + operations = [ + migrations.AlterField( + model_name='order', + name='delivered_count', + field=models.PositiveIntegerField(default=0, verbose_name='已交货数量'), + ), + migrations.AlterField( + model_name='order', + name='number', + field=models.CharField(max_length=100, unique=True, verbose_name='订单编号'), + ), + migrations.CreateModel( + name='Sale', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('count', models.PositiveIntegerField(default=0, verbose_name='交货数量')), + ('is_audited', models.BooleanField(default=False, verbose_name='是否审核')), + ('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sale_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')), + ('customer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sam.customer', verbose_name='客户')), + ('order', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sam.order', verbose_name='关联订单')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.material', verbose_name='所需产品')), + ('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sale_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SaleProduct', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')), + ('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')), + ('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')), + ('number', models.CharField(max_length=50, unique=True, verbose_name='物品编号')), + ('is_mtested', models.BooleanField(default=False, verbose_name='是否军检')), + ('is_mtestok', models.BooleanField(default=True, verbose_name='是否军检合格')), + ('iproduct', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sale_iproduct', to='inm.iproduct', verbose_name='关联库存产品')), + ('sale', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sam.sale', verbose_name='关联销售记录')), + ], + options={ + 'unique_together': {('sale', 'iproduct')}, + }, + ), + ] diff --git a/hb_server/apps/sam/migrations/0007_saleproduct_remark.py b/hb_server/apps/sam/migrations/0007_saleproduct_remark.py new file mode 100644 index 0000000..846c3bc --- /dev/null +++ b/hb_server/apps/sam/migrations/0007_saleproduct_remark.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-12-06 05:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0006_auto_20211206_0958'), + ] + + operations = [ + migrations.AddField( + model_name='saleproduct', + name='remark', + field=models.TextField(blank=True, null=True, verbose_name='备注'), + ), + ] diff --git a/hb_server/apps/sam/migrations/0008_alter_saleproduct_number.py b/hb_server/apps/sam/migrations/0008_alter_saleproduct_number.py new file mode 100644 index 0000000..72e87f2 --- /dev/null +++ b/hb_server/apps/sam/migrations/0008_alter_saleproduct_number.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-12-06 06:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0007_saleproduct_remark'), + ] + + operations = [ + migrations.AlterField( + model_name='saleproduct', + name='number', + field=models.CharField(max_length=50, verbose_name='物品编号'), + ), + ] diff --git a/hb_server/apps/sam/migrations/0009_alter_saleproduct_is_mtestok.py b/hb_server/apps/sam/migrations/0009_alter_saleproduct_is_mtestok.py new file mode 100644 index 0000000..d034dfa --- /dev/null +++ b/hb_server/apps/sam/migrations/0009_alter_saleproduct_is_mtestok.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.9 on 2021-12-07 00:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sam', '0008_alter_saleproduct_number'), + ] + + operations = [ + migrations.AlterField( + model_name='saleproduct', + name='is_mtestok', + field=models.BooleanField(blank=True, null=True, verbose_name='是否军检合格'), + ), + ] diff --git a/hb_server/apps/sam/models.py b/hb_server/apps/sam/models.py index 583fe87..e82137a 100644 --- a/hb_server/apps/sam/models.py +++ b/hb_server/apps/sam/models.py @@ -1,4 +1,4 @@ -from apps.system.models import CommonAModel +from apps.system.models import CommonADModel, CommonAModel from django.db import models from django.contrib.auth.models import AbstractUser from django.db.models.base import Model @@ -58,16 +58,45 @@ class Order(CommonAModel): """ 订单信息 """ - number = models.CharField('订单编号', max_length=100) + number = models.CharField('订单编号', max_length=100, unique=True) customer = models.ForeignKey(Customer, verbose_name='客户', on_delete=models.CASCADE) contract = models.ForeignKey(Contract, verbose_name='所属合同', null=True, blank=True, on_delete=models.SET_NULL) product = models.ForeignKey(Material, verbose_name='所需产品', on_delete=models.CASCADE) - count = models.IntegerField('所需数量', default=0) - planed_count = models.IntegerField('已排数量', default=0) + count = models.PositiveIntegerField('所需数量', default=0) + planed_count = models.PositiveIntegerField('已排数量', default=0) + delivered_count = models.PositiveIntegerField('已交货数量', default=0) delivery_date = models.DateField('交货日期') class Meta: verbose_name = '订单信息' verbose_name_plural = verbose_name + + +class Sale(CommonADModel): + """ + 销售记录 + """ + 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) + is_audited = models.BooleanField('是否审核', default=False) + + +class SaleProduct(BaseModel): + """ + 具体产品 + """ + sale = models.ForeignKey(Sale, verbose_name='关联销售记录', on_delete=models.CASCADE) + number = models.CharField('物品编号', max_length=50) + iproduct = models.ForeignKey('inm.iproduct', verbose_name='关联库存产品', on_delete=models.CASCADE, related_name='sale_iproduct') + is_mtested = models.BooleanField('是否军检', default=False) + is_mtestok = models.BooleanField('是否军检合格', null=True, blank=True) + remark = models.TextField('备注', null=True, blank=True) + + class Meta: + unique_together = ( + ('sale','iproduct'), # 联合唯一 + ) + + - def __str__(self): - return self.name \ No newline at end of file diff --git a/hb_server/apps/sam/serializers.py b/hb_server/apps/sam/serializers.py index a4ed099..eb3a840 100644 --- a/hb_server/apps/sam/serializers.py +++ b/hb_server/apps/sam/serializers.py @@ -1,6 +1,10 @@ -from rest_framework import serializers +from django.db import transaction +from rest_framework import exceptions, serializers -from .models import Contract, Customer, Order +from apps.inm.models import IProduct +from apps.inm.serializers import IProductListSerializer + +from .models import Contract, Customer, Order, Sale, SaleProduct from apps.mtm.serializers import MaterialSimpleSerializer @@ -54,3 +58,52 @@ class OrderSimpleSerializer(serializers.ModelSerializer): class Meta: model = Order fields = '__all__' + +class SaleCreateSerializer(serializers.ModelSerializer): + iproducts = serializers.PrimaryKeyRelatedField(queryset=IProduct.objects.all(), many=True) + class Meta: + model = Sale + fields = ['customer', 'order', 'product', 'iproducts'] + + def validate(self, attrs): + order = attrs.get('order', None) + if order: + if order.customer: + attrs['customer'] = order.customer + attrs['product'] = order.product + for i in attrs['iproducts']: + if i.material is not attrs['product']: + raise exceptions.APIException('产品选取错误') + return super().validate(attrs) + + +class SaleListSerializer(serializers.ModelSerializer): + customer_ = CustomerSimpleSerializer(source='customer', read_only=True) + order_ = OrderSimpleSerializer(source='order', read_only=True) + product_ = MaterialSimpleSerializer(source='product', read_only=True) + class Meta: + model = Sale + fields = '__all__' + +class SaleProductListSerializer(serializers.ModelSerializer): + iproduct_ = IProductListSerializer(source='iproduct', read_only=True) + class Meta: + model = SaleProduct + fields = '__all__' + +class SaleProductCreateSerializer(serializers.ModelSerializer): + class Meta: + model = SaleProduct + 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() + return instance + +class SaleProductMtestSerializer(serializers.ModelSerializer): + class Meta: + model = SaleProduct + fields = ['remark', 'is_mtestok'] \ No newline at end of file diff --git a/hb_server/apps/sam/urls.py b/hb_server/apps/sam/urls.py index 4aa607f..7a66e01 100644 --- a/hb_server/apps/sam/urls.py +++ b/hb_server/apps/sam/urls.py @@ -1,6 +1,6 @@ from django.db.models import base from rest_framework import urlpatterns -from apps.sam.views import CustomerViewSet,ContractViewSet,OrderViewSet +from apps.sam.views import CustomerViewSet,ContractViewSet,OrderViewSet, SaleProductViewSet, SaleViewSet from django.urls import path, include from rest_framework.routers import DefaultRouter @@ -8,6 +8,8 @@ router = DefaultRouter() router.register('customer', CustomerViewSet, basename='customer') router.register('contract', ContractViewSet, basename='contract') router.register('order', OrderViewSet, basename='order') +router.register('sale', SaleViewSet, basename='sale') +router.register('sale_product', SaleProductViewSet, basename='sale_product') urlpatterns = [ path('', include(router.urls)), diff --git a/hb_server/apps/sam/views.py b/hb_server/apps/sam/views.py index 441e7fa..e3098ef 100644 --- a/hb_server/apps/sam/views.py +++ b/hb_server/apps/sam/views.py @@ -1,11 +1,19 @@ -from apps.sam.serializers import ContractCreateUpdateSerializer, ContractSerializer, CustomerCreateUpdateSerializer, CustomerSerializer, OrderCreateUpdateSerializer, OrderSerializer -from apps.sam.models import Contract, Customer, Order -from rest_framework.viewsets import ModelViewSet +from django.db import transaction +from django.db.models.aggregates import Count +from rest_framework import exceptions, serializers +from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin +from apps.mtm.models import Material +from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, WareHouse +from apps.inm.signals import update_inm +from apps.sam.serializers import ContractCreateUpdateSerializer, ContractSerializer, CustomerCreateUpdateSerializer, CustomerSerializer, OrderCreateUpdateSerializer, OrderSerializer, SaleCreateSerializer, SaleListSerializer, SaleProductCreateSerializer, SaleProductListSerializer, SaleProductMtestSerializer +from apps.sam.models import Contract, Customer, Order, Sale, SaleProduct +from rest_framework.viewsets import GenericViewSet, ModelViewSet from apps.system.mixins import CreateUpdateCustomMixin from django.shortcuts import render from rest_framework.decorators import action from django.db.models import F from rest_framework.response import Response +from django.utils import timezone # Create your views here. class CustomerViewSet(CreateUpdateCustomMixin, ModelViewSet): """ @@ -57,7 +65,7 @@ class OrderViewSet(CreateUpdateCustomMixin, ModelViewSet): def get_serializer_class(self): if self.action in ['create', 'update']: return OrderCreateUpdateSerializer - return OrderSerializer + return super().get_serializer_class() @action(methods=['get'], detail=False, perms_map={'get':'*'}) def toplan(self, request, pk=None): @@ -67,4 +75,145 @@ class OrderViewSet(CreateUpdateCustomMixin, ModelViewSet): serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) - return Response(serializer.data) \ No newline at end of file + return Response(serializer.data) + + +class SaleViewSet(CreateUpdateCustomMixin, ListModelMixin, RetrieveModelMixin, CreateModelMixin, DestroyModelMixin, GenericViewSet): + """ + 销售记录 + """ + perms_map = {'*': '*'} + queryset = Sale.objects.select_related('customer', 'order', 'product', 'order__contract').all() + serializer_class = SaleListSerializer + search_fields = ['customer__name', 'order__number'] + filterset_fields = ['product', 'order', 'customer'] + ordering_fields = ['create_time'] + ordering = ['-create_time'] + + def get_serializer_class(self): + if self.action == 'create': + return SaleCreateSerializer + elif self.action == 'retrieve': + return SaleListSerializer + return super().get_serializer_class() + + def create(self, request, *args, **kwargs): + data = request.data + serializer = SaleCreateSerializer(data=data) + serializer.is_valid(raise_exception=True) + vdata = serializer.validated_data + with transaction.atomic(): + iproducts = vdata.pop('iproducts') + vdata['count'] = len(iproducts) + sale = Sale.objects.create(**vdata) + i_l = [] + for i in iproducts: + i_d ={} + i_d['sale'] = sale + i_d['number'] = i.number + i_d['iproduct'] = i + i_l.append(SaleProduct(**i_d)) + SaleProduct.objects.bulk_create(i_l) + return Response() + + @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=serializers.Serializer) + @transaction.atomic + def audit(self, request, pk=None): + """ + 审核 + """ + obj = self.get_object() + if obj.is_audited: + raise exceptions.APIException('已审核通过') + # 创建出库记录 + fifo = FIFO() + fifo.type = FIFO.FIFO_TYPE_SALE_OUT + fifo.is_audited = True + fifo.auditor = request.user + fifo.inout_date = timezone.now() + fifo.create_by = request.user + fifo.save() + # 出库条目 + spds = SaleProduct.objects.filter(sale=obj) + for i in spds: + if i.is_mtested and i.is_mtestok: + pass + else: + raise exceptions.APIException('存在未军检产品') + # 创建出库条目 + 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.is_tested = True + fifoitem.is_testok = True + 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) + # 更新库存 + 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() + return Response() + +class SaleProductViewSet(ListModelMixin, DestroyModelMixin, CreateModelMixin, GenericViewSet): + """ + 销售记录关联产品 + """ + perms_map = {'*': '*'} + queryset = SaleProduct.objects.select_related('iproduct', 'iproduct__material', 'iproduct__warehouse').all() + serializer_class = SaleProductListSerializer + search_fields = [] + filterset_fields = ['sale', 'iproduct'] + ordering_fields = ['create_time'] + ordering = ['id'] + + def get_serializer_class(self): + if self.action == 'create': + return SaleProductCreateSerializer + return super().get_serializer_class() + + def destroy(self, request, *args, **kwargs): + obj = self.get_object() + obj.sale.count = SaleProduct.objects.filter(sale=obj.sale).count() + obj.sale.save() + obj.delete() + return Response() + + @action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=SaleProductMtestSerializer) + def mtest(self, request, pk=None): + """ + 军检 + """ + obj = self.get_object() + if obj.is_mtested: + raise exceptions.APIException('已进行军检') + obj.remark = request.data.get('remark', None) + obj.is_mtested = True + obj.is_mtestok = request.data.get('is_mtestok') + obj.save() + return Response() \ No newline at end of file diff --git a/hb_server/apps/wf/migrations/0017_auto_20211203_1501.py b/hb_server/apps/wf/migrations/0017_auto_20211203_1501.py new file mode 100644 index 0000000..f5b3766 --- /dev/null +++ b/hb_server/apps/wf/migrations/0017_auto_20211203_1501.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.9 on 2021-12-03 07:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wf', '0016_auto_20211024_2349'), + ] + + operations = [ + migrations.AlterField( + model_name='customfield', + name='field_type', + field=models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('textarea', '文本域'), ('selectuser', '单选用户'), ('selectusers', '多选用户'), ('file', '附件'), ('draw', '绘图')], help_text='5.字符串,10.整形,15.浮点型,20.布尔,25.日期,30.日期时间,35.单选框,40.多选框,45.下拉列表,50.多选下拉列表,55.文本域,60.用户名, 70.多选的用户名, 80.附件(只保存路径,多个使用逗号隔开)', max_length=50, verbose_name='类型'), + ), + migrations.AlterField( + model_name='ticket', + name='ticket_data', + field=models.JSONField(default=dict, help_text='工单自定义字段内容', verbose_name='工单数据'), + ), + ] diff --git a/hb_server/apps/wf/models.py b/hb_server/apps/wf/models.py index a9ae071..bc1597c 100644 --- a/hb_server/apps/wf/models.py +++ b/hb_server/apps/wf/models.py @@ -152,7 +152,8 @@ class CustomField(CommonAModel): ('textarea', '文本域'), ('selectuser', '单选用户'), ('selectusers', '多选用户'), - ('file', '附件') + ('file', '附件'), + ('draw', '绘图') ) workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') field_type = models.CharField('类型', max_length=50, choices=field_type_choices, help_text='5.字符串,10.整形,15.浮点型,20.布尔,25.日期,30.日期时间,35.单选框,40.多选框,45.下拉列表,50.多选下拉列表,55.文本域,60.用户名, 70.多选的用户名, 80.附件(只保存路径,多个使用逗号隔开)') @@ -201,7 +202,7 @@ class Ticket(CommonBModel): state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='当前状态', related_name='ticket_state') parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单') parent_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单状态', related_name='ticket_parent_state') - ticket_data = models.JSONField('工单数据', default=dict, help_text='工单所有字段内容') + ticket_data = models.JSONField('工单数据', default=dict, help_text='工单自定义字段内容') in_add_node = models.BooleanField('加签状态中', default=False, help_text='是否处于加签状态下') add_node_man = models.ForeignKey(User, verbose_name='加签人', on_delete=models.SET_NULL, null=True, blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效') @@ -211,16 +212,17 @@ class Ticket(CommonBModel): multi_all_person = models.JSONField('全部处理的结果', default=dict, blank=True, help_text='需要当前状态处理人全部处理时实际的处理结果,json格式') -class TicketData(): - """ - 工单数据,自定义字段值 - """ - form_field = models.ForeignKey(CustomField, verbose_name='关联字段', on_delete=models.SET_NULL, db_constraint=False, null=True, blank=True) - field_name = models.CharField('字段名', max_length=50) - field_key = models.CharField('字段标识', max_length=50) - field_type = models.CharField('字段类型', choices=CustomField.field_type_choices, max_length=50) - field_value = models.JSONField('录入值', default=dict, blank=True) - sort = models.IntegerField('排序号', default=1) +# class TicketCustomField(BaseModel): +# """ +# 工单数据,自定义字段值 +# """ +# ticket = models.ForeignKey(Ticket, verbose_name='关联工单', on_delete=models.CASCADE) +# form_field = models.ForeignKey(CustomField, verbose_name='关联字段', on_delete=models.SET_NULL, db_constraint=False, null=True, blank=True) +# field_name = models.CharField('字段名', max_length=50) +# field_key = models.CharField('字段标识', max_length=50) +# field_type = models.CharField('字段类型', choices=CustomField.field_type_choices, max_length=50) +# field_value = models.JSONField('录入值', default=dict, blank=True) +# sort = models.IntegerField('排序号', default=1) class TicketFlow(BaseModel): """ diff --git a/hb_server/apps/wpm/views.py b/hb_server/apps/wpm/views.py index 9d3b427..40b4b93 100644 --- a/hb_server/apps/wpm/views.py +++ b/hb_server/apps/wpm/views.py @@ -58,7 +58,7 @@ class WPlanViewSet(ListModelMixin, GenericViewSet): elif request.method=='POST': serializer= PickHalfsSerializer(data=request.data) serializer.is_valid(raise_exception=True) - vdata = serializer.data + vdata = serializer.validated_data first_step = Step.objects.get(pk=sp.steps[0]['id']) # 创建领料记录 pick = Pick() @@ -68,12 +68,12 @@ class WPlanViewSet(ListModelMixin, GenericViewSet): pick.save() for i in vdata: if 'wproducts' in i and len(i['wproducts'])>0: - spp = SubProductionProgress.objects.get(pk=i['id']) + spp = i['id'] spp.count_pick = spp.count_pick + len(i['wproducts']) # if spp.count_pick > spp.count: # raise exceptions.APIException('超过计划数') spp.save() - wps = WProduct.objects.filter(pk__in=[x for x in i['wproducts']], act_state=WProduct.WPR_ACT_STATE_OK) + wps = WProduct.objects.filter(pk__in=[x.id for x in i['wproducts']], act_state=WProduct.WPR_ACT_STATE_OK) wps.update(step=first_step, act_state=WProduct.WPR_ACT_STATE_TORETEST, is_hidden=False, warehouse=None, subproduction_plan=sp, update_by=request.user, update_time=timezone.now()) @@ -99,11 +99,11 @@ class WPlanViewSet(ListModelMixin, GenericViewSet): """ serializer= WplanPutInSerializer(data=request.data) serializer.is_valid(raise_exception=True) - vdata = serializer.data + vdata = serializer.validated_data subplan = self.get_object() material = subplan.main_product batch = subplan.number - warehouse = WareHouse.objects.get(id=vdata['warehouse']) + warehouse = vdata['warehouse'] wproducts = WProduct.objects.filter(subproduction_plan=subplan, act_state=WProduct.WPR_ACT_STATE_OK, material=material, is_deleted=False) if wproducts.exists(): @@ -253,9 +253,9 @@ class WProductViewSet(ListModelMixin, GenericViewSet): """ serializer= WproductPutInsSerializer(data=request.data) serializer.is_valid(raise_exception=True) - vdata = serializer.data - wproducts = WProduct.objects.filter(pk__in=[x for x in vdata['wproducts']]) - warehouse = WareHouse.objects.get(id=vdata['warehouse']) + vdata = serializer.validated_data + wproducts = WProduct.objects.filter(pk__in=[x.id for x in vdata['wproducts']]) + warehouse = vdata['warehouse'] for i in wproducts: if i.act_state is not WProduct.WPR_ACT_STATE_OK: raise exceptions.APIException('存在不可入库半成品') @@ -267,12 +267,12 @@ class WProductViewSet(ListModelMixin, GenericViewSet): is_audited=True, auditor=request.user, inout_date=timezone.now(), create_by=request.user, remark=remark) # 创建入库明细 for i in wproducts_a: - spi = SubProductionPlan.objects.get(pk=i['subproduction_plan']) + spi = i['subproduction_plan'] fifoitem = FIFOItem() fifoitem.is_tested = True fifoitem.is_testok = True fifoitem.warehouse = warehouse - fifoitem.material = Material.objects.get(pk=i['material']) + fifoitem.material = i['material'] fifoitem.count = i['total'] fifoitem.batch = spi.number fifoitem.fifo = fifo @@ -303,9 +303,9 @@ class WProductViewSet(ListModelMixin, GenericViewSet): """ serializer= WproductPutInSerializer(data=request.data) serializer.is_valid(raise_exception=True) - vdata = serializer.data + vdata = serializer.validated_data wproduct = self.get_object() - warehouse = WareHouse.objects.get(id=vdata['warehouse']) + warehouse = vdata['warehouse'] if wproduct.act_state != WProduct.WPR_ACT_STATE_OK: raise exceptions.APIException('半成品不可入库') material = wproduct.material @@ -598,11 +598,11 @@ class OperationRecordViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): def submit(self, request, pk=None): serializer = OperationRecordSubmitSerializer(data=request.data, context={'request':self.request}) serializer.is_valid(raise_exception=True) - vdata = serializer.data + vdata = serializer.validated_data opr = self.get_object() wrds = [] for m in vdata['record_data']: # 保存记录详情 - form_field = RecordFormField.objects.get(pk=m['form_field']) + form_field = m['form_field'] m['form_field'] = form_field m['field_name'] = form_field.field_name m['field_key'] = form_field.field_key