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

This commit is contained in:
shijing 2021-12-15 23:07:28 +08:00
commit 1f83b26fa5
31 changed files with 753 additions and 221 deletions

View File

@ -22,6 +22,7 @@
"compression-webpack-plugin": "^5.0.1", "compression-webpack-plugin": "^5.0.1",
"d3": "^5.14.2", "d3": "^5.14.2",
"dagre-d3": "^0.6.4", "dagre-d3": "^0.6.4",
"dhtmlx-gantt": "^6.2.1",
"element-ui": "^2.15.5", "element-ui": "^2.15.5",
"file-saver": "^2.0.2", "file-saver": "^2.0.2",
"fuse.js": "^6.4.6", "fuse.js": "^6.4.6",
@ -30,6 +31,7 @@
"nprogress": "0.2.0", "nprogress": "0.2.0",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-function-api": "^2.1.2",
"vue-json-editor": "^1.4.3", "vue-json-editor": "^1.4.3",
"vue-quill-editor": "^3.0.6", "vue-quill-editor": "^3.0.6",
"vue-router": "^3.5.2", "vue-router": "^3.5.2",

View File

@ -227,7 +227,7 @@ export const asyncRoutes = [
path: 'need', path: 'need',
name: 'need', name: 'need',
component: () => import('@/views/wpm/need'), component: () => import('@/views/wpm/need'),
meta: { title: '半成品检验', icon: 'example', perms: ['index_manage'] } meta: { title: '过程检验', icon: 'example', perms: ['index_manage'] }
} }
, ,
{ {
@ -320,15 +320,15 @@ export const asyncRoutes = [
{ {
path: '/qm', path: '/qm',
component: Layout, component: Layout,
redirect: '/qm/standard', redirect: '/qm/product',
name: 'qm', name: 'qm',
meta: { title: '质量管理', icon: 'example', perms: ['equipment_set'] }, meta: { title: '质量管理', icon: 'example', perms: ['equipment_set'] },
children: [ children: [
{ {
path: 'standard', path: 'product',
name: 'standard', name: 'product',
component: () => import('@/views/qm/standard'), component: () => import('@/views/qm/product'),
meta: { title: '标准', icon: 'example', perms: ['index_manage'] } meta: { title: '军检', icon: 'example', perms: ['index_manage'] }
}, },
{ {
path: 'testitem', path: 'testitem',

View File

@ -45,21 +45,7 @@
<el-tag v-else-if="scope.row.is_mtestok == true">合格</el-tag></template> <el-tag v-else-if="scope.row.is_mtestok == true">合格</el-tag></template>
</el-table-column> </el-table-column>
<el-table-column
align="center"
label="操作"
width="220px"
>
<template slot-scope="scope">
<el-link
v-if="scope.row.is_mtested == false"
@click="handleMtest(scope)"
>军检</el-link
>
</template>
</el-table-column>
</el-table> </el-table>
<pagination <pagination
v-show="iproductData.count > 0" v-show="iproductData.count > 0"
@ -68,34 +54,7 @@
:limit.sync="listQuery.page_size" :limit.sync="listQuery.page_size"
@pagination="getList" @pagination="getList"
/> />
<el-dialog
:visible.sync="dialogVisible"
:close-on-click-modal="false"
title="军检"
>
<el-form
ref="Form"
:model="mtest"
label-width="150px"
label-position="right"
>
<el-form-item label="军检是否合格">
<el-radio v-model="mtest.is_mtestok" label=True >合格</el-radio>
<el-radio v-model="mtest.is_mtestok" label=False >不合格</el-radio>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="mtest.remark" placeholder="备注" />
</el-form-item>
</el-form>
<div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="smtconfirm('Form')">确认</el-button>
</div>
</el-dialog>
</el-card> </el-card>
</div> </div>
@ -136,6 +95,7 @@ export default {
//半成品列表 //半成品列表
getList() { getList() {
this.listLoading = true; this.listLoading = true;
this.listQuery.material__type=1;
getiproductList(this.listQuery).then((response) => { getiproductList(this.listQuery).then((response) => {
if (response.data) { if (response.data) {
this.iproductData= response.data; this.iproductData= response.data;
@ -147,18 +107,7 @@ export default {
this.saleproduct=scope.row.id; this.saleproduct=scope.row.id;
this.dialogVisible=true; this.dialogVisible=true;
}, },
smtconfirm(){
saleMtest(this.saleproduct,this.mtest).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
}
});
}
}, },
}; };
</script> </script>

View File

@ -76,6 +76,7 @@ export default {
//半成品列表 //半成品列表
getList() { getList() {
this.listLoading = true; this.listLoading = true;
this.listQuery.material__type=2;
getiproductList(this.listQuery).then((response) => { getiproductList(this.listQuery).then((response) => {
if (response.data) { if (response.data) {
this.iproductData= response.data; this.iproductData= response.data;

View File

@ -0,0 +1,165 @@
<template>
<div class="app-container">
<el-card style="margin-top: 2px">
<el-table
v-loading="listLoading"
:data="iproductData.results"
border
fit
stripe
highlight-current-row
max-height="700"
height="100"
v-el-height-adaptive-table="{bottomOffset: 50}"
>
<el-table-column type="index" width="50" />
<el-table-column label="成品编号">
<template slot-scope="scope">{{ scope.row.number }}</template>
</el-table-column>
<el-table-column label="成品批次">
<template slot-scope="scope">{{ scope.row.batch }}</template>
</el-table-column>
<el-table-column label="成品名称">
<template slot-scope="scope">{{ scope.row.material_.name }}</template>
</el-table-column>
<el-table-column label="所在仓库">
<template slot-scope="scope">{{ scope.row.warehouse_.name }}</template>
</el-table-column>
<el-table-column label="是否已军检">
<template slot-scope="scope">
<el-tag v-if="scope.row.is_mtested == false">未军检</el-tag>
<el-tag v-else>已军检</el-tag></template>
</el-table-column>
<el-table-column label="军检">
<template slot-scope="scope">
<el-tag v-if="scope.row.is_mtestok == false">不合格</el-tag>
<el-tag v-else-if="scope.row.is_mtestok == true">合格</el-tag></template>
</el-table-column>
<el-table-column
align="center"
label="操作"
width="220px"
>
<template slot-scope="scope">
<el-link
v-if="scope.row.is_mtested == false"
@click="handleMtest(scope)"
>军检</el-link
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="iproductData.count > 0"
:total="iproductData.count"
:page.sync="listQuery.page"
:limit.sync="listQuery.page_size"
@pagination="getList"
/>
<el-dialog
:visible.sync="dialogVisible"
:close-on-click-modal="false"
title="军检"
>
<el-form
ref="Form"
:model="mtest"
label-width="150px"
label-position="right"
>
<el-form-item label="军检是否合格">
<el-radio v-model="mtest.is_mtestok" label=True >合格</el-radio>
<el-radio v-model="mtest.is_mtestok" label=False >不合格</el-radio>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="mtest.remark" placeholder="备注" />
</el-form-item>
</el-form>
<div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="smtconfirm('Form')">确认</el-button>
</div>
</el-dialog>
</el-card>
</div>
</template>
<script>
import { getiproductList,saleMtest} from "@/api/inm";
import checkPermission from "@/utils/permission";
import { genTree } from "@/utils";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
export default {
components: { Pagination },
data() {
return {
iproductData: {
count: 0,
},
listQuery: {
page: 1,
page_size: 20,
},
mtest: {},
salesdetail:"",
saleproduct:"",
dialogVisible:false,
};
},
computed: {},
watch: {},
created() {
this.getList();
},
methods: {
checkPermission,
//半成品列表
getList() {
this.listLoading = true;
this.listQuery.type=1;
getiproductList(this.listQuery).then((response) => {
if (response.data) {
this.iproductData= response.data;
}
this.listLoading = false;
});
},
handleMtest(scope){
this.saleproduct=scope.row.id;
this.dialogVisible=true;
},
smtconfirm(){
saleMtest(this.saleproduct,this.mtest).then((res) => {
if (res.code >= 200) {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
}
});
}
},
};
</script>

View File

@ -143,10 +143,48 @@
</el-form-item> </el-form-item>
<el-form-item label="选择产品" prop="iproducts"> <el-form-item label="选择产品" prop="iproducts">
<div class="trdiv">
<el-transfer v-model="sale.iproducts" :data="iproductoptions" :titles="['未选产品', '已选产品']" right-check-change="change"></el-transfer> <el-table
</div> v-loading="listLoading"
:data="iproductoptions"
border
fit
stripe
highlight-current-row
ref="multipleTable"
>
<el-table-column
type="selection"
width="55">
</el-table-column>
<el-table-column type="index" width="50" />
<el-table-column label="成品编号">
<template slot-scope="scope">{{ scope.row.number }}</template>
</el-table-column>
<el-table-column label="成品批次">
<template slot-scope="scope">{{ scope.row.batch }}</template>
</el-table-column>
<el-table-column label="成品名称">
<template slot-scope="scope">{{ scope.row.material_.name }}</template>
</el-table-column>
<el-table-column label="所在仓库">
<template slot-scope="scope">{{ scope.row.warehouse_.name }}</template>
</el-table-column>
<el-table-column label="是否已军检">
<template slot-scope="scope">
<el-tag v-if="scope.row.is_mtested == false">未军检</el-tag>
<el-tag v-else>已军检</el-tag></template>
</el-table-column>
</el-table>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -183,10 +221,11 @@ export default {
page: 1, page: 1,
page_size: 20, page_size: 20,
}, },
iproducts:[],
orderoptions:[], orderoptions:[],
customeroptions:[], customeroptions:[],
materialoptions:[], materialoptions:[],
iproductoptions:[], iproductoptions:"",
dialogVisible: false, dialogVisible: false,
dialogType: "new", dialogType: "new",
@ -221,19 +260,12 @@ export default {
if (response.data) { if (response.data) {
this.sale.customer = response.data.customer; this.sale.customer = response.data.customer;
this.sale.product = response.data.product; this.sale.product = response.data.product;
this.iproductoptions=[]; getiproductList({page:0,material__type:1,material:this.sale.product}).then((response) => {
getiproductList({material: this.sale.product,page:0}).then((response) => {
if (response.data) { if (response.data) {
response.data.forEach((item) => { this.iproductoptions=response.data;
this.iproductoptions.push({
label: item.number+"__"+item.material_.name+"__"+(item.is_mtested==true?"已军检":"未军检") ,
key: item.id
})
});
} }
}); });
} }
@ -270,39 +302,27 @@ export default {
}); });
}, },
selectproduct(selval)
{
this.iproductoptions=[];
getiproductList({material:selval,page:0}).then((response) => {
if (response.data) {
response.data.forEach((item) => {
this.iproductoptions.push({
label: item.number+"__"+item.material_.name+"__"+(item.is_mtested==true?"已军检":"未军检") ,
key: item.id
})
});
}
});
},
//成品 //成品
getproductList() { getproductList() {
this.iproductoptions=[],
getiproductList({page:0}).then((response) => { getiproductList({page:0,material__type:1}).then((response) => {
if (response.data) { if (response.data) {
response.data.forEach((item) => { this.iproductoptions=response.data;
this.iproductoptions.push({
label: item.number+"__"+item.material_.name+"__"+(item.is_mtested==true?"已军检":"未军检") ,
key: item.id
})
});
} }
}); });
}, },
//根据选择的产品弹出对应库里的产品
selectproduct(){
getiproductList({page:0,material__type:1,material:this.sale.product}).then((response) => {
if (response.data) {
this.iproductoptions=response.data;
}
});
},
handleFilter() { handleFilter() {
@ -320,7 +340,7 @@ export default {
this.sale = Object.assign({}, defaulteSale); this.sale = Object.assign({}, defaulteSale);
this.dialogType = "new"; this.dialogType = "new";
this.dialogVisible = true; this.dialogVisible = true;
this.iproductoptions=[]; this.iproducts=[];
this.$nextTick(() => { this.$nextTick(() => {
this.$refs["Form"].clearValidate(); this.$refs["Form"].clearValidate();
}); });
@ -364,6 +384,17 @@ export default {
} }
}); });
} else { } else {
this.$refs.multipleTable.selection.forEach((item) => {
this.iproducts.push(
item.id
);
});
this.sale.iproducts=this.iproducts;
createSale(this.sale).then((res) => { createSale(this.sale).then((res) => {
if (res.code >= 200) { if (res.code >= 200) {
this.getList(); this.getList();

View File

@ -14,4 +14,4 @@ class IProductFilterSet(filters.FilterSet):
order = filters.NumberFilter(field_name="wproduct__subproduction_plan__production_plan__order") order = filters.NumberFilter(field_name="wproduct__subproduction_plan__production_plan__order")
class Meta: class Meta:
model = IProduct model = IProduct
fields = ['material', 'warehouse', 'batch', 'order', 'is_mtested', 'is_mtestok'] fields = ['material', 'warehouse', 'batch', 'order', 'is_mtested', 'is_mtestok', 'material__type']

View File

@ -75,8 +75,8 @@ class FIFOItem(BaseModel):
""" """
出入库详细条目 出入库详细条目
""" """
is_tested = models.BooleanField('是否已检', default=False) is_tested = models.BooleanField('是否已检', default=False)
is_testok = models.BooleanField('是否检合格', default=False) is_testok = models.BooleanField('是否检合格', default=False)
warehouse = models.ForeignKey(WareHouse, on_delete=models.CASCADE, verbose_name='仓库') warehouse = models.ForeignKey(WareHouse, on_delete=models.CASCADE, verbose_name='仓库')
material = models.ForeignKey(Material, verbose_name='物料类型', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='物料类型', on_delete=models.CASCADE)
count = models.PositiveIntegerField('数量', default=0) count = models.PositiveIntegerField('数量', default=0)

View File

@ -39,7 +39,7 @@ class InventoryViewSet(ListModelMixin, GenericViewSet):
仓库物料表 仓库物料表
""" """
perms_map = {'*': '*'} perms_map = {'*': '*'}
queryset = Inventory.objects.select_related('material', 'warehouse').all() queryset = Inventory.objects.select_related('material', 'warehouse').filter(count__gt=0).all()
serializer_class = InventorySerializer serializer_class = InventorySerializer
filterset_fields = ['material', 'warehouse'] filterset_fields = ['material', 'warehouse']
search_fields = [] search_fields = []
@ -48,7 +48,7 @@ class InventoryViewSet(ListModelMixin, GenericViewSet):
class MaterialBatchViewSet(ListModelMixin, GenericViewSet): class MaterialBatchViewSet(ListModelMixin, GenericViewSet):
perms_map = {'*': '*'} perms_map = {'*': '*'}
queryset = MaterialBatch.objects.select_related('material', 'warehouse').all() queryset = MaterialBatch.objects.select_related('material', 'warehouse').filter(count__gt=0).all()
serializer_class = MaterialBatchSerializer serializer_class = MaterialBatchSerializer
# filterset_fields = ['material', 'warehouse'] # filterset_fields = ['material', 'warehouse']
filterset_class = MbFilterSet filterset_class = MbFilterSet
@ -87,14 +87,14 @@ class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
@action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=InmTestRecordCreateSerializer) @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=InmTestRecordCreateSerializer)
def test(self, request, pk=None): def test(self, request, pk=None):
""" """
""" """
serializer = InmTestRecordCreateSerializer(data=request.data) serializer = InmTestRecordCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data vdata = serializer.validated_data
record_data = vdata.pop('record_data') record_data = vdata.pop('record_data')
if 'is_testok' not in vdata: if 'is_testok' not in vdata:
raise APIException('未填写检结论') raise APIException('未填写检结论')
with transaction.atomic(): with transaction.atomic():
obj = serializer.save(create_by = self.request.user) obj = serializer.save(create_by = self.request.user)
tris = [] tris = []
@ -111,7 +111,7 @@ class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
tris.append(TestRecordItem(**m)) tris.append(TestRecordItem(**m))
TestRecordItem.objects.bulk_create(tris) TestRecordItem.objects.bulk_create(tris)
# 如果检合格 # 如果检合格
if obj.fifo_item: if obj.fifo_item:
obj.fifo_item.is_testok = True if obj.is_testok else False obj.fifo_item.is_testok = True if obj.is_testok else False
obj.fifo_item.is_tested = True obj.fifo_item.is_tested = True

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2021-12-14 07:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0041_alter_material_type'),
]
operations = [
migrations.AlterField(
model_name='recordformfield',
name='field_type',
field=models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('time', '时间'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('textarea', '文本域'), ('table', '表格'), ('draw', '绘图')], max_length=50, verbose_name='类型'),
),
]

View File

@ -143,6 +143,7 @@ class RecordFormField(CommonAModel):
FIELD_SELECTS = 'selects' FIELD_SELECTS = 'selects'
FIELD_TEXTAREA = 'textarea' FIELD_TEXTAREA = 'textarea'
FIELD_DRAW = 'draw' FIELD_DRAW = 'draw'
FIELD_TABLE = 'table'
FIELD_FROMSYSTEM = 'fromsystem' FIELD_FROMSYSTEM = 'fromsystem'
field_type_choices = ( field_type_choices = (
('string', '字符串'), ('string', '字符串'),
@ -157,6 +158,7 @@ class RecordFormField(CommonAModel):
('select', '单选下拉'), ('select', '单选下拉'),
('selects', '多选下拉'), ('selects', '多选下拉'),
('textarea', '文本域'), ('textarea', '文本域'),
('table', '表格'),
('draw', '绘图'), ('draw', '绘图'),
) )
high_rule_choices = ( high_rule_choices = (

View File

@ -0,0 +1,49 @@
# Generated by Django 3.2.9 on 2021-12-13 06:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('pm', '0016_auto_20211209_1638'),
]
operations = [
migrations.AlterField(
model_name='productionplan',
name='count',
field=models.PositiveIntegerField(default=1, verbose_name='生产数量'),
),
migrations.AlterField(
model_name='productionplan',
name='count_ok',
field=models.PositiveIntegerField(default=0, verbose_name='合格数'),
),
migrations.AlterField(
model_name='productionplan',
name='count_real',
field=models.PositiveIntegerField(default=0, verbose_name='实际产出数'),
),
migrations.AlterField(
model_name='subproductionplan',
name='main_count',
field=models.PositiveIntegerField(default=0, verbose_name='应产出数'),
),
migrations.AlterField(
model_name='subproductionplan',
name='main_count_ok',
field=models.PositiveIntegerField(default=0, verbose_name='合格数'),
),
migrations.AlterField(
model_name='subproductionplan',
name='main_count_real',
field=models.PositiveIntegerField(default=0, verbose_name='实际产出数'),
),
migrations.AlterField(
model_name='subproductionplan',
name='production_plan',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subplan_plan', to='pm.productionplan', verbose_name='关联主生产计划'),
),
]

View File

@ -14,12 +14,19 @@ class ProductionPlan(CommonAModel):
""" """
生产计划 生产计划
""" """
# PLAN_STATE_WAIT = 6
# PLAN_STATE_WORKING = 10
# PLAN_STATE_DONE = 20
# state_choices = (
# (PLAN_STATE_WORKING, '进行中'),
# (PLAN_STATE_DONE, '已完成')
# )
number = models.CharField('编号', max_length=50, unique=True) number = models.CharField('编号', max_length=50, unique=True)
order = models.ForeignKey(Order, verbose_name='关联订单', null=True, blank=True, on_delete=models.SET_NULL) order = models.ForeignKey(Order, verbose_name='关联订单', null=True, blank=True, on_delete=models.SET_NULL)
product = models.ForeignKey(Material, verbose_name='生产产品', on_delete=models.CASCADE) product = models.ForeignKey(Material, verbose_name='生产产品', on_delete=models.CASCADE)
count = models.IntegerField('生产数量', default=1) count = models.PositiveIntegerField('生产数量', default=1)
count_real = models.IntegerField('实际产出数', default=0) count_real = models.PositiveIntegerField('实际产出数', default=0)
count_ok = models.IntegerField('合格数', default=0) count_ok = models.PositiveIntegerField('合格数', default=0)
start_date = models.DateField('计划开工日期') start_date = models.DateField('计划开工日期')
end_date = models.DateField('计划完工日期') end_date = models.DateField('计划完工日期')
is_planed = models.BooleanField('是否已排产', default=False) is_planed = models.BooleanField('是否已排产', default=False)
@ -47,7 +54,7 @@ class SubProductionPlan(CommonAModel):
(SUBPLAN_STATE_DONE, '已完成') (SUBPLAN_STATE_DONE, '已完成')
) )
number = models.CharField('子计划编号', max_length=50, unique=True, null=True, blank=True) number = models.CharField('子计划编号', max_length=50, unique=True, null=True, blank=True)
production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE) production_plan = models.ForeignKey(ProductionPlan, verbose_name='关联主生产计划', on_delete=models.CASCADE, related_name='subplan_plan')
subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE, related_name='subplan_subprod') subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE, related_name='subplan_subprod')
start_date = models.DateField('计划开工日期') start_date = models.DateField('计划开工日期')
end_date = models.DateField('计划完工日期') end_date = models.DateField('计划完工日期')
@ -56,9 +63,9 @@ class SubProductionPlan(CommonAModel):
process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE) process = models.ForeignKey(Process, verbose_name='关联大工序', on_delete=models.CASCADE)
main_product = models.ForeignKey(Material, verbose_name='主要产品', on_delete=models.CASCADE, null=True, blank=True) main_product = models.ForeignKey(Material, verbose_name='主要产品', on_delete=models.CASCADE, null=True, blank=True)
main_count = models.IntegerField('应产出数', default=0) main_count = models.PositiveIntegerField('应产出数', default=0)
main_count_real = models.IntegerField('实际产出数', default=0) main_count_real = models.PositiveIntegerField('实际产出数', default=0)
main_count_ok = models.IntegerField('合格数', default=0) main_count_ok = models.PositiveIntegerField('合格数', default=0)
steps = models.JSONField('工艺步骤', default=list) steps = models.JSONField('工艺步骤', default=list)

View File

@ -23,8 +23,8 @@ def update_subplan_main(sender, instance, created, **kwargs):
if subplan.main_product.type == Material.MA_TYPE_GOOD: if subplan.main_product.type == Material.MA_TYPE_GOOD:
# 如果是产品,更新主计划进度 # 如果是产品,更新主计划进度
plan = subplan.production_plan plan = subplan.production_plan
plan.count_real = subplan.count_real plan.count_real = subplan.main_count_real
plan.count_ok = subplan.count_ok plan.count_ok = subplan.main_count_ok
plan.save() plan.save()

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.9 on 2021-12-13 06:01
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wpm', '0031_auto_20211209_1638'),
('qm', '0016_auto_20211210_1338'),
]
operations = [
migrations.AlterField(
model_name='testrecord',
name='wproduct',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='test_wproduct', to='wpm.wproduct', verbose_name='关联的动态产品'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2021-12-14 04:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qm', '0017_alter_testrecord_wproduct'),
]
operations = [
migrations.AddField(
model_name='testrecord',
name='is_submited',
field=models.BooleanField(default=False, verbose_name='是否提交'),
),
]

View File

@ -0,0 +1,26 @@
# Generated by Django 3.2.9 on 2021-12-14 07:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qm', '0018_testrecord_is_submited'),
]
operations = [
migrations.RemoveField(
model_name='testrecorditem',
name='need_judge',
),
migrations.RemoveField(
model_name='testrecorditem',
name='sort',
),
migrations.AddField(
model_name='testrecorditem',
name='is_hidden',
field=models.BooleanField(default=False, verbose_name='是否隐藏'),
),
]

View File

@ -1,7 +1,7 @@
from django.db import models from django.db import models
from django.db.models.enums import Choices from django.db.models.enums import Choices
from apps.mtm.models import RecordForm, RecordFormField from apps.mtm.models import RecordForm, RecordFormField
from apps.system.models import CommonAModel, File from apps.system.models import CommonADModel, CommonAModel, File
from utils.model import BaseModel from utils.model import BaseModel
# Create your models here. # Create your models here.
class Standard(CommonAModel): class Standard(CommonAModel):
@ -43,7 +43,7 @@ class AnalysisItem(CommonAModel):
class Meta: class Meta:
verbose_name = '检验分析项' verbose_name = '检验分析项'
class TestRecord(CommonAModel): class TestRecord(CommonADModel):
""" """
检验记录 检验记录
""" """
@ -70,6 +70,7 @@ class TestRecord(CommonAModel):
subproduction_plan = models.ForeignKey('pm.subproductionplan', verbose_name='关联的生产子计划', on_delete=models.CASCADE, null=True, blank=True) subproduction_plan = models.ForeignKey('pm.subproductionplan', verbose_name='关联的生产子计划', on_delete=models.CASCADE, null=True, blank=True)
fifo_item = models.ForeignKey('inm.fifoitem', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True) fifo_item = models.ForeignKey('inm.fifoitem', verbose_name='关联的出入库批次', on_delete=models.CASCADE, null=True, blank=True)
origin_test = models.ForeignKey('self', verbose_name='原检验记录', on_delete=models.CASCADE, null=True, blank=True) origin_test = models.ForeignKey('self', verbose_name='原检验记录', on_delete=models.CASCADE, null=True, blank=True)
is_submited = models.BooleanField('是否提交', default=False)
remark = models.TextField('备注', default='') remark = models.TextField('备注', default='')
@ -77,13 +78,12 @@ class TestRecordItem(BaseModel):
""" """
记录表格字段值 记录表格字段值
""" """
form_field = models.ForeignKey(RecordFormField, verbose_name='关联字段', on_delete=models.CASCADE, db_constraint=False) form_field = models.ForeignKey(RecordFormField, verbose_name='关联自定义表格字段', on_delete=models.CASCADE, db_constraint=False)
field_name = models.CharField('字段名', max_length=50) field_name = models.CharField('字段名', max_length=50)
field_key = models.CharField('字段标识', max_length=50) field_key = models.CharField('字段标识', max_length=50)
field_type = models.CharField('字段类型', choices=RecordForm.type_choices, max_length=50) field_type = models.CharField('字段类型', choices=RecordForm.type_choices, max_length=50)
field_value = models.JSONField('录入值', default=dict, blank=True) field_value = models.JSONField('录入值', default=dict, blank=True)
need_judge = models.BooleanField('是否需要判定', default=False) is_hidden = models.BooleanField('是否隐藏', default=False)
sort = models.IntegerField('排序号', default=1)
is_testok = models.BooleanField('是否合格', null=True, blank=True) is_testok = models.BooleanField('是否合格', null=True, blank=True)
is_testok_robot = models.BooleanField('自动判定的是否合格', null=True, blank=True) is_testok_robot = models.BooleanField('自动判定的是否合格', null=True, blank=True)
test_record = models.ForeignKey(TestRecord, verbose_name='关联的检记录', on_delete=models.CASCADE, related_name='item_test_record') test_record = models.ForeignKey(TestRecord, verbose_name='关联的检记录', on_delete=models.CASCADE, related_name='item_test_record')

View File

@ -1,8 +1,9 @@
from rest_framework import serializers from rest_framework import serializers
from apps.mtm.models import RecordForm, RecordFormField from apps.mtm.models import RecordForm, RecordFormField
from apps.mtm.serializers import RecordFormFieldSerializer, RecordFormSimpleSerializer from apps.mtm.serializers import RecordFormFieldSerializer, RecordFormSimpleSerializer
from apps.system.serializers import FileSimpleSerializer from apps.system.serializers import FileSimpleSerializer, UserSimpleSerializer
from .models import Standard, TestItem, TestRecord, TestRecordItem from .models import Standard, TestItem, TestRecord, TestRecordItem
from django.db import transaction
class StandardCreateUpdateSerializer(serializers.ModelSerializer): class StandardCreateUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
@ -38,9 +39,21 @@ class AnalysisItemSerializer(serializers.ModelSerializer):
class TestRecordItemCreateSerializer(serializers.ModelSerializer): class TestRecordItemCreateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = TestRecordItem model = TestRecordItem
fields = ['form_field', 'field_value'] fields = ['form_field', 'field_value', 'is_testok']
class TestRecordItemUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = TestRecordItem
fields = ['id', 'field_value', 'is_testok', 'is_hidden']
class TestRecordItemSerializer(serializers.ModelSerializer): class TestRecordItemSerializer(serializers.ModelSerializer):
need_judge = serializers.BooleanField(source='form_field.need_judge', read_only=True)
rule_expression = serializers.JSONField(source='form_field.rule_expression', read_only=True)
display_expression = serializers.JSONField(source='form_field.display_expression', read_only=True)
is_hidden = serializers.BooleanField(source='form_field.is_hidden', read_only=True)
help_text = serializers.CharField(source='form_field.help_text', read_only=True)
sort = serializers.IntegerField(source='form_field.sort', read_only=True)
class Meta: class Meta:
model = TestRecordItem model = TestRecordItem
fields = '__all__' fields = '__all__'
@ -52,28 +65,67 @@ class TestRecordCreateSerializer(serializers.ModelSerializer):
fields = ['form', 'record_data', 'is_testok', 'fifo_item'] fields = ['form', 'record_data', 'is_testok', 'fifo_item']
class TestRecordListSerializer(serializers.ModelSerializer): class TestRecordListSerializer(serializers.ModelSerializer):
form_ = RecordFormSimpleSerializer(source='form', read_only=True)
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
class Meta: class Meta:
model = TestRecord model = TestRecord
fields = '__all__' fields = '__all__'
class TestRecordDetailBaseSerializer(serializers.ModelSerializer):
class TestRecordDetailSerializer(serializers.ModelSerializer):
form_ = RecordFormSimpleSerializer(source='form', read_only=True) form_ = RecordFormSimpleSerializer(source='form', read_only=True)
record_data_ = TestRecordItemSerializer(source='item_test_record', read_only=True, many=True) record_data = serializers.SerializerMethodField()
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
class Meta: class Meta:
model = TestRecord model = TestRecord
fields = '__all__' fields = '__all__'
@staticmethod def get_record_data(self, obj):
def setup_eager_loading(queryset): return TestRecordItemSerializer(instance=obj.item_test_record.order_by('form_field__sort'), many=True).data
queryset = queryset.select_related('form','fifo_item')
return queryset
# def get_record_data_(self, obj): class TestRecordDetailSerializer(serializers.ModelSerializer):
# record_data = obj.record_data form_ = RecordFormSimpleSerializer(source='form', read_only=True)
# all_fields = RecordFormField.objects.filter(form=obj.form, is_deletd=False).order_by('sort') # record_data = TestRecordItemSerializer(source='item_test_record', read_only=True, many=True)
# all_fields_l = RecordFormFieldSerializer(instance=all_fields, many=True).data record_data = serializers.SerializerMethodField()
# for i in all_fields_l: origin_test_ = TestRecordDetailBaseSerializer(source='origin_test', read_only=True)
# key = i['field_key'] class Meta:
# i['field_value'] = record_data.get(key, None) model = TestRecord
# return all_fields_l fields = '__all__'
def get_record_data(self, obj):
return TestRecordItemSerializer(instance=obj.item_test_record.order_by('form_field__sort'), many=True).data
def to_representation(self, instance):
ret = super().to_representation(instance)
if instance.origin_test and instance.type == TestRecord.TEST_PROCESS_RE:
origin_test = ret['origin_test']
o_dict = {}
for i in origin_test['record_data']:
o_dict[i['field_key']] = i['field_value']
for i in ret['record_data']:
i['origin_value'] = o_dict[i['field_key']] if i['field_key'] in o_dict else None
return super().to_representation(instance)
class TestRecordItemUpdatexSerializer(serializers.Serializer):
id = serializers.PrimaryKeyRelatedField(queryset=TestRecordItem.objects.all())
field_value = serializers.JSONField(allow_null=True, required=False)
is_testok = serializers.BooleanField(allow_null=True, required=False)
is_hidden = serializers.BooleanField(default=False)
class TestRecordUpdateSerializer(serializers.ModelSerializer):
record_data = TestRecordItemUpdatexSerializer(many=True, write_only=True)
class Meta:
model = TestRecord
fields = ['is_testok', 'record_data']
def update(self, instance, validated_data):
record_data = validated_data.pop('record_data')
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
for i in record_data:
tri = i['id']
tri.field_value = i['field_value']
tri.is_testok = i['is_testok']
tri.is_hidden = i['is_hidden']
tri.save()
return instance

View File

@ -1,5 +1,6 @@
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework import exceptions, serializers
from apps.qm.serializers import StandardCreateUpdateSerializer, StandardSerializer, TestItemCreateUpdateSerializer, TestItemSerializer, TestRecordCreateSerializer, TestRecordDetailSerializer, TestRecordListSerializer from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from apps.qm.serializers import StandardCreateUpdateSerializer, StandardSerializer, TestItemCreateUpdateSerializer, TestItemSerializer, TestRecordCreateSerializer, TestRecordDetailSerializer, TestRecordListSerializer, TestRecordUpdateSerializer
from apps.qm.models import Standard, TestItem, TestRecord, TestRecordItem from apps.qm.models import Standard, TestItem, TestRecord, TestRecordItem
from django.shortcuts import render from django.shortcuts import render
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
@ -8,6 +9,10 @@ from rest_framework.exceptions import APIException
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from django.db import transaction from django.db import transaction
from rest_framework.decorators import action
from apps.wpm.models import WProduct
from apps.wpm.services import WpmServies
# Create your views here. # Create your views here.
class StandardViewSet(CreateUpdateModelAMixin, ModelViewSet): class StandardViewSet(CreateUpdateModelAMixin, ModelViewSet):
""" """
@ -43,12 +48,12 @@ class TestItemViewSet(CreateUpdateModelAMixin, ModelViewSet):
return TestItemCreateUpdateSerializer return TestItemCreateUpdateSerializer
return TestItemSerializer return TestItemSerializer
class TestRecordViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): class TestRecordViewSet(ListModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
""" """
记录 记录
""" """
perms_map = {'*': '*'} perms_map = {'*': '*'}
queryset = TestRecord.objects.select_related('fifo_item', 'form').all() queryset = TestRecord.objects.select_related('fifo_item', 'form').prefetch_related('item_test_record').all()
serializer_class = TestRecordListSerializer serializer_class = TestRecordListSerializer
filterset_fields = ['wproduct', 'material', 'step', 'subproduction_plan', 'fifo_item', 'origin_test', 'type'] filterset_fields = ['wproduct', 'material', 'step', 'subproduction_plan', 'fifo_item', 'origin_test', 'type']
ordering = ['-id'] ordering = ['-id']
@ -58,15 +63,40 @@ class TestRecordViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
return TestRecordListSerializer return TestRecordListSerializer
elif self.action == 'retrieve': elif self.action == 'retrieve':
return TestRecordDetailSerializer return TestRecordDetailSerializer
elif self.action == 'update':
return TestRecordUpdateSerializer
return super().get_serializer_class() return super().get_serializer_class()
def update(self, request, *args, **kwargs):
obj = self.get_object()
if obj.is_submited:
raise exceptions.APIException('该记录已提交不可编辑')
return super().update(request, *args, **kwargs)
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
if obj.is_submited:
raise exceptions.APIException('该记录已提交不可删除')
return super().destroy(request, *args, **kwargs)
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TestRecordUpdateSerializer)
def submit(self, request, pk=None):
obj = self.get_object()
# 校验是否有未填项目
if obj.type != TestRecord.TEST_PROCESS_RE:
if TestRecordItem.objects.filter(field_value__isnull=True, is_hidden=False).exists():
raise exceptions.APIException('存在未填写项目')
with transaction.atomic():
WpmServies.update_wproduct_by_test(obj, request.user)
return Response()
# def create(self, request, *args, **kwargs): # def create(self, request, *args, **kwargs):
# serializer = self.get_serializer(data=request.data) # serializer = self.get_serializer(data=request.data)
# serializer.is_valid(raise_exception=True) # serializer.is_valid(raise_exception=True)
# vdata = serializer.validated_data # vdata = serializer.validated_data
# record_data = vdata.pop('record_data') # record_data = vdata.pop('record_data')
# if 'is_testok' not in vdata: # if 'is_testok' not in vdata:
# raise APIException('未填写检测结论') # raise APIException('未填写检结论')
# with transaction.atomic(): # with transaction.atomic():
# obj = serializer.save(create_by = self.request.user) # obj = serializer.save(create_by = self.request.user)
# tris = [] # tris = []
@ -83,7 +113,7 @@ class TestRecordViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
# tris.append(TestRecordItem(**m)) # tris.append(TestRecordItem(**m))
# TestRecordItem.objects.bulk_create(tris) # TestRecordItem.objects.bulk_create(tris)
# # 如果检合格 # # 如果检合格
# if obj.fifo_item: # if obj.fifo_item:
# obj.fifo_item.is_testok = True if obj.is_testok else False # obj.fifo_item.is_testok = True if obj.is_testok else False
# obj.fifo_item.is_tested = True # obj.fifo_item.is_tested = True

View File

@ -0,0 +1,22 @@
from rest_framework import serializers
from apps.pm.models import ProductionPlan, SubProductionPlan
from apps.mtm.serializers import ProcessSimpleSerializer
class SubplanGanttSerializer(serializers.ModelSerializer):
count = serializers.IntegerField(source='main_count')
count_real = serializers.IntegerField(source='main_count_real')
count_ok = serializers.IntegerField(source='main_count_ok')
process_ = ProcessSimpleSerializer(source='process', read_only=True)
class Meta:
model = SubProductionPlan
fields = ['id', 'number', 'start_date', 'end_date', 'count', 'count_real', 'count_ok', 'start_date_real', 'end_date_real', 'process_']
class PlanGanttSerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = ProductionPlan
fields = ['id', 'number', 'start_date', 'end_date', 'children', 'count', 'count_real', 'count_ok']
def get_children(self, obj):
subplans = SubProductionPlan.objects.filter(production_plan=obj).order_by('process__number')
return SubplanGanttSerializer(instance=subplans, many=True).data

View File

@ -0,0 +1,13 @@
from django.db.models import base
from rest_framework import urlpatterns
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.srm.views import GanttPlan
router = DefaultRouter()
urlpatterns = [
path('gantt/plan/', GanttPlan.as_view()),
path('', include(router.urls)),
]

View File

@ -1,8 +1,17 @@
from django.shortcuts import render from django.shortcuts import render
from rest_framework import serializers
from rest_framework.generics import ListAPIView from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from apps.pm.models import ProductionPlan, SubProductionPlan
from apps.srm.serializers import PlanGanttSerializer
# Create your views here. # Create your views here.
class GanttOrder(ListAPIView): class GanttPlan(ListAPIView):
""" """
订单-计划-子计划甘特图 计划-子计划甘特图
""" """
perms_map = {'get':'*'}
serializer_class = PlanGanttSerializer
queryset = ProductionPlan.objects.filter(is_deleted=False, is_planed=True).prefetch_related('subplan_plan', 'subplan_plan__process')
ordering = ['-id']

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2021-12-13 06:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wf', '0017_auto_20211203_1501'),
]
operations = [
migrations.AddField(
model_name='workflow',
name='key',
field=models.CharField(blank=True, max_length=20, null=True, unique=True, verbose_name='工作流标识'),
),
]

View File

@ -13,7 +13,7 @@ class Workflow(CommonAModel):
工作流 工作流
""" """
name = models.CharField('名称', max_length=50) name = models.CharField('名称', max_length=50)
key = models.CharField('工作流标识', unique=True, max_length=20) key = models.CharField('工作流标识', unique=True, max_length=20, null=True, blank=True)
sn_prefix = models.CharField('流水号前缀', max_length=50, default='hb') sn_prefix = models.CharField('流水号前缀', max_length=50, default='hb')
description = models.CharField('描述', max_length=200) description = models.CharField('描述', max_length=200)
view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单') view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单')
@ -213,18 +213,6 @@ class Ticket(CommonBModel):
multi_all_person = models.JSONField('全部处理的结果', default=dict, blank=True, help_text='需要当前状态处理人全部处理时实际的处理结果json格式') multi_all_person = models.JSONField('全部处理的结果', default=dict, blank=True, help_text='需要当前状态处理人全部处理时实际的处理结果json格式')
# 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): class TicketFlow(BaseModel):
""" """
工单流转日志 工单流转日志

View File

@ -0,0 +1,36 @@
# Generated by Django 3.2.9 on 2021-12-14 04:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0018_workflow_key'),
('qm', '0018_testrecord_is_submited'),
('wpm', '0031_auto_20211209_1638'),
]
operations = [
migrations.AddField(
model_name='wproduct',
name='test',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wp_test', to='qm.testrecord', verbose_name='当前检验'),
),
migrations.AddField(
model_name='wproduct',
name='ticket',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wp_ticket', to='wf.ticket', verbose_name='当前工单'),
),
migrations.AlterField(
model_name='wproduct',
name='operation',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='wp_operation', to='wpm.operation', verbose_name='当前操作'),
),
migrations.AlterField(
model_name='wprouctticket',
name='ticket',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wt_ticket', to='wf.ticket', verbose_name='关联工单'),
),
]

View File

@ -1,4 +1,5 @@
import re import re
from rest_framework import exceptions
from django.db import models from django.db import models
from django.db.models.base import Model from django.db.models.base import Model
import django.utils.timezone as timezone import django.utils.timezone as timezone
@ -7,6 +8,7 @@ from apps.inm.models import FIFO, WareHouse
from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress
from apps.qm.models import TestRecord from apps.qm.models import TestRecord
from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File
from apps.wf.models import Ticket
from utils.model import SoftModel, BaseModel from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from apps.mtm.models import Material, Process, RecordFormField, Step, RecordForm, SubprodctionMaterial from apps.mtm.models import Material, Process, RecordFormField, Step, RecordForm, SubprodctionMaterial
@ -54,15 +56,54 @@ class WProduct(CommonAModel):
remark = models.CharField('备注', max_length=200, null=True, blank=True) remark = models.CharField('备注', max_length=200, null=True, blank=True)
subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE, related_name='wproduct_subplan') subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE, related_name='wproduct_subplan')
warehouse = models.ForeignKey(WareHouse, verbose_name='所在仓库', on_delete=models.SET_NULL, null=True, blank=True) warehouse = models.ForeignKey(WareHouse, verbose_name='所在仓库', on_delete=models.SET_NULL, null=True, blank=True)
operation = models.ForeignKey('wpm.operation', verbose_name='关联操作', operation = models.ForeignKey('wpm.operation', verbose_name='当前操作',
on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_operation') on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_operation')
test = models.ForeignKey('qm.testrecord', verbose_name='当前检验',
on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_test')
ticket = models.ForeignKey('wf.ticket', verbose_name='当前工单',
on_delete=models.SET_NULL, null=True, blank=True, related_name='wp_ticket')
@property @property
def last_process_test(self): def last_process_test(self):
""" """
最后的工序自检 最后提交的工序自检
""" """
return self.test_wproduct.filter(type=TestRecord.TEST_PROCESS).order_by('-id').first() return self.test_wproduct.filter(type=TestRecord.TEST_PROCESS, is_submited=True).order_by('-id').first()
# @property
# def last_test(self):
# """
# 最后提交的检验
# """
# return self.test_wproduct.filter(is_submited=True).order_by('-id').first()
# @property
# def current_test(self):
# """
# 当前未提交的检验
# """
# trs = self.test_wproduct.filter(is_submited=False).order_by('-id')
# if trs.count() == 1:
# return trs[0]
# elif trs.count() == 0:
# return None
# else:
# raise exceptions.APIException('存在多条未提交检验记录')
# @property
# def current_ticket(self):
# """
# 当前是否有进行中工单
# """
# tickets = Ticket.objects.filter(wt_ticket__wproduct=self, act_state=Ticket.TICKET_ACT_STATE_ONGOING).order_by('-id')
# if tickets.count() == 1:
# return tickets[0]
# elif tickets.count() == 0:
# return None
# else:
# raise exceptions.APIException('存在多条进行中工单')
class WprouctTicket(CommonAModel): class WprouctTicket(CommonAModel):
""" """
玻璃审批工单 玻璃审批工单
@ -72,7 +113,7 @@ class WprouctTicket(CommonAModel):
material = models.ForeignKey(Material, verbose_name='所在物料状态', on_delete=models.CASCADE) material = models.ForeignKey(Material, verbose_name='所在物料状态', on_delete=models.CASCADE)
step = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE) step = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE)
subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='所在子生产计划', on_delete=models.CASCADE) subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='所在子生产计划', on_delete=models.CASCADE)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.CASCADE) ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.CASCADE, related_name='wt_ticket')
class Pick(CommonADModel): class Pick(CommonADModel):
""" """

