手动创建证书

This commit is contained in:
caoqianming 2022-02-09 11:10:36 +08:00
parent 9d625e76ac
commit 0df6bbfe31
20 changed files with 470 additions and 91 deletions

View File

@ -6,4 +6,21 @@ export function getCandidateList(query) {
method: 'get',
params:query
})
}
export function createCandidate(data){
return request({
url:'/crm/candidate/',
method: 'post',
data
})
}
export function updateCandidate(id, data) {
return request({
url: `/crm/candidate/${id}/`,
method: 'put',
data
})
}

View File

@ -1,96 +1,235 @@
<template>
<div class="app-container">
<div style="margin-top:10px">
<el-input
v-model="listQuery.search"
placeholder="输入关键词进行搜索"
style="width: 300px;"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button
<div style="margin-top: 10px">
<el-input
v-model="listQuery.search"
placeholder="输入关键词进行搜索"
style="width: 300px"
class="filter-item"
@keyup.enter.native="handleFilter"
/>
<el-button
class="filter-item"
type="primary"
icon="el-icon-refresh-left"
@click="resetFilter"
>刷新重置</el-button>
</div>
<el-table
:data="tableData.results"
style="width: 100%;margin-top:10px;"
border
fit
v-loading="listLoading"
highlight-current-row
max-height="600"
row-key="id"
default-expand-all
>刷新重置</el-button
>
<el-button
type="primary"
@click="handleAdd"
v-if="checkPermission(['candidate_create'])"
>手动创建</el-button
>
</div>
<el-table
:data="tableData.results"
style="width: 100%; margin-top: 10px"
border
fit
v-loading="listLoading"
highlight-current-row
max-height="600"
row-key="id"
default-expand-all
>
<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.consumer_name }}</template>
</el-table-column>
<el-table-column label="身份证号">
<template slot-scope="scope">{{ scope.row.ID_number }}</template>
</el-table-column>
<el-table-column label="工作类别">
<template slot-scope="scope">{{ scope.row.workscope_name }}</template>
</el-table-column>
<el-table-column label="单位">
<template slot-scope="scope">{{ scope.row.company_name }}</template>
</el-table-column>
<el-table-column label="有效期始">
<template slot-scope="scope">{{ scope.row.start_date }}</template>
</el-table-column>
<el-table-column label="有效期止">
<template slot-scope="scope">{{ scope.row.end_date }}</template>
</el-table-column>
<el-table-column label="手动创建">
<template slot-scope="scope">
<span v-if="scope.row.is_manual"></span>
<span v-else></span></template
>
<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.consumer_name }}</template>
</el-table-column>
<el-table-column label="身份证号">
<template slot-scope="scope">{{ scope.row.ID_number }}</template>
</el-table-column>
<el-table-column label="工作类别">
<template slot-scope="scope">{{ scope.row.workscope_name }}</template>
</el-table-column>
<el-table-column label="单位">
<template slot-scope="scope">{{ scope.row.company_name }}</template>
</el-table-column>
<el-table-column label="有效期始">
<template slot-scope="scope">{{ scope.row.start_date }}</template>
</el-table-column>
<el-table-column label="有效期止">
<template slot-scope="scope">{{ scope.row.end_date }}</template>
</el-table-column>
<el-table-column label="创建人">
<template slot-scope="scope">
<span>{{ scope.row.create_admin_username }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作" fixed="right">
<template slot-scope="scope">
<el-button
type="primary"
size="small"
@click="handleView(scope)"
>查看证书</el-button>
</template>
</el-table-column>
</el-table>
</el-table-column>
<el-table-column label="创建人">
<template slot-scope="scope">
<span>{{ scope.row.create_admin_username }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="操作" fixed="right">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="handleView(scope)"
>查看证书</el-button
>
<el-button
type="primary"
size="small"
@click="handleEdit(scope)"
icon="el-icon-edit"
v-if="checkPermission(['candidate_update'])&&scope.row.is_manual"
></el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="tableData.count>0"
v-show="tableData.count > 0"
:total="tableData.count"
:page.sync="listQuery.page"
:limit.sync="listQuery.limit"
@pagination="getList"
/>
<el-dialog :visible.sync="dialogVisible" :title="dialogType === 'update' ? '编辑' : '新增'">
<el-form
:model="candidate"
label-width="80px"
label-position="right"
:rules="candidateRule"
ref="candidateForm"
>
<el-row>
<el-col :span="12">
<el-form-item label="报告单号" prop="number">
<el-input v-model="candidate.number" placeholder="报告单号" />
</el-form-item>
<el-form-item label="姓名" prop="consumer_name">
<el-input v-model="candidate.consumer_name" placeholder="姓名" />
</el-form-item>
<el-form-item label="身份证号" prop="ID_number">
<el-input v-model="candidate.ID_number" placeholder="身份证号" />
</el-form-item>
<el-form-item label="工作类别" prop="workscope_name">
<el-input v-model="candidate.workscope_name" placeholder="工作类别" />
</el-form-item>
<el-form-item label="单位" prop="company_name">
<el-input v-model="candidate.company_name" placeholder="单位" />
</el-form-item>
<el-form-item label="部门" prop="deptname">
<el-input v-model="candidate.deptname" placeholder="部门" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发证日期" prop="issue_date">
<el-date-picker
v-model="candidate.issue_date"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd">
</el-date-picker>
</el-form-item>
<el-form-item label="有效期始" prop="start_date">
<el-date-picker
v-model="candidate.start_date"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd">
</el-date-picker>
</el-form-item>
<el-form-item label="有效期止" prop="end_date">
<el-date-picker
v-model="candidate.end_date"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd">
</el-date-picker>
</el-form-item>
<el-form-item label="考试时间" prop="examtest_date">
<el-date-picker
v-model="candidate.examtest_date"
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd">
</el-date-picker>
</el-form-item>
<el-form-item label="照片" prop="photo">
<el-upload
class="avatar-uploader"
:action="upUrl"
accept="image/jpeg, image/gif, image/png, image/bmp"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:headers="upHeaders"
>
<img v-if="candidate.photo" :src="candidate.photo" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div style="text-align: right">
<el-button type="danger" @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmCandidate('candidateForm')"
>确认</el-button
>
</div>
</el-dialog>
</div>
</template>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100px;
height: 120px;
line-height: 100px;
text-align: center;
}
.avatar {
width: 100px;
height: 120px;
display: block;
}
</style>
<script>
import { getCandidateList } from "@/api/candidate";
import { getCandidateList, createCandidate, updateCandidate } from "@/api/candidate";
import { deepClone } from "@/utils";
import { upUrl, upHeaders } from "@/api/file"
import checkPermission from "@/utils/permission";
import Pagination from "@/components/Pagination"
import Pagination from "@/components/Pagination";
const listQuery = {
page: 1,
limit: 20,
search: ""
}
search: "",
};
export default {
components: { Pagination },
data() {
return {
listQuery:listQuery,
tableData: {count:0},
upHeaders: upHeaders(),
upUrl: upUrl(),
listQuery: listQuery,
tableData: { count: 0 },
listLoading: true,
dialogVisible: false,
dialogType: "create",
candidate: {photo:''},
candidateRule: {
number: [{ required: true, message: "请输入编号", trigger: "blur" }],
},
};
},
computed: {},
@ -99,18 +238,28 @@ export default {
},
methods: {
checkPermission,
handleAvatarSuccess(res, file) {
this.candidate.photo = res.data.path
},
beforeAvatarUpload(file) {
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isLt2M) {
this.$message.error("上传图片大小不能超过 2MB!");
}
return isLt2M;
},
getList() {
this.listLoading = true
getCandidateList(this.listQuery).then(response => {
this.tableData = response.data
this.listLoading = false
this.listLoading = true;
getCandidateList(this.listQuery).then((response) => {
this.tableData = response.data;
this.listLoading = false;
});
},
resetFilter() {
this.listQuery = {
page: 1,
limit: 20,
search: ""
search: "",
};
this.getList();
},
@ -118,9 +267,50 @@ export default {
this.listQuery.page = 1;
this.getList();
},
handleView(scope){
window.open('https://apitest.ahctc.cn/crm/candidate/img?id='+scope.row.id)
}
}
handleView(scope) {
window.open(
// "https://apitest.ahctc.cn/crm/candidate/img?id=" + scope.row.id
"http://localhost:8000/crm/candidate/img?id=" + scope.row.id
);
},
handleAdd() {
this.dialogVisible = true;
this.dialogType = "create";
this.$nextTick(() => {
this.$refs["candidateForm"].clearValidate();
});
},
handleEdit(scope){
this.candidate = scope.row
this.dialogType = "update";
this.dialogVisible = true;
this.$nextTick(() => {
this.$refs["candidateForm"].clearValidate();
});
},
confirmCandidate(form) {
this.$refs[form].validate((valid) => {
if (valid) {
const isEdit = this.dialogType === "update";
if(isEdit){
updateCandidate(this.candidate.id, this.candidate).then(() => {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
});
}else{
createCandidate(this.candidate).then((res) => {
this.getList();
this.dialogVisible = false;
this.$message.success("成功");
});
}
} else {
return false;
}
});
},
},
};
</script>

