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

This commit is contained in:
shijing 2022-02-22 16:31:45 +08:00
commit 52b0f469d9
40 changed files with 1476 additions and 628 deletions

View File

@ -29,3 +29,22 @@ export function getEmployee(data) {
data
})
}
//个人离岗说明
export function getNotWorkList(query) {
return request({
url: '/hrm/not_work_remark/',
method: 'get',
params: query
})
}
export function getClockrecordList(query) {
return request({
url: '/hrm/clock_record/',
method: 'get',
params: query
})
}

View File

@ -377,3 +377,34 @@ export function deletetechdoc(id, data) {
})
}
//装箱
//记录表格字段表
export function getpackitemList(query) {
return request({
url: '/mtm/packitem/',
method: 'get',
params: query
})
}
export function createpackitem(data) {
return request({
url: '/mtm/packitem/',
method: 'post',
data
})
}
export function updatepackitem(id, data) {
return request({
url: `/mtm/packitem/${id}/`,
method: 'put',
data
})
}
export function deletepackitem(id, data) {
return request({
url: `/mtm/packitem/${id}/`,
method: 'delete',
data
})
}

View File

@ -2,41 +2,59 @@
<div class="app-container">
<el-card>
<el-card>
<el-descriptions title="物料基本信息" direction="vertical" :column="8" border>
<el-descriptions-item label="物料编号"> {{materialdetail.number}}</el-descriptions-item>
<el-descriptions-item label="物料名称"> {{materialdetail.name}}</el-descriptions-item>
<el-descriptions-item label="规格型号" :span="2"> {{materialdetail.specification}}</el-descriptions-item>
<el-descriptions-item label="计量单位"> {{materialdetail.unit}} </el-descriptions-item>
<el-descriptions-item label="数量"> {{materialdetail.count}}</el-descriptions-item>
<el-descriptions
title="物料基本信息"
direction="vertical"
:column="8"
border
>
<el-descriptions-item label="物料编号">
{{ materialdetail.number }}</el-descriptions-item
>
<el-descriptions-item label="物料名称">
{{ materialdetail.name }}</el-descriptions-item
>
<el-descriptions-item label="规格型号" :span="2">
{{ materialdetail.specification }}</el-descriptions-item
>
<el-descriptions-item label="计量单位">
{{ materialdetail.unit }}
</el-descriptions-item>
<el-descriptions-item label="数量">
{{ materialdetail.count }}</el-descriptions-item
>
</el-descriptions>
</el-card>
<el-tabs v-model="activeName" type="card">
<el-tab-pane label="供应商" name="5" v-if="this.type==3||this.type==4">
<el-tab-pane
label="供应商"
name="5"
v-if="this.type == 3 || this.type == 4"
>
</el-tab-pane>
<el-tab-pane label="批次" name="3">
<el-table
:data="InventoryList"
border
fit
stripe
height="460"
highlight-current-row
>
<el-table-column type="index" width="50" />
<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>
<template slot-scope="scope">{{
scope.row.material_.name
}}</template>
</el-table-column>
<el-table-column label="规格型号">
<template slot-scope="scope">{{ scope.row.material_.specification }}</template>
<template slot-scope="scope">{{
scope.row.material_.specification
}}</template>
</el-table-column>
<el-table-column label="物料编号">
@ -58,19 +76,16 @@
<template slot-scope="scope">{{ scope.row.count }}</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="关联的订单" name="1">
<el-table
:data="orderlist"
border
fit
stripe
highlight-current-row
height="460"
>
<el-table-column type="index" width="50" />
@ -78,27 +93,38 @@
<template slot-scope="scope">{{ scope.row.number }}</template>
</el-table-column>
<el-table-column label="客户" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.customer_.name }}</template>
<template slot-scope="scope">{{
scope.row.customer_.name
}}</template>
</el-table-column>
<el-table-column label="所属合同" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.contract_.name }}</template>
<template slot-scope="scope">{{
scope.row.contract_.name
}}</template>
</el-table-column>
<el-table-column label="产品名称" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.product_.name }}</template>
<template slot-scope="scope">{{
scope.row.product_.name
}}</template>
</el-table-column>
<el-table-column label="产品型号" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.product_.specification }}</template>
<template slot-scope="scope">{{
scope.row.product_.specification
}}</template>
</el-table-column>
<el-table-column label="产品数量">
<template slot-scope="scope">{{ scope.row.count }}</template>
</el-table-column>
<el-table-column label="交货日期">
<template slot-scope="scope">{{ scope.row.delivery_date }}</template>
<template slot-scope="scope">{{
scope.row.delivery_date
}}</template>
</el-table-column>
<el-table-column label="创建时间">
<template slot-scope="scope">{{ scope.row.create_time }}</template>
<template slot-scope="scope">{{
scope.row.create_time
}}</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="关联的计划" name="2">
@ -109,23 +135,23 @@
stripe
style="width: 100%"
height="460"
>
<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.order_.number }}</template>
<template slot-scope="scope">{{
scope.row.order_.number
}}</template>
</el-table-column>
<el-table-column label="合同编号">
<template slot-scope="scope">{{ scope.row.order_.contract_.number }}</template>
<template slot-scope="scope">{{
scope.row.order_.contract_.number
}}</template>
</el-table-column>
<el-table-column label="生产数量">
<template slot-scope="scope">{{ scope.row.count }}</template>
</el-table-column>
@ -136,7 +162,9 @@
<template slot-scope="scope">{{ scope.row.end_date }}</template>
</el-table-column>
<el-table-column label="交付截止时间">
<template slot-scope="scope">{{ scope.row.order_.delivery_date }}</template>
<template slot-scope="scope">{{
scope.row.order_.delivery_date
}}</template>
</el-table-column>
<el-table-column label="是否生成子计划">
@ -146,12 +174,17 @@
</template>
</el-table-column>
<el-table-column label="创建时间">
<template slot-scope="scope">{{ scope.row.create_time }}</template>
<template slot-scope="scope">{{
scope.row.create_time
}}</template>
</el-table-column>
</el-table>
</el-tab-pane>
<el-tab-pane label="物料" name="4" v-if="this.type==1||this.type==2">
<el-tab-pane
label="物料"
name="4"
v-if="this.type == 1 || this.type == 2"
>
<el-table
:data="wproductList"
border
@ -159,20 +192,15 @@
stripe
style="width: 100%"
height="460"
>
<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.step_.name
}}</template>
<template slot-scope="scope">{{ scope.row.step_.name }}</template>
</el-table-column>
<el-table-column label="状态">
<template slot-scope="scope">{{
@ -180,12 +208,123 @@
}}</template>
</el-table-column>
<el-table-column label="仓库">
<template slot-scope="scope" v-if=" scope.row.warehouse_">{{ scope.row.warehouse_.name }}</template>
<template slot-scope="scope" v-if="scope.row.warehouse_">{{
scope.row.warehouse_.name
}}</template>
</el-table-column>
warehouse_
</el-table>
</el-tab-pane>
<el-tab-pane label="装箱单" name="5" v-if="this.type == 1">
<el-button type="primary" icon="el-icon-plus" @click="handleCreate">
新增装箱单
</el-button>
<el-table
:data="pickList"
border
fit
stripe
style="width: 100%"
height="460"
>
<el-table-column type="index" width="50" />
<el-table-column label="名称">
<template slot-scope="scope">{{ scope.row.name }}</template>
</el-table-column>
<el-table-column label="单位">
<template slot-scope="scope">{{ scope.row.unit }}</template>
</el-table-column>
<el-table-column label="数量">
<template slot-scope="scope" >{{
scope.row.count
}}</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
width="220px"
>
<template slot-scope="scope">
<el-link
type="primary"
@click="handleEdit(scope)"
>编辑
</el-link>
<el-link
type="danger"
@click="handleDelete(scope)"
>删除
</el-link>
</template>
</el-table-column>
</el-table>
<el-dialog
:visible.sync="dialogVisible"
:close-on-click-modal="false"
:title="dialogType === 'edit' ? '编辑装箱单' : '新增装箱单'"
>
<el-form
ref="Form"
:model="packitem"
label-width="100px"
label-position="right"
:rules="rule1"
>
<el-form-item label="物料"
prop="material"
>
<el-select v-model="packitem.material" @change="selectmaterial" >
<el-option
v-for="item in materialoptions"
:key="item.id"
:value="item.id"
:label="item.name"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="packitem.name" placeholder="名称" />
</el-form-item>
<el-form-item label="单位" prop="unit">
<el-input v-model="packitem.unit" placeholder="单位" />
</el-form-item>
<el-form-item label="型号" prop="specification">
<el-input v-model="packitem.specification" placeholder="型号" />
</el-form-item>
<el-form-item label="数量" prop="count">
<el-input-number
style="width: 100%"
v-model="packitem.count"
:step="1"
:min="0"
step-strictly
placeholder="数量"
></el-input-number>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-input-number
v-model="packitem.sort"
:min="1"
placeholder="排序"
></el-input-number>
</el-form-item>
</el-form>
<div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false"
>取消</el-button
>
<el-button type="primary" @click="confirm('Form')">确认</el-button>
</div>
</el-dialog>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
@ -198,15 +337,19 @@
import { getProductionplanList } from "@/api/pm";
import { getmaterialbatchList } from "@/api/inm";
import { getOrderList } from "@/api/sam";
import {getMaterial } from "@/api/mtm";
import { getMaterial,getpackitemList,createpackitem,updatepackitem,deletepackitem,getMaterialList } from "@/api/mtm";
import { getwproductList } from "@/api/wpm";
import { upFile } from "@/api/file";
import { genTree } from "@/utils";
import Pagination from "@/components/Pagination"; // secondary package based on el-pagination
import Treeselect from '@riophae/vue-treeselect'
import '@riophae/vue-treeselect/dist/vue-treeselect.css'
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
const defaultpackitem = {
name:"",
unit:"",
specification:"",
};
export default {
components: { Pagination, vueJsonEditor, Treeselect },
data() {
@ -216,7 +359,12 @@
wproductList: [],
productionplanList: [],
InventoryList: [],
activeName:'3',
activeName: "3",
packitem:defaultpackitem,
dialogVisible: false,
dialogType: "new",
pickList:[],
materialoptions:[],
actstate_: {
6: "待复检",
10: "操作进行中",
@ -229,6 +377,11 @@
26: "待夹层检验",
70: "报废",
},
rule1: {
name: [{required: true, message: "请输入名称", trigger: "blur"}],
unit: [{required: true, message: "请输入单位", trigger: "blur"}],
},
};
},
computed: {},
@ -236,59 +389,149 @@
created() {
this.material = this.$route.params.id;
this.type = this.$route.params.type;
this.getMaterialist()
this.getMaterial();
this.getpickList();
this.getOrderList();
this.getplanList();
this.getmaterialbatchList();
this.getwproductList();
},
methods: {
//新增装箱单
getMaterialist()
{
getMaterialList({ pageoff: true,type:3 }).then((response) => {
if (response.data) {
this.materialoptions = response.data;
}
});
},
selectmaterial(selval){
getMaterial(selval).then((response) => {
if (response.data) {
this.packitem.name = response.data.name;
this.packitem.unit = response.data.unit;
this.packitem.specification = response.data.specification;
}
});
},
handleCreate() {
this.packitem = Object.assign({}, defaultpackitem);
this.dialogType = "new";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
handleEdit(scope) {
this.packitem = Object.assign({}, scope.row); // copy obj
this.dialogType = "edit";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["Form"].clearValidate();
});
},
handleDelete(scope) {
this.$confirm("确认删除?", "警告", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "error",
})
.then(async () => {
await deletepackitem(scope.row.id);
this.getpickList();
this.$message.success("成功");
})
.catch((err) => {
console.error(err);
});
},
async confirm(form) {
this.$refs[form].validate((valid) => {
if (valid) {
const isEdit = this.dialogType === "edit";
if (isEdit) {
updatepackitem(this.packitem.id, this.packitem).then((res) => {
if (res.code >= 200) {
this.getpickList();
this.dialogVisible = false;
this.$message.success("成功");
}
});
} else {
this.packitem.product=this.material;
createpackitem(this.packitem).then((res) => {
if (res.code >= 200) {
this.getpickList();
this.dialogVisible = false;
this.$message.success("成功");
}
});
}
} else {
return false;
}
});
},
getpickList()
{
getpackitemList({ product: this.material, page: 0 }).then((response) => {
if (response.data) {
this.pickList = response.data; //zhuangxiang信息
}
});
},
getMaterial() {
getMaterial(this.material).then((response) => {
if (response.data) {
this.materialdetail = response.data; //物料基本信息
}
})
});
},
getOrderList() {
getOrderList({ material: this.material, page: 0 }).then((response) => {
if (response.data) {
this.orderlist = response.data; //物料关联的订单
}
})
});
},
getplanList() {
getProductionplanList({material:this.material,page:0}).then((response) => {
getProductionplanList({ material: this.material, page: 0 }).then(
(response) => {
if (response.data) {
this.productionplanList = response.data; //物料关联计划
}
});
}
);
},
getmaterialbatchList() {
getmaterialbatchList({material:this.material,page:0}).then((response) => {
getmaterialbatchList({ material: this.material, page: 0 }).then(
(response) => {
if (response.data) {
this.InventoryList = response.data;
}
});
}
);
},
getwproductList() {
getwproductList({ material: this.material, page: 0 }).then((response) => {
if (response.data) {
this.wproductList = response.data; //半成品
}
});
}
},
},
};
</script>

