caihgudingdan

This commit is contained in:
shilixia 2022-02-10 09:03:44 +08:00
commit efdc534a02
40 changed files with 1434 additions and 465 deletions

View File

@ -153,8 +153,7 @@ export const asyncRoutes = [
meta: { title: '产品管理', icon: 'example', perms: ['mtm_productprocess'] }
},
]
}
,
},
{
path: '/pm',
component: Layout,
@ -208,8 +207,7 @@ export const asyncRoutes = [
}
]
}
,
},
{
path: '/wpm',
component: Layout,
@ -339,8 +337,7 @@ export const asyncRoutes = [
hidden: true
}
]
}
,
},
{
path: '/qm',
component: Layout,
@ -407,7 +404,6 @@ export const asyncRoutes = [
]
},
{
path: '/personnel',
component: Layout,
@ -435,8 +431,7 @@ export const asyncRoutes = [
},
]
}
,
},
{
path: '/inm',
component: Layout,
@ -492,36 +487,74 @@ export const asyncRoutes = [
]
},
{
path: '/procurement',
component: Layout,
redirect: '/procurement/vendor',
name: 'procurement',
meta: { title: '采购管理', icon: 'example', perms: ['procurement_set'] },
children: [
{
path: 'vendor',
name: 'vendor',
component: () => import('@/views/procurement/vendor'),
meta: { title: '供应商', icon: 'example', perms: ['vendor_manage'] }
},
{
path: 'puorder',
name: 'puorder',
component: () => import('@/views/procurement/puorder'),
meta: { title: '采购订单', icon: 'example', perms: ['vendor_manage'] }
},
{
path: 'puorderitem/:id',
name: 'puorderitem',
component: () => import('@/views/procurement/puorderitem'),
meta: { title: '采购订单项', perms: ['vendor_manage'] },
hidden: true
}
]
},
{
path: '/procurement',
component: Layout,
redirect: '/procurement/vendor',
name: 'procurement',
meta: { title: '采购管理', icon: 'example', perms: ['procurement_set'] },
children: [
{
path: 'vendor',
name: 'vendor',
component: () => import('@/views/procurement/vendor'),
meta: { title: '供应商', icon: 'example', perms: ['vendor_manage'] }
},
{
path: 'puorder',
name: 'puorder',
component: () => import('@/views/procurement/puorder'),
meta: { title: '采购订单', icon: 'example', perms: ['vendor_manage'] }
},
{
path: 'puorderitem/:id',
name: 'puorderitem',
component: () => import('@/views/procurement/puorderitem'),
meta: { title: '采购订单项', perms: ['vendor_manage'] },
hidden: true
}
]
},
{
path: '/statistics',
component: Layout,
redirect: '/statistics/progressStatistics',
name: 'statistics',
meta: { title: '统计分析', icon: 'example', perms: ['workflow_manage'] },
children: [
{
path: 'progressStatistics',
name: 'progressStatistics',
component: () => import('@/views/statistics/progressStatistics'),
meta: { title: '进度统计', icon: 'example', perms: ['workflow_index'] }
},
{
path: 'materialStatistics',
name: 'materialStatistics',
component: () => import('@/views/statistics/materialStatistics'),
meta: { title: '物料统计', icon: 'example', perms: ['workflow_index'] }
},
{
path: 'personStatistics',
name: 'personStatistics',
component: () => import('@/views/statistics/personStatistics'),
meta: { title: '人员统计', icon: 'example', perms: ['workflow_index'] }
},
{
path: 'costStatistics',
name: 'costStatistics',
component: () => import('@/views/statistics/costStatistics'),
meta: { title: '成本统计', icon: 'example', perms: ['workflow_index'] }
},
{
path: 'testStatistics',
name: 'testStatistics',
component: () => import('@/views/statistics/testStatistics'),
meta: { title: '检验统计', icon: 'example', perms: ['workflow_index'] }
},
]
},
{
path: '/workflow',
component: Layout,
@ -707,6 +740,12 @@ export const asyncRoutes = [
component: () => import('@/views/testModel/faceLogin'),
meta: { title: '人脸识别登录', icon: 'example' }
},
{
path: 'caram',
name: 'caram',
component: () => import('@/views/testModel/caram'),
meta: { title: '相机调用', icon: 'example' }
},
{
path: 'markImage',
name: 'markImage',

View File

@ -8,7 +8,7 @@
<el-input
v-model="listQuery.search"
placeholder="采购订单编号、供应商名称"
placeholder="供应商名称"
style="width: 300px"
class="filter-item"
@keyup.enter.native="handleFilter"

View File

@ -0,0 +1,69 @@
<template>
<div :id="id" :class="className" style="width: 100%!important;" :style="{ height: height, width: width }"/>
</template>
<script>
export default {
name: "charts",
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '100%'
},
height: {
type: String,
default: '2.5rem'
},
options: {
type: Object,
default: () => ({})
}
},
data() {
return {
chart: null
}
},
watch: {
options: {
handler(options) {
this.chart.setOption(options, true)
},
deep: true
}
},
mounted() {
this.initChart();
/*window.addEventListener("resize",function () {
this.chart = this.$echarts.init(document.getElementById(this.id));
let myCharts=document.getElementById(this.id); // 增加这一行
myCharts.style.width=window.innerWidth-400+ 'px'; // 再增加这一行
this.$echarts.init(document.getElementById(this.id)).resize();
})*/
},
beforeDestroy() {
this.chart.dispose();
this.chart = null
},
methods: {
initChart() {
this.chart = this.$echarts.init(document.getElementById(this.id));
this.chart.setOption(this.options);
}
}
}
</script>
<style scoped>
.chartsName{
width: 100%!important;
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<div></div>
</template>
<script>
export default {
name: "largeScreen"
name: "costStatistics"
}
</script>

View File

@ -0,0 +1,364 @@
<template>
<div class="app-container">
<el-tabs v-model="activeName" type="border-card" @tab-click="handleClick">
<el-tab-pane label="废料统计" name="废料统计">
<el-row :gutter="5">
<el-col :span="3" style="height: 1px"></el-col>
<el-col :span="5">
<div class="chartsTitle">废料原因占比</div>
<Echart
:options="pieOptions"
id="pieChart"
height="400px"
width="260px"
></Echart>
</el-col>
<el-col :span="13">
<div class="chartsTitle">废料原因统计</div>
<div id="barChart" style="width:100%;height: 400px;"></div>
</el-col>
</el-row>
<div style="padding: 20px;">
<div class="chartsTitle">废料明细表</div>
<el-table
small
:data="list"
fit stripe
size="mini"
pager-count="3"
style="border-top: 1px solid #f5f5f5;"
>
<el-table-column label="序号" type="index">
</el-table-column>
<el-table-column label="物料编号" prop="card">
</el-table-column>
<el-table-column label="物料型号" prop="sco">
</el-table-column>
<el-table-column label="物料名称" prop="name" show-overflow-tooltip>
</el-table-column>
<el-table-column label="报废原因" prop="reason">
</el-table-column>
<el-table-column label="报废数量" prop="num">
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="采购统计" name="采购统计">
<el-row :gutter="5" v-if="activeName==='采购统计'">
<el-col :span="2" style="height: 1px;"></el-col>
<el-col :span="10">
<div class="chartsTitle">采购物料表</div>
<charts
:id="chartId1"
:options="barOptions"
:className="chartsName"
height="400px"
width="600px"
>
</charts>
</el-col>
<el-col :span="10">
<div class="chartsTitle">采购供应商表</div>
<charts
:id="chartId2"
:options="barOptions"
:className="chartsName"
height="400px"
width="600px"
>
</charts>
</el-col>
</el-row>
<div style="padding: 20px">
<div class="chartsTitle">采购明细表</div>
<el-table
small
:data="list"
fit stripe
size="mini"
pager-count="3"
style="border-top: 1px solid #f5f5f5;"
>
<el-table-column label="序号" type="index" width="50">
</el-table-column>
<el-table-column label="名称" prop="name" show-overflow-tooltip>
</el-table-column>
<el-table-column label="数量" prop="num" show-overflow-tooltip>
</el-table-column>
<el-table-column label="单位" prop="name" show-overflow-tooltip>
</el-table-column>
<el-table-column label="供应商" prop="sco" show-overflow-tooltip>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="废料来源" name="废料来源">
<el-row>
<el-col v-if="activeName==='废料来源'">
<div class="chartsTitle">废料来源</div>
<charts
:id="chartId3"
:options="barOptions"
:className="chartsName"
height="400px"
>
</charts>
</el-col>
</el-row>
<div style="padding: 20px;">
<div class="chartsTitle">废料来源明细表</div>
<el-table
small
:data="list"
fit stripe
size="mini"
pager-count="3"
style="border-top: 1px solid #f5f5f5;"
>
<el-table-column label="序号" type="index">
</el-table-column>
<el-table-column label="编号" prop="card">
</el-table-column>
<el-table-column label="名称" prop="name" show-overflow-tooltip>
</el-table-column>
<el-table-column label="数量" prop="num">
</el-table-column>
<el-table-column label="供应商" prop="sco" show-overflow-tooltip>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script>
import charts from './charts.vue'
import Echart from '@/components/echart';
export default {
name: "materialStatistics",
components: {
charts,
Echart
},
data() {
return {
chartId1:'chart1',
chartId2:'chart2',
chartId3:'chart3',
barChart:null,
barChart1:null,
pieOptions: {},
barOptions: {},
activeName:'废料统计',
chartsName:"chartsName",
cdata: {
xData: ["气泡", "划痕"],
seriesData: [
{value: 30, name: "气泡"},
{value: 70, name: "划痕"},
]
},
chartData: {
xAxisData: ["气泡", "划痕"],
seriesData: [30, 70],
},
xAxisbar:["供应商1", "供应商2", "供应商3", "供应商4", "供应商5", "供应商6", "供应商7"],
barData: [80, 95, 96, 96, 96, 98, 99],
list: [
{id: 1, name: 'HIehd91', card: '3331', sco: 'REF-31',num:2,reason:"气泡"},
{id: 2, name: 'HIehd92', card: '3332', sco: 'REF-32',num:1,reason:"划痕"},
{id: 3, name: 'HIehd93', card: '3333', sco: 'REF-33',num:3,reason:"擦伤"},
{id: 4, name: 'HIehd94', card: '3334', sco: 'REF-34',num:2,reason:"气泡"},
{id: 5, name: 'HIehd95', card: '3335', sco: 'REF-35',num:1,reason:"擦伤"},
{id: 6, name: 'HIehd96', card: '3336', sco: 'REF-36',num:2,reason:"气泡"},
{id: 7, name: 'HIehd97', card: '3337', sco: 'REF-37',num:1,reason:"划痕"},
],
}
},
watch: {
cdata: {
handler(newData) {
this.pieOptions = {
color: [
"#32c5e9",
"#ffdb5c"
],
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
toolbox: {
show: true
},
calculable: true,
series: [
{
name: '废料原因',
type: 'pie', // 设置图表类型为饼图
radius: '65%', // 饼图的半径外半径为可视区尺寸容器高宽中较小一项 55% 长度
center: ['50%', '50%'], //圆心位置
data:newData.seriesData
}
]
}
},
immediate: true,
},
immediate: true,
deep: true
},
methods: {
drawChart() {
let that = this;
this.barChart = this.$echarts.init(document.getElementById('barChart'));
this.barChart.setOption({
grid: {
top: '10%',
left: '3%',
right: '5%',
bottom: '1%',
containLabel: true
},
tooltip: {
trigger: 'item',
formatter: function (params) {
let color = params.color;//图例颜色
let htmlStr = '<div>';
htmlStr += params.name + '<br/>';
htmlStr += '<span style="height:10px;width:10px;font-size:12px;border-radius:5px;margin-right:5px;font-family:Consolas;display:inline-block;background:' + color + ';"></span>';
htmlStr += params.seriesName + '' + params.value + '%';
htmlStr += '</div>';
return htmlStr;
}
},
xAxis: {
axisTick: {
show: false
},
splitLine: {
show: false, //去掉X轴分割线
},
data: that.chartData.xAxisData,
},
yAxis: {
axisLine: {
show: true,//y轴线
},
axisTick: {
show: false//Y轴刻度线
},
axisLabel: {
color: '#333333'//Y轴文本颜色
},
splitLine: {
show: true, //Y轴分割线
lineStyle: {
color: '#dddddd'//Y轴分割线颜色
}
},
},
series: [{
name: '成品率',
type: 'bar',
barWidth: 20,
data: that.chartData.seriesData,
label: {
show: true, //开启显示
position: 'top', //在上方显示
formatter: '{c}%',//显示百分号
textStyle: { //数值样式
color: 'black',//字体颜色
fontSize: 10//字体大小
}
},
itemStyle: {
color: '#409EFF'
},
}]
});
},
handleClick(tab){
// console.log(tab);
// debugger;
},
},
mounted() {
this.drawChart();
this.barOptions ={
grid: {
top: '10%',
left: '3%',
right: '5%',
bottom: '1%',
containLabel: true
},
tooltip: {
trigger: 'item',
formatter: function (params) {
let color = params.color;//图例颜色
let htmlStr = '<div>';
htmlStr += params.name + '<br/>';
htmlStr += '<span style="height:10px;width:10px;font-size:12px;border-radius:5px;margin-right:5px;font-family:Consolas;display:inline-block;background:' + color + ';"></span>';
htmlStr += params.seriesName + '' + params.value + '%';
htmlStr += '</div>';
return htmlStr;
}
},
xAxis: {
axisTick: {
show: false
},
splitLine: {
show: false, //去掉X轴分割线
},
data: this.xAxisbar,
},
yAxis: {
axisLine: {
show: true,//y轴线
},
axisTick: {
show: false//Y轴刻度线
},
axisLabel: {
color: '#333333'//Y轴文本颜色
},
splitLine: {
show: true, //Y轴分割线
lineStyle: {
color: '#dddddd'//Y轴分割线颜色
}
},
},
series: [{
name: '成品率',
type: 'bar',
barWidth: 20,
data: this.barData,
label: {
show: true, //开启显示
position: 'top', //在上方显示
formatter: '{c}',//显示百分号
textStyle: { //数值样式
color: 'black',//字体颜色
fontSize: 10//字体大小
}
},
itemStyle: {
color: '#409EFF'
},
}]
}
}
}
</script>
<style scoped>
.chartsTitle{
text-align: center;
height: 35px;
line-height: 35px;
font-size: 22px;
}
</style>

View File

@ -0,0 +1,13 @@
<template>
<div></div>
</template>
<script>
export default {
name: "personStatistics"
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,175 @@
<template>
<div class="app-container">
<el-row :gutter="5">
<el-col :span="8">
<el-card>
<Echart
:options="pieOptions"
id="pieChart"
height="400px"
width="260px"
></Echart>
</el-card>
</el-col>
<el-col :span="16">
<el-card>
<charts
:id="chartId1"
:options="barOptions"
:className="chartsName"
height="400px"
width="600px"
>
</charts>
</el-card>
</el-col>
</el-row>
<el-col>
<el-card>
<charts
:id="chartId2"
:options="barOptions"
:className="chartsName"
height="480px"
width="100%"
>
</charts>
</el-card>
</el-col>
</div>
</template>
<script>
import charts from './charts.vue'
import Echart from '@/components/echart';
export default {
name: "progressStatistics",
components: {
Echart,
charts
},
data() {
return {
chartsName:'barChart',
chartId1:'chart1',
chartId2:'chart2',
pieOptions: {},
barOptions: {},
cdata: {
xData: ["交付率", "逾期率"],
seriesData: [
{value: 99, name: "交付率"},
{value: 1, name: "逾期率"},
]
},
xAxisbar:["冷加工", "热弯", "钢化", "镀膜", "夹层", "包边", "装框"],
barData: [80, 95, 96, 96, 96, 98, 99],
}
},
watch: {
cdata: {
handler(newData) {
this.pieOptions = {
color: [
"#32c5e9",
"#ffdb5c"
],
tooltip: {
trigger: "item",
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
toolbox: {
show: true
},
calculable: true,
series: [
{
name: '访问来源',
type: 'pie', // 设置图表类型为饼图
radius: '65%', // 饼图的半径外半径为可视区尺寸容器高宽中较小一项 55% 长度
center: ['50%', '50%'], //圆心位置
data:newData.seriesData
}
]
}
},
immediate: true,
},
immediate: true,
deep: true
},
methods: {
},
mounted() {
this.barOptions ={
grid: {
top: '10%',
left: '3%',
right: '5%',
bottom: '1%',
containLabel: true
},
tooltip: {
trigger: 'item',
formatter: function (params) {
let color = params.color;//图例颜色
let htmlStr = '<div>';
htmlStr += params.name + '<br/>';
htmlStr += '<span style="height:10px;width:10px;font-size:12px;border-radius:5px;margin-right:5px;font-family:Consolas;display:inline-block;background:' + color + ';"></span>';
htmlStr += params.seriesName + '' + params.value + '%';
htmlStr += '</div>';
return htmlStr;
}
},
xAxis: {
axisTick: {
show: false
},
splitLine: {
show: false, //去掉X轴分割线
},
data: this.xAxisbar,
},
yAxis: {
axisLine: {
show: true,//y轴线
},
axisTick: {
show: false//Y轴刻度线
},
axisLabel: {
color: '#333333'//Y轴文本颜色
},
splitLine: {
show: true, //Y轴分割线
lineStyle: {
color: '#dddddd'//Y轴分割线颜色
}
},
},
series: [{
name: '成品率',
type: 'bar',
barWidth: 20,
data: this.barData,
label: {
show: true, //开启显示
position: 'top', //在上方显示
formatter: '{c}%',//显示百分号
textStyle: { //数值样式
color: 'black',//字体颜色
fontSize: 10//字体大小
}
},
itemStyle: {
color: '#409EFF'
},
}]
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<template>
<div></div>
</template>
<script>
export default {
name: "testStatistics"
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,117 @@
<template>
<div>
<el-button type="primary" @click="openVideo">打开摄像头</el-button>
<el-button @click="closeVideo">关闭摄像头</el-button>
<el-button type="danger" @click="screenshot">截图</el-button>
<div>
<video id="video" ref="video" />
</div>
<canvas ref="canvas" width="400" height="300" style="display:none" />
<!-- 截图展示图片 -->
<img ref="screenshot">
</div>
</template>
<script>
export default {
name: "caram",
methods:{
// 打开摄像头
openVideo() {
// 老的浏览器可能根本没有实现 mediaDevices所以我们可以先设置一个空的对象
console.log("navigator.mediaDevices", navigator.mediaDevices);
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// 一些浏览器部分支持 mediaDevices我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性这里我们只会在没有getUserMedia属性的时候添加它
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = constraints => {
// 首先如果有getUserMedia的话就获得它
const getUserMedia =
navigator.getUserMedia ||
(navigator.getUserMedia =
navigator.mozGetUserMedia ||
navigator.webkitGetUserMedia ||
navigator.msGetUserMedia);
console.log("getUserMedia", getUserMedia);
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error("该浏览器暂不支持摄像头!"));
}
// 否则为老的navigator.getUserMedia方法包裹一个Promise
return new Promise((resolve, reject) => {
getUserMedia.call(navigator, constraints, resolve, reject);
});
};
}
const constraints = {
audio: true,
video: {
width: { ideal: 1280, max: 1920 },
height: { ideal: 720, max: 1080 }
}
};
console.log("window.navigator", window.navigator);
window.navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
const video = this.$refs.video;
// 旧的浏览器可能没有srcObject
if ("srcObject" in video) {
video.srcObject = stream;
} else {
// 防止在新的浏览器里使用它因为它已经不再支持了
video.src = window.URL.createObjectURL(stream);
}
video.onloadedmetadata = e => {
video.play();
};
})
.catch(err => {
console.log(err.name + ": " + err.message);
});
},
// 截图
screenshot() {
// 获取视频
const video = this.$refs.video;
// 获取截图的地址
const screenshot = this.$refs.screenshot;
// 获取canvas
const canvas = this.$refs.canvas;
// 渲染一个2d平面的视图
const ctx = canvas.getContext("2d");
// 设置canvas 视图文件地址和大小
ctx.drawImage(video, 0, 0, 400, 300);
// 将数据转为base64赋值给img标签的src属性
screenshot.src = canvas.toDataURL("image/png");
console.log("screenshot.src", screenshot.src);
const imgData = canvas
.toDataURL("image/png")
.replace("image/png", "image/octet-stream");
// 下载图片到本地
const save_link = document.createElementNS(
"http://www.w3.org/1999/xhtml",
"a"
);
save_link.href = imgData;
save_link.download = "file_" + new Date().getTime() + ".png";
save_link.click();
},
// 关闭摄像头
closeVideo() {
console.log("srcObject", this.$refs.video.srcObject);
const srcObject = this.$refs.video.srcObject ? this.$refs.video.srcObject.getTracks() : this.$refs.video.src.getTracks()
srcObject.forEach(track => {
track.stop();
this.$refs.video.src = null;
});
},
}
}
</script>
<style scoped>
</style>

View File

@ -2,7 +2,7 @@ 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.develop.views import CleanDataView, UpdateCuttingView, UpdateEquipState, UpdateFIFOItem, UpdateLastTestResult, UpdateNeedToOrder, UpdateSpg
from apps.develop.views import CleanDataView, UpdateCuttingView, UpdateEquipState, UpdateFIFOItem, UpdateFIFONumber, UpdateLastTestResult, UpdateNeedToOrder, UpdateSpg
urlpatterns = [
path('cleandata/', CleanDataView.as_view()),
@ -12,6 +12,7 @@ urlpatterns = [
path('update_fifoitem/', UpdateFIFOItem.as_view()),
path('update_spg/', UpdateSpg.as_view()),
path('update_equip_state/', UpdateEquipState.as_view()),
path('update_need_to_order/', UpdateNeedToOrder.as_view())
path('update_need_to_order/', UpdateNeedToOrder.as_view()),
path('update_fifo_number/', UpdateFIFONumber.as_view())
]

View File

@ -10,7 +10,7 @@ from apps.pm.models import ProductionPlan, SubProductionPlan
from apps.sam.models import Order
from apps.wf.models import Ticket
from apps.wpm.models import Operation, OperationMaterial, WProduct, WproductFlow
from apps.wpm.services import WpmServies
from apps.wpm.services import WpmService
from apps.em.tasks import update_equip_state_by_next_check_date
# Create your views here.
@ -46,7 +46,7 @@ class UpdateCuttingView(APIView):
i.coperation = op
i.save()
WproductFlow.objects.filter(wproduct=i).update(coperation=op)
WpmServies.update_cutting_list_with_operation(op)
WpmService.update_cutting_list_with_operation(op)
return Response()
from apps.qm.models import TestRecord
@ -90,7 +90,7 @@ class UpdateSpg(APIView):
冷加工重新计算合格率
"""
for i in SubProductionPlan.objects.filter(subproduction__process__id=1):
WpmServies.update_subproduction_progress_main(sp=i)
WpmService.update_subproduction_progress_main(sp=i)
return Response()
@ -105,4 +105,16 @@ class UpdateNeedToOrder(APIView):
permission_classes = [IsAdminUser]
def post(self, request):
WProduct.objects.exclude(to_order=None).update(need_to_order=True)
return Response()
class UpdateFIFONumber(APIView):
permission_classes = [IsAdminUser]
def post(self, request):
from utils.tools import ranstr
for i in FIFO.objects.all():
if i.type in [FIFO.FIFO_TYPE_DO_IN, FIFO.FIFO_TYPE_PUR_IN]:
i.number = 'RK'+ ranstr(7)
else:
i.number = 'CK' + ranstr(7)
i.save()
return Response()

View File

@ -2,7 +2,4 @@ from django.apps import AppConfig
class InmConfig(AppConfig):
name = 'apps.inm'
verbose_name = '库存管理'
def ready(self):
import apps.inm.signals
verbose_name = '库存管理'

View File

@ -0,0 +1,31 @@
# Generated by Django 3.2.9 on 2022-01-28 01:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('pum', '0004_puorder_puorderitem'),
('inm', '0029_fifo_pu_order'),
]
operations = [
migrations.AddField(
model_name='fifo',
name='number',
field=models.CharField(default=1, max_length=100, verbose_name='记录编号'),
preserve_default=False,
),
migrations.AddField(
model_name='fifo',
name='vendor',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pum.vendor', verbose_name='供应商'),
),
migrations.AlterField(
model_name='fifoitemproduct',
name='fifoitem',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='inm.fifoitem', verbose_name='关联出入库条目'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.9 on 2022-01-28 06:32
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('pum', '0004_puorder_puorderitem'),
('inm', '0030_auto_20220128_0943'),
]
operations = [
migrations.AddField(
model_name='fifoitem',
name='pu_order_item',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='pum.puorderitem', verbose_name='关联采购订单条目'),
),
]

View File

@ -2,7 +2,7 @@ from django.db import models
from django.db.models.base import Model
import django.utils.timezone as timezone
from django.db.models.query import QuerySet
from apps.pum.models import PuOrder
from apps.pum.models import PuOrder, PuOrderItem, Vendor
from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File
from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords
@ -73,12 +73,15 @@ class FIFO(CommonADModel):
(FIFO_TYPE_PUR_IN, '采购入库'),
(FIFO_TYPE_DO_IN, '生产入库')
)
number = models.CharField('记录编号', max_length=100)
type = models.IntegerField('出入库类型', default=1)
is_audited = models.BooleanField('是否审核', default=False)
auditor = models.ForeignKey(
User, verbose_name='审核人', on_delete=models.CASCADE, null=True, blank=True)
inout_date = models.DateField('出入库日期', null=True, blank=True)
remark = models.CharField('备注', max_length=1000, default='')
vendor = models.ForeignKey(Vendor, verbose_name='供应商',
on_delete=models.CASCADE, null=True, blank=True)
pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单',
null=True, blank=True, on_delete=models.CASCADE)
@ -100,6 +103,8 @@ class FIFOItem(BaseModel):
subproduction_plan = models.ForeignKey(
SubProductionPlan, verbose_name='关联子生产计划', on_delete=models.CASCADE, null=True, blank=True)
files = models.ManyToManyField(File, verbose_name='上传材料', blank=True)
pu_order_item = models.ForeignKey(PuOrderItem,
verbose_name='关联采购订单条目', null=True, blank=True, on_delete=models.CASCADE)
class IProduct(BaseModel):
@ -119,14 +124,17 @@ class IProduct(BaseModel):
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,
related_name='fifoitem_wproduct')
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,
related_name='fifoitem_wproduct')
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)
IProduct, verbose_name='关联库存产品',
null=True, blank=True, on_delete=models.SET_NULL)

View File

@ -2,6 +2,7 @@ from rest_framework import exceptions
from rest_framework import serializers
from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, MaterialBatch, WareHouse, Inventory
from apps.pum.models import PuOrder, Vendor
from apps.qm.models import TestRecord, TestRecordItem
from apps.sam.serializers import OrderSimpleSerializer
@ -10,6 +11,8 @@ from apps.system.serializers import FileSimpleSerializer, UserSimpleSerializer
from apps.mtm.serializers import MaterialSimpleSerializer
from django.db import transaction
from utils.mixins import DynamicFieldsSerializerMixin
from utils.tools import ranstr
from rest_framework.exceptions import ValidationError
@ -73,6 +76,28 @@ class FIFOListSerializer(serializers.ModelSerializer):
model = FIFO
fields = '__all__'
class FIFOItemCreateSerializer(serializers.ModelSerializer):
class Meta:
model = FIFOItem
fields = ['warehouse',
'material', 'batch', 'fifo', 'files', 'pu_order_item']
def create(self, validated_data):
fifo = validated_data['fifo']
pu_order_item = validated_data.get('pu_order_item', None)
if pu_order_item:
if fifo.pu_order != pu_order_item.pu_order:
raise ValidationError('项目与采购订单不一致')
validated_data['material']=pu_order_item.material
else:
if fifo.pu_order is not None:
raise ValidationError('非采购订单')
return super().create(validated_data)
class FIFOItemUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = FIFOItem
fields = ['warehouse', 'batch', 'files']
class FIFOItemSerializer(serializers.ModelSerializer):
warehouse_ = WareHouseSimpleSerializer(source='warehouse', read_only=True)
@ -83,14 +108,14 @@ class FIFOItemSerializer(serializers.ModelSerializer):
fields = '__all__'
class IProductInPurSerializer(serializers.ModelSerializer):
class FIFOItemProductCreateSerializer(serializers.ModelSerializer):
class Meta:
model = IProduct
fields = ['number']
class FIFODetailInPurSerializer(serializers.ModelSerializer):
details = IProductInPurSerializer(many=True, required=False)
details = FIFOItemProductCreateSerializer(many=True, required=False)
class Meta:
model = FIFOItem
@ -101,41 +126,26 @@ class MaterialBatchQuerySerializer(serializers.Serializer):
warehouse = serializers.IntegerField(label="仓库ID", required=False)
materials = serializers.ListField(child=serializers.IntegerField(label="物料ID"), required=False)
class FIFOInPurSerializer(serializers.ModelSerializer):
class FIFOInOtherSerializer(serializers.ModelSerializer):
"""
采购入库序列化
其他入库序列化
"""
details = FIFODetailInPurSerializer(many=True)
details = FIFODetailInPurSerializer(many=True, required=False)
vendor = serializers.PrimaryKeyRelatedField(label='供应商ID',
queryset=Vendor.objects.all())
class Meta:
model = FIFO
fields = ['details']
fields = ['details', 'vendor']
def create(self, validated_data):
details = validated_data.pop('details')
if len(details) > 0:
pass
else:
raise serializers.ValidationError('没有入库内容')
# for i in details:
# # 校验批次
# try:
# if i['batch']:
# obj = MaterialBatch.objects.get(batch=i['batch'], material=i['material'])
# if obj.warehouse != validated_data['warehouse']:
# raise serializers.ValidationError('批次号{}在其他仓库已存在'.format(i['batch']))
# except:
# pass
# 创建采购入库
with transaction.atomic():
validated_data['type'] = FIFO.FIFO_TYPE_PUR_IN
obj = FIFO(**validated_data)
obj.save()
for i in details:
if 'details' in i:
i['number'] = 'RK' + ranstr(7)
p_details = i.pop('details')
if len(p_details) != i['count']:
raise serializers.ValidationError('数目对不上')
@ -148,19 +158,30 @@ class FIFOInPurSerializer(serializers.ModelSerializer):
x['fifoitem'] = fifoitem
p_list0.append(FIFOItemProduct(**x))
FIFOItemProduct.objects.bulk_create(p_list0)
p_list = []
for x in p_details:
x['material'] = i['material']
x['warehouse'] = validated_data['warehouse']
x['batch'] = i['batch']
p_list.append(IProduct(**x))
IProduct.objects.bulk_create(p_list)
else:
i['fifo'] = obj
i['number'] = 'RK' + ranstr(7)
FIFOItem.objects.create(**i)
return obj
class FIFOInPurSerializer(serializers.ModelSerializer):
"""
采购入库序列化
"""
pu_order = serializers.PrimaryKeyRelatedField(label='采购订单ID',
queryset = PuOrder.objects.filter(is_audited=True))
class Meta:
model = FIFO
fields = ['pu_order']
def create(self, validated_data):
pu_order = validated_data['pu_order']
validated_data['vendor'] = pu_order.vendor
validated_data['number'] = 'RK' + ranstr(7)
obj = FIFO.objects.create(**validated_data)
return obj
class InmTestRecordItemCreateSerializer(serializers.ModelSerializer):
class Meta:

View File

@ -0,0 +1,75 @@
from rest_framework.exceptions import ValidationError
from apps.inm.models import FIFOItemProduct, IProduct, Inventory, MaterialBatch, FIFO, FIFOItem
class InmService:
@classmethod
def update_inm(cls, instance:FIFO, type:int=1):
"""
更新库存(正反)
"""
if instance.type in [FIFO.FIFO_TYPE_PUR_IN, FIFO.FIFO_TYPE_DO_IN]: # 采购入库, 生产入库
# 更新相关表
for i in FIFOItem.objects.filter(fifo=instance):
material = i.material
warehouse = i.warehouse
o1, _ = Inventory.objects.get_or_create(material=material, warehouse=warehouse, \
defaults={'material':material, 'warehouse':warehouse, 'count':0})
o1.count = o1.count + i.count
o1.save()
o2, _ = MaterialBatch.objects.get_or_create(material=material, warehouse=warehouse, batch=i.batch,\
defaults={'material':material, 'warehouse':warehouse, 'count':0, 'batch':i.batch})
o2.count = o2.count + i.count
o2.save()
material.count = material.count + i.count
material.save()
# 创建IProduct
ips2 = []
for m in FIFOItemProduct.objects.filter(fifoitem=i):
ip = {}
ip['warehouse'] = warehouse
ip['batch'] = i.batch
wp = m.wproduct
ip['wproduct'] = wp
ip['number'] = m.number
ip['material'] = m.material
ips2.append(IProduct(**ip))
IProduct.objects.bulk_create(ips2)
# 如果是采购入库更新采购订单表
if instance.type == FIFO.FIFO_TYPE_PUR_IN:
pur_order_item = i.pur_order_item
delivered_count = pur_order_item.delivered_count + i.count
if delivered_count > pur_order_item.count:
raise ValidationError('超出采购订单所需量')
pur_order_item.delivered_count = delivered_count
pur_order_item.save()
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
warehouse = i.warehouse
o1 = Inventory.objects.get(material=material, warehouse=warehouse)
temp_count = o1.count - i.count
if temp_count < 0:
raise ValidationError('库存不足,操作失败')
o1.count = temp_count
o1.save()
o2 = MaterialBatch.objects.get(material=material, warehouse=warehouse, batch=i.batch)
temp_count = o2.count - i.count
if temp_count < 0:
raise ValidationError('库存不足,操作失败')
o2.count = temp_count
o2.save()
temp_count = material.count - i.count
if temp_count < 0:
raise ValidationError('库存不足,操作失败')
material.count = temp_count
material.save()
# 删除IProduct
if instance.type == FIFO.FIFO_TYPE_DO_OUT:
# 生产领料的情况直接从IProduct中删除
numbers = FIFOItemProduct.objects.filter(fifoitem=i).values_list('number', flat=True)
IProduct.objects.filter(number__in=numbers).delete()

View File

@ -1,58 +0,0 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from apps.inm.models import FIFOItemProduct, IProduct, Inventory, MaterialBatch, FIFO, FIFOItem
def update_inm(instance:FIFO, type:int=1):
"""
更新库存(正反)
"""
if instance.type in [FIFO.FIFO_TYPE_PUR_IN, FIFO.FIFO_TYPE_DO_IN]: # 采购入库, 生产入库
# 更新相关表
for i in FIFOItem.objects.filter(fifo=instance):
material = i.material
warehouse = i.warehouse
o1, _ = Inventory.objects.get_or_create(material=material, warehouse=warehouse, \
defaults={'material':material, 'warehouse':warehouse, 'count':0})
o1.count = o1.count + i.count
o1.save()
o2, _ = MaterialBatch.objects.get_or_create(material=material, warehouse=warehouse, batch=i.batch,\
defaults={'material':material, 'warehouse':warehouse, 'count':0, 'batch':i.batch})
o2.count = o2.count + i.count
o2.save()
material.count = material.count + i.count
material.save()
# 创建IProduct
ips2 = []
for m in FIFOItemProduct.objects.filter(fifoitem=i):
ip = {}
ip['warehouse'] = warehouse
ip['batch'] = i.batch
wp = m.wproduct
ip['wproduct'] = wp
ip['number'] = m.number
ip['material'] = m.material
ips2.append(IProduct(**ip))
IProduct.objects.bulk_create(ips2)
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
warehouse = i.warehouse
o1 = Inventory.objects.get(material=material, warehouse=warehouse)
o1.count = o1.count - i.count
o1.save()
o2 = MaterialBatch.objects.get(material=material, warehouse=warehouse, batch=i.batch)
o2.count = o2.count - i.count
o2.save()
material.count = material.count - i.count
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

@ -1,15 +1,15 @@
from rest_framework import serializers
from rest_framework import exceptions
from rest_framework.exceptions import APIException
from rest_framework.mixins import DestroyModelMixin, ListModelMixin, UpdateModelMixin
from rest_framework.exceptions import APIException, ValidationError
from rest_framework.mixins import DestroyModelMixin, ListModelMixin, UpdateModelMixin, CreateModelMixin
from rest_framework.viewsets import GenericViewSet, ModelViewSet
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, FIFOItemUpdateSerializer, FIFOListSerializer, IProductListSerializer, \
from apps.inm.serializers import FIFOInOtherSerializer, FIFOItemCreateSerializer, FIFOItemSerializer, FIFOInPurSerializer, FIFOItemUpdateSerializer, FIFOListSerializer, IProductListSerializer, \
InmTestRecordCreateSerializer, MaterialBatchQuerySerializer, MaterialBatchSerializer, WareHouseSerializer, \
WareHouseCreateUpdateSerializer, InventorySerializer
from apps.inm.signals import update_inm
from apps.inm.services import InmService
from apps.qm.models import TestRecordItem
from apps.system.mixins import CreateUpdateModelAMixin
from rest_framework.decorators import action
@ -77,7 +77,7 @@ class MaterialBatchViewSet(ListModelMixin, GenericViewSet):
return Response(MaterialBatchSerializer(instance=queryset, many=True).data)
class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet):
class FIFOItemViewSet(ListModelMixin, CreateModelMixin, DestroyModelMixin, UpdateModelMixin, GenericViewSet):
"""
出入库记录详情表
"""
@ -93,12 +93,27 @@ class FIFOItemViewSet(ListModelMixin, DestroyModelMixin, UpdateModelMixin, Gener
def get_serializer_class(self):
if self.action == 'update':
return FIFOItemUpdateSerializer
elif self.action == 'create':
return FIFOItemCreateSerializer
return super().get_serializer_class()
def perform_destroy(self, instance):
if instance.fifo.is_audited:
raise APIException('该出入库记录已通过审核, 无法删除')
return super().perform_destroy(instance)
def create(self, request, *args, **kwargs):
obj = self.get_object()
if obj.fifo.is_audited:
raise ValidationError('该出入库记录已审核')
return super().create(request, *args, **kwargs)
def update(self, request, *args, **kwargs):
obj = self.get_object()
if obj.fifo.is_audited:
raise ValidationError('该出入库记录已审核')
return super().update(request, *args, **kwargs)
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
if obj.fifo.is_audited:
raise ValidationError('该出入库记录已审核')
return super().destroy(request, *args, **kwargs)
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=InmTestRecordCreateSerializer)
def test(self, request, pk=None):
@ -160,6 +175,17 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
serializer.is_valid(raise_exception=True)
serializer.save(create_by=request.user)
return Response()
@action(methods=['post'], detail=False, perms_map={'post': '*'},
serializer_class=FIFOInOtherSerializer)
def in_other(self, request, pk=None):
"""
其他入库
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(create_by=request.user)
return Response()
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=serializers.Serializer)
def audit(self, request, pk=None):
@ -167,6 +193,8 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
审核通过
"""
obj = self.get_object()
if not FIFOItem.objects.filter(fifo=obj).exists():
raise ValidationError('出入库条目为空')
for i in FIFOItem.objects.filter(fifo=obj, need_test=True):
if not i.is_testok:
raise APIException('未检验通过, 不可审核')
@ -177,7 +205,7 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
obj.auditor = request.user
obj.inout_date = timezone.now() # 也是审核日期
obj.save()
update_inm(obj) # 更新库存
InmService.update_inm(obj) # 更新库存
return Response()

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2022-01-29 07:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mtm', '0044_subproduction_need_combtest'),
]
operations = [
migrations.AddField(
model_name='usedstep',
name='need_test',
field=models.BooleanField(default=False, verbose_name='工序内检验'),
),
]

View File

@ -242,6 +242,7 @@ class UsedStep(CommonADModel):
涉及的生产子工序
"""
step = models.ForeignKey(Step, verbose_name='子工序', on_delete=models.CASCADE, related_name='usedstep')
need_test = models.BooleanField('工序内检验', default=False)
remark = models.TextField('生产备注', null=True, blank=True)
subproduction = models.ForeignKey(SubProduction, verbose_name='关联生产分解', on_delete=models.CASCADE, related_name='usedstep_subproduction')

View File

@ -3,7 +3,7 @@ from apps.mtm.models import Material, Step
from apps.pm.models import ProductionPlan, SubProductionProgress
from apps.wpm.models import Operation, WProduct
from datetime import *
from apps.wpm.services import WpmServies
from apps.wpm.services import WpmService
from django.db.models import F
from utils.mixins import DynamicFieldsFilterMixin
@ -52,9 +52,9 @@ class SubproductionProgressFilterSet(filters.FilterSet):
wproducts = WProduct.objects.filter(ow_wproduct__operation=value)
step = operation.step
if wproducts.exists():
subplans = WpmServies.get_subplans_queryset_from_wproducts(wproducts)
subplans = WpmService.get_subplans_queryset_from_wproducts(wproducts)
else:
subplans = WpmServies.get_subplans_queyset_from_step(step)
subplans = WpmService.get_subplans_queyset_from_step(step)
queryset = queryset.filter(subproduction_plan__in=subplans)
if step.type == Step.STEP_TYPE_NOM:
queryset = queryset.exclude(material__type__in =[Material.MA_TYPE_HALFGOOD, Material.MA_TYPE_GOOD])

View File

@ -89,7 +89,8 @@ class ProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, CreateModel
subps = SubProduction.objects.filter(product=production_plan.product).order_by('process__number')
for index, i in enumerate(subps):
steps = Step.objects.filter(usedstep__subproduction=i, usedstep__subproduction__is_deleted=False,
usedstep__is_deleted=False, is_deleted=False).values('id', 'number', 'name', 'usedstep__remark')
usedstep__is_deleted=False, is_deleted=False
).values('id', 'number', 'name', 'usedstep__remark', need_test=F('usedstep__need_test'))
instance = SubProductionPlan.objects.create(production_plan=production_plan, subproduction=i,
start_date=production_plan.start_date, end_date=production_plan.end_date,
workshop=i.process.workshop, process=i.process, create_by=request.user,

View File

@ -1,8 +1,7 @@
from rest_framework.serializers import ModelSerializer
from rest_framework.serializers import ModelSerializer, ValidationError
from apps.mtm.serializers import MaterialSimpleSerializer
from .models import PuOrder, PuOrderItem, Vendor
class VendorSerializer(ModelSerializer):
class Meta:
model = Vendor
@ -19,6 +18,22 @@ class PuOrderItemSerializer(ModelSerializer):
model = PuOrderItem
fields = '__all__'
class PuOrderItemCreateSerializer(ModelSerializer):
class Meta:
model = PuOrderItem
fields = ['count', 'delivery_date', 'pu_order', 'material']
def validate(self, attrs):
pu_order = attrs['pu_order']
if pu_order.is_audited:
raise ValidationError('采购订单已审核')
return super().validate(attrs)
class PuOrderItemUpdateSerializer(ModelSerializer):
class Meta:
model = PuOrderItem
fields = ['count', 'delivery_date']
class PuOrderSerializer(ModelSerializer):
vendor_ = VendorSimpleSerializer(source='vendor', read_only=True)
items = PuOrderItemSerializer(source='item_pu_order', many=True, read_only=True)
@ -28,4 +43,5 @@ class PuOrderSerializer(ModelSerializer):
class PuOrderCreateUpdateSerializer(ModelSerializer):
class Meta:
model = PuOrder
fields = ['number', 'vendor']

View File

@ -1,11 +1,13 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.pum.views import VendorViewSet
from apps.pum.views import PuOrderItemViewSet, PuOrderViewSet, VendorViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('vendor', VendorViewSet, basename='vendor')
router.register('pu_order', PuOrderViewSet, basename='pu_order')
router.register('pu_order_item', PuOrderItemViewSet, basename='pu_order_item')
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -1,13 +1,17 @@
from django.shortcuts import render
from numpy import delete
from rest_framework.viewsets import ModelViewSet
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin
from rest_framework.viewsets import GenericViewSet
from apps.pum.models import PuOrder, Vendor
from apps.pum.serializers import PuOrderCreateUpdateSerializer, PuOrderSerializer, VendorSerializer
from apps.pum.models import PuOrder, PuOrderItem, Vendor
from apps.pum.serializers import PuOrderCreateUpdateSerializer, PuOrderItemCreateSerializer, PuOrderItemSerializer, PuOrderItemUpdateSerializer, PuOrderSerializer, VendorSerializer
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from rest_framework.exceptions import APIException, ValidationError
from rest_framework.decorators import action
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework import status
# Create your views here.
class VendorViewSet(CreateUpdateModelAMixin, ModelViewSet):
"""
@ -41,7 +45,49 @@ class PuOrderViewSet(CreateUpdateModelAMixin, ModelViewSet):
return PuOrderCreateUpdateSerializer
return super().get_serializer_class()
def update(self, request, *args, **kwargs):
obj = self.get_object()
if obj.is_audited:
raise ValidationError('该采购订单已审核')
return super().update(request, *args, **kwargs)
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
if obj.is_audited:
raise ValidationError('该采购订单已审核')
return super().destroy(request, *args, **kwargs)
@action(methods=['post'], detail=True, perms_map={'post':'*'},
serializer_class=serializers.Serializer)
def audit(self, request, pk=None):
obj = self.get_object()
if obj.item_pu_order.exists() and not obj.is_audited:
obj.is_audited = True
obj.update_by = request.user
obj.save()
return Response()
return Response('订单状态有误', status=status.HTTP_400_BAD_REQUEST)
class PuOrderItemViewSet(CreateModelMixin, DestroyModelMixin, GenericViewSet):
pass
class PuOrderItemViewSet(CreateModelMixin, ListModelMixin, DestroyModelMixin, GenericViewSet):
"""
采购订单条目
"""
perms_map = {'get': '*', 'post': '*',
'put': '*', 'delete': '*'}
queryset = PuOrderItem.objects.select_related('material').all()
serializer_class = PuOrderItemSerializer
filterset_fields = ['pu_order', 'material']
ordering = ['-create_time']
def get_serializer_class(self):
if self.action == 'create':
return PuOrderItemCreateSerializer
elif self.action == 'update':
return PuOrderItemUpdateSerializer
return super().get_serializer_class()
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
if obj.pu_order.is_audited:
raise ValidationError('所属采购已审核')
return super().destroy(request, *args, **kwargs)

View File

@ -0,0 +1,36 @@
# Generated by Django 3.2.9 on 2022-01-29 07:12
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('qm', '0022_auto_20211216_1401'),
]
operations = [
migrations.AddField(
model_name='testrecord',
name='is_midtesing',
field=models.BooleanField(default=False, verbose_name='是否子工序检验中'),
),
migrations.AddField(
model_name='testrecorditem',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='testrecorditem_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AddField(
model_name='testrecorditem',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='testrecorditem_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AlterField(
model_name='testrecord',
name='type',
field=models.PositiveSmallIntegerField(choices=[(20, '工序检验'), (30, '工序复检'), (36, '夹层检验'), (40, '成品检验')], default=20),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2022-01-29 07:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('qm', '0023_auto_20220129_1512'),
]
operations = [
migrations.RenameField(
model_name='testrecord',
old_name='is_midtesing',
new_name='is_midtesting',
),
]

View File

@ -47,13 +47,11 @@ class TestRecord(CommonADModel):
"""
检验记录
"""
TEST_STEP = 10
TEST_PROCESS = 20
TEST_PROCESS_RE = 30
TEST_COMB = 36
TEST_FINAL = 40
type_choice = (
(TEST_STEP, '子工序检验'),
(TEST_PROCESS, '工序检验'),
(TEST_PROCESS_RE, '工序复检'),
(TEST_COMB, '夹层检验'),
@ -70,10 +68,11 @@ class TestRecord(CommonADModel):
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)
is_submited = models.BooleanField('是否提交', default=False)
is_midtesting = models.BooleanField('是否子工序检验中', default=False)
remark = models.TextField('备注', default='')
class TestRecordItem(BaseModel):
class TestRecordItem(CommonADModel):
"""
记录表格字段值
"""

View File

@ -124,14 +124,18 @@ class TestRecordUpdateSerializer(serializers.ModelSerializer):
fields = ['is_testok', 'record_data']
def update(self, instance, validated_data):
# 获取更新人
update_by = self.context['request'].user
record_data = validated_data.pop('record_data')
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
instance.save(update_by=update_by)
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()
if i['field_value'] != tri.field_value:
tri.field_value = i['field_value']
tri.update_by = update_by
tri.is_testok = i['is_testok']
tri.is_hidden = i['is_hidden']
tri.save()
return instance

View File

@ -12,7 +12,7 @@ from django.db import transaction
from rest_framework.decorators import action
from apps.wpm.models import WProduct
from apps.wpm.services import WpmServies
from apps.wpm.services import WpmService
# Create your views here.
class StandardViewSet(CreateUpdateModelAMixin, ModelViewSet):
"""
@ -69,7 +69,7 @@ class TestRecordViewSet(ListModelMixin, UpdateModelMixin, RetrieveModelMixin, De
def update(self, request, *args, **kwargs):
obj = self.get_object()
if obj.is_submited:
if obj.is_submited and obj.is_midtesting is False:
raise exceptions.APIException('该记录已提交不可编辑')
return super().update(request, *args, **kwargs)
@ -77,20 +77,22 @@ class TestRecordViewSet(ListModelMixin, UpdateModelMixin, RetrieveModelMixin, De
obj = self.get_object()
if obj.is_submited:
raise exceptions.APIException('该记录已提交不可删除')
WpmServies.add_wproduct_flow_log(obj.wproduct, 'test_delete')
WpmService.add_wproduct_flow_log(obj.wproduct, 'test_delete')
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.is_submited and obj.is_midtesting is False:
raise exceptions.APIException('该记录已提交')
# 校验是否有未填项目
if obj.type != TestRecord.TEST_PROCESS_RE:
if obj.type != TestRecord.TEST_PROCESS_RE and obj.is_midtesting is False:
if TestRecordItem.objects.filter(field_value__isnull=True, is_hidden=False, test_record=obj).exists():
raise exceptions.APIException('存在未填写项目')
with transaction.atomic():
obj.is_submited=True
obj.save()
WpmServies.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算
WpmService.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算
return Response()
# def create(self, request, *args, **kwargs):

View File

@ -2,6 +2,7 @@ from rest_framework.mixins import ListModelMixin, DestroyModelMixin, CreateModel
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, WareHouse
from apps.inm.services import InmService
from apps.mtm.models import Material
from apps.sam.models import Sale, SaleProduct
from apps.sam.serializers_sale import SaleCreateSerializer, SaleListSerializer, SaleProductCreateSerializer, SaleProductListSerializer
@ -11,10 +12,11 @@ from rest_framework.decorators import action
from django.utils import timezone
from apps.system.mixins import CreateUpdateModelAMixin
from apps.inm.signals import update_inm
from rest_framework import serializers
from django.db.models import Count
from utils.tools import ranstr
class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, CreateModelMixin, DestroyModelMixin, GenericViewSet):
"""
销售记录
@ -71,6 +73,7 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
fifo.auditor = request.user
fifo.inout_date = timezone.now()
fifo.create_by = request.user
fifo.number = 'CK' + ranstr(7)
fifo.save()
# 创建出库条目
ips = IProduct.objects.filter(sale_iproduct__sale=obj)
@ -104,7 +107,7 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update(
act_state=WProduct.WPR_ACT_STATE_SELLED)
# 更新库存
update_inm(fifo)
InmService.update_inm(fifo)
# 变更审核状态
obj.is_audited = True
obj.save()

View File

@ -1,7 +1,7 @@
from django_filters import rest_framework as filters
from apps.mtm.models import Material, Step
from apps.wpm.services import WpmServies
from apps.wpm.services import WpmService
from utils.mixins import DynamicFieldsFilterMixin
from .models import Operation, OperationMaterial, OperationRecord, WMaterial, WProduct
@ -20,10 +20,10 @@ class WMaterialFilterSet(filters.FilterSet):
wproducts = WProduct.objects.filter(ow_wproduct__operation=value)
step = operation.step
if wproducts.exists():
subplans = WpmServies.get_subplans_queryset_from_wproducts(
subplans = WpmService.get_subplans_queryset_from_wproducts(
wproducts)
else:
subplans = WpmServies.get_subplans_queyset_from_step(step)
subplans = WpmService.get_subplans_queyset_from_step(step)
queryset = queryset.filter(subproduction_plan__in=subplans).exclude(
material__type=Material.MA_TYPE_HALFGOOD)
return queryset

View File

@ -0,0 +1,65 @@
# Generated by Django 3.2.9 on 2022-01-29 07:12
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mtm', '0045_usedstep_need_test'),
('wpm', '0052_auto_20220125_1116'),
]
operations = [
migrations.AddField(
model_name='wproduct',
name='material_check',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wp_material_check', to='mtm.material', verbose_name='按物料状态检查'),
),
migrations.AddField(
model_name='wproductflow',
name='material_check',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wpf_material_check', to='mtm.material', verbose_name='按物料状态检查'),
),
migrations.AlterField(
model_name='wproduct',
name='child',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wp_child', to='wpm.wproduct'),
),
migrations.AlterField(
model_name='wproduct',
name='material',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wp_material', to='mtm.material', verbose_name='所属物料状态'),
),
migrations.AlterField(
model_name='wproduct',
name='pre_step',
field=models.ForeignKey(blank=True, help_text='已执行完的步骤', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wp_pre_step', to='mtm.step', verbose_name='已执行到'),
),
migrations.AlterField(
model_name='wproduct',
name='step',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wp_step', to='mtm.step', verbose_name='所在步骤'),
),
migrations.AlterField(
model_name='wproductflow',
name='child',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wpf_child', to='wpm.wproductflow'),
),
migrations.AlterField(
model_name='wproductflow',
name='material',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wpf_material', to='mtm.material', verbose_name='所属物料状态'),
),
migrations.AlterField(
model_name='wproductflow',
name='pre_step',
field=models.ForeignKey(blank=True, help_text='已执行完的步骤', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wpf_pre_step', to='mtm.step', verbose_name='已执行到'),
),
migrations.AlterField(
model_name='wproductflow',
name='step',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='wpf_step', to='mtm.step', verbose_name='所在步骤'),
),
]

View File

@ -1,4 +1,5 @@
import re
from rest_framework import exceptions
from django.db import models
from django.db.models.base import Model
@ -88,16 +89,20 @@ class WProduct(CommonAModel):
number = models.CharField(
'物品编号', unique=True, null=True, blank=True, max_length=50)
material = models.ForeignKey(
Material, verbose_name='所属物料状态', on_delete=models.CASCADE)
Material, verbose_name='所属物料状态', on_delete=models.CASCADE,
related_name='wp_material')
material_check = models.ForeignKey(
Material, verbose_name='按物料状态检查', on_delete=models.CASCADE,
null=True, blank=True, related_name='wp_material_check')
pre_step = models.ForeignKey(Step, verbose_name='已执行到', help_text='已执行完的步骤', null=True, blank=True,
on_delete=models.CASCADE, related_name='w_pre_step')
on_delete=models.CASCADE, related_name='wp_pre_step')
step = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True,
related_name='w_step')
related_name='wp_step')
act_state = models.IntegerField(
'进行状态', default=0, choices=act_state_choices)
is_hidden = models.BooleanField('是否隐藏', default=False)
child = models.ForeignKey('self', blank=True, null=True,
on_delete=models.CASCADE, related_name='wproduct_child')
on_delete=models.CASCADE, related_name='wp_child')
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')
@ -164,16 +169,19 @@ class WproductFlow(CommonAModel):
WProduct, on_delete=models.CASCADE, verbose_name='关联产品', null=True, blank=True)
number = models.CharField('物品编号', null=True, blank=True, max_length=50)
material = models.ForeignKey(
Material, verbose_name='所属物料状态', on_delete=models.CASCADE)
Material, verbose_name='所属物料状态', on_delete=models.CASCADE, related_name='wpf_material')
material_check = models.ForeignKey(
Material, verbose_name='按物料状态检查', on_delete=models.CASCADE,
null=True, blank=True, related_name='wpf_material_check')
pre_step = models.ForeignKey(Step, verbose_name='已执行到', help_text='已执行完的步骤', null=True, blank=True,
on_delete=models.CASCADE, related_name='wl_pre_step')
on_delete=models.CASCADE, related_name='wpf_pre_step')
step = models.ForeignKey(Step, verbose_name='所在步骤', on_delete=models.CASCADE, null=True, blank=True,
related_name='wl_step')
related_name='wpf_step')
act_state = models.IntegerField(
'进行状态', default=0, choices=WProduct.act_state_choices)
is_hidden = models.BooleanField('是否隐藏', default=False)
child = models.ForeignKey('self', blank=True, null=True,
on_delete=models.CASCADE, related_name='wproduct_child')
on_delete=models.CASCADE, related_name='wpf_child')
remark = models.CharField('备注', max_length=200, null=True, blank=True)
subproduction_plan = models.ForeignKey(
SubProductionPlan, verbose_name='当前子生产计划', on_delete=models.CASCADE)

View File

@ -4,8 +4,8 @@ from rest_framework.serializers import ModelSerializer
from apps.em.models import Equipment
from apps.em.serializers import EquipmentSimpleSerializer
from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct, IProduct, MaterialBatch, WareHouse
from apps.inm.signals import update_inm
from apps.inm.serializers import WareHouseSimpleSerializer
from apps.inm.services import InmService
from apps.mtm.models import Material, RecordForm, RecordFormField, Step, SubprodctionMaterial
from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, RecordFormSimpleSerializer, StepSimpleSerializer
@ -21,6 +21,7 @@ from apps.wpm.models import Operation, OperationEquip, OperationMaterial, Operat
from django.db import transaction
from apps.sam.models import Order
from utils.mixins import DynamicFieldsSerializerMixin
from utils.tools import ranstr
class PickHalfSerializer(serializers.Serializer):
id = serializers.PrimaryKeyRelatedField(queryset=SubProductionProgress.objects.all(), label='子计划进度ID')
@ -29,12 +30,14 @@ class PickHalfSerializer(serializers.Serializer):
class PickHalfsSerializer(serializers.ListSerializer):
child = PickHalfSerializer()
class PickDetailSerializer(serializers.Serializer):
material = serializers.PrimaryKeyRelatedField(queryset=Material.objects.all(), label="物料ID")
batch = serializers.CharField(label='物料批次', allow_blank=True)
warehouse = serializers.PrimaryKeyRelatedField(queryset=WareHouse.objects.all(), label="仓库ID")
pick_count = serializers.IntegerField(label="领料数量", required=False)
iproducts = serializers.PrimaryKeyRelatedField(queryset=IProduct.objects.all(), label='库存半成品ID',required=False, many=True)
iproducts = serializers.PrimaryKeyRelatedField(queryset=IProduct.objects.all(), label='库存半成品ID',
required=False, many=True)
class PickSerializer(serializers.Serializer):
subproduction_plan=serializers.PrimaryKeyRelatedField(queryset=SubProductionPlan.objects.all(), label="子计划ID")
@ -58,7 +61,9 @@ class PickSerializer(serializers.Serializer):
# 创建出库记录
with transaction.atomic():
fifo = FIFO.objects.create(type=FIFO.FIFO_TYPE_DO_OUT, inout_date=timezone.now(), create_by=self.context['request'].user)
fifo = FIFO.objects.create(type=FIFO.FIFO_TYPE_DO_OUT,
inout_date=timezone.now(), create_by=self.context['request'].user,
number = 'CK' + ranstr(7))
for i in picks:
isLowLevel = False
# 更新出库详情
@ -123,7 +128,7 @@ class PickSerializer(serializers.Serializer):
# 更新库存
fifo.is_audited = True
fifo.save()
update_inm(fifo)
InmService.update_inm(fifo)
return fifo
class WMaterialListSerializer(serializers.ModelSerializer):

View File

@ -9,19 +9,21 @@ from apps.system.models import User
from apps.wf.models import State, TicketFlow, Transition
from apps.wpm.models import Operation, OperationMaterial, WProduct, WproductFlow, WprouctTicket
from utils.tools import ranstr
class WpmServies(object):
class WpmService(object):
@classmethod
def get_next_step(cls, subproduction_plan:SubProductionPlan, nowstep:Step):
"""
获取下一步骤
"""
stepIds = [i['id'] for i in subproduction_plan.steps]
steps_list = subproduction_plan.steps
stepIds = [i['id'] for i in steps_list]
pindex = stepIds.index(nowstep.id)
need_test = steps_list[pindex].get('need_test', False)
if pindex + 1 < len(stepIds):
return Step.objects.get(pk=stepIds[pindex+1]), True
return Step.objects.get(pk=stepIds[pindex+1]), need_test
else:
return nowstep, False
return nowstep, need_test
@classmethod
def get_subplans_queryset_from_wproducts(cls, wproducts:List):
@ -47,9 +49,15 @@ class WpmServies(object):
"""
is_testok = test.is_testok
wproduct = test.wproduct
test_i = None
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 \
test.is_midtesing is True:
wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT
test_i = test
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
@ -97,7 +105,7 @@ class WpmServies(object):
wproduct.update_by = user
wproduct.update_time = timezone.now()
wproduct.test = None
wproduct.test = test_i
wproduct.last_test_result = is_testok
wproduct.save()
# 添加日志

View File

@ -8,7 +8,7 @@ from django.dispatch import receiver
from rest_framework import exceptions
from apps.wpm.models import WProduct, WproductFlow, WprouctTicket
from apps.wpm.models import OperationWproduct
from apps.wpm.services import WpmServies
from apps.wpm.services import WpmService
@receiver(post_save, sender=Ticket)
@ -34,7 +34,7 @@ def handleTicket(sender, instance, created, **kwargs):
# 工单绑定半成品
wproduct.ticket = instance
wproduct.save()
WpmServies.add_wproduct_flow_log(wproduct, 'ticket_create')
WpmService.add_wproduct_flow_log(wproduct, 'ticket_create')
elif instance.act_state == Ticket.TICKET_ACT_STATE_FINISH:
@ -77,7 +77,7 @@ def handleTicket(sender, instance, created, **kwargs):
wp.save()
# 添加日志
WpmServies.add_wproduct_flow_log(wp, 'ticket_finish')
WpmService.add_wproduct_flow_log(wp, 'ticket_finish')
# 更新子计划合格进度
WpmServies.update_subproduction_progress_main(sp=wp.subproduction_plan)
WpmService.update_subproduction_progress_main(sp=wp.subproduction_plan)

View File

@ -3,7 +3,7 @@ from rest_framework import urlpatterns
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.wpm.views import CuttingListViewSet, DoFormInit, DoFormSubmit, OperationEquipViewSet, OperationMaterialInputViewSet, OperationMaterialOutputViewSet, OperationMaterialToolViewSet, OperationRecordViewSet, OperationViewSet, OperationWproductViewSet, WMaterialViewSet, WPlanViewSet, WProductViewSet, WproductTicketViewSet
from apps.wpm.views import CuttingListViewSet, OperationEquipViewSet, OperationMaterialInputViewSet, OperationMaterialOutputViewSet, OperationMaterialToolViewSet, OperationRecordViewSet, OperationViewSet, OperationWproductViewSet, WMaterialViewSet, WPlanViewSet, WProductViewSet, WproductTicketViewSet
router = DefaultRouter()
router.register('wmaterial', WMaterialViewSet, basename='wmaterial')
@ -19,8 +19,6 @@ router.register('operation_tool', OperationMaterialToolViewSet, basename='operat
router.register('subplan', WPlanViewSet, basename='wplan')
router.register('cutting_list', CuttingListViewSet, basename='cutting_list')
urlpatterns = [
path('do/init/', DoFormInit.as_view()),
path('do/submit/', DoFormSubmit.as_view()),
path('', include(router.urls)),
]

View File

@ -3,7 +3,7 @@ from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, \
ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.viewsets import GenericViewSet
from apps.inm.models import FIFO, FIFOItem, FIFOItemProduct
from apps.inm.signals import update_inm
from apps.inm.services import InmService
from apps.mtm.models import Material, RecordForm, RecordFormField, Step, SubprodctionMaterial, TechDoc
from apps.mtm.serializers import RecordFormDetailSerializer, SubprodctionMaterialListSerializer, TechDocListSerializer
from apps.pm.models import SubProductionPlan, SubProductionProgress
@ -32,11 +32,13 @@ from apps.wpm.serializers import CuttingListSerializer, OperationEquipListSerial
from rest_framework.response import Response
from django.db import transaction
from rest_framework import exceptions, serializers
from apps.wpm.services import WpmServies
from apps.wpm.services import WpmService
from django.utils import timezone
from rest_framework import status
from django.db.models import Count
from utils.tools import ranstr
# Create your views here.
@ -103,7 +105,7 @@ class WPlanViewSet(ListModelMixin, GenericViewSet):
m.update_by = request.user
m.update_time = timezone.now()
m.save()
WpmServies.add_wproduct_flow_log(
WpmService.add_wproduct_flow_log(
instance=m, change_str='pick_half')
pw = PickWproduct()
pw.pick = pick
@ -149,7 +151,7 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
perms_map = {'*': '*'}
queryset = WProduct.objects.select_related('step', 'material',
'subproduction_plan', 'warehouse', 'subproduction_plan__production_plan__order',
'to_order').prefetch_related('wproduct_child')
'to_order').prefetch_related('wp_child')
serializer_class = WProductListSerializer
filterset_class = WProductFilterSet
search_fields = ['number']
@ -186,9 +188,8 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
WProduct.WPR_ACT_STATE_TORETEST, WProduct.WPR_ACT_STATE_TOFINALTEST,
WProduct.WPR_ACT_STATE_TOCOMBTEST]:
raise exceptions.APIException('该产品当前状态不可检验')
savedict = dict(
create_by=self.request.user,
create_by=request.user,
wproduct=wproduct,
material=wproduct.material,
number=wproduct.number,
@ -206,21 +207,26 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
savedict['type'] = TestRecord.TEST_FINAL
elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOCOMBTEST:
savedict['type'] = TestRecord.TEST_COMB
elif wproduct.act_state == WProduct.WPR_ACT_STATE_TOTEST and\
wproduct.step != wproduct.pre_step: # 如果是工序内检验
savedict['is_midtesting'] = True
tr = TestRecord.objects.create(**savedict)
# 更新wproduct
wproduct.test = tr
wproduct.update_by = request.user
wproduct.update_time = timezone.now()
wproduct.save()
WpmServies.add_wproduct_flow_log(wproduct, 'test_init')
WpmService.add_wproduct_flow_log(wproduct, 'test_init')
# 创建检验条目
for i in RecordFormField.objects.filter(form=form, is_deleted=False):
tri = TestRecordItem()
tri.test_record = tr
tri.form_field = i
tri.is_hidden = i.is_hidden
tri.create_by = request.user
tri.save()
return Response(TestRecordDetailSerializer(instance=tr).data)
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproductPutInsSerializer)
@transaction.atomic
@ -242,9 +248,10 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
'subproduction_plan', 'material', 'subproduction_plan__number').annotate(total=Count('id'))
# 创建入库记录
remark = vdata.get('remark', '')
fifo = FIFO.objects.create(type=FIFO.FIFO_TYPE_DO_IN,
is_audited=True, auditor=request.user, inout_date=timezone.now(),
create_by=request.user, remark=remark)
fifo = FIFO.objects.create(
type=FIFO.FIFO_TYPE_DO_IN,
is_audited=True, auditor=request.user, inout_date=timezone.now(),
create_by=request.user, remark=remark, number='RK'+ranstr(7))
# 创建入库明细
for i in wproducts_a:
spi = SubProductionPlan.objects.get(pk=i['subproduction_plan'])
@ -270,14 +277,14 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
ips.append(FIFOItemProduct(**ip))
FIFOItemProduct.objects.bulk_create(ips)
# 更新库存并修改半成品进行状态
update_inm(fifo)
InmService.update_inm(fifo)
for i in wproducts:
i.act_state = WProduct.WPR_ACT_STATE_INM
i.warehouse = warehouse
i.update_by = request.user
i.update_time = timezone.now()
i.save()
WpmServies.add_wproduct_flow_log(i, 'putins')
WpmService.add_wproduct_flow_log(i, 'putins')
return Response()
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=WproductPutInSerializer)
@ -320,11 +327,11 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
ips.append(FIFOItemProduct(**ip))
FIFOItemProduct.objects.bulk_create(ips)
# 更新库存并修改半成品进行状态
update_inm(fifo)
InmService.update_inm(fifo)
wproduct.act_state = WProduct.WPR_ACT_STATE_INM
wproduct.warehouse = warehouse
wproduct.save()
WpmServies.add_wproduct_flow_log(wproduct, 'putin')
WpmService.add_wproduct_flow_log(wproduct, 'putin')
return Response()
@action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=ScrapSerializer)
@ -350,9 +357,9 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
obj.update_by = request.user
obj.update_time = timezone.now()
obj.save()
WpmServies.add_wproduct_flow_log(obj, 'scrap')
WpmService.add_wproduct_flow_log(obj, 'scrap')
if obj.step.process.id == 1: # 如果是冷加工
WpmServies.update_cutting_list_with_operation(obj.coperation)
WpmService.update_cutting_list_with_operation(obj.coperation)
return Response()
# @action(methods=['get'], detail=False, perms_map={'get':'*'})
@ -407,14 +414,14 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
is_mtestok = request.data.get('is_mtestok')
obj.is_mtestok = is_mtestok
if is_mtestok:
WpmServies.update_plan_state_by_mtestok(
WpmService.update_plan_state_by_mtestok(
obj.subproduction_plan.production_plan)
obj.update_by = request.user
obj.save()
change_str = 'mtest_notok'
if is_mtestok:
change_str = 'mtest_ok'
WpmServies.add_wproduct_flow_log(instance=obj, change_str=change_str)
WpmService.add_wproduct_flow_log(instance=obj, change_str=change_str)
return Response()
@action(methods=['get'], detail=True, perms_map={'get': '*'})
@ -457,7 +464,7 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
i.need_to_order = True
i.update_by = request.user
i.save()
WpmServies.add_wproduct_flow_log(i, change_str='need_to_order')
WpmService.add_wproduct_flow_log(i, change_str='need_to_order')
return Response()
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=WproductToOrderSerializer)
@ -482,7 +489,7 @@ class WProductViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
i.to_order = order
i.update_by = request.user
i.save()
WpmServies.add_wproduct_flow_log(i,change_str='to_order')
WpmService.add_wproduct_flow_log(i,change_str='to_order')
return Response()
class WproductTicketViewSet(ListModelMixin, GenericViewSet):
@ -540,7 +547,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd
i.act_state = WProduct.WPR_ACT_STATE_DOWAIT
i.update_by = request.user
i.save()
WpmServies.add_wproduct_flow_log(i, 'operation_delete')
WpmService.add_wproduct_flow_log(i, 'operation_delete')
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
@ -561,14 +568,14 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd
# 创建操作所用半成品关联记录
if 'wproducts' in vdata:
owps = []
splans = WpmServies.get_subplans_queryset_from_wproducts(
splans = WpmService.get_subplans_queryset_from_wproducts(
vdata['wproducts'])
for wpd in vdata['wproducts']:
wpd.operation = op
wpd.act_state = WProduct.WPR_ACT_STATE_DOING
wpd.update_by = request.user
wpd.save()
WpmServies.add_wproduct_flow_log(wpd, 'operation_create')
WpmService.add_wproduct_flow_log(wpd, 'operation_create')
owp = {}
owp['operation'] = op
owp['wproduct'] = wpd
@ -579,7 +586,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd
owps.append(OperationWproduct(**owp))
OperationWproduct.objects.bulk_create(owps)
else:
splans = WpmServies.get_subplans_queryset_from_wproducts(
splans = WpmService.get_subplans_queryset_from_wproducts(
vdata['wproducts'])
# 查询需要填写的自定义表格
forms = RecordForm.objects.filter(
@ -673,22 +680,31 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd
wsp = i.subproduction_plan
# 获取下一步子工序
newstep, hasNext = WpmServies.get_next_step(wsp, step)
newstep, needTest = WpmService.get_next_step(wsp, step)
wp.step = newstep
wp.pre_step = step
if hasNext:
wp.act_state = WProduct.WPR_ACT_STATE_DOWAIT
else:
if step == newstep:
wp.act_state = WProduct.WPR_ACT_STATE_TOTEST
wp.material = wsp.product
if wp.test:# 如果有正在进行的工序中检验
wp.test.is_midtesting = False
wp.test.is_submited = False
wp.test.save()
else:
wp.act_state = WProduct.WPR_ACT_STATE_DOWAIT
if needTest:
wp.act_state = WProduct.WPR_ACT_STATE_TOTEST
wp.material_check = wsp.product
wp.operation = None
wp.update_by = request.user
wp.save()
WpmServies.add_wproduct_flow_log(wp, 'operation_submit')
WpmService.add_wproduct_flow_log(wp, 'operation_submit')
for i in ows.values('subproduction_plan').distinct():
# 更新进度
WpmServies.update_subproduction_progress_main(sp=wsp)
WpmService.update_subproduction_progress_main(sp=wsp)
elif step.type == Step.STEP_TYPE_DIV:
# 更新物料产出情况
@ -696,7 +712,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd
raise exceptions.APIException('请选择物料产出')
for i in omos:
if i.subproduction_progress.is_main:
newstep, _ = WpmServies.get_next_step(
newstep, _ = WpmService.get_next_step(
i.subproduction_plan, step)
wpr = dict(material=i.material, step=newstep,
act_state=WProduct.WPR_ACT_STATE_DOWAIT, remark='',
@ -705,33 +721,41 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd
for x in range(i.count):
ins = WProduct.objects.create(**wpr)
# 添加日志
WpmServies.add_wproduct_flow_log(
WpmService.add_wproduct_flow_log(
ins, 'wproduct_create')
# 更新进度
WpmServies.update_subproduction_progress_main(
WpmService.update_subproduction_progress_main(
sp=i.subproduction_plan)
elif step.type == Step.STEP_TYPE_COMB:
oms_w = omos.filter(subproduction_progress__is_main=True)
if len(oms_w) == 1:
oms_w = oms_w[0]
# 校验单片数量是否正确, 暂时未写
newstep, hasNext = WpmServies.get_next_step(
newstep, needTest = WpmService.get_next_step(
oms_w.subproduction_plan, step)
wproduct = WProduct()
wproduct.material = oms_w.material
wproduct.step = newstep
wproduct.subproduction_plan = oms_w.subproduction_plan
if hasNext:
wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT
else:
if step == newstep:
wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST
# 更新子计划进度
WpmServies.update_subproduction_progress_main(
sp=oms_w.subproduction_plan)
if wproduct.test:# 如果有正在进行的工序中检验
wproduct.test.is_midtesting = False
wproduct.test.is_submited = False
wproduct.test.save()
else:
wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT
if needTest:
wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST
wproduct.material_check = wproduct.product
# 更新子计划进度
WpmService.update_subproduction_progress_main(
sp=oms_w.subproduction_plan)
wproduct.create_by = request.user
wproduct.coperation = op
wproduct.save()
WpmServies.add_wproduct_flow_log(wproduct, 'wproduct_create')
WpmService.add_wproduct_flow_log(wproduct, 'wproduct_create')
# 隐藏原半成品
wps = WProduct.objects.filter(ow_wproduct__operation=op)
wps.update(is_hidden=True, child=wproduct,
@ -743,7 +767,7 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd
# 如果是冷加工
if step.process.id == 1:
WpmServies.update_cutting_list_with_operation(op=op)
WpmService.update_cutting_list_with_operation(op=op)
return Response()
@ -967,213 +991,3 @@ class OperationMaterialToolViewSet(ListModelMixin, CreateModelMixin, DestroyMode
return Response()
class DoFormInit(CreateAPIView, GenericAPIView):
perms_map = {'*': '*'}
serializer_class = OperationInitSerializer
def post(self, request, format=None):
"""
调用操作表单
"""
data = request.data
serializer = OperationInitSerializer(data=data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
ret = {}
ret_0 = {}
ret_0['step'] = data['step']
splans = []
ret_0['input'] = []
# ret_0['subproduction_plan'] = data['subproduction_plan']
if 'wproducts' in data and data['wproducts']:
ret_0['wproducts'] = data['wproducts']
splans = WProduct.objects.filter(id__in=data['wproducts']).values_list(
'subproduction_plan', flat=True)
# 调出所属子计划现有物料
ret_0['input'] = WMaterialListSerializer(instance=WMaterial.objects.filter(
subproduction_plan__in=splans), many=True).data
else:
if 'subproduction_plan' in vdata:
splans = [vdata['subproduction_plan']]
else:
splans = SubProductionPlan.objects.filter(is_deleted=False,
subproduction__usedstep_subproduction__step=vdata[
'step'],
state=3)
ret_0['wproducts'] = []
ret_0['input'] = WMaterialListSerializer(instance=WMaterial.objects.filter(
subproduction_plan__in=splans), many=True).data
for i in ret_0['input']:
i['count_input'] = 0
# 需要输出的物料
if ret_0['wproducts']:
# 排除主要产物, 因为已经放到半成品里了, 由半成品进行处理, 夹层可能需要特殊处理
o_objs = SubProductionProgress.objects.filter(
subproduction_plan__in=splans, type=SubprodctionMaterial.SUB_MA_TYPE_OUT).exclude(is_main=True)
else:
# 此时显示所有子计划需要输出的物料
o_objs = SubProductionProgress.objects.filter(
subproduction_plan__in=splans, type=SubprodctionMaterial.SUB_MA_TYPE_OUT)
ret_0['output'] = list(o_objs.values(
'subproduction_plan', 'material', 'material__name', 'material__number'))
for i in ret_0['output']:
i['count_output'] = 0
ret['forms'] = []
ret_0['id'] = 0
ret_0['name'] = '基本信息'
# 查询工具工装
ret_0['tools'] = SubprodctionMaterialListSerializer(
instance=SubprodctionMaterial.objects.filter(type=SubprodctionMaterial.SUB_MA_TYPE_TOOL,
subproduction__subplan_subprod__in=splans), many=True).data
# 查询技术文档
ret_0['techdocs'] = TechDocListSerializer(
instance=TechDoc.objects.filter(
subproduction__subplan_subprod__in=splans, enabled=True)
.distinct(), many=True).data
ret['forms'].append(ret_0)
forms = RecordForm.objects.filter(
step=vdata['step'], type=RecordForm.RF_TYPE_DO)
if forms.exists():
ret['forms'].extend(RecordFormDetailSerializer(
instance=forms, many=True).data)
return Response(ret)
class DoFormSubmit(CreateAPIView, GenericAPIView):
perms_map = {'*': '*'}
serializer_class = OperationSubmitSerializer
@transaction.atomic
def post(self, request, format=None):
"""
提交操作表单
"""
data = request.data
serializer = OperationSubmitSerializer(
data=data, context={'request': self.request})
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data # 校验之后的数据
# 创建一个生产操作记录
action_obj = Operation()
action_obj.step = vdata['step']
action_obj.remark = vdata.get('remark', '') # 操作备注
action_obj.create_by = request.user
action_obj.use_scrap = vdata.get('use_scrap', False)
action_obj.save()
# 保存关联半成品
if 'wproducts' in data and data['wproducts']:
owps = []
for i in data['wproducts']:
owp = {}
owp['operation'] = action_obj
wp = WProduct.objects.get(pk=i)
owp['wproduct'] = wp
owp['number'] = wp.number
owp['material'] = wp.material
owp['subproduction_plan'] = wp.subproduction_plan
owps.append(OperationWproduct(**owp))
OperationWproduct.objects.bulk_create(owps)
# 保存物料消耗
for i in vdata['input']:
if i['count_input']: # 如果有消耗
i_wmat = i['id']
OperationMaterial.objects.create(type=1, operation=action_obj,
wmaterial=i_wmat, count=i['count_input'])
# 更新车间物料
i_wmat.count = i_wmat.count - i['count_input']
i_wmat.save()
# 更新子计划物料消耗情况
sp = SubProductionProgress.objects.get(subproduction_plan=i_wmat.subproduction_plan,
material=i_wmat.material)
sp.count_real = sp.count_real + i['count_input']
sp.save()
# 物料产出
if 'output' in data and data['output']:
for i in vdata['output']: # 已经序列化好的数据
ma = i['material']
if i['subproduction_plan'].product == ma: # 如果是该计划主产物
# 如果是切割
# 获取下一步子工序
if vdata['step'].type == Step.STEP_TYPE_DIV:
newstep, _ = WpmServies.get_next_step(
i['subproduction_plan'], vdata['step'])
wpr = dict(material=ma, step=newstep,
act_state=WProduct.WPR_ACT_STATE_DOWAIT, remark='',
subproduction_plan=i['subproduction_plan'])
for x in range(i['count_output']):
WProduct.objects.create(**wpr)
else:
# 更新操作产出物料表
OperationMaterial.objects.create(type=2, operation=action_obj,
material=ma, count=i['count_output'])
# 更新车间物料表
ins, _ = WMaterial.objects.get_or_create(subproduction_plan=i['subproduction_plan'],
material=ma)
ins.count = ins.count + i['count_output']
ins.save()
# 更新子计划进度表
sp = SubProductionProgress.objects.get(subproduction_plan=i['subproduction_plan'],
material=ma)
sp.count_real = sp.count_real + i['count_input']
sp.save()
# 更新动态产品表
if 'wproducts' in vdata and vdata['wproducts']:
if vdata['step'].type == Step.STEP_TYPE_COMB:
wproducts = vdata['wproducts']
if 'suproduction_plan' in vdata:
wproducts.update(is_hidden=True) # 隐藏
newstep, hasNext = WpmServies.get_next_step(
i['subproduction_plan'], vdata['step'])
wproduct = WProduct()
wproduct.material = vdata['subproduction_plan'].product
wproduct.step = newstep
wproduct.subproduction_plan = vdata['subproduction_plan']
wproduct.parent = data['wproducts']
if hasNext:
wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT
else:
wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST
wproduct.save()
else:
raise exceptions.APIException('请指定子计划')
else:
for wproduct in vdata['wproducts']:
# 获取下一步子工序
newstep, hasNext = WpmServies.get_next_step(
wproduct.subproduction_plan, vdata['step'])
wproduct.step = newstep
wproduct.pre_step = vdata['step']
if hasNext:
wproduct.act_state = WProduct.WPR_ACT_STATE_DOWAIT
else:
wproduct.act_state = WProduct.WPR_ACT_STATE_TOTEST
wproduct.material = wproduct.subproduction_plan.product
wproduct.save()
# 保存自定义表单结果
for i in vdata['forms']:
wr = OperationRecord()
wr.form = i['form']
wr.create_by = request.user
wr.operation = action_obj
wr.save()
wrds = []
for m in i['record_data']: # 保存记录详情
form_field = m['form_field']
m['field_name'] = form_field.field_name
m['field_key'] = form_field.field_key
m['field_type'] = form_field.field_type
m['field_value'] = m['field_value']
m['sort'] = form_field.sort
m['operation_record'] = wr
wrds.append(OperationRecordItem(**m))
OperationRecordItem.objects.bulk_create(wrds)
return Response()