Merge branch 'develop' of https://e.coding.net/ctcdevteam/hberp/hberp into develop

This commit is contained in:
shilixia 2021-12-07 15:05:47 +08:00
commit 6097bcaa62
21 changed files with 1121 additions and 639 deletions

View File

@ -19,7 +19,7 @@
<el-input
v-model="checkForm[item.field_key]"
placeholder="请输入"
@input="keyChange(item.field_key)"
@input="keyChange($index,item.field_key)"
/>
</el-form-item>
<el-form-item
@ -30,7 +30,7 @@
v-model="checkForm[item.field_key]"
type="number"
placeholder="请输入"
@input="keyChange(item.field_key)"
@input="keyChange($index,item.field_key)"
/>
</el-form-item>
<el-form-item
@ -41,7 +41,7 @@
v-model="checkForm[item.field_key]"
type="number"
placeholder="请输入"
@input="keyChange(item.field_key)"
@input="keyChange($index,item.field_key)"
/>
</el-form-item>
<el-form-item
@ -54,7 +54,7 @@
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
@change="keyChange(item.field_key)"
@change="keyChange($index,item.field_key)"
/>
<!--</el-date-picker>-->
</el-form-item>
@ -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)"
/>
<!--</el-date-picker>-->
</el-form-item>
@ -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)"
>
<el-option
v-for="item1 in item.field_choice"
@ -100,12 +100,14 @@
style="width: 100%"
multiple
placeholder="请选择"
@change="keyChange($index,item.field_key)"
>
<el-option
v-for="item1 in item.field_choice"
:key="item1"
:label="item1"
:value="item1"
@change="keyChange($index,item.field_key)"
>
</el-option>
</el-select>
@ -135,11 +137,22 @@
</div>
</el-form-item>
</el-row>
<div style="text-align: right">
<el-button type="primary" @click="judgeForm">
合格验证
</el-button>
<el-row>
<el-form-item label="是否合格">
<!--<el-radio-group v-model="is_testok">
<el-radio :label="testokTrue">检查合格</el-radio>
<el-radio :label="testokFalse">检查不合格</el-radio>
</el-radio-group>-->
<el-radio v-model="is_testok" :label="testokTrue">检查合格</el-radio>
<el-radio v-model="is_testok" :label="testokFalse">检查不合格</el-radio>
</el-form-item>
<div class="dialog-footer" style="text-align: right">
<el-button @click="innerVisible = false"> </el-button>
<el-button type="primary" @click="submitfield">提交检查项目</el-button>
</div>
</el-row>
</el-form>
</div>
</template>
@ -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<this.results.length;i++){
let key = this.results[i].field_key;
//checkForm接收表单数据的对象
that.checkForm[key]='';
that.$set(that.checkForm,key,'')
}
// debugger;
// console.log(that.checkForm);
let listJudge = this.formData.filter(item => {
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<arr.length;i++){
//获取判断依据
let a = '`'+ arr[i]+'`';
let tem =a.match(reg)[0];
let ky = tem.replace(/\{|\}/g, '');//qipao
//有值时进行判断
if(that.checkForm[ky]!==''&&that.checkForm[ky]!==null&&that.checkForm[ky]!==undefined){
//替换变量
a = a.replace(ky, 'yyy');
let yyy = "'"+that.checkForm[ky]+"'";
if(eval(eval( a))){
str += 'true';
}else{
str += 'false';
}
}
}
if(item.indexOf('&&')>-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].judge = false;
that.judgeList[i].is_testok = false;
}
}else{
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);
},
}
}
</script>

View File