View File

@ -411,6 +411,7 @@
const defaultstep = {
name: "",
number: "",
type:null,
};
const defaultrecordform = {
@ -486,15 +487,15 @@
'selects': '多选',
},
typeoption: [{
value: '1',
value: 1,
label: '常规'
},
{
value: '2',
value: 2,
label: '分割'
},
{
value: '3',
value: 3,
label: '结合'
}],
fieldtypeoptions: [{
@ -684,7 +685,7 @@
recordformLists()
{
this.listQueryrecordform.step=this.stepid;
this.listQueryrecordform.type=1;
this.listQueryrecordform.type=10;
getrecordformList(this.listQueryrecordform).then((response) => {
if (response.data) {
this.recordformList = response.data;

View File

@ -4,7 +4,6 @@
<el-tabs type="border-card">
<el-tab-pane label="今日到岗">
<el-table
:data="userList.results"
style="width: 100%; margin-top: 6px"
highlight-current-row
@ -29,7 +28,9 @@
</template>
</el-table-column>
<el-table-column align="center" label="离岗备注">
<template slot-scope="scope">{{ scope.row.not_work_remark }}</template>
<template slot-scope="scope">{{
scope.row.not_work_remark
}}</template>
</el-table-column>
<el-table-column align="header-center" label="部门">
@ -37,12 +38,7 @@
scope.row.dept_.name
}}</template>
</el-table-column>
<el-table-column
align="center"
label="操作"
fixed="right"
>
<el-table-column align="center" label="操作" fixed="right">
<template slot-scope="scope">
<el-link
type="primary"
@ -72,37 +68,37 @@
:model="stopwork"
label-width="100px"
label-position="right"
>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="stopwork.not_work_remark" placeholder="备注" />
<el-input
type="textarea"
v-model="stopwork.not_work_remark"
placeholder="备注"
/>
</el-form-item>
</el-form>
<div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false">取消</el-button>
<el-button type="danger" @click="dialogVisible = false"
>取消</el-button
>
<el-button type="primary" @click="smtconfirm()">确认</el-button>
</div>
</el-dialog>
</el-tab-pane>
<el-tab-pane label="到岗统计">
<el-row>
<el-col :span="12">
<div class="container">
<span class="demonstration"></span>
<el-date-picker
v-model="value2"
type="month"
placeholder="选择年月">
placeholder="选择年月"
>
</el-date-picker>
<el-button type="primary" @click="submit">查询</el-button>
</div>
<el-row>
<el-col :span="12">
<el-table
:data="atworkList"
style="width: 100%; margin-top: 6px"
highlight-current-row
@ -110,6 +106,7 @@
height="680"
stripe
border
@current-change="handleCurrentChange"
v-el-height-adaptive-table="{ bottomOffset: 41 }"
>
<el-table-column type="index" width="50" label="序号" />
@ -121,35 +118,33 @@
</el-table-column>
<el-table-column align="header-center" label="部门">
<template slot-scope="scope">{{ scope.row.dept_name }}</template>
<template slot-scope="scope">{{
scope.row.dept_name
}}</template>
</el-table-column>
<el-table-column align="center" label="出勤天数">
<template slot-scope="scope">{{ scope.row.count }}</template>
</el-table-column>
<el-table-column align="center" label="应到天数">
<template slot-scope="scope">{{ scope.row.count_workday }}</template>
<template slot-scope="scope">{{
scope.row.count_workday
}}</template>
</el-table-column>
</el-table>
</el-col>
<el-col :span="12">
<el-calendar >
<template
slot="dateCell"
slot-scope="{date, data}">
<el-calendar v-model="value">
<template slot="dateCell" slot-scope="{ date, data }">
<p>
{{ data.day.split('-').slice(1).join('-') }}<br /> {{dealMyDate(data.day)}}
{{ data.day.split("-").slice(1).join("-") }}<br />
{{ dealMyDate(data.day) }}
</p>
</template>
</el-calendar>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
@ -181,7 +176,7 @@
<script>
import { getEmployeeList } from "@/api/employee";
import checkPermission from "@/utils/permission";
import { notWork } from "@/api/hrm";
import { notWork, getNotWorkList ,getClockrecordList} from "@/api/hrm";
import { getatwork } from "@/api/srm";
import { upUrl, upHeaders } from "@/api/file";
@ -201,83 +196,108 @@ value2:null,
listLoading: true,
dialogVisible: false,
stopworkID: null,
notworkList: "",
listQuery: {
page: 1,
page_size: 20,
},
resDate: [
{"date":"2022-02-20","content":"放假"},
{"date":"2022-02-26","content":"去交电费"},
{"date":"2022-02-25","content":"去学习vue"}
{ date: "2022-02-20", content: "放假" },
{ date: "2022-02-26", content: "去交电费" },
{ date: "2022-02-25", content: "去学习vue" },
],
atworkDate: { year: null, month: null },
value: new Date(),
};
},
computed: {},
watch: {},
created() {
this.getList();
},
methods: {
checkPermission,
//今日到岗
getList() {
this.listQuery.fields='number,name,is_atwork,dept_,id,not_work_remark';
this.listQuery.fields = "number,name,is_atwork,dept_,id,not_work_remark";
getEmployeeList(this.listQuery).then((response) => {
if (response.data) {
this.userList = response.data;
}
});
},
handlestopwork(scope)
{
handlestopwork(scope) {
this.dialogVisible = true;
this.stopworkID = scope.row.id;
},
handleCurrentChange(row) {
this.resDate=[],
getNotWorkList({page:0,user:row.user_id}).then((response) => {
if (response.data) {
response.data.forEach((item) => {
this.resDate.push({
data: item.not_work_date,
content: item.remark,
});
});
}
});
getClockrecordList({page:0,create_by:row.user_id}).then((response) => {
if (response.data) {
response.data.forEach((item) => {
var curTime=(item.update_time).substring(0,10)
console.log(curTime);
this.resDate.push({
data: curTime,
content: "在岗",
});
});
}
});
},
//离职备注提交
smtconfirm()
{
console.log(this.stopwork);
smtconfirm() {
notWork(this.stopworkID, this.stopwork).then((res) => {
if (res.code >= 200) {
this.$message.success("离职备注提交成功!");
this.dialogVisible = false;
this.getList();
}
})
});
},
//到岗统计
submit()
{
submit() {
this.atworkDate.year = this.value2.getFullYear();
this.atworkDate.month = this.value2.getMonth() + 1;
this.value = this.value2;
(this.resDate = []),
getatwork(this.atworkDate).then((response) => {
if (response.data) {
this.atworkList = response.data;
}
});
},
//考勤日历
dealMyDate(v) {
console.log(v)
let len = this.resDate.length
let res = ""
let len = this.resDate.length;
let res = "";
for (let i = 0; i < len; i++) {
if(this.resDate[i].date == v) {
res = this.resDate[i].content
break
if (this.resDate[i].data == v) {
res = this.resDate[i].content;
break;
}
}
return res
}
return res;
},
},
};
</script>

View File

@ -192,14 +192,13 @@
</el-table-column>
<el-table-column label="设备状态">
<template slot-scope="scope">
<div v-if="scope.row.equip_.type === 2">
<el-tag
v-if="scope.row.equip_.state === 40"
type="danger"
>
<div v-if="scope.row.type===2">
<el-tag v-if="scope.row.state===40" type="danger">
禁用
</el-tag>
<el-tag v-else type="success"> 合格 </el-tag>
<el-tag v-else type="success">
合格
</el-tag>
</div>
<div v-else>
<el-tag v-if="scope.row.state===10" type="success">
@ -216,9 +215,6 @@
</el-tag>
</div>
</template>
<template slot-scope="scope">
{{ state_[scope.row.state] }}</template
>
</el-table-column>
<el-table-column label="设备编号">
<template slot-scope="scope">

View File

@ -1,16 +1,38 @@
from django_filters import rest_framework as filters
from apps.hrm.models import ClockRecord, Employee
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
from utils.mixins import DynamicFieldsFilterMixin
class ClockRecordFilterSet(filters.FilterSet):
create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte')
create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte')
year = filters.NumberFilter(method='filter_year')
month = filters.NumberFilter(method='filter_month')
class Meta:
model = ClockRecord
fields = ['create_by', 'create_time_start', 'create_time_end']
fields = ['create_by', 'create_time_start', 'create_time_end', 'year', 'month']
def filter_year(self, queryset, name, value):
return queryset.filter(create_time_date__year=value)
def filter_month(self, queryset, name, value):
return queryset.filter(create_time_date__month=value)
class EmployeeFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
class Meta:
model = Employee
fields = ['job_state']
class NotWorkRemarkFilterSet(filters.FilterSet):
year = filters.NumberFilter(method='filter_year')
month = filters.NumberFilter(method='filter_month')
class Meta:
model = NotWorkRemark
fields = ['year', 'month', 'user']
def filter_year(self, queryset, name, value):
return queryset.filter(not_work_date__year=value)
def filter_month(self, queryset, name, value):
return queryset.filter(not_work_date__month=value)

View File

@ -0,0 +1,32 @@
# Generated by Django 3.2.9 on 2022-02-22 03:12
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('hrm', '0007_auto_20220218_0843'),
]
operations = [
migrations.RemoveField(
model_name='notworkremark',
name='day',
),
migrations.RemoveField(
model_name='notworkremark',
name='month',
),
migrations.RemoveField(
model_name='notworkremark',
name='year',
),
migrations.AddField(
model_name='notworkremark',
name='not_work_date',
field=models.DateField(default=django.utils.timezone.now, verbose_name='未打卡日期'),
preserve_default=False,
),
]

View File

@ -44,9 +44,7 @@ class NotWorkRemark(CommonAModel):
"""
离岗说明
"""
year = models.PositiveSmallIntegerField('', default=2022)
month = models.PositiveSmallIntegerField('', default=2)
day = models.PositiveSmallIntegerField('', default=1)
not_work_date = models.DateField('未打卡日期')
user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE)
remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200)
@ -59,3 +57,5 @@ class ClockRecord(CommonADModel):
(ClOCK_WORK1, '上班打卡'),
)
type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1)