View File

@ -65,7 +65,7 @@ class PickSerializer(serializers.Serializer):
if isLowLevel: if isLowLevel:
iproducts = i.pop('iproducts') iproducts = i.pop('iproducts')
i['fifo'] = fifo i['fifo'] = fifo
i['is_testok'] = True # 默认检合格 i['is_testok'] = True # 默认检合格
i['subproduction_plan'] = sp i['subproduction_plan'] = sp
fifoitem = FIFOItem.objects.create(**i) fifoitem = FIFOItem.objects.create(**i)
# 创建再下一个层级 # 创建再下一个层级
@ -282,27 +282,29 @@ class OperationSubmitSerializer(serializers.Serializer):
class WpmTestRecordItemCreateSerializer(serializers.ModelSerializer): class WpmTestRecordItemCreateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = TestRecordItem model = TestRecordItem
fields = ['form_field', 'field_value', 'is_testok'] fields = ['form_field', 'field_value', 'is_testok', 'is_hidden']
class WpmTestRecordCreateSerializer(serializers.ModelSerializer): class WpmTestRecordCreateSerializer(serializers.ModelSerializer):
record_data = WpmTestRecordItemCreateSerializer(many=True) record_data = WpmTestRecordItemCreateSerializer(many=True)
wproduct = serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all(), required=True) wproduct = serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all(), required=True)
is_testok = serializers.BooleanField(required=False) is_testok = serializers.BooleanField(required=False)
origin_test = serializers.PrimaryKeyRelatedField(queryset=TestRecord.objects.all(), default=None)
class Meta: class Meta:
model = TestRecord model = TestRecord
fields = ['form', 'record_data', 'is_testok', 'wproduct'] fields = ['form', 'record_data', 'is_testok', 'wproduct', 'is_submited', 'origin_test']
class WpmTestFormInitSerializer(serializers.Serializer): class WpmTestFormInitSerializer(serializers.Serializer):
wproduct = serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all(), required=True) wproduct = serializers.PrimaryKeyRelatedField(queryset=WProduct.objects.all(), required=True)
form = serializers.PrimaryKeyRelatedField(queryset=RecordForm.objects.all(), required=False) form = serializers.PrimaryKeyRelatedField(queryset=RecordForm.objects.all(), required=True)
def validate(self, attrs): # def validate(self, attrs):
wproduct = attrs['wproduct'] # wproduct = attrs['wproduct']
form = attrs.get('form', None) # form = attrs.get('form', None)
if wproduct.act_state != WProduct.WPR_ACT_STATE_TORETEST: # if wproduct.act_state != WProduct.WPR_ACT_STATE_TORETEST:
if not form: # if not form:
raise exceptions.APIException('请指定检查表') # raise exceptions.APIException('请指定检查表')
return super().validate(attrs) # return super().validate(attrs)
class WplanPutInSerializer(serializers.Serializer): class WplanPutInSerializer(serializers.Serializer):
warehouse = serializers.PrimaryKeyRelatedField(queryset=WareHouse.objects.all(), label="仓库ID") warehouse = serializers.PrimaryKeyRelatedField(queryset=WareHouse.objects.all(), label="仓库ID")