View File

@ -0,0 +1,35 @@
# Generated by Django 3.0.4 on 2022-02-09 01:12
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('examtest', '0032_auto_20210613_2234'),
('crm', '0035_auto_20210622_1420'),
]
operations = [
migrations.AddField(
model_name='candidate',
name='examtest_date',
field=models.DateField(blank=True, null=True, verbose_name='考试时间'),
),
migrations.AddField(
model_name='candidate',
name='is_manual',
field=models.BooleanField(default=False, verbose_name='是否手动创建'),
),
migrations.AlterField(
model_name='candidate',
name='consumer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='candidate_consumer', to='crm.Consumer'),
),
migrations.AlterField(
model_name='candidate',
name='workscope',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='candidate_workscope', to='examtest.WorkScope'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2022-02-09 02:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('crm', '0036_auto_20220209_0912'),
]
operations = [
migrations.AddField(
model_name='candidate',
name='photo',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='照片'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.4 on 2022-02-09 03:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('crm', '0037_candidate_photo'),
]
operations = [
migrations.AddField(
model_name='candidate',
name='gender',
field=models.CharField(default='', max_length=6, verbose_name='性别'),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.0.4 on 2022-02-09 03:09
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('crm', '0038_candidate_gender'),
]
operations = [
migrations.RemoveField(
model_name='candidate',
name='gender',
),
]

View File

@ -105,9 +105,10 @@ class SendCode(CommonModel):
code = models.CharField(max_length=6, verbose_name= '验证码')
class Candidate(CommonModel):
consumer = models.ForeignKey(Consumer, on_delete=models.DO_NOTHING, related_name='candidate_consumer')
workscope = models.ForeignKey(WorkScope, on_delete=models.DO_NOTHING, related_name='candidate_workscope')
consumer = models.ForeignKey(Consumer, on_delete=models.DO_NOTHING, related_name='candidate_consumer', null=True, blank=True)
workscope = models.ForeignKey(WorkScope, on_delete=models.DO_NOTHING, related_name='candidate_workscope', null=True, blank=True)
examtest = models.OneToOneField(to='examtest.examtest', verbose_name='关联考试', null=True, blank=True, on_delete=models.DO_NOTHING)
is_manual = models.BooleanField('是否手动创建', default=False)
# 下面是证书信息
number = models.CharField('报告单号', max_length=60, null=True, blank=True)
@ -119,8 +120,10 @@ class Candidate(CommonModel):
issue_date = models.DateField('发证日期', null=True, blank=True)
start_date = models.DateField('有效期始', null=True, blank=True)
end_date = models.DateField('有效期止', null=True, blank=True)
examtest = models.OneToOneField(to='examtest.examtest', verbose_name='关联考试', null=True, blank=True, on_delete=models.DO_NOTHING)
examtest_date = models.DateField('考试时间', null=True, blank=True)
photo = models.CharField('照片', max_length=200, null=True, blank=True)
create_admin = models.ForeignKey(UserProfile, verbose_name="创建管理员", null=True, blank=True, on_delete=models.SET_NULL)
class Meta:
verbose_name = '证书'
verbose_name_plural = verbose_name

View File

@ -1,3 +1,4 @@
from operator import truediv
from rest_framework import serializers
from .models import Candidate, Company, Consumer, PaySubject, ConsumerPerm, ConsumerRole, SendCode
from question.models import Question, Questioncat
@ -80,6 +81,16 @@ class CandidateSerializer(serializers.ModelSerializer):
class Meta:
model = Candidate
fields = '__all__'
class CandidateCreateSerializer(serializers.ModelSerializer):
class Meta:
model = Candidate
exclude = ['consumer', 'examtest', 'workscope', 'is_manual', 'create_admin']
def create(self, validated_data):
validated_data['is_manual'] = True
validated_data['create_admin'] = self.context['request'].user
return super().create(validated_data)
class MsgSerializer(serializers.ModelSerializer):
class Meta:

View File

@ -103,7 +103,12 @@
<div id="RBfDmosc8Wsjo3iNMXZVRh" class="cell moveable " title="自定义文本"
style="position: absolute; width: 99mm; min-height: 5mm; line-height: 1.5; text-indent: 0em; top: 21.33mm; left: 68mm; font-size: 16pt; text-align: left; color: rgb(0, 0, 0);"
draggable="true">
<div class="editable" style="background-color: transparent; word-break: break-all;">Ⅲ类射线装置辐射工作人员
<div class="editable" style="background-color: transparent; word-break: break-all;">
{% if candidate.is_manual %}
核技术利用辐射安全与防护考核
{% else %}
Ⅲ类射线装置辐射工作人员
{% endif %}
</div>
</div>
<div id="AyDcC9mtcxH32vophekNA6" class="cell moveable " title="自定义文本"
@ -111,23 +116,26 @@
draggable="true">
<div class="editable" style="background-color: transparent; word-break: break-all;">成绩报告单</div>
</div>
<!-- <img id="WczkWV4Dj1eUzqHrNQyeQb" class="cell moveable " src="./报告单_files/342921199403040528.jpg"
{% if candidate.photo %}
<img id="WczkWV4Dj1eUzqHrNQyeQb" class="cell moveable " src="{{candidate.photo}}"
title="考生照片" alt="考生照片"
style="position: absolute; width: 25mm; height: 35mm; top: 23.84mm; left: 157.7mm;"
draggable="true"> -->
draggable="true">
{% endif %}
<div id="Qg25ZuNSy2Ps4wQGcKtpqu" class="cell moveable " title="自定义文本"
style="position: absolute; width: 150mm; min-height: 5mm; line-height: 2; text-indent: 2em; top: 66.3mm; left: 28.92mm; font-size: 12pt; color: rgb(0, 0, 0); font-weight: normal;"
draggable="true">
<div class="editable" style="background-color: transparent; word-break: break-all;"
contenteditable="false"><span contenteditable="false" class="inner-cell"
data-tag="name">{{candidate.consumer_name}}</span>
<!-- <span contenteditable="false" class="inner-cell" data-tag="gender">女</span>
<span contenteditable="false" class="inner-cell" data-tag="birthday">1994年03月04</span>生, -->
<span contenteditable="false" class="inner-cell" data-tag="gender">{{candidate.msg.sex}}</span>
<span contenteditable="false" class="inner-cell" data-tag="birthday">{{candidate.msg.year}}年{{candidate.msg.month}}月{{candidate.msg.day}}</span>生,
<span contenteditable="false"
class="inner-cell" data-tag="idtype">身份证</span><span contenteditable="false"
class="inner-cell" data-tag="idno">{{candidate.ID_number}}</span>,于
<!-- <span contenteditable="false" class="inner-cell" data-tag="examyear">2021</span>年 -->
<span contenteditable="false" class="inner-cell" data-tag="exammonth">{{candidate.examtest.start_time|date}}</span>参加&nbsp;
<span contenteditable="false" class="inner-cell" data-tag="exammonth">{{candidate.examtest_date|date}}</span>参加&nbsp;
<span contenteditable="false"
class="inner-cell" data-tag="pworktype">{{candidate.workscope_name}}</span>&nbsp;辐射安全与防护考核,成绩合格。</div>
</div>

View File

@ -11,7 +11,7 @@ from openpyxl import Workbook, load_workbook
from rest_framework import status
from rest_framework.decorators import action, authentication_classes, permission_classes
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
@ -29,7 +29,7 @@ from utils.custom import CommonPagination
from .filters import ConsumerFilter
from .exports import export_consumer
from .models import Candidate, Company, Consumer, PaySubject, SendCode, ConsumerPerm, ConsumerRole
from .serializers import CandidateSerializer, CompanySerializer, ConsumerSerializer, ConsumerPermSerializer, ConsumerRoleSerializer, ConsumerDetailSerializer, MsgSerializer
from .serializers import CandidateCreateSerializer, CandidateSerializer, CompanySerializer, ConsumerSerializer, ConsumerPermSerializer, ConsumerRoleSerializer, ConsumerDetailSerializer, MsgSerializer
from lxml import etree
from rbac.models import UserProfile
from django.http import Http404
@ -660,18 +660,31 @@ def candidate(request):
if request.GET.get('id', None):
try:
candidate = Candidate.objects.get(id=request.GET.get('id'), number__isnull=False)
candidate.msg = get_msg_from_id(candidate.ID_number)
return render(request, 'index.html', {"candidate":candidate})
except:
raise Http404
raise Http404
def get_msg_from_id(id_number):
year=id_number[6:10]
month=id_number[10:12]
day=id_number[12:14]
sex=id_number[16:17]
sex=int(sex)
if sex%2:
sex=""
else:
sex=""
return {'year':year, 'month':month, 'day':day, 'sex':sex}
from examtest.exports import exportw_test
class CandidateViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet):
class CandidateViewSet(RetrieveModelMixin, ListModelMixin, CreateModelMixin, UpdateModelMixin, GenericViewSet):
"""
出征记录:列表
"""
perms_map = [
{'get': '*'}, {'post': 'candidate_issue'},
{'get': '*'}, {'post': 'candidate_create'},
{'put': 'candidate_update'}, {'delete': 'candidate_delete'}]
queryset = Candidate.objects.filter(number__isnull=False)
serializer_class = CandidateSerializer
@ -682,11 +695,17 @@ class CandidateViewSet(RetrieveModelMixin, ListModelMixin, GenericViewSet):
ordering_fields = ('-id', 'update_time')
ordering = ['-update_time']
def get_serializer_class(self):
if self.action in ['create', 'update']:
return CandidateCreateSerializer
return super().get_serializer_class()
def get_authenticators(self):
if self.detail:
if self.detail and self.request.method == 'GET':
return []
return super().get_authenticators()
def get_permissions(self):
if self.action == 'retrieve':
return []

View File

View File

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

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class DevelopConfig(AppConfig):
name = 'develop'

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

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

View File

@ -0,0 +1,9 @@
from django.urls import path,include
from .views import *
urlpatterns = [
# path('correct_candidate/', CorrectCandidate.as_view()),
]

View File

@ -0,0 +1,17 @@
from django.shortcuts import render
from requests.api import request
from rest_framework.views import APIView
from rest_framework.response import Response
from crm.models import Candidate
class CorrectCandidate(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, *args, **kwargs):
for i in Candidate.objects.all():
if i.examtest:
i.examtest_date = i.examtest.start_time.date()
i.save()
return Response()

View File

@ -533,6 +533,7 @@ class ExamTestViewSet(PageOrNot, ModelViewSet):
candidate.issue_date = now
candidate.start_date = now
candidate.end_date = now + timedelta(days=5*365) # 5年有效期
candidate.examtest_date = obj.start_time.date()
candidate.workscope_name = obj.workscope.name
candidate.consumer_name = obj.consumer_detail['name']
candidate.ID_number = obj.consumer_detail['ID_number']

View File

@ -48,7 +48,8 @@ INSTALLED_APPS = [
'examtest',
'analyse',
'cms',
'qtest'
'qtest',
'develop'
]
MIDDLEWARE = [

View File

@ -25,6 +25,7 @@ from utils.view import redirect
urlpatterns = [
path('rbac/', include('rbac.urls')),
path('develop/', include('develop.urls')),
path('crm/', include('crm.urls')),
path('question/', include('question.urls')),
path('examtest/', include('examtest.urls')),