@ -35,7 +35,8 @@
<el-link
v-if="checkPermission(['warehouse_update'])"
@click="handleInspection(scope)"
>检验</el-link
>检验
</el-link
>
</template>
</el-table-column>
@ -77,13 +78,13 @@
<el-table-column label="所在子工序">
<template slot-scope="scope">{{ scope.row.step_.name }}</template>
</el-table-column>
<el-table-column align="center" label="操作" width="220px">
<template slot-scope="scope">
<el-link
v-if="checkPermission(['warehouse_update'])"
@click="handleInspection(scope)"
>检验</el-link
>检验
</el-link
>
</template>
</el-table-column>
@ -100,7 +101,8 @@
<el-tab-pane label="已合格半成品">
<el-card style="margin-top: 2px">
<el-button type="primary" icon="el-icon-plus" @click="handleCreate"
>批量入库</el-button
>批量入库
</el-button
>
<el-table
v-loading="listLoading"
@ -140,7 +142,8 @@
<el-link
v-if="checkPermission(['warehouse_update'])"
@click="handlePutin(scope)"
>入库</el-link
>入库
</el-link
>
</template>
</el-table-column>
@ -157,7 +160,7 @@
</el-tabs>
<el-dialog title="物料检查表" :close-on-click-modal="false" :visible.sync="outerVisible">
<el-select style="width: 100%" v-model="recordform" placeholder="请选择">
<el-select style="width: 100%" v-model="recordform" placeholder="请选择" @change="recordformChange">
<el-option
v-for="item in recordformList"
:key="item.id"
@ -169,146 +172,21 @@
<el-dialog
width="50%"
title="检查项目"
:title="formName"
:visible.sync="innerVisible"
append-to-body
>
<el-form label-width="100px" label-position="right">
<el-row
:gutter="2"
v-for="(item, $index) in fieldList"
:key="$index"
>
<el-col :span="12">
<el-form-item
v-if="item.field_type === 'string'"
:label="item.field_name"
>
<el-input placeholder="请输入" v-model="item.sort" />
</el-form-item>
<el-form-item
v-else-if="item.field_type === 'int'"
:label="item.field_name"
>
<el-input
width="120"
type="number"
placeholder="请输入"
v-model="item.sort"
<customForm
:results="fieldList"
:hasPicture="hasPicture"
:formID="recordform"
:wproduct="wproduct"
@formFunc="formFunc"
/>
</el-form-item>
<el-form-item
v-else-if="item.field_type === 'float'"
:label="item.field_name"
>
<el-input
type="number"
placeholder="请输入"
v-model="item.sort"
/>
</el-form-item>
<el-form-item
v-else-if="item.field_type === 'date'"
:label="item.field_name"
>
<el-date-picker
v-model="item.create_time"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
<el-form-item
v-else-if="item.field_type === 'datetime'"
:label="item.field_name"
>
<el-date-picker
v-model="item.create_time"
type="datetime"
placeholder="选择日期"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 100%"
>
</el-date-picker>
</el-form-item>
<el-form-item
v-else-if="item.field_type === 'select'"
:label="item.field_name"
>
<el-select
style="width: 100%"
v-model="item.sort"
placeholder="请选择"
>
<el-option
v-for="item1 in item.field_choice"
:key="item1"
:label="item1"
:value="item1"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item
v-else-if="item.field_type === 'selects'"
:label="item.field_name"
>
<el-select
style="width: 100%"
v-model="optio"
multiple
placeholder="请选择"
>
<el-option
v-for="item1 in item.field_choice"
:key="item1"
:label="item1"
:value="item1"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否检验合格" width="50px">
<el-select
style="width: 100%"
v-model="item.is_testok"
placeholder="请选择"
>
<el-option
v-for="item in choice"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-form-item label="是否合格" prop="sort_str">
<el-radio v-model="is_testok" label="true">检查合格</el-radio>
<el-radio v-model="is_testok" label="false">检查不合格</el-radio>
</el-form-item>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="innerVisible = false"> </el-button>
<el-button type="primary" @click="submitfield"
>提交检查项目</el-button
>
</div>
</el-dialog>
<div slot="footer" class="dialog-footer">
<el-button @click="outerVisible = false"> </el-button>
<el-button type="primary" @click="submitrecordform"
>填写检查项目</el-button
>
<el-button type="primary" @click="submitrecordform">填写检查项目</el-button>
</div>
</el-dialog>
<el-dialog title="半成品入库" :close-on-click-modal="false" :visible.sync="dialogFormVisible">
@ -368,6 +246,7 @@
</div>
</template>
<script>
import customForm from '@/components/customForm/index';
import {getwproductList, wproductTest, wproductPutin, createputins} from "@/api/wpm";
import checkPermission from "@/utils/permission";
import {getWarehouseList} from "@/api/inm";
@ -376,7 +255,7 @@ import { genTree } from "@/utils";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
const defaultetestitem = {};
export default {
components: { Pagination },
components: {Pagination, customForm},
data() {
return {
testitem: defaultetestitem,
@ -402,6 +281,8 @@ export default {
page: 1,
page_size: 20,
},
formLabelWidth:'',
formLabelWidthL:'',
actstate_: {
6: "待复检",
10: "生产中",
@ -433,12 +314,15 @@ export default {
page: 0,
},
mutipID: [],
wproduct:null,
hasPicture: false,
outerVisible: false,
innerVisible: false,
dialogFormVisible: false,
dialogFormVisibles: false,
testrecord: {},
WarehouseData: "",
formName:'项目检查表',
};
},
computed: {},
@ -447,7 +331,7 @@ export default {
this.getList();
this.getList2();
this.getList1();
this.getLists();
// this.getLists();
},
methods: {
checkPermission,
@ -503,7 +387,11 @@ export default {
});
console.log(_this.mutipID);
createputins({warehouse:this.form.warehouse,wproducts: _this.mutipID,remark:this.form.remark}).then((res) => {
createputins({
warehouse: this.form.warehouse,
wproducts: _this.mutipID,
remark: this.form.remark
}).then((res) => {
if (res.code >= 200) {
this.$message.success("批量入库成功!");
this.dialogFormVisibles = false;
@ -513,12 +401,10 @@ export default {
},
//仓库列表
getWarehouseLists() {
getWarehouseList({page: 0}).then((response) => {
if (response.data) {
this.WarehouseData = response.data;
}
});
},
handleInspection(scope) {
@ -535,13 +421,29 @@ export default {
}
});
},
recordformChange(){
let that = this;
let arr = this.recordformList.filter(item=>{
return item.id=that.recordform;
})
that.formName = arr[0].name;
},
//根据选择的表渲染检查项目
submitrecordform() {
let that = this;
if (this.recordform != "") {
getrffieldList({ form: this.recordform, page: 0 }).then((response) => {
getrffieldList({form: this.recordform, page: 1, page_size: 100}).then((response) => {
if (response.data) {
this.fieldList = response.data;
this.innerVisible = true;
that.hasPicture = false;
let fieldList = response.data.results;
that.fieldList = [...fieldList];
let arr = fieldList.filter(item => {
return item.field_type === 'draw'
});
if (arr.length > 0) {
that.hasPicture = true;
}
that.innerVisible = true;
}
});
} else this.$message.error("请选择检查表!");
@ -566,7 +468,6 @@ export default {
this.testrecord.wproduct = this.wproduct;//半成品ID
wproductTest(this.testrecord).then((res) => {
if (res.code >= 200) {
this.innerVisible = false;
@ -578,8 +479,6 @@ export default {
});
},
//半成品入库
handlePutin(scope) {
@ -595,7 +494,18 @@ export default {
this.getList1();
}
});
}
},
};
formFunc(value) {
wproductTest(value).then((res) => {
if (res.code >= 200) {
this.innerVisible = false;
this.outerVisible = false;
this.getList();
this.getList1();
this.getList2();
}
});
},
},
}
</script>

View File

@ -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']
class IProductFilterSet(filters.FilterSet):
order = filters.NumberFilter(field_name="wproduct__subproduction_plan__production_plan__order")
class Meta:
model = IProduct
fields = ['material', 'warehouse', 'batch', 'order']

View File

@ -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='是否售出'),
),
]

View File

@ -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='关联库存产品'),
),
]

View File

@ -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)

View File

@ -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
@ -51,6 +51,7 @@ def update_inm(instance:FIFO, type:int=1):
material.save()
# 删除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()

View File

@ -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']

View File

@ -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):

View File

@ -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='已排数量'),
),
]

View File

@ -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')},
},
),
]

View File

@ -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='备注'),
),
]

View File

@ -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='物品编号'),
),
]

View File

@ -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='是否军检合格'),
),
]

View File

@ -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
def __str__(self):
return self.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'), # 联合唯一
)

View File

@ -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']

View File

@ -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)),

View File

@ -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):
@ -68,3 +76,144 @@ class OrderViewSet(CreateUpdateCustomMixin, ModelViewSet):
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
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()

View File

@ -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='工单数据'),
),
]

View File

@ -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):
"""

View File

@ -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