View File

@ -3,7 +3,7 @@ from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from utils.mixins import DynamicFieldsSerializerMixin
from .models import ClockRecord, Employee
from .models import ClockRecord, Employee, NotWorkRemark
from apps.system.serializers import OrganizationSimpleSerializer, UserListSerializer, UserSimpleSerializer
from django.db.models.query import Prefetch
@ -30,3 +30,8 @@ class ClockRecordListSerializer(serializers.ModelSerializer):
class Meta:
model = ClockRecord
fields = '__all__'
class NotWorkRemarkListSerializer(serializers.ModelSerializer):
class Meta:
model = NotWorkRemark
fields = '__all__'

View File

@ -10,7 +10,7 @@ def update_all_employee_not_atwork():
"""
将所有员工设为非在岗状态
"""
Employee.objects.all().update(is_atwork=False, last_check_time = None)
Employee.objects.all().update(is_atwork=False, last_check_time = None, not_work_remark=None)
@shared_task
def update_all_user_facedata_cache():

View File

@ -1,12 +1,13 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, FaceLogin
from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, FaceLogin, NotWorkRemarkViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('employee', EmployeeViewSet, basename='employee')
router.register('clock_record', ClockRecordViewSet, basename='clock_record')
router.register('not_work_remark', NotWorkRemarkViewSet, basename='not_work_reamrk')
urlpatterns = [
path('facelogin/', FaceLogin.as_view()),
path('', include(router.urls)),

View File

@ -5,12 +5,12 @@ from django.utils import timezone
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, CreateModelMixin, ListModelMixin
from apps.hrm.filters import ClockRecordFilterSet, EmployeeFilterSet
from apps.hrm.filters import ClockRecordFilterSet, EmployeeFilterSet, NotWorkRemarkFilterSet
from apps.hrm.services import HRMService
from apps.hrm.tasks import update_all_user_facedata_cache
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
from apps.hrm.serializers import ClockRecordListSerializer, EmployeeNotWorkRemarkSerializer, EmployeeSerializer, FaceClockCreateSerializer, FaceLoginSerializer
from apps.hrm.serializers import ClockRecordListSerializer, EmployeeNotWorkRemarkSerializer, EmployeeSerializer, FaceClockCreateSerializer, FaceLoginSerializer, NotWorkRemarkListSerializer
@ -67,16 +67,12 @@ class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMix
remark = request.data.get('not_work_remark', '')
obj.not_work_remark = remark
obj.save()
now_local = timezone.localtime()
now = timezone.now()
instance, created = NotWorkRemark.objects.get_or_create(
year = now_local.year,
month = now_local.month,
day = now_local.day,
not_work_date = now.date(),
user = obj.user,
defaults={
"year":now_local.year,
"month":now_local.month,
"day":now_local.day,
"not_work_date":now.date(),
"user":obj.user,
"remark":remark,
"create_by":request.user,
@ -134,7 +130,15 @@ class ClockRecordViewSet(CreateModelMixin, ListModelMixin, GenericViewSet):
return Response('非打卡时间范围', status=status.HTTP_400_BAD_REQUEST)
class NotWorkRemarkViewSet(ListModelMixin, GenericViewSet):
"""
离岗说明
"""
perms_map = {'get':'*'}
queryset = NotWorkRemark.objects.select_related('user').all()
serializer_class = NotWorkRemarkListSerializer
filterset_class = NotWorkRemarkFilterSet
ordering = ['-pk']
import base64

View File

@ -28,6 +28,5 @@ class IProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
update_time_end = filters.DateFilter(field_name="update_time", lookup_expr='lte')
class Meta:
model = IProduct
fields = ['material', 'warehouse', 'batch', 'order', 'material__type',
'is_saled', 'update_time_start', 'update_time_end',
fields = ['material', 'warehouse', 'batch', 'order', 'material__type', 'update_time_start', 'update_time_end',
'to_order', 'need_to_order']

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.9 on 2022-02-22 01:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sam', '0013_auto_20220222_0941'),
('inm', '0031_fifoitem_pu_order_item'),
]
operations = [
migrations.RemoveField(
model_name='iproduct',
name='is_saled',
),
migrations.AddField(
model_name='fifo',
name='sale',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='sam.sale', verbose_name='关联销售记录'),
),
migrations.AddField(
model_name='iproduct',
name='state',
field=models.IntegerField(choices=[(10, '可销售'), (20, '已锁定'), (30, '已售出')], default=10, verbose_name='状态'),
),
]

View File

@ -3,6 +3,7 @@ from django.db.models.base import Model
import django.utils.timezone as timezone
from django.db.models.query import QuerySet
from apps.pum.models import PuOrder, PuOrderItem, Vendor
from apps.sam.models import Sale
from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File
from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords
@ -86,6 +87,8 @@ class FIFO(CommonADModel):
on_delete=models.CASCADE, null=True, blank=True)
pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单',
null=True, blank=True, on_delete=models.CASCADE)
sale = models.ForeignKey(Sale, verbose_name='关联销售记录',
null=True, blank=True, on_delete=models.CASCADE)
class FIFOItem(BaseModel):
@ -113,6 +116,14 @@ class IProduct(BaseModel):
"""
具体产品条目
"""
SALE_OK = 10
SALE_LOCK = 20
SALED = 30
state_choices = (
(SALE_OK, '可销售'),
(SALE_LOCK, '已锁定'),
(SALED, '已售出')
)
number = models.CharField('物品编号', unique=True, max_length=50)
material = models.ForeignKey(
Material, verbose_name='物料类型', on_delete=models.CASCADE)
@ -121,7 +132,7 @@ class IProduct(BaseModel):
batch = models.CharField('所属批次号', max_length=100, default='')
wproduct = models.ForeignKey('wpm.wproduct', on_delete=models.CASCADE, verbose_name='关联的动态产品', db_constraint=False,
null=True, blank=True, related_name='iproduct_wproduct')
is_saled = models.BooleanField('是否售出', default=False)
state = models.IntegerField('状态', default=SALE_OK, choices=state_choices)
class FIFOItemProduct(BaseModel):