View File

@ -1,7 +1,10 @@
from typing import List from typing import List
from apps.pm.models import SubProductionPlan from apps.pm.models import SubProductionPlan, SubProductionProgress
from apps.mtm.models import Step, SubprodctionMaterial from apps.mtm.models import Material, Step, SubprodctionMaterial
from apps.qm.models import TestRecord
from apps.system.models import User
from apps.wpm.models import WProduct from apps.wpm.models import WProduct
from utils.tools import ranstr
class WpmServies(object): class WpmServies(object):
@classmethod @classmethod
@ -31,4 +34,33 @@ class WpmServies(object):
""" """
splans = SubProductionPlan.objects.filter(is_deleted=False, splans = SubProductionPlan.objects.filter(is_deleted=False,
subproduction__usedstep_subproduction__step=step, state=SubProductionPlan.SUBPLAN_STATE_WORKING) subproduction__usedstep_subproduction__step=step, state=SubProductionPlan.SUBPLAN_STATE_WORKING)
return splans return splans
@classmethod
def update_wproduct_by_test(cls, test:TestRecord, user:User):
"""
根据检验结果更新玻璃及相关状态
"""
is_testok = test.is_testok
wproduct = test.wproduct
if is_testok:
if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST: # 复检
wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT
elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and wproduct.material.type == Material.MA_TYPE_GOOD: # 成品检验
wproduct.act_state = WProduct.WPR_ACT_STATE_TOFINALTEST
else:
wproduct.act_state = WProduct.WPR_ACT_STATE_OK
if wproduct.number is None: # 产生半成品编号
wproduct.number = 'WP'+ranstr(7)
# 更新子计划状态
# 更新子计划主产品数
instance = SubProductionProgress.objects.get(subproduction_plan=wproduct.subproduction_plan,
is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT)
instance.count_ok = instance.count_ok + 1 # 这个地方可能会有问题
instance.save()
else:# 如果不合格
wproduct.act_state = WProduct.WPR_ACT_STATE_NOTOK
# 需要走不合格品审理的工单
wproduct.update_by = user
wproduct.test = None
wproduct.save()

