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

This commit is contained in:
shilixia 2022-03-16 16:38:42 +08:00
commit 289a7a2335
36 changed files with 837 additions and 60 deletions

View File

@ -6,68 +6,69 @@ import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
NProgress.configure({ showSpinner: false }) ;// NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
const whiteList = ['/login']; // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
NProgress.start()
NProgress.start();
// set page title
document.title = getPageTitle(to.meta.title)
document.title = getPageTitle(to.meta.title);
// determine whether the user has logged in
const hasToken = getToken()
const hasToken = getToken();
if (hasToken) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
next({ path: '/' });
NProgress.done()
} else {
// determine whether the user has obtained his permission perms through getInfo
const hasPerms = store.getters.perms && store.getters.perms.length > 0
const hasPerms = store.getters.perms && store.getters.perms.length > 0;
if (hasPerms) {
next()
} else {
try {
// get user info
// note: perms must be a object array! such as: ['admin'] or ,['developer','editor']
const { perms } = await store.dispatch('user/getInfo')
const { perms } = await store.dispatch('user/getInfo');
// generate accessible routes map based on perms
const accessRoutes = await store.dispatch('permission/generateRoutes', perms)
const accessRoutes = await store.dispatch('permission/generateRoutes', perms);
// dynamically add accessible routes
router.addRoutes(accessRoutes)
router.addRoutes(accessRoutes);
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
Message.error(error || 'Has Error')
next(`/login?redirect=${to.path}`)
await store.dispatch('user/resetToken');
Message.error(error || 'Has Error');
next(`/login?redirect=${to.path}`);
NProgress.done()
}
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
if (to.path === '/index'){
next()
}else if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login?redirect=${to.path}`)
next(`/login?redirect=${to.path}`);
NProgress.done()
}
}
})
});
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
});

View File

@ -20,7 +20,7 @@ service.interceptors.request.use(
// please modify it according to the actual situation
config.headers['Authorization'] = 'Bearer ' + getToken()
}
let data = config.data;
let data = config.data?config.data:config.params;
/*debugger;
console.log(data)*/
if(data){
@ -28,6 +28,9 @@ service.interceptors.request.use(
if(token){
config.headers['Authorization'] = 'Bearer ' + token
}
if(data.type==='big_screen'){
config.headers['Authorization'] = 'big_screen '
}
}
/* debugger;
console.log(config.headers['Authorization'])*/

View File

@ -7,7 +7,7 @@
:key="item.title"
>
<p class="ml-3 colorBlue fw-b fs-xl">{{ item.title }}</p>
<div>
<div v-if="numberShow">
<dv-digital-flop
class="dv-dig-flop ml-1 mt-2 pl-3"
:config="item.number"
@ -20,7 +20,7 @@
<span>
<el-icon class="el-icon-pie-chart"></el-icon>
</span>
<span class="fs-xl text mx-2 mb-1 pl-3">任务完成进度</span>
<span class="fs-xl text mx-2 mb-1 pl-3">工序生产进度</span>
<dv-scroll-ranking-board class="dv-scr-rank-board mt-1" :config="ranking" />
</div>
<div class="percent">
@ -49,11 +49,20 @@
</template>
<script>
import CenterChart from '@/components/echart/chartRate.vue'
import {getProductionplanList} from "@/api/pm";
import {getContractList, getOrderList} from "@/api/sam";
import CenterChart from '@/components/echart/chartRate.vue'
export default {
data() {
return {
contractTotalCurrent:0,
orderTotalCurrent:0,
planTotalCurrent:0,
selProductCurrent:0,
noProductCurrent:0,
numberShow:true,
/* create_time_start:'',
titleItem: [
{
title: '本月合同数',
@ -198,11 +207,101 @@ export default {
}
}
}
]
]*/
}
},
components: {
CenterChart
},
props: {
titleItem:{
type:Array,
default: () => {
return []
}
} ,
rate:{
type:Array,
default: () => {
return []
}
} ,
water:{
type:Object,
default: () => {
return {}
}
} ,
ranking:{
type:Object,
default: () => {
return {}
}
} ,
},
methods:{
/* getData(){
let that = this;
that.numberShow = false;
let dat = new Date();
that.week = dat.getDay();
that.currentTime = dat.getTime();
that.currentYear = dat.getFullYear();
let month = dat.getMonth() + 1;
that.currentMonth = month > 9 ? month : '0' + month;
that.currentDay = dat.getDate();
that.create_time_start = that.currentYear + '-' + that.currentMonth + '-01';
that.$nextTick(()=>{
})
},*/
getContract(){
let that = this;
//合同
getContractList({type:'big_screen',page: 1, page_size: 1, create_time_start: that.create_time_start}).then((response) => {
if (response.data) {
that.numberShow = false;
that.titleItem[0].number.number[0] = response.data.count;
that.numberShow = true;
}
});
},
getOrder(){
let that = this;
//订单
getOrderList({type:'big_screen',page: 1, page_size: 1, create_time_start: that.create_time_start}).then((response) => {
if (response.data) {
that.numberShow = false;
that.titleItem[1].number.number[0] = response.data.count;
that.numberShow = true;
}
});
},
getProductionplan(){
let that = this;
//已排产任务
getProductionplanList({type:'big_screen',page: 1, page_size: 1, tag: 'working'}).then((response) => {
if (response.data) {
that.numberShow = false;
that.titleItem[2].number.number[0] = response.data.count;
that.numberShow = true;
}
});
},
},
mounted() {
let that = this;
let dat = new Date();
that.week = dat.getDay();
that.currentTime = dat.getTime();
that.currentYear = dat.getFullYear();
let month = dat.getMonth() + 1;
that.currentMonth = month > 9 ? month : '0' + month;
that.currentDay = dat.getDate();
that.create_time_start = that.currentYear + '-' + that.currentMonth + '-01';
that.getContract();
that.getOrder();
that.getProductionplan();
}
}
</script>

View File

@ -10,7 +10,7 @@
</div>
</div>
<div class="d-flex jc-center body-box">
<dv-scroll-board class="dv-scr-board" :config="config" />
<dv-scroll-board class="dv-scr-board" :config="userConfig" />
</div>
</div>
</div>
@ -19,7 +19,7 @@
export default {
data() {
return {
config: {
/* config: {
header: ['姓名', '部门', '到岗情况'],
data: [
['张思', '一车间', "<span class='colorGrass'>已到岗</span>"],
@ -41,9 +41,17 @@ export default {
index: false,
// columnWidth: [50],
align: ['center']
}*/
}
},
props:{
userConfig:{
type:Object,
default:()=>{
return {}
}
}
}
},
}
</script>

View File

@ -19,7 +19,7 @@
export default {
data() {
return {
config: {
/*config: {
data: [
{
name: '冷加工',
@ -50,6 +50,14 @@ export default {
value: 100
}
]
}*/
}
},
props:{
config:{
type:Object,
default:()=>{
return {}
}
}
},

View File

@ -4,9 +4,9 @@
<!--<dv-loading v-show="loading">Loading...</dv-loading>-->
<div class="host-body">
<div class="d-flex jc-center" id="firstLine">
<dv-decoration-10 class="dv-dec-10" />
<dv-decoration-10 class="dv-dec-10"/>
<div class="d-flex jc-center">
<dv-decoration-8 class="dv-dec-8" :color="['#568aea', '#000000']" />
<dv-decoration-8 class="dv-dec-8" :color="['#568aea', '#000000']"/>
<div class="title">
<div class="title-text">
<span style="margin-right: 20px">航玻生产</span><span>管理系统</span>
@ -23,7 +23,7 @@
:color="['#568aea', '#000000']"
/>
</div>
<dv-decoration-10 class="dv-dec-10-s" />
<dv-decoration-10 class="dv-dec-10-s"/>
</div>
<!-- 第二行 -->
<div class="d-flex jc-between px-2">
@ -50,36 +50,45 @@
<!-- 第三行数据 -->
<div id="centerWrap" class="content-box">
<!-- 数据统计 -->
<div>
<center />
<div v-if="numberShow">
<center
:titleItem="titleItem"
:rate="rate"
:water="water"
:ranking="ranking"
/>
</div>
<div>
<centerRight2 />
<div v-if="processRate">
<centerRight2
:config="config"
/>
</div>
<div>
<dv-border-box-1>
<centerLeft1 />
<centerLeft1/>
</dv-border-box-1>
</div>
<div>
<dv-border-box-1>
<centerLeft2 />
<centerLeft2/>
</dv-border-box-1>
</div>
<div>
<div v-if="listUser">
<dv-border-box-13>
<centerRight1 />
<centerRight1
:userConfig="userConfig"
/>
</dv-border-box-13>
</div>
</div>
<!-- 第四行数据 -->
<div class="bototm-box">
<dv-border-box-3>
<bottomRight />
<bottomRight/>
</dv-border-box-3>
<dv-border-box-13>
<bottomLeft />
<bottomLeft/>
</dv-border-box-13>
</div>
</div>
@ -90,18 +99,22 @@
<script>
import drawMixin from "../../utils/drawMixin";
import { formatTimeBigScreen } from '../../utils/index.js'
import {formatTimeBigScreen} from '../../utils/index.js'
import centerLeft1 from './centerLeft1'
import centerLeft2 from './centerLeft2'
import centerRight1 from './centerRight1'
import centerRight2 from './centerRight2'
import center from './center'
import {getEmployee} from "@/api/hrm";
import bottomLeft from './bottomLeft'
import bottomRight from './bottomRight'
import { getPlanGantt } from "@/api/srm";
import {getPlanGantt} from "@/api/srm";
import {getProductionplanList} from "@/api/pm";
import {getProcessYield} from "@/api/srm";
import {getContractList, getOrderList} from "@/api/sam";
export default {
mixins: [ drawMixin ],
mixins: [drawMixin],
data() {
return {
timing: null,
@ -109,7 +122,183 @@
dateDay: null,
dateYear: null,
dateWeek: null,
planGanttList:[],
planGanttList: [],
numberShow: false,
processRate: false,
listUser: false,
create_time_start: '',
titleItem: [
{
title: '本月合同数',
number: {
number: [12],//数字数值
toFixed: 0,//小数位数
textAlign: 'left',//水平对齐方式
content: '{nt}',//内容模版//rowGap行间距
style: {//样式配置
fontSize: 26
}
}
},
{
title: '本月生产订单数',
number: {
number: [12],
toFixed: 0,
textAlign: 'left',
content: '{nt}',
style: {
fontSize: 26
}
}
},
{
title: '本月在制任务数',
number: {
number: [2],
toFixed: 0,
textAlign: 'left',
content: '{nt}',
style: {
fontSize: 26
}
}
},
{
title: '本月交付产品数',
number: {
number: [8],
toFixed: 0,
textAlign: 'left',
content: '{nt}',
style: {
fontSize: 26
}
}
},
{
title: '本月不合格产品数',
number: {
number: [2],
toFixed: 0,
textAlign: 'left',
content: '{nt}',
style: {
fontSize: 26
}
}
},
{
title: '本月军检合格率',
number: {
number: [99],
toFixed: 1,
textAlign: 'left',
content: '{nt}%',
style: {
fontSize: 26
}
}
},
],
ranking: {
data: [
{
name: '冷加工',
value: 100
},
{
name: '热弯成型',
value: 100
},
{
name: '化学钢化',
value: 100
},
{
name: '镀膜',
value: 100
},
{
name: '夹层',
value: 100
},
{
name: '包边',
value: 100
},
{
name: '装框',
value: 100
}
],
carousel: 'single',
unit: '%'
},
water: {
data: [24, 45],
shape: 'roundRect',
formatter: '{value}%',
waveNum: 3
},
// 通过率和达标率的组件复用数据
rate: [
{
id: 'centerRate1',
tips: 98,
colorData: {
textStyle: '#3fc0fb',
series: {
color: ['#00bcd44a', 'transparent'],
dataColor: {
normal: '#03a9f4',
shadowColor: '#97e2f5'
}
}
}
},
{
id: 'centerRate2',
tips: 99,
colorData: {
textStyle: '#67e0e3',
series: {
color: ['#faf3a378', 'transparent'],
dataColor: {
normal: '#ff9800',
shadowColor: '#fcebad'
}
}
}
}
],
config: {
data: []
},
//人员到岗
userConfig: {
header: ['姓名', '部门', '到岗情况'],
data: [
['张思', '一车间', "<span class='colorGrass'>已到岗</span>"],
['李森', '一车间', "<span class='colorGrass'>已到岗</span>"],
['王师', '一车间', "<span class='colorRed'>未到岗</span>"],
['赵迪', '一车间', "<span class='colorGrass'>已到岗</span>"],
['孟津', '一车间', "<span class='colorGrass'>已到岗</span>"],
['孙东课', '一车间', "<span class='colorGrass'>已到岗</span>"],
['周神秘', '二车间', "<span class='colorGrass'>已到岗</span>"],
['吴老弟', '二车间', "<span class='colorRed'>未到岗</span>"],
['郑成功', '二车间', "<span class='colorGrass'>已到岗</span>"],
['冯宝宝', '二车间', "<span class='colorGrass'>已到岗</span>"]
],
rowNum: 7, //表格行数
headerHeight: 35,
headerBGC: '#0f1325', //表头
oddRowBGC: '#0f1325', //奇数行
evenRowBGC: '#171c33', //偶数行
index: false,
// columnWidth: [50],
align: ['center']
},
weekday: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
}
},
@ -122,21 +311,33 @@
bottomLeft,
bottomRight
},
created(){
created() {
// window.open('http://49.232.14.174:2222/#/index');
// this.$router.go(-1)
},
mounted() {
let that = this;
this.timeFn();
this.cancelLoading();
this.getPageData();
let bigHeight = document.getElementsByClassName('host-body')[0].clientHeight;
let firstHeight = document.getElementById('firstLine').clientHeight;
let secondHeight = document.getElementsByClassName('jc-between')[0].clientHeight;
let domHeight = (bigHeight/2) - firstHeight - secondHeight-30;
let domHeight = (bigHeight / 2) - firstHeight - secondHeight - 30;
document.getElementById('centerWrap').style.height = domHeight + 'px';
let dat = new Date();
that.week = dat.getDay();
that.currentTime = dat.getTime();
that.currentYear = dat.getFullYear();
let month = dat.getMonth() + 1;
that.currentMonth = month > 9 ? month : '0' + month;
that.currentDay = dat.getDate();
that.create_time_start = that.currentYear + '-' + that.currentMonth + '-01';
that.getCenterData();
that.getCenterRight2Data();
that.getUserList();
},
beforeDestroy () {
beforeDestroy() {
clearInterval(this.timing)
},
methods: {
@ -147,9 +348,9 @@
this.dateWeek = this.weekday[new Date().getDay()]
}, 1000)
},
getPageData(){
getPageData() {
let that = this;
getPlanGantt({Authorization:'big_screen'}).then(res => {
getPlanGantt({type: 'big_screen'}).then(res => {
if (res.code === 200) {
this.planGanttList = res.data.results;
} else {
@ -161,7 +362,86 @@
setTimeout(() => {
this.loading = false
}, 500)
},
getCenterData() {
let that = this;
that.numberShow = false;
//合同
getContractList({
type: 'big_screen',
page: 1,
page_size: 1,
create_time_start: that.create_time_start
}).then((response) => {
if (response.data) {
that.titleItem[0].number.number[0] = response.data.count;
getOrderList({
type: 'big_screen',
page: 1,
page_size: 1,
create_time_start: that.create_time_start
}).then((response) => {
if (response.data) {
that.titleItem[1].number.number[0] = response.data.count;
getProductionplanList({type: 'big_screen', page: 1, page_size: 1, tag: 'working'}).then((response) => {
if (response.data) {
that.titleItem[2].number.number[0] = response.data.count;
that.numberShow = true;
}
});
}
});
}
});
},
getCenterRight2Data() {
let that = this;
that.processRate = false;
let dat = new Date();
let Year = dat.getFullYear();
let month = dat.getMonth() + 1;
let day = dat.getDate();
let searchTime = Year + '-' + month + '-' + day;
getProcessYield({datetime_start: searchTime, datetime_end: searchTime,type:'big_screen'}).then((response) => {
if (response.data) {
let list = response.data;
let data = [];
list.forEach(item => {
let obj = new Object();
obj.name = item.name;
obj.value = Math.floor(item.rate * 100);
// obj.value = rate.toFixed(2);
data.push(obj)
});
that.config.data = data;
that.processRate = true;
}
});
},
//人员到岗情况列表
getUserList() {
let that = this;
that.listUser = false;
getEmployee({page: 0,type:'big_screen'}).then((response) => {
if (response.data) {
let list = response.data.results;
let data = [];
list.forEach(item => {
let obj = [];
obj.push(item.name);
obj.push(item.dept_.name);
if (item.is_atwork) {
obj.push("<span class='colorGrass'>已到岗</span>")
} else {
obj.push("<span class='colorRed'>未到岗</span>")
}
data.push(obj)
});
that.userConfig.data = data;
that.listUser = true;
}
});
},
}
}
</script>

View File

@ -5,7 +5,7 @@
<img class="faceLoginBtn" src="./../../assets/face.png" @click="takePhoto()">
</div>
<div style="width: 360px;">
<h3 class="title">航玻生产管理系统</h3>
<h3 class="title" @click="toBigScreen">航玻生产管理系统</h3>
<el-tabs v-model="activeName" :stretch="true">
<el-tab-pane label="账号密码登录">
<el-form
@ -135,6 +135,9 @@
},
},
methods: {
toBigScreen(){
this.$router.push('/index')
},
showPwd() {
if (this.passwordType === "password") {
this.passwordType = "";

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class CmsConfig(AppConfig):
name = 'apps.cms'
verbose_name = '内容管理'

View File

@ -0,0 +1,37 @@
# Generated by Django 3.2.9 on 2022-03-16 02:25
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Article',
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='删除标记')),
('title', models.CharField(max_length=100, verbose_name='标题')),
('content', models.TextField(verbose_name='内容')),
('author', models.CharField(blank=True, max_length=100, null=True, verbose_name='作者')),
('is_top', models.BooleanField(default=False, verbose_name='是否置顶')),
('is_published', models.BooleanField(default=False, verbose_name='是否发布')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='article_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='article_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,12 @@
from django.db import models
from apps.system.models import CommonAModel
# Create your models here.
class Article(CommonAModel):
"""
文章
"""
title = models.CharField('标题', max_length=100)
content = models.TextField('内容')
author = models.CharField('作者', max_length=100, null=True, blank=True)
is_top = models.BooleanField('是否置顶', default=False)
is_published = models.BooleanField('是否发布', default=False)

View File

@ -0,0 +1,21 @@
from rest_framework import serializers
from apps.cms.models import Article
from apps.system.serializers import UserSimpleSerializer
class ArticleListSerializer(serializers.ModelSerializer):
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
class Meta:
model = Article
exclude = ['content']
class ArticleCreateUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['title', 'content', 'author', 'is_published', 'is_top']
class ArticleDetailSerializer(serializers.ModelSerializer):
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
class Meta:
model = Article
fields = '__all__'

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@ -0,0 +1,10 @@
from rest_framework.routers import DefaultRouter
from django.urls import path, include
from apps.cms.views import ArticleViewSet
router = DefaultRouter()
router.register('article', ArticleViewSet, basename='article')
urlpatterns = [
path('', include(router.urls)),
]

View File

@ -0,0 +1,26 @@
from django.shortcuts import render
from rest_framework.viewsets import ModelViewSet
from apps.cms.models import Article
from apps.cms.serializers import ArticleCreateUpdateSerializer, ArticleDetailSerializer, ArticleListSerializer
from apps.system.mixins import CreateUpdateModelAMixin
# Create your views here.
class ArticleViewSet(CreateUpdateModelAMixin, ModelViewSet):
"""
文章增删改查
"""
perms_map = {'get': '*', 'post': 'article_create'
, 'put':'article_update', 'delete':'article_delete'}
queryset = Article.objects.select_related('create_by')
filterset_fields = ['is_top', 'is_published']
serializer_class = ArticleListSerializer
search_fields = ['title', 'author']
ordering = ['is_published', 'is_top', 'update_time']
def get_serializer_class(self):
if self.action in ['create', 'update']:
return ArticleCreateUpdateSerializer
elif self.action in ['retrieve']:
return ArticleDetailSerializer
return super().get_serializer_class()

View File

@ -121,6 +121,12 @@ class UpdateFIFONumber(APIView):
i.save()
return Response()
class CorrectWproduct(APIView):
permission_classes = [IsAdminUser]
def post(self, request):
"""
"""
# WProduct.objects.filter(is_hidden=True).update(act_state=WProduct.WPR_ACT_STATE_USED)
class ReloadServer(APIView):

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2022-03-15 07:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('em', '0012_auto_20220120_1048'),
]
operations = [
migrations.AlterField(
model_name='equipment',
name='state',
field=models.PositiveIntegerField(choices=[(10, '完好'), (20, '限用'), (30, '在修'), (40, '禁用'), (50, '报废')], default=0, verbose_name='设备状态'),
),
]

View File

@ -1,4 +1,4 @@
from django.db.models import base
from rest_framework import urlpatterns
from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, FaceLogin, NotWorkRemarkViewSet
from django.urls import path, include

View File

@ -23,10 +23,11 @@ class MbFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
class IProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
order = filters.NumberFilter(field_name="wproduct__subproduction_plan__production_plan__order")
to_order = filters.NumberFilter(field_name="wproduct__to_order")
to_order_need_mtest = filters.BooleanFilter(field_name="wproduct__to_order__need_mtest")
need_to_order = filters.BooleanFilter(field_name="wproduct__need_to_order")
update_time_start = filters.DateFilter(field_name="update_time", lookup_expr='gte')
update_time_end = filters.DateFilter(field_name="update_time", lookup_expr='lte')
class Meta:
model = IProduct
fields = ['material', 'warehouse', 'batch', 'order', 'material__type', 'update_time_start', 'update_time_end',
'to_order', 'need_to_order', 'state']
'to_order', 'need_to_order', 'state', 'to_order_need_mtest']

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.9 on 2022-03-15 07:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sam', '0016_sale_iproducts'),
('inm', '0033_fifoitem_expiration_date'),
]
operations = [
migrations.AlterField(
model_name='fifo',
name='sale',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fifo_sale', to='sam.sale', verbose_name='关联销售记录'),
),
]

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.mtm.models import Material
from apps.pum.models import PuOrder, Vendor
from apps.qm.models import TestRecord, TestRecordItem
from apps.sam.serializers import OrderSimpleSerializer
@ -173,6 +174,8 @@ class FIFOOutOtherSerializer(serializers.ModelSerializer):
obj.save()
for i in details:
mb = i.pop('material_batch')
if mb.material.type in [Material.MA_TYPE_GOOD, Material.MA_TYPE_HALFGOOD]:
raise ValidationError('不可直接出成品或半成品')
i['material'] = mb.material
i['batch'] = mb.batch
i['warehouse'] = mb.warehouse

View File

@ -6,6 +6,8 @@ from apps.sam.models import SalePack, SaleProduct
from django.db.models import Count
from django.db.models.aggregates import Sum
import logging
from apps.wpm.services import WpmService
logger = logging.getLogger('log')
class InmService:
@ -133,8 +135,10 @@ class InmService:
# 更新动态产品表情况
from apps.wpm.models import WProduct
WProduct.objects.filter(id__in=ips.values_list('wproduct', flat=True)).update(
wps = WProduct.objects.filter(id__in=ips.values_list('wproduct', flat=True))
wps.update(
act_state=WProduct.WPR_ACT_STATE_SELLED)
WpmService.add_wproducts_flow_log(instances=wps, change_str='selled')
# 变更销售记录实际发货数
sale.count_real = ips.count()

View File

@ -163,6 +163,7 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
return super().destroy(request, *args, **kwargs)
@action(methods=['post'], detail=False, perms_map={'post': 'fifo_in_pur'}, serializer_class=FIFOInPurSerializer)
@transaction.atomic()
def in_pur(self, request, pk=None):
"""
采购入库
@ -174,6 +175,7 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
@action(methods=['post'], detail=False, perms_map={'post': 'fifo_in_other'},
serializer_class=FIFOInOtherSerializer)
@transaction.atomic()
def in_other(self, request, pk=None):
"""
其他入库
@ -185,6 +187,7 @@ class FIFOViewSet(ListModelMixin, DestroyModelMixin, GenericViewSet):
@action(methods=['post'], detail=False, perms_map={'post': 'fifo_out_other'},
serializer_class=FIFOOutOtherSerializer)
@transaction.atomic()
def out_other(self, request, pk=None):
"""
其他出库

View File

@ -1,6 +1,8 @@
from email.policy import default
from rest_framework import serializers
from apps.pm.models import ProductionPlan, SubProductionPlan
from apps.mtm.serializers import MaterialSimpleSerializer, ProcessSimpleSerializer
from apps.system.models import Organization
class SubplanGanttSerializer(serializers.ModelSerializer):
process_ = ProcessSimpleSerializer(source='process', read_only=True)
@ -25,6 +27,19 @@ class ProcessYieldSerializer(serializers.Serializer):
datetime_start = serializers.DateField(label='开始时间', required=False, allow_null=True)
datetime_end = serializers.DateField(label='结束时间', required=False, allow_null=True)
class SrmCountSerializer(serializers.Serializer):
datetime_start = serializers.DateField(label='开始时间', required=False, allow_null=True)
datetime_end = serializers.DateField(label='结束时间', required=False, allow_null=True)
class ProductCountSerializer(serializers.Serializer):
tag_choices=(
(1, '统计成品'),
(2, '统计全部')
)
datetime_start = serializers.DateField(label='开始时间', required=False, allow_null=True)
datetime_end = serializers.DateField(label='结束时间', required=False, allow_null=True)
tag = serializers.ChoiceField(choices=tag_choices, label='统计范围1成品2全部', default=1)
dept = serializers.PrimaryKeyRelatedField(queryset=Organization.objects.all(), label="车间", required=False)
class AtWorkCountSerializer(serializers.Serializer):
year = serializers.IntegerField(label='')

View File

@ -0,0 +1,74 @@
from apps.mtm.models import Material
from apps.pm.models import ProductionPlan
from apps.sam.models import Order
from apps.wpm.models import WProduct, WproductFlow
from django.db.models import F
from apps.wpm.serializers import WProductDetailSerializer
class SrmServices:
"""
数据统计分析
"""
@classmethod
def get_product_count(cls, datetime_start=None, datetime_end=None, tag=1, dept=None):
"""
根据生产情况统计相关数量
"""
if tag == 1:
objs = WproductFlow.objects.filter(is_lastlog=True, material__type=Material.MA_TYPE_GOOD)
else:
objs = WproductFlow.objects.filter(is_lastlog=True)
if datetime_start:
objs = objs.filter(create_time__gte=datetime_start)
if datetime_end:
objs = objs.filter(create_time__lte=datetime_end)
if dept:
objs = objs.filter(subproduction_plan__workshop=dept)
count = objs.count()
count_ok = objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_INM,
WProduct.WPR_ACT_STATE_OK, WProduct.WPR_ACT_STATE_SELLED]).count()
# count_notok = objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP]).count()
count_notok = (
objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP]).exclude(step__process__id=1)
| objs.filter(act_state__in=[WProduct.WPR_ACT_STATE_NOTOK, WProduct.WPR_ACT_STATE_SCRAP],
step__process__id=1).exclude(number=None)
).count()
count_selled = objs.filter(act_state=WProduct.WPR_ACT_STATE_SELLED).count()
count_mtestok = objs.filter(is_mtestok=True).count()
count_mtestnotok = objs.filter(is_mtestok=False).count()
count_doing = objs.filter(act_state__in=[
WProduct.WPR_ACT_STATE_TOTEST, WProduct.WPR_ACT_STATE_TOCOMBTEST, WProduct.WPR_ACT_STATE_TOFINALTEST,
WProduct.WPR_ACT_STATE_TORETEST, WProduct.WPR_ACT_STATE_DOWAIT, WProduct.WPR_ACT_STATE_DOING
], subproduction_plan__product=F('material')).count()
return dict(count=count,count_ok=count_ok, count_notok=count_notok,
count_selled=count_selled, count_mtestok=count_mtestok, count_mtestnotok=count_mtestnotok, count_doing=count_doing)
@classmethod
def get_plan_count(cls, datetime_start=None, datetime_end=None):
"""
任务数量
"""
objs = ProductionPlan.objects.all()
if datetime_start:
objs = objs.filter(end_date__gte=datetime_start)
if datetime_end:
objs = objs.filter(end_date__lte=datetime_end)
count = objs.count()
count_use = objs.exclude(state__in=[ProductionPlan.PLAN_STATE_PAUSE, ProductionPlan.PLAN_STATE_STOP]).count()
count_completed = objs.filter(state__in=[ProductionPlan.PLAN_STATE_DONE, ProductionPlan.PLAN_MTEST_DONE]).count()
return dict(count=count, count_use=count_use, count_completed=count_completed)
@classmethod
def get_order_count(cls, datetime_start=None, datetime_end=None):
"""
订单数量
"""
objs = Order.objects.all()
if datetime_start:
objs = objs.filter(delivery_date__gte=datetime_start)
if datetime_end:
objs = objs.filter(delivery_date__lte=datetime_end)
count = objs.count()
count_delivered = objs.filter(delivered_count__gte=F('count')).count()
return dict(count=count, count_delivered=count_delivered)

View File

@ -3,12 +3,16 @@ from rest_framework import urlpatterns
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.srm.views import AtWorkCountView, GanttPlan, ProcessYieldView
from apps.srm.views import AtWorkCountView, GanttPlan, OrderCountView, PlanCountView, ProcessNowView, ProcessYieldView, ProductCountView
router = DefaultRouter()
urlpatterns = [
path('gantt/plan/', GanttPlan.as_view()),
path('product/count/', ProductCountView.as_view()),
path('plan/count/', PlanCountView.as_view()),
path('order/count/', OrderCountView.as_view()),
path('process/yield/', ProcessYieldView.as_view()),
path('process/now/', ProcessNowView.as_view()),
path('at_work/', AtWorkCountView.as_view()),
path('', include(router.urls)),
]

View File

@ -1,6 +1,7 @@
from datetime import date, timedelta
from django.shortcuts import render
from idna import valid_contextj
from numpy import number
from rest_framework import serializers
from rest_framework.generics import ListAPIView, CreateAPIView
@ -9,9 +10,10 @@ from rest_framework.response import Response
from apps.hrm.models import ClockRecord
from apps.mtm.models import Process, Step
from apps.pm.models import ProductionPlan, SubProductionPlan
from apps.srm.serializers import AtWorkCountSerializer, PlanGanttSerializer, ProcessYieldSerializer
from apps.srm.serializers import AtWorkCountSerializer, PlanGanttSerializer, ProcessYieldSerializer, ProductCountSerializer, SrmCountSerializer
from apps.srm.services import SrmServices
from apps.wpm.models import WProduct, WproductFlow
from django.db.models import Count, F
from django.db.models import Count, F, Sum
# Create your views here.
class GanttPlan(ListAPIView):
@ -23,6 +25,62 @@ class GanttPlan(ListAPIView):
queryset = ProductionPlan.objects.filter(is_deleted=False, is_planed=True).prefetch_related('subplan_plan', 'subplan_plan__process')
ordering = ['-id']
class ProductCountView(CreateAPIView):
"""
产品数量统计
"""
perms_map = {'post':'*'}
serializer_class = ProductCountSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
res = SrmServices.get_product_count(**vdata)
return Response(res)
class PlanCountView(CreateAPIView):
"""
计划数量统计
"""
perms_map = {'post':'*'}
serializer_class = SrmCountSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
res = SrmServices.get_plan_count(**vdata)
return Response(res)
class OrderCountView(CreateAPIView):
"""
订单数量统计
"""
perms_map = {'post':'*'}
serializer_class = SrmCountSerializer
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
res = SrmServices.get_order_count(**vdata)
return Response(res)
class ProcessNowView(CreateAPIView):
"""
工序当前进度
"""
perms_map = {'post':'*'}
serializer_class = serializers.Serializer
def create(self, request, *args, **kwargs):
objs = SubProductionPlan.objects.filter(production_plan__state__in =[ProductionPlan.PLAN_STATE_WORKING,
ProductionPlan.PLAN_STATE_ASSGINED]).order_by('process__number').values('process',
'process__name').annotate(count_ok=Sum('count_ok'),
count=Sum('count'), count_real=Sum('count_real'), count_notok=Sum('count_notok'))
return Response(objs)
class ProcessYieldView(CreateAPIView):
"""
工序成品率统计

View File

@ -33,6 +33,7 @@ class WProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
tag = filters.CharFilter(method='filter_tag')
production_plan = filters.NumberFilter(
field_name='subproduction_plan__production_plan')
to_order_need_mtest = filters.BooleanFilter(field_name="to_order__need_mtest")
def filter_fields(self, queryset, name, value):
return queryset
@ -41,7 +42,7 @@ class WProductFilterSet(DynamicFieldsFilterMixin, filters.FilterSet):
class Meta:
model = WProduct
fields = ['step', 'subproduction_plan', 'material',
'step__process', 'act_state', 'material__type', 'need_to_order']
'step__process', 'act_state', 'material__type', 'need_to_order', 'to_order_need_mtest']
def filter_tag(self, queryset, name, value):
if value == 'no_scrap':

View File

@ -0,0 +1,28 @@
# Generated by Django 3.2.9 on 2022-03-15 07:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wpm', '0053_auto_20220129_1512'),
]
operations = [
migrations.AlterField(
model_name='operationequip',
name='state',
field=models.PositiveSmallIntegerField(choices=[(10, '完好'), (20, '限用'), (30, '在修'), (40, '禁用'), (50, '报废')], default=10, verbose_name='当前设备状态'),
),
migrations.AlterField(
model_name='wproduct',
name='act_state',
field=models.IntegerField(choices=[(6, '待复检'), (8, '操作准备中'), (10, '操作进行中'), (20, '待检验'), (26, '待夹层检验'), (30, '已合格'), (40, '已入库'), (50, '不合格'), (60, '待成品检验'), (70, '已报废'), (80, '已售出'), (90, '已使用')], default=0, verbose_name='进行状态'),
),
migrations.AlterField(
model_name='wproductflow',
name='act_state',
field=models.IntegerField(choices=[(6, '待复检'), (8, '操作准备中'), (10, '操作进行中'), (20, '待检验'), (26, '待夹层检验'), (30, '已合格'), (40, '已入库'), (50, '不合格'), (60, '待成品检验'), (70, '已报废'), (80, '已售出'), (90, '已使用')], default=0, verbose_name='进行状态'),
),
]

View File

@ -43,6 +43,7 @@ class WProduct(CommonAModel):
WPR_ACT_STATE_TOFINALTEST = 60
WPR_ACT_STATE_SCRAP = 70
WPR_ACT_STATE_SELLED = 80
WPR_ACT_STATE_USED = 90
act_state_choices = (
(WPR_ACT_STATE_TORETEST, '待复检'),
(WPR_ACT_STATE_DOWAIT, '操作准备中'),
@ -55,6 +56,7 @@ class WProduct(CommonAModel):
(WPR_ACT_STATE_TOFINALTEST, '待成品检验'),
(WPR_ACT_STATE_SCRAP, '已报废'),
(WPR_ACT_STATE_SELLED, '已售出'),
(WPR_ACT_STATE_USED, '已使用'),
)
SCRAP_REASON_QIPAO = 10
SCRAP_REASON_PODIAN = 20

View File

@ -167,6 +167,23 @@ class WpmService(object):
ins.change_str = change_str
ins.save()
@classmethod
def add_wproducts_flow_log(cls, instances, change_str=''):
"""
批量创建产品变动日志
"""
WproductFlow.objects.filter(wproduct__in=instances).update(is_lastlog=False)
wfw = []
for i in instances:
ins = WproductFlow()
ins.wproduct = i
for f in WproductFlow.__meta.fields:
if f.name not in ['id', 'wproduct', 'is_lastlog']:
setattr(ins, f.name, getattr(i, f.name, None))
ins.change_str = change_str
wfw.append(ins)
WproductFlow.objects.bulk_create(wfw)
@classmethod
def update_cutting_list_with_operation(cls, op:Operation):
"""

View File

@ -781,8 +781,9 @@ class OperationViewSet(ListModelMixin, RetrieveModelMixin, CreateModelMixin, Upd
WpmService.add_wproduct_flow_log(wproduct, 'wproduct_create')
# 隐藏原半成品
wps = WProduct.objects.filter(ow_wproduct__operation=op)
wps.update(is_hidden=True, child=wproduct,
wps.update(act_state=WProduct.WPR_ACT_STATE_USED, child=wproduct, is_hidden=True,
update_by=request.user, update_time=timezone.now())
WpmService.add_wproducts_flow_log(wps, change_str='wproduct_create')
else:
raise exceptions.APIException('产出物料未填写或填写错误')
op.is_submited = True

View File

@ -60,7 +60,8 @@ INSTALLED_APPS = [
'apps.pm',
'apps.wpm',
'apps.srm',
'apps.develop'
'apps.develop',
'apps.cms'
]
X_FRAME_OPTIONS = 'SAMEORIGIN'

View File

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