View File

@ -0,0 +1,17 @@
# Generated by Django 3.2.9 on 2022-02-21 06:00
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('mtm', '0047_packitem'),
]
operations = [
migrations.RemoveField(
model_name='step',
name='need_test',
),
]

View File

@ -0,0 +1,25 @@
# Generated by Django 3.2.9 on 2022-02-22 01:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('mtm', '0048_remove_step_need_test'),
]
operations = [
migrations.AddField(
model_name='packitem',
name='product',
field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.CASCADE, related_name='pack_product', to='mtm.material', verbose_name='装箱产品'),
preserve_default=False,
),
migrations.AlterField(
model_name='packitem',
name='material',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pack_material', to='mtm.material', verbose_name='装箱配件'),
),
]

View File

@ -54,8 +54,10 @@ class PackItem(CommonAModel):
"""
装箱项目
"""
material = models.ForeignKey(Material, verbose_name='关联成品',
on_delete=models.CASCADE)
product = models.ForeignKey(Material, verbose_name='装箱产品',
on_delete=models.CASCADE, related_name='pack_product')
material = models.ForeignKey(Material, verbose_name='装箱配件',
on_delete=models.CASCADE, null=True, blank=True, related_name='pack_material')
name = models.CharField('名称', max_length=100)
specification = models.CharField('型号', max_length=100, null=True, blank=True)
unit = models.CharField('单位', max_length=10)
@ -105,7 +107,6 @@ class Step(CommonAModel):
name = models.CharField('工序步骤名称', max_length=100)
number = models.CharField('步骤编号', max_length=100, null=True, blank=True)
instruction_content = models.TextField('相应操作指导', null=True, blank=True)
need_test = models.BooleanField('是否需要检验', default=False)
sort = models.IntegerField('排序号', default=1)
equipments = models.ManyToManyField(Equipment, verbose_name='使用设备', related_name='step_equips')

View File