View File

@ -16,6 +16,7 @@ from apps.qm.serializers import TestRecordDetailSerializer
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from rest_framework.decorators import action from rest_framework.decorators import action
from apps.wf.models import Workflow
from apps.wpm.filters import WMaterialFilterSet from apps.wpm.filters import WMaterialFilterSet
from apps.wpm.models import OperationEquip, OperationWproduct, Pick, PickWproduct, WMaterial, WProduct, Operation, OperationMaterial, OperationRecord, OperationRecordItem from apps.wpm.models import OperationEquip, OperationWproduct, Pick, PickWproduct, WMaterial, WProduct, Operation, OperationMaterial, OperationRecord, OperationRecordItem
@ -193,26 +194,29 @@ class WProductViewSet(ListModelMixin, GenericViewSet):
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data vdata = serializer.validated_data
wproduct = vdata['wproduct'] wproduct = vdata['wproduct']
form = vdata.get('form', None) form = vdata['form']
if wproduct.test:
raise exceptions.APIException('存在进行中检验')
data = RecordFormDetailSerializer(instance=form).data
# 如果是复检记录, 需要带入原数据 data['origin_test'] = None
data['form'] = form.id
# 如果是复检, 需要带入原数据
if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST: if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST:
# 查找最近一条检验记录 # 查找最近一条检验记录
trs = wproduct.last_process_test trs = wproduct.last_process_test
if trs: if trs:
origin_test = TestRecordDetailSerializer(instance=trs).data origin_test = TestRecordDetailSerializer(instance=trs).data
data = RecordFormDetailSerializer(instance=trs.form).data data['origin_test_'] = origin_test
data['origin_test'] = origin_test data['origin_test'] = origin_test.get('id', None)
o_dict = {} o_dict = {}
for i in origin_test['record_data_']: for i in origin_test['record_data']:
o_dict[i['field_key']] = i['field_value'] o_dict[i['field_key']] = i['field_value']
for i in data['form_fields']: for i in data['form_fields']:
i['origin_value'] = o_dict[i['field_key']] if i['field_key'] in o_dict else None i['origin_value'] = o_dict[i['field_key']] if i['field_key'] in o_dict else None
i['is_hidden'] = o_dict[i['is_hidden']] if i['is_hidden'] in o_dict else False
else: else:
raise exceptions.APIException('原工序检验记录不存在') raise exceptions.APIException('原工序检验记录不存在')
else:
data = RecordFormDetailSerializer(instance=form).data
# 后续加入系统自带数据 # 后续加入系统自带数据
return Response(data) return Response(data)
@ -221,7 +225,7 @@ class WProductViewSet(ListModelMixin, GenericViewSet):
@transaction.atomic @transaction.atomic
def test(self, request, pk=None): def test(self, request, pk=None):
""" """
验记录提交
""" """
serializer = WpmTestRecordCreateSerializer(data=request.data) serializer = WpmTestRecordCreateSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -232,11 +236,13 @@ class WProductViewSet(ListModelMixin, GenericViewSet):
WProduct.WPR_ACT_STATE_TORETEST, WProduct.WPR_ACT_STATE_TOFINALTEST, WProduct.WPR_ACT_STATE_TOCOMBTEST]: WProduct.WPR_ACT_STATE_TORETEST, WProduct.WPR_ACT_STATE_TOFINALTEST, WProduct.WPR_ACT_STATE_TOCOMBTEST]:
raise exceptions.APIException('该产品当前状态不可检验') raise exceptions.APIException('该产品当前状态不可检验')
if 'is_testok' not in vdata: if 'is_testok' not in vdata:
raise exceptions.APIException('未填写检结论') raise exceptions.APIException('未填写检结论')
savedict = dict(create_by = self.request.user, savedict = dict(create_by = self.request.user,
material=wproduct.material, number=wproduct.number, subproduction_plan=wproduct.subproduction_plan, step=wproduct.step) material=wproduct.material, number=wproduct.number, subproduction_plan=wproduct.subproduction_plan, step=wproduct.step)
if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST: if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST:
if not vdata['origin_test']:
raise exceptions.APIException('自检记录不存在')
savedict['type'] = TestRecord.TEST_PROCESS_RE savedict['type'] = TestRecord.TEST_PROCESS_RE
elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOFINALTEST: elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOFINALTEST:
savedict['type'] = TestRecord.TEST_FINAL savedict['type'] = TestRecord.TEST_FINAL
@ -249,37 +255,20 @@ class WProductViewSet(ListModelMixin, GenericViewSet):
m['field_name'] = form_field.field_name m['field_name'] = form_field.field_name
m['field_key'] = form_field.field_key m['field_key'] = form_field.field_key
m['field_type'] = form_field.field_type m['field_type'] = form_field.field_type
m['sort'] = form_field.sort
m['need_judge'] = form_field.need_judge
m['is_testok'] = m['is_testok'] if 'is_testok' in m else None m['is_testok'] = m['is_testok'] if 'is_testok' in m else None
m['is_hidden'] = m['is_hidden'] if 'is_hidden' in m else None
m['test_record'] = obj m['test_record'] = obj
tris.append(TestRecordItem(**m)) tris.append(TestRecordItem(**m))
TestRecordItem.objects.bulk_create(tris) TestRecordItem.objects.bulk_create(tris)
# 如果检测合格, 变更动态产品进行状态 # 如果提交检验
if obj.is_submited:
if obj.is_testok: WpmServies.update_wproduct_by_test(obj, request.user)
if wproduct.act_state == WProduct.WPR_ACT_STATE_TORETEST: # 复检 else:
obj.origin_test = wproduct.last_process_test # 保存当前检验
obj.save() wproduct.test = obj
wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT wproduct.update_by = request.user
elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and wproduct.material.type == Material.MA_TYPE_GOOD: # 成品检验 wproduct.save()
wproduct.act_state = WProduct.WPR_ACT_STATE_TOFINALTEST
else:
wproduct.act_state = WProduct.WPR_ACT_STATE_OK
if wproduct.number is None: # 产生半成品编号
wproduct.number = 'WP'+ranstr(7)
# 更新子计划状态
# 更新子计划主产品数
instance = SubProductionProgress.objects.get(subproduction_plan=wproduct.subproduction_plan,
is_main=True, type=SubprodctionMaterial.SUB_MA_TYPE_OUT)
instance.count_ok = instance.count_ok + 1 # 这个地方可能会有问题
instance.save()
else:# 如果不合格
wproduct.act_state = WProduct.WPR_ACT_STATE_NOTOK
# 需要走不合格品审理单
wproduct.update_by = request.user
wproduct.save()
return Response() return Response()
@action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=WproductPutInsSerializer) @action(methods=['post'], detail=False, perms_map={'post':'*'}, serializer_class=WproductPutInsSerializer)
@ -379,6 +368,12 @@ class WProductViewSet(ListModelMixin, GenericViewSet):
wproduct.save() wproduct.save()
return Response() return Response()
@action(methods=['get'], detail=False, perms_map={'post':'*'})
def workflows(self, request, pk=None):
"""
可发起的工作流
"""
wfs = Workflow.objects.get()
class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet): class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin, GenericViewSet):
""" """
生产操作记录 生产操作记录
@ -619,7 +614,7 @@ class OperationEquipViewSet(ListModelMixin, DestroyModelMixin, UpdateModelMixin,
instance.delete() instance.delete()
return Response() return Response()
class OperationRecordViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet): class OperationRecordViewSet(ListModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet):
""" """
操作使用的自定义表格 操作使用的自定义表格
""" """
@ -630,6 +625,10 @@ class OperationRecordViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
ordering_fields = ['id'] ordering_fields = ['id']
ordering = ['-id'] ordering = ['-id']
def get_serializer_class(self):
if self.action == 'update':
return OperationRecordSubmitSerializer
return super().get_serializer_class()
@transaction.atomic() @transaction.atomic()
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
instance = self.get_object() instance = self.get_object()
@ -648,12 +647,14 @@ class OperationRecordViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
# 后续加入系统带入数据 # 后续加入系统带入数据
return Response(data) return Response(data)
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=OperationRecordSubmitSerializer)
def submit(self, request, pk=None): def update(self, request, *args, **kwargs):
serializer = OperationRecordSubmitSerializer(data=request.data, context={'request':self.request}) serializer = OperationRecordSubmitSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data vdata = serializer.validated_data
opr = self.get_object() opr = self.get_object()
if opr.operation.is_submited:
raise exceptions.APIException('操作已提交不可修改')
wrds = [] wrds = []
for m in vdata['record_data']: # 保存记录详情 for m in vdata['record_data']: # 保存记录详情
form_field = m['form_field'] form_field = m['form_field']

View File

@ -69,7 +69,7 @@ urlpatterns = [
path('api/qm/', include('apps.qm.urls')), path('api/qm/', include('apps.qm.urls')),
path('api/pm/', include('apps.pm.urls')), path('api/pm/', include('apps.pm.urls')),
path('api/wpm/', include('apps.wpm.urls')), path('api/wpm/', include('apps.wpm.urls')),
path('api/srm/', include('apps.srm.urls')),
# 工具 # 工具
path('api/utils/signature/', GenSignature.as_view()), path('api/utils/signature/', GenSignature.as_view()),
path('api/utils/develop/', UpdateDevelop.as_view()), path('api/utils/develop/', UpdateDevelop.as_view()),