@ -24,6 +24,8 @@ class MaterialDetailSerializer(serializers.ModelSerializer):
objs = Process.objects.filter(subproduction_process__product=obj, subproduction_process__is_deleted=False, is_deleted=False).distinct().order_by('number')
return ProcessSimpleSerializer(instance=objs, many=True).data
class PackItemSerializer(serializers.ModelSerializer):
class Meta:
model = PackItem
@ -32,7 +34,7 @@ class PackItemSerializer(serializers.ModelSerializer):
class PackItemCreateSerializer(serializers.ModelSerializer):
class Meta:
model = PackItem
fields = ['material', 'name', 'specification', 'unit', 'count', 'sort']
fields = ['product', 'material', 'name', 'specification', 'unit', 'count', 'sort']
class PackItemUpdateSerializer(serializers.ModelSerializer):
class Meta:
@ -44,6 +46,12 @@ class MaterialSimpleSerializer(serializers.ModelSerializer):
model = Material
fields = ['id', 'name', 'number', 'unit','specification', 'type']
class PackItemDetailSerializer(serializers.ModelSerializer):
material_ = MaterialSimpleSerializer(source='material', read_only=True)
class Meta:
model = PackItem
fields = '__all__'
class ProcessSerializer(serializers.ModelSerializer):
instruction_ = FileSimpleSerializer(source='instruction', read_only=True)
workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True)

View File

@ -40,7 +40,7 @@ class PackItemViewSet(CreateUpdateModelAMixin, ModelViewSet):
queryset = PackItem.objects.all()
serializer_class = PackItemSerializer
search_fields = ['name', 'number']
filterset_fields = ['material']
filterset_fields = ['material', 'product']
ordering = ['sort']
def get_serializer_class(self):

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.9 on 2022-02-21 02:27
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('qm', '0025_alter_testrecord_type'),
('pm', '0026_auto_20220218_1636'),
]
operations = [
migrations.RemoveField(
model_name='subproductionplan',
name='form',
),
migrations.RemoveField(
model_name='subproductionplan',
name='is_testok',
),
migrations.RemoveField(
model_name='subproductionplan',
name='tester',
),
migrations.AddField(
model_name='subproductionplan',
name='first_test',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='qm.testrecord'),
),
migrations.DeleteModel(
name='FirstItem',
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.9 on 2022-02-22 01:58
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('qm', '0026_alter_testrecord_is_testok'),
('pm', '0027_auto_20220221_1027'),
]
operations = [
migrations.AlterField(
model_name='subproductionplan',
name='first_test',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='qm.testrecord'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.9 on 2022-02-22 02:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('qm', '0026_alter_testrecord_is_testok'),
('pm', '0028_alter_subproductionplan_first_test'),
]
operations = [
migrations.AlterField(
model_name='subproductionplan',
name='first_test',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='qm.testrecord', verbose_name='首件检验'),
),
]

View File

@ -1,4 +1,5 @@
from apps.qm.models import TestRecord
from apps.system.models import CommonAModel, Organization, User
from django.db import models
from django.contrib.auth.models import AbstractUser
@ -69,6 +70,7 @@ class SubProductionPlan(CommonAModel):
(SUBPLAN_STATE_WORKING, '生产中'),
(SUBPLAN_STATE_DONE, '已完成'),
)
number = models.CharField('子计划编号', max_length=50, unique=True, null=True, blank=True)
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')
@ -91,11 +93,8 @@ class SubProductionPlan(CommonAModel):
end_date_real = models.DateField('实际完工日期', null=True, blank=True)
is_picked = models.BooleanField('是否已领料', default=False)
# wproducts = models.JSONField('半成品表', default=list, blank=True)
is_testok = models.BooleanField('首件是否合格', null=True, blank=True)
form = models.ForeignKey(RecordForm, verbose_name='首件检查表', on_delete=models.CASCADE, null=True, blank=True)
tester = models.ForeignKey(User, on_delete=models.CASCADE,
verbose_name="首件检查员", null=True, blank=True, related_name='first_tester')
first_test = models.ForeignKey('qm.testrecord', on_delete=models.SET_NULL,
null=True, blank=True, verbose_name='首件检验')
leader_1 = models.ForeignKey(User, on_delete=models.CASCADE,
verbose_name="工序负责人", null=True, blank=True, related_name='first_leader_1')
leader_2 = models.ForeignKey(User, on_delete=models.CASCADE,
@ -109,15 +108,9 @@ class SubProductionPlan(CommonAModel):
verbose_name = '子生产计划'
verbose_name_plural = verbose_name
class FirstItem(BaseModel):
"""
首件确认表记录条目
"""
form_field = models.ForeignKey(RecordFormField, verbose_name='关联自定义表格字段', on_delete=models.CASCADE)
field_value = models.JSONField('录入值', null=True, blank=True)
is_hidden = models.BooleanField('是否隐藏', default=False)
is_testok = models.BooleanField('是否合格', null=True, blank=True)
subproduction_plan = models.ForeignKey(SubProductionPlan, verbose_name='关联的子计划', on_delete=models.CASCADE, related_name='item_test_record')
# @property
# def first_test(self):
# return self.test_subplan.filter(type=TestRecord.TEST_FIRST, is_deleted=False).first()
class SubProductionProgress(BaseModel):
"""

View File

@ -1,6 +1,8 @@
from apps.mtm.models import RecordForm
from apps.pm.models import ProductionPlan, SubProductionPlan, SubProductionProgress
from rest_framework import serializers
from apps.qm.models import TestRecord
from apps.qm.serializers import TestRecordShortSerializer
from apps.sam.serializers import OrderSerializer, OrderSimpleSerializer
from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer, RecordFormSimpleSerializer, SubProductionSimpleSerializer
from apps.system.serializers import OrganizationSimpleSerializer, UserSimpleSerializer
@ -33,12 +35,17 @@ class ResourceConvertListSerializer(serializers.ListSerializer):
class ResourceCalListSerializer(serializers.ListSerializer):
child = ResourceCalSerializer()
class SubProductionPlanListSerializer(serializers.ModelSerializer):
class SubProductionPlanListSerializer(DynamicFieldsSerializerMixin, serializers.ModelSerializer):
workshop_ = OrganizationSimpleSerializer(source='workshop', read_only=True)
process_ = ProcessSimpleSerializer(source='process', read_only=True)
subproduction_ = SubProductionSimpleSerializer(source='subproduction', read_only=True)
product_ = MaterialSimpleSerializer(source='product', read_only=True)
plan_product_ = serializers.SerializerMethodField()
leader_1_ = UserSimpleSerializer(source='leader_1', read_only=True)
leader_2_ = UserSimpleSerializer(source='leader_2', read_only=True)
leader_3_ = UserSimpleSerializer(source='leader_3', read_only=True)
first_test_ = TestRecordShortSerializer(source='first_test', read_only=True)
# first_test_ = serializers.SerializerMethodField()
class Meta:
model=SubProductionPlan
fields = '__all__'
@ -46,7 +53,10 @@ class SubProductionPlanListSerializer(serializers.ModelSerializer):
def get_plan_product_(self, obj):
return MaterialSimpleSerializer(instance=obj.production_plan.product).data
# def get_first_test_(self, obj):
# if obj.first_test:
# return TestRecordShortSerializer(instance=obj.first_test).data
# return None
class SubProductionPlanUpdateSerializer(serializers.ModelSerializer):
@ -81,20 +91,6 @@ class SubProductionProgressSerializer(serializers.ModelSerializer):
class FirstTestInitSerializer(serializers.Serializer):
form = serializers.PrimaryKeyRelatedField(queryset=RecordForm.objects.all(), required=True)
class FirstTestDetailSerializer(serializers.ModelSerializer):
tester_ = UserSimpleSerializer(source='tester', read_only=True)
leader_1_ = UserSimpleSerializer(source='leader_1', read_only=True)
leader_2_ = UserSimpleSerializer(source='leader_2', read_only=True)
leader_3_ = UserSimpleSerializer(source='leader_3', 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()
class Meta:
model = SubProductionPlan
fields = ['id', 'form', 'form_', 'is_testok', 'remark', 'first_sign_time'
'tester', 'tester_', 'leader_1', 'leader_1_', 'leader_2',
'leader_2_', 'leader_3', 'leader_3_']
def get_record_data(self, obj):
return None
class FirstTestAuditSerializer(serializers.Serializer):
leader = serializers.CharField()

View File

@ -5,23 +5,29 @@ from rest_framework import serializers
from rest_framework.views import APIView
from apps.em.models import Equipment
from apps.em.serializers import EquipmentSimpleSerializer
from apps.hrm.services import HRMService
from apps.inm.models import MaterialBatch
from apps.inm.serializers import MaterialBatchSerializer
from apps.mtm.models import Material, Step, SubProduction, SubprodctionMaterial
from apps.mtm.models import Material, RecordFormField, Step, SubProduction, SubprodctionMaterial
from apps.pm.filters import PlanFilterSet, SubproductionProgressFilterSet
from apps.qm.models import TestRecord, TestRecordItem
from apps.qm.serializers import TestRecordDetailBaseSerializer
from apps.system.mixins import CreateUpdateModelAMixin
from apps.pm.serializers import FirstTestInitSerializer, GenSubPlanSerializer, PickNeedSerializer, PlanDestorySerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, ResourceConvertListSerializer, ResourceConvertSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer
from apps.pm.serializers import FirstTestAuditSerializer, FirstTestInitSerializer, GenSubPlanSerializer, PickNeedSerializer, PlanDestorySerializer, ProductionPlanCreateFromOrderSerializer, ProductionPlanSerializer, ResourceCalListSerializer, ResourceCalSerializer, ResourceConvertListSerializer, ResourceConvertSerializer, SubProductionPlanListSerializer, SubProductionPlanUpdateSerializer, SubProductionProgressSerializer
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from apps.pm.models import ProductionPlan, SubProductionProgress, SubProductionPlan
from rest_framework.viewsets import GenericViewSet, ModelViewSet
from django.shortcuts import render
from apps.sam.models import Order
from rest_framework.exceptions import APIException, ParseError
from rest_framework.exceptions import APIException, ParseError, ValidationError
from rest_framework.response import Response
from rest_framework.decorators import action
from django.db.models import F
from apps.system.serializers import UserSimpleSerializer
from utils.tools import ranstr
from django.db import transaction
from rest_framework import status
from django.utils import timezone
# Create your views here.
def updateOrderPlanedCount(order):
@ -145,7 +151,9 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
子生产计划-列表/修改
"""
perms_map = {'get': '*', 'put':'subplan_update'}
queryset = SubProductionPlan.objects.select_related('process', 'workshop', 'subproduction', 'product', 'production_plan__product')
queryset = SubProductionPlan.objects.select_related('process',
'workshop', 'subproduction', 'product',
'production_plan__product', 'leader_1', 'leader_2', 'leader_3', 'first_test')
search_fields = []
serializer_class = SubProductionPlanListSerializer
filterset_fields = ['production_plan', 'process', 'state', 'product', 'workshop']
@ -229,20 +237,68 @@ class SubProductionPlanViewSet(CreateUpdateModelAMixin, ListModelMixin, UpdateMo
have = MaterialBatchSerializer(instance=objs, many=True).data
return Response({'need':need, 'have':have})
@action(methods=['put'], detail=True, perms_map={'post':'first_test'}, serializer_class=FirstTestInitSerializer)
@action(methods=['post'], detail=True, perms_map={'post':'first_test'}, serializer_class=FirstTestInitSerializer)
@transaction.atomic
def first_test_init(self, request, pk=None):
"""
获取首件检查表
首件检查表初始化
"""
obj = self.get_object()
if obj.is_testok is None:
first_test = obj.first_test
if first_test:
return Response(TestRecordDetailBaseSerializer(instance=first_test).data)
else:
rdata = request.data
serializer = self.get_serializer(data=rdata)
serializer.is_valid(raise_exception=True)
form = serializer.validated_data.get('form')
savedict = dict(
create_by=request.user,
subproduction_plan=obj,
type = TestRecord.TEST_FIRST,
form=form)
tr = TestRecord.objects.create(**savedict)
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()
obj.first_test = tr
obj.save()
return Response(TestRecordDetailBaseSerializer(instance=tr).data)
raise APIException('已经过首件确认')
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=FirstTestAuditSerializer)
@transaction.atomic
def first_audit(self, request, pk=None):
"""
首件审核
"""
obj = self.get_object()
if obj.leader_1 and obj.leader_2 and obj.leader_3:
raise ValidationError('首件确认已完成')
if obj.first_test is None:
raise ValidationError('未进行首件检查')
if not obj.first_test.is_submited:
raise ValidationError('首件检查未提交')
if not obj.first_test.is_testok:
raise ValidationError('首件检查不合格')
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
le = vdata.get('leader')
if le not in ['leader_1', 'leader_2', 'leader_3']:
return Response('审核人有误', status=status.HTTP_400_BAD_REQUEST)
if vdata.get('leader') == 'leader_1':
obj.leader_1 = request.user
elif vdata.get('leader') == 'leader_2':
obj.leader_2 = request.user
else:
obj.leader_3 = request.user
obj.first_sign_time = timezone.now()
obj.save()
return Response(UserSimpleSerializer(instance=request.user).data)

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2022-02-21 02:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qm', '0024_rename_is_midtesing_testrecord_is_midtesting'),
]
operations = [
migrations.AlterField(
model_name='testrecord',
name='type',
field=models.PositiveSmallIntegerField(choices=[(20, '工序检验'), (30, '工序复检'), (36, '夹层检验'), (40, '成品检验'), (10, '首件检验')], default=20),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2022-02-21 07:20
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('qm', '0025_alter_testrecord_type'),
]
operations = [
migrations.AlterField(
model_name='testrecord',
name='is_testok',
field=models.BooleanField(blank=True, null=True, verbose_name='是否合格'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.9 on 2022-02-22 02:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('pm', '0029_alter_subproductionplan_first_test'),
('qm', '0026_alter_testrecord_is_testok'),
]
operations = [
migrations.AlterField(
model_name='testrecord',
name='subproduction_plan',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='test_subplan', to='pm.subproductionplan', verbose_name='关联的生产子计划'),
),
]

View File

@ -47,6 +47,7 @@ class TestRecord(CommonADModel):
"""
检验记录
"""
TEST_FIRST = 10
TEST_PROCESS = 20
TEST_PROCESS_RE = 30
TEST_COMB = 36
@ -55,16 +56,17 @@ class TestRecord(CommonADModel):
(TEST_PROCESS, '工序检验'),
(TEST_PROCESS_RE, '工序复检'),
(TEST_COMB, '夹层检验'),
(TEST_FINAL, '成品检验')
(TEST_FINAL, '成品检验'),
(TEST_FIRST, '首件检验')
)
form = models.ForeignKey('mtm.recordform', verbose_name='所用表格', on_delete=models.CASCADE)
type = models.PositiveSmallIntegerField(choices=type_choice, default=TEST_PROCESS)
is_testok = models.BooleanField('是否合格', default=True)
is_testok = models.BooleanField('是否合格', null=True, blank=True)
number = models.CharField('产品编号', null=True, blank=True, max_length=50)
wproduct = models.ForeignKey('wpm.wproduct', verbose_name='关联的动态产品', on_delete=models.CASCADE, null=True, blank=True, related_name='test_wproduct')
material = models.ForeignKey('mtm.material', verbose_name='关联的物料状态', on_delete=models.CASCADE, null=True, blank=True)
step = models.ForeignKey('mtm.step', 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)
subproduction_plan = models.ForeignKey('pm.subproductionplan', verbose_name='关联的生产子计划', on_delete=models.CASCADE, null=True, blank=True, related_name='test_subplan')
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)

View File

@ -64,6 +64,10 @@ class TestRecordItemSerializer(serializers.ModelSerializer):
model = TestRecordItem
fields = '__all__'
class TestRecordShortSerializer(serializers.ModelSerializer):
class Meta:
model = TestRecord
fields = ['id', 'form', 'is_testok', 'is_submited']
class TestRecordCreateSerializer(serializers.ModelSerializer):
record_data = TestRecordItemCreateSerializer(many=True)
class Meta:

View File

@ -93,6 +93,7 @@ class TestRecordViewSet(ListModelMixin, UpdateModelMixin, RetrieveModelMixin, De
with transaction.atomic():
obj.is_submited=True
obj.save()
if obj.wproduct:
WpmService.update_wproduct_by_test(obj, request.user) # 这里已经做了日志记录和进度计算
return Response()

View File

@ -0,0 +1,53 @@
# Generated by Django 3.2.9 on 2022-02-22 01:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sam', '0012_alter_order_delivery_date'),
]
operations = [
migrations.AddField(
model_name='sale',
name='count_real',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='实际发货数量'),
),
migrations.AddField(
model_name='sale',
name='edelivery_date',
field=models.DateField(blank=True, null=True, verbose_name='预计发货日期'),
),
migrations.AddField(
model_name='sale',
name='receiver',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='收货人'),
),
migrations.AddField(
model_name='sale',
name='receiver_address',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='收获地址'),
),
migrations.AddField(
model_name='sale',
name='receiver_phone',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='收货人联系电话'),
),
migrations.AddField(
model_name='sale',
name='remark',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='备注'),
),
migrations.AddField(
model_name='sale',
name='trans_mode',
field=models.IntegerField(blank=True, choices=[(10, '铁路快运'), (20, '铁路慢件'), (30, '铁路整车'), (40, '汽车运输'), (50, '空运')], null=True, verbose_name='运输方式'),
),
migrations.AlterField(
model_name='sale',
name='count',
field=models.PositiveIntegerField(default=0, verbose_name='预计发货数量'),
),
]

View File

@ -0,0 +1,36 @@
# Generated by Django 3.2.9 on 2022-02-22 07:30
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('mtm', '0049_auto_20220222_0944'),
('sam', '0013_auto_20220222_0941'),
]
operations = [
migrations.AddField(
model_name='saleproduct',
name='packnum',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='装箱单号'),
),
migrations.CreateModel(
name='SalePack',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('count', models.PositiveSmallIntegerField(verbose_name='打包数量')),
('packitem', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='mtm.packitem', verbose_name='打包项目')),
('sale_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sam.saleproduct', verbose_name='关联销售产品')),
],
options={
'abstract': False,
},
),
]

View File

@ -7,7 +7,7 @@ from django.db.models.query import QuerySet
from utils.model import SoftModel, BaseModel
from apps.mtm.models import Material
from apps.mtm.models import Material, PackItem
@ -69,12 +69,30 @@ class Sale(CommonADModel):
"""
销售记录
"""
TRANS_RAIL_FAST = 10
TRANS_RAIL_SLOW = 20
TRANS_RAIL_WHOLE = 30
TRANS_LORRY = 40
TRANS_AIR = 50
trans_choices=(
(TRANS_RAIL_FAST, '铁路快运'),
(TRANS_RAIL_SLOW, '铁路慢件'),
(TRANS_RAIL_WHOLE, '铁路整车'),
(TRANS_LORRY, '汽车运输'),
(TRANS_AIR, '空运'),
)
customer = models.ForeignKey(Customer, verbose_name='客户', on_delete=models.CASCADE)
order = models.ForeignKey(Order, verbose_name='关联订单', on_delete=models.CASCADE, null=True, blank=True)
product = models.ForeignKey(Material, verbose_name='所需产品', on_delete=models.CASCADE)
count = models.PositiveIntegerField('交货数量', default=0)
count = models.PositiveIntegerField('预计发货数量', default=0)
count_real = models.PositiveIntegerField('实际发货数量', null=True, blank=True)
is_audited = models.BooleanField('是否审核', default=False)
edelivery_date = models.DateField('预计发货日期', null=True, blank=True)
trans_mode = models.IntegerField('运输方式', null=True, blank=True, choices=trans_choices)
receiver = models.CharField('收货人', null=True, blank=True, max_length=200)
receiver_phone = models.CharField('收货人联系电话', null=True, blank=True, max_length=20)
receiver_address = models.CharField('收获地址', null=True, blank=True, max_length=200)
remark = models.CharField('备注', null=True, blank=True, max_length=200)
class SaleProduct(BaseModel):
"""
@ -84,11 +102,22 @@ class SaleProduct(BaseModel):
number = models.CharField('物品编号', max_length=50)
iproduct = models.ForeignKey('inm.iproduct', verbose_name='关联库存产品', on_delete=models.CASCADE, related_name='sale_iproduct')
remark = models.TextField('备注', null=True, blank=True)
packnum = models.CharField('装箱单号', max_length=100, null=True, blank=True)
class Meta:
unique_together = (
('sale','iproduct'), # 联合唯一
)
class SalePack(BaseModel):
"""
销售打包
"""
sale_product = models.ForeignKey(SaleProduct, verbose_name='关联销售产品',
on_delete=models.CASCADE)
packitem = models.ForeignKey(PackItem, verbose_name='打包项目',
on_delete=models.CASCADE)
count = models.PositiveSmallIntegerField('打包数量')

View File

@ -2,15 +2,20 @@ from rest_framework import serializers
from rest_framework import exceptions
from apps.inm.models import IProduct
from apps.inm.serializers import IProductListSerializer
from apps.mtm.serializers import MaterialSimpleSerializer
from apps.sam.models import Sale, SaleProduct
from apps.mtm.models import Material, PackItem
from apps.mtm.serializers import MaterialSimpleSerializer, PackItemDetailSerializer
from apps.sam.models import Sale, SalePack, SaleProduct
from apps.sam.serializers import CustomerSimpleSerializer, OrderSimpleSerializer
from django.db import transaction
from rest_framework.exceptions import ValidationError
class SaleCreateSerializer(serializers.ModelSerializer):
iproducts = serializers.PrimaryKeyRelatedField(queryset=
IProduct.objects.all(), many=True)
class Meta:
model = Sale
fields = ['customer', 'order', 'product', 'iproducts']
fields = ['customer', 'order', 'product', 'iproducts',
'edelivery_date', 'trans_mode', 'receiver', 'receiver_phone'
, 'receiver_address', 'remark']
def validate(self, attrs):
order = attrs.get('order', None)
@ -18,8 +23,10 @@ class SaleCreateSerializer(serializers.ModelSerializer):
if order.customer:
attrs['customer'] = order.customer
attrs['product'] = order.product
for i in attrs['iproducts']:
if i.material != attrs['product']:
ips = IProduct.objects.filter(id__in=[i.id for i in attrs['iproducts']])
if ips.exclude(state=IProduct.SALE_OK).exists():
raise exceptions.APIException('选取了非可用的产品')
if ips.count() != ips.filter(material=attrs['product']).count():
raise exceptions.APIException('产品选取错误')
return super().validate(attrs)
@ -44,8 +51,43 @@ class SaleProductCreateSerializer(serializers.ModelSerializer):
fields = ['sale', 'iproduct']
def create(self, validated_data):
with transaction.atomic():
validated_data['number'] = validated_data['iproduct'].number
instance = SaleProduct.objects.create(**validated_data)
instance.sale.count = SaleProduct.objects.filter(sale=instance.sale).count()
instance.sale.save()
sale = instance.sale
sale.count = SaleProduct.objects.filter(sale=instance.sale).count()
sale.save()
order = sale.order
if order:
if sale.count+order.delivered_count>order.count:
raise exceptions.APIException('超过订单所需数量')
return instance
class SPackItemSerializer(serializers.ModelSerializer):
name = serializers.CharField(source='packitem.name', read_only=True)
specification = serializers.CharField(source='packitem.specification', read_only=True)
unit = serializers.CharField(source='packitem.unit', read_only=True)
material_ = MaterialSimpleSerializer(source='packitem.material', read_only=True)
class Meta:
model = SalePack
fields = '__all__'
class SaleProductPackDetailSerializer(serializers.ModelSerializer):
detail = serializers.SerializerMethodField()
class Meta:
model = ['packnum', 'detail']
def get_detail(self, obj):
return SPackItemSerializer(instance=SalePack.objects.filter(sale_product=obj)
, many=True).data
class SPackItemCreateSerializer(serializers.Serializer):
id = serializers.PrimaryKeyRelatedField(queryset=PackItem.objects.all())
count = serializers.IntegerField()
class SaleProductPackSerializer(serializers.ModelSerializer):
detail = SPackItemCreateSerializer(many=True)
class Meta:
model = SaleProduct
fields = ['packnum', 'detail']

View File

@ -1,11 +1,12 @@
from rest_framework.mixins import ListModelMixin, DestroyModelMixin, CreateModelMixin, RetrieveModelMixin
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
from apps.mtm.models import Material, PackItem
from apps.sam.models import Sale, SalePack, SaleProduct
from apps.sam.serializers_sale import SaleCreateSerializer, SaleListSerializer, SaleProductCreateSerializer, SaleProductListSerializer, SaleProductPackDetailSerializer, SaleProductPackSerializer
from rest_framework import exceptions
from django.db import transaction
from rest_framework.decorators import action
@ -36,6 +37,13 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
return SaleListSerializer
return super().get_serializer_class()
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
if obj.is_audited:
raise exceptions.APIException('该销售记录已审核,不可删除')
obj.delete()
IProduct.objects.filter(sale_iproduct__sale=obj).update()
def create(self, request, *args, **kwargs):
data = request.data
serializer = SaleCreateSerializer(data=data)
@ -49,6 +57,8 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
sale = Sale.objects.create(**vdata)
i_l = []
for i in iproducts:
i.state = IProduct.SALE_LOCK
i.save()
i_d ={}
i_d['sale'] = sale
i_d['number'] = i.number
@ -66,56 +76,59 @@ class SaleViewSet(CreateUpdateModelAMixin, ListModelMixin, RetrieveModelMixin, C
obj = self.get_object()
if obj.is_audited:
raise exceptions.APIException('已审核通过')
if obj.order:
if obj.count + obj.order.delivered_count > obj.order.count:
raise exceptions.APIException('超过订单所需数量')
# 创建出库记录
fifo = FIFO()
fifo.sale = obj
fifo.type = FIFO.FIFO_TYPE_SALE_OUT
fifo.is_audited = True
fifo.is_audited = False
fifo.auditor = request.user
fifo.inout_date = timezone.now()
fifo.create_by = request.user
fifo.number = 'CK' + ranstr(7)
fifo.save()
# 创建出库条目
ips = IProduct.objects.filter(sale_iproduct__sale=obj)
items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id'))
for i in items:
warehouse = WareHouse.objects.get(id=i['warehouse'])
material = Material.objects.get(id=i['material'])
fifoitem = FIFOItem()
fifoitem.need_test = False
fifoitem.warehouse = warehouse
fifoitem.material = material
fifoitem.count = i['total']
fifoitem.batch = i['batch']
fifoitem.fifo = fifo
fifoitem.save()
items_p = ips.filter(warehouse=warehouse, batch=i['batch'])
ipxs = []
for i in items_p:
# 创建出库明细半成品
ip = {}
ip['fifoitem'] = fifoitem
ip['number'] = i.number
ip['material'] = i.material
ip['iproduct'] = i
ipxs.append(FIFOItemProduct(**ip))
FIFOItemProduct.objects.bulk_create(ipxs)
# 更新成品库情况
ips.update(is_saled=True)
# ips = IProduct.objects.filter(sale_iproduct__sale=obj)
# items = ips.values('warehouse', 'material', 'batch').annotate(total=Count('id'))
# for i in items:
# warehouse = WareHouse.objects.get(id=i['warehouse'])
# material = Material.objects.get(id=i['material'])
# fifoitem = FIFOItem()
# fifoitem.need_test = False
# fifoitem.warehouse = warehouse
# fifoitem.material = material
# fifoitem.count = i['total']
# fifoitem.batch = i['batch']
# fifoitem.fifo = fifo
# fifoitem.save()
# items_p = ips.filter(warehouse=warehouse, batch=i['batch'])
# ipxs = []
# for i in items_p:
# # 创建出库明细半成品
# ip = {}
# ip['fifoitem'] = fifoitem
# ip['number'] = i.number
# ip['material'] = i.material
# ip['iproduct'] = i
# ipxs.append(FIFOItemProduct(**ip))
# FIFOItemProduct.objects.bulk_create(ipxs)
# 更新动态产品表情况
from apps.wpm.models import WProduct
WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update(
act_state=WProduct.WPR_ACT_STATE_SELLED)
# from apps.wpm.models import WProduct
# WProduct.objects.filter(iproduct_wproduct__sale_iproduct__sale=obj).update(
# act_state=WProduct.WPR_ACT_STATE_SELLED)
# 更新库存
InmService.update_inm(fifo)
# 变更审核状态
# InmService.update_inm(fifo)
# 变更销售提货审核状态
obj.is_audited = True
obj.save()
# 变更订单状态
if obj.order:
order = obj.order
order.delivered_count = order.delivered_count + obj.count
order.save()
# if obj.order:
# order = obj.order
# order.delivered_count = order.delivered_count + obj.count
# order.save()
return Response()
@ -142,7 +155,35 @@ class SaleProductViewSet(ListModelMixin, DestroyModelMixin, CreateModelMixin, Ge
sale = obj.sale
if sale.is_audited:
raise exceptions.APIException('该销售记录已审核,不可删除产品')
obj.delete()
sale.count = SaleProduct.objects.filter(sale=obj.sale).count()
sale.save()
obj.delete()
return Response()
@action(methods=['get', 'post'], detail=True, perms_map={'post':'sale_pack'}, serializer_class=serializers.Serializer)
@transaction.atomic
def pack(self, request, pk=None):
"""
打包装箱
"""
obj = self.get_object()
if request.method == 'GET':
for i in PackItem.objects.filter(product=obj.product.material, is_deleted=False):
SalePack.objects.get_or_create(sale_product=obj, packitem=i,
defaults={
"sale_product":obj,
"packitem":i,
"count":i.count
})
return Response(SaleProductPackDetailSerializer(instance=obj).data)
elif request.method == 'POST':
serializer = SaleProductPackSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
obj.packnum = vdata['packnum']
for i in vdata['detail']:
pi = i['id']
pi.count = i['count']
pi.save()
return Response()

View File

@ -90,7 +90,7 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
perms_map = {'get':'*', 'post':'workflow_update',
'put':'workflow_update', 'delete':'workflow_delete'}
'put':'workflow_update', 'delete':'workflow_update'}
queryset = State.objects.all()
serializer_class = StateSerializer
search_fields = ['name']
@ -99,7 +99,7 @@ class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, Destr
class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
perms_map = {'get':'*', 'post':'workflow_update',
'put':'workflow_update', 'delete':'workflow_delete'}
'put':'workflow_update', 'delete':'workflow_update'}
queryset = Transition.objects.all()
serializer_class = TransitionSerializer
search_fields = ['name']
@ -108,7 +108,7 @@ class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin,
class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
perms_map = {'get':'*', 'post':'workflow_update',
'put':'workflow_update', 'delete':'workflow_delete'}
'put':'workflow_update', 'delete':'workflow_update'}
queryset = CustomField.objects.all()
serializer_class = CustomFieldSerializer
search_fields = ['field_name']

View File

@ -62,7 +62,7 @@ class FitJSONRenderer(JSONRenderer):
if isinstance(data, list):
data = data[0]
response_body.msg = prefix + ":" + str(data) # 取一部分放入msg,方便前端alert
response_body.msg = prefix + str(data) # 取一部分放入msg,方便前端alert
else:
response_body.data = data
renderer_context.get("response").status_code = 200 # 统一成200响应, 可用body里code区分业务异常