mirror of https://github.com/raiots/TasksManager
Compare commits
40 Commits
Author | SHA1 | Date |
---|---|---|
|
f889efc8cd | |
|
8bee3fca96 | |
|
d813066e03 | |
|
483432b913 | |
|
f8cb32e702 | |
|
8d710ac088 | |
|
b0433783c8 | |
|
97a5e22795 | |
|
1936734006 | |
|
3b891ad229 | |
|
ca826cfb19 | |
|
6103d632d0 | |
|
b87ea4c9bd | |
|
fc4bbc748c | |
|
6a46184fda | |
|
c6e965b38a | |
|
b0cdc33fb1 | |
|
6495b8cf2b | |
|
86bf9bd1c6 | |
|
1fa02689f8 | |
|
066dd333a8 | |
|
69af43c01c | |
|
92971237f5 | |
|
df9e174bd3 | |
|
3047ba6ed5 | |
|
ead6413fcc | |
|
4f827bdee4 | |
|
7930bd4c89 | |
|
3283478b42 | |
|
81c2e817d1 | |
|
177ff0ea9a | |
|
a194cb664d | |
|
1f0f65dfb9 | |
|
a31135cfcc | |
|
27f54562df | |
|
d7359e3ea8 | |
|
3d08ffce71 | |
|
80f3f7f9d8 | |
|
a6022a3865 | |
|
151fcaacb5 |
|
@ -0,0 +1 @@
|
|||
static/** linguist-vendored
|
|
@ -0,0 +1,20 @@
|
|||
name: Qodana
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- 'releases/*'
|
||||
|
||||
jobs:
|
||||
qodana:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 'Qodana Scan'
|
||||
uses: JetBrains/qodana-action@main
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
|
@ -0,0 +1,30 @@
|
|||
name: Django CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
python-version: [3.7, 3.8, 3.9]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
- name: Run Tests
|
||||
run: |
|
||||
python manage.py test
|
|
@ -4,3 +4,7 @@
|
|||
/db.sqlite3
|
||||
/identifier.sqlite
|
||||
migrations/
|
||||
|
||||
node_modules
|
||||
.temp
|
||||
.cache
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# 从仓库拉取 带有 python 3.8 的 Linux 环境
|
||||
FROM python:3.8
|
||||
|
||||
# 设置 python 环境变量
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
# 创建 code 文件夹并将其设置为工作目录
|
||||
RUN mkdir /code
|
||||
WORKDIR /code
|
||||
# 更新 pip
|
||||
RUN pip install pip -U
|
||||
# 将 requirements.txt 复制到容器的 code 目录
|
||||
ADD requirements.txt /code/
|
||||
# 安装库
|
||||
RUN pip install -r requirements.txt -i https://mirrors.ustc.edu.cn/pypi/web/simple
|
||||
# 将当前目录复制到容器的 code 目录
|
||||
ADD . /code/
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Raiot
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
26
README.md
26
README.md
|
@ -9,7 +9,7 @@ Author: [面壁的雨](https://raiot.me)
|
|||
|
||||
到时候会出一个文档的(或许
|
||||
|
||||
from 2020-7-13 to 2021-2-2
|
||||
from 2020-7-13 to 2021-3-31
|
||||
|
||||
鬼知道为什么会用这么长时间
|
||||
|
||||
|
@ -18,3 +18,27 @@ from 2020-7-13 to 2021-2-2
|
|||
#### 注意!
|
||||
|
||||
* 此版本仅为beta版本,请勿在生产环境使用
|
||||
|
||||
### 介绍
|
||||
|
||||
这是一个由Django开发的任务分发系统
|
||||
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
#### feature:
|
||||
|
||||
* 员工可以看到自己负责的承办任务和协办任务
|
||||
* 部门全年任务时间表
|
||||
* 高定制度的后台管理页面
|
||||
|
||||
|
||||
### Contributors
|
||||
|
||||
JetBrains OS License
|
||||
|
||||
TasksManager has been developed with PyCharm IDE under JetBrains Open Source License.
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ SECRET_KEY = '&!38pk#dv=r!_c(+b&oegc0m(ndzoue+ez*7kvjv2uubuqootp'
|
|||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['192.168.31.198', '127.0.0.1', '172.20.22.40']
|
||||
ALLOWED_HOSTS = ['192.168.31.198', '127.0.0.1', '172.20.22.40', '110.42.209.79']
|
||||
|
||||
|
||||
# Application definition
|
||||
|
@ -39,6 +39,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'import_export',
|
||||
'apps.users',
|
||||
'apps.tasks',
|
||||
]
|
||||
|
@ -135,7 +136,10 @@ LOGIN_REDIRECT_URL = '/'
|
|||
# 自定义用户模型
|
||||
AUTH_USER_MODEL = 'users.User'
|
||||
|
||||
# 它确定库是否会在数据导入中使用数据库事务,以确保安全。
|
||||
IMPORT_EXPORT_USE_TRANSACTIONS = True
|
||||
|
||||
SITE_NAME = '任务管理工具'
|
||||
# SimpleUI 配置
|
||||
|
||||
# 离线模式
|
||||
|
@ -146,7 +150,7 @@ SIMPLEUI_HOME_INFO = False
|
|||
SIMPLEUI_HOME_QUICK = True
|
||||
SIMPLEUI_HOME_ACTION = True
|
||||
SIMPLEUI_ANALYSIS = False
|
||||
SIMPLEUI_HOME_TITLE = '任务管理系统'
|
||||
SIMPLEUI_HOME_TITLE = SITE_NAME
|
||||
# SIMPLEUI_HOME_PAGE = 'https://www.baidu.com' # 可用于嵌入其他链接,这里可以直接方便的嵌入报表链接
|
||||
SIMPLEUI_HOME_ICON = 'el el-icon-platform-eleme'
|
||||
# ICON 支持element-ui和fontawesome eg:fa fa-user
|
||||
|
|
|
@ -15,12 +15,13 @@ Including another URLconf
|
|||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from TasksManager.settings import SITE_NAME
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('', include('apps.tasks.urls', namespace='tasks')),
|
||||
]
|
||||
|
||||
admin.site.site_header = '任务进度管理系统'
|
||||
admin.site.site_title = '任务进度管理系统'
|
||||
admin.site.index_title = u'任务进度管理系统'
|
||||
admin.site.site_header = SITE_NAME
|
||||
admin.site.site_title = SITE_NAME
|
||||
admin.site.index_title = SITE_NAME
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
from datetime import datetime
|
||||
import re
|
||||
|
||||
from django.contrib import admin
|
||||
from django.http import JsonResponse
|
||||
from django.utils.html import format_html
|
||||
from import_export.admin import ImportExportModelAdmin
|
||||
from import_export.formats import base_formats
|
||||
|
||||
from . import models
|
||||
from apps.users.models import TaskProperty, User
|
||||
from .resources import TodoResources, TaskResources
|
||||
|
||||
|
||||
class TodoInline(admin.StackedInline):
|
||||
|
@ -24,9 +29,11 @@ class TodoInline(admin.StackedInline):
|
|||
model = models.Todo
|
||||
extra = 0
|
||||
# classes = ['collapse']
|
||||
#TODO 选择年度任务时排序 https://www.codenong.com/40740869/
|
||||
|
||||
|
||||
class TaskAdmin(admin.ModelAdmin):
|
||||
class TaskAdmin(ImportExportModelAdmin):
|
||||
resource_class = TaskResources
|
||||
|
||||
# def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||
# if db_field.name == "related_task":
|
||||
|
@ -57,6 +64,18 @@ class TaskAdmin(admin.ModelAdmin):
|
|||
else:
|
||||
return qs.filter(department=request.user.department)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
mvDict = dict(request.POST)
|
||||
# 解决当工作包协办人数均为0时报错
|
||||
# print(len(mvDict['related_task-0-sub_executor']))
|
||||
try:
|
||||
obj.related_task__sub_executor_count = int(len(mvDict['related_task-0-sub_executor']))
|
||||
except:
|
||||
obj.related_task__sub_executor_count = 0
|
||||
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
list_display = (
|
||||
'task_property', 'task_id', 'task_topic', 'task_origin', 'aim_value', 'deadline', 'duty_group', 'principal',
|
||||
'leader', 'task_note',
|
||||
|
@ -76,8 +95,23 @@ class TaskAdmin(admin.ModelAdmin):
|
|||
# autocomplete_fields = ('related_task',)
|
||||
# search_fields = ('related_task',)
|
||||
|
||||
# 导入导出功能限制
|
||||
def get_export_formats(self): # 该方法是限制格式为XLS
|
||||
formats = (
|
||||
base_formats.XLS,
|
||||
)
|
||||
return [f for f in formats if f().can_export()]
|
||||
|
||||
class TodoAdmin(admin.ModelAdmin):
|
||||
def has_import_permission(self, request): # 这是隐藏导入按钮,如果隐藏其他按钮也可以这样操作,
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
# class TodoAdmin(admin.ModelAdmin):
|
||||
class TodoAdmin(ImportExportModelAdmin):
|
||||
resource_class = TodoResources
|
||||
|
||||
# 工作包页面仅显示所属本部门的年度任务、承办人、协办人
|
||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||
|
@ -104,9 +138,9 @@ class TodoAdmin(admin.ModelAdmin):
|
|||
(None, {
|
||||
'fields': [
|
||||
'related_task', 'todo_topic', 'todo_note', 'deadline', 'duty_group', 'main_executor', 'sub_executor',
|
||||
'predict_work', 'evaluate_factor',
|
||||
'sub_executor_count', 'predict_work', 'evaluate_factor', 'real_work'
|
||||
],
|
||||
'description': 'aaa'
|
||||
'description': ''
|
||||
}),
|
||||
|
||||
(None, {
|
||||
|
@ -124,6 +158,8 @@ class TodoAdmin(admin.ModelAdmin):
|
|||
'duty_group',
|
||||
'main_executor',
|
||||
'list_sub_executor',
|
||||
'predict_work',
|
||||
'evaluate_factor',
|
||||
'maturity',
|
||||
'real_work',
|
||||
'complete_note',
|
||||
|
@ -137,6 +173,7 @@ class TodoAdmin(admin.ModelAdmin):
|
|||
# raw_id_fields = ("sub_executor",)
|
||||
search_fields = ('todo_topic',)
|
||||
ordering = ('related_task', )
|
||||
readonly_fields = ["attachment"]
|
||||
|
||||
def approval_state(self, obj):
|
||||
return format_html('<span style="color:{};">{}</span>', 'green', obj.approval)
|
||||
|
@ -158,7 +195,181 @@ class TodoAdmin(admin.ModelAdmin):
|
|||
return obj.related_task
|
||||
lined_task.short_description = '任务名称'
|
||||
|
||||
# TODO 任务编辑界面按部门显示
|
||||
# 导入导出功能限制
|
||||
def get_export_formats(self): # 该方法是限制格式为XLS
|
||||
formats = (
|
||||
base_formats.XLS,
|
||||
)
|
||||
return [f for f in formats if f().can_export()]
|
||||
|
||||
def has_import_permission(self, request): # 这是隐藏导入按钮,如果隐藏其他按钮也可以这样操作,
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# 对工作包页面选择其所属的年度任务中,对年度任务进行筛选。条件为:年度任务的完成时间不早于今年或年度任务中有工作包的完成时间晚于今年
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
form = super(TodoAdmin, self).get_form(request, obj, **kwargs)
|
||||
query = models.Task.objects.filter(
|
||||
department=request.user.department, deadline__year__gte=datetime.now().strftime('%Y')).order_by('task_id')\
|
||||
| models.Task.objects.filter(
|
||||
department=request.user.department, related_task__deadline__year__gte=datetime.now().strftime('%Y'))\
|
||||
.order_by('task_id')
|
||||
form.base_fields['related_task'].queryset = query.distinct()
|
||||
return form
|
||||
|
||||
# def save_model(self, request, obj, form, change):
|
||||
# # 这一行代码写了一个晚上呜呜! 解决了当保存时,无法从未保存的数据中获取协办人数的问题!
|
||||
# mvDict = dict(request.POST)
|
||||
# dicts = request.POST
|
||||
# print(dicts)
|
||||
# for key, values in dicts:
|
||||
# print(key, values)
|
||||
# obj.user = request.user
|
||||
|
||||
# obj.sub_executor_count = int(len(mvDict['sub_executor']))
|
||||
# super().save_model(request, obj, form, change)
|
||||
|
||||
# 增加批量操作按钮
|
||||
actions = ['bulk_action']
|
||||
|
||||
def bulk_action(self, request, queryset):
|
||||
post = request.POST
|
||||
# 这里获取到数据后,可以做些业务处理
|
||||
# post中的_action 是方法名
|
||||
# post中 _selected 是选中的数据,逗号分割
|
||||
if not post.get('_selected'):
|
||||
return JsonResponse(data={
|
||||
'status': 'error',
|
||||
'msg': '请先选中数据!'
|
||||
})
|
||||
else:
|
||||
return JsonResponse(data={
|
||||
'status': 'success',
|
||||
'msg': '处理成功!'
|
||||
})
|
||||
|
||||
# 显示的文本,与django admin一致
|
||||
bulk_action.short_description = '批量操作'
|
||||
# icon,参考element-ui icon与https://fontawesome.com
|
||||
bulk_action.icon = 'el-icon-files'
|
||||
|
||||
# 指定element-ui的按钮类型,参考https://element.eleme.cn/#/zh-CN/component/button
|
||||
bulk_action.type = 'warning'
|
||||
|
||||
# 给按钮追加自定义的颜色
|
||||
bulk_action.style = 'color:white;'
|
||||
|
||||
# 指定为弹出层,这个参数最关键
|
||||
bulk_action.layer = {
|
||||
# 弹出层中的输入框配置
|
||||
|
||||
# 这里指定对话框的标题
|
||||
'title': '弹出层输入框',
|
||||
# 提示信息
|
||||
'tips': '这个弹出对话框是需要在admin中进行定义,数据新增编辑等功能,需要自己来实现。',
|
||||
# 确认按钮显示文本
|
||||
'confirm_button': '确认提交',
|
||||
# 取消按钮显示文本
|
||||
'cancel_button': '取消',
|
||||
|
||||
# 弹出层对话框的宽度,默认50%
|
||||
'width': '40%',
|
||||
|
||||
# 表单中 label的宽度,对应element-ui的 label-width,默认80px
|
||||
'labelWidth': "80px",
|
||||
'params': [{
|
||||
# 这里的type 对应el-input的原生input属性,默认为input
|
||||
'type': 'input',
|
||||
# key 对应post参数中的key
|
||||
'key': 'name',
|
||||
# 显示的文本
|
||||
'label': '名称',
|
||||
# 为空校验,默认为False
|
||||
'require': True
|
||||
}, {
|
||||
'type': 'select',
|
||||
'key': 'type',
|
||||
'label': '类型',
|
||||
'width': '200px',
|
||||
# size对应elementui的size,取值为:medium / small / mini
|
||||
'size': 'small',
|
||||
# value字段可以指定默认值
|
||||
'value': '0',
|
||||
'options': [{
|
||||
'key': '0',
|
||||
'label': '收入'
|
||||
}, {
|
||||
'key': '1',
|
||||
'label': '支出'
|
||||
}]
|
||||
}, {
|
||||
'type': 'number',
|
||||
'key': 'money',
|
||||
'label': '金额',
|
||||
# 设置默认值
|
||||
'value': 1000
|
||||
}, {
|
||||
'type': 'date',
|
||||
'key': 'date',
|
||||
'label': '日期',
|
||||
}, {
|
||||
'type': 'datetime',
|
||||
'key': 'datetime',
|
||||
'label': '时间',
|
||||
}, {
|
||||
'type': 'rate',
|
||||
'key': 'star',
|
||||
'label': '评价等级'
|
||||
}, {
|
||||
'type': 'color',
|
||||
'key': 'color',
|
||||
'label': '颜色'
|
||||
}, {
|
||||
'type': 'slider',
|
||||
'key': 'slider',
|
||||
'label': '滑块'
|
||||
}, {
|
||||
'type': 'switch',
|
||||
'key': 'switch',
|
||||
'label': 'switch开关'
|
||||
}, {
|
||||
'type': 'input_number',
|
||||
'key': 'input_number',
|
||||
'label': 'input number'
|
||||
}, {
|
||||
'type': 'checkbox',
|
||||
'key': 'checkbox',
|
||||
# 必须指定默认值
|
||||
'value': [],
|
||||
'label': '复选框',
|
||||
'options': [{
|
||||
'key': '0',
|
||||
'label': '收入'
|
||||
}, {
|
||||
'key': '1',
|
||||
'label': '支出'
|
||||
}, {
|
||||
'key': '2',
|
||||
'label': '收益'
|
||||
}]
|
||||
}, {
|
||||
'type': 'radio',
|
||||
'key': 'radio',
|
||||
'label': '单选框',
|
||||
'options': [{
|
||||
'key': '0',
|
||||
'label': '收入'
|
||||
}, {
|
||||
'key': '1',
|
||||
'label': '支出'
|
||||
}, {
|
||||
'key': '2',
|
||||
'label': '收益'
|
||||
}]
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
admin.site.register(models.Task, TaskAdmin)
|
||||
|
|
|
@ -1,20 +1,38 @@
|
|||
from django import forms
|
||||
from .models import Todo
|
||||
|
||||
|
||||
class LoginForm(forms.Form):
|
||||
username = forms.CharField(error_messages={'required': '用户名不能为空'})
|
||||
password = forms.CharField()
|
||||
remember = forms.BooleanField(required=False)
|
||||
|
||||
|
||||
# TODO 数据不可为空
|
||||
class TodoForm(forms.ModelForm):
|
||||
required_css_class = 'required'
|
||||
|
||||
# (confused by Form & ModelForm https://stackoverflow.com/questions/2303268/djangos-forms-form-vs-forms-modelform)
|
||||
# maturity = forms.ChoiceField(widget=forms.Select(attrs={'class': 'form-control'}), choices=(
|
||||
# ('0%', '0%'),
|
||||
# ('10%', '10%'),
|
||||
# ('50%', '50%'),
|
||||
# ('90%', '90%'),
|
||||
# ('100%', '100%')
|
||||
# ))
|
||||
# real_work = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'}))
|
||||
# sub_executor = forms.MultipleChoiceField(widget=forms.SelectMultiple(attrs={'class': 'form-control'}))
|
||||
|
||||
class Meta:
|
||||
model = Todo
|
||||
fields = ['maturity', 'real_work', 'complete_note']
|
||||
labels ={'text': ''}
|
||||
widgets = {'rows': '3'}
|
||||
fields = ['maturity', 'real_work', 'sub_executor', 'evaluate_factor', 'complete_note']
|
||||
widgets = {'complete_note': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
# 'evaluate_factor': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
}
|
||||
|
||||
|
||||
# TODO 数据不可为空
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(TodoForm, self).__init__(*args, **kwargs)
|
||||
# self.fields['sub_executor'].widget.attrs['class'] = 'form-control'
|
||||
fields = ['maturity', 'real_work', 'sub_executor', 'evaluate_factor', 'complete_note']
|
||||
for i in fields:
|
||||
self.fields[i].widget.attrs['class'] = 'form-control'
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-05 19:27
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('tasks', '0003_auto_20210304_2224'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='aim_value',
|
||||
field=models.CharField(blank=True, max_length=50, verbose_name='目标值'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='leader',
|
||||
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='leader', to=settings.AUTH_USER_MODEL, verbose_name='主管领导'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='principal',
|
||||
field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='principal', to=settings.AUTH_USER_MODEL, verbose_name='负责人'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='task_note',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='任务说明'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='task_origin',
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name='任务来源'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='todo',
|
||||
name='evaluate_factor',
|
||||
field=models.DecimalField(blank=True, decimal_places=1, default='1', max_digits=5, verbose_name='折算系数'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='todo',
|
||||
name='predict_work',
|
||||
field=models.DecimalField(blank=True, decimal_places=1, max_digits=5, verbose_name='预计工作量'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='todo',
|
||||
name='related_task',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='related_task', to='tasks.task', verbose_name='年度任务'),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-05 20:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tasks', '0004_auto_20210305_1927'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='task_id',
|
||||
field=models.CharField(max_length=50, unique=True, verbose_name='编号'),
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-05 21:06
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tasks', '0005_auto_20210305_2059'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='todo',
|
||||
name='predict_work',
|
||||
field=models.DecimalField(blank=True, decimal_places=1, max_digits=5, null=True, verbose_name='预计工作量'),
|
||||
),
|
||||
]
|
|
@ -1,21 +0,0 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-05 22:25
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('tasks', '0006_auto_20210305_2106'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='leader',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leader', to=settings.AUTH_USER_MODEL, verbose_name='主管领导'),
|
||||
),
|
||||
]
|
|
@ -1,21 +0,0 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-05 22:26
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('tasks', '0007_auto_20210305_2225'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='principal',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='principal', to=settings.AUTH_USER_MODEL, verbose_name='负责人'),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# Generated by Django 3.1.5 on 2021-03-05 23:17
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('tasks', '0008_auto_20210305_2226'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='todo',
|
||||
name='sub_executor',
|
||||
field=models.ManyToManyField(blank=True, related_name='sub_executor', to=settings.AUTH_USER_MODEL, verbose_name='协办人'),
|
||||
),
|
||||
]
|
|
@ -1,4 +1,8 @@
|
|||
import time
|
||||
|
||||
from django.db import models
|
||||
from django.http import request
|
||||
|
||||
from apps.users.models import User, MyGroup, QualityMark, TaskProperty, Department
|
||||
|
||||
|
||||
|
@ -18,9 +22,10 @@ class Todo(models.Model):
|
|||
main_executor = models.ForeignKey(User, related_name='main_executor', on_delete=models.CASCADE,
|
||||
verbose_name='承/督办人', blank=True, null=True)
|
||||
sub_executor = models.ManyToManyField(User, related_name='sub_executor', verbose_name='协办人', blank=True)
|
||||
sub_executor_count = models.CharField('协办人数', max_length=32)
|
||||
related_task = models.ForeignKey('Task', related_name='related_task', on_delete=models.CASCADE, verbose_name='年度任务')
|
||||
predict_work = models.DecimalField('预计工作量', max_digits=5, decimal_places=1, blank=True, null=True)
|
||||
evaluate_factor = models.DecimalField('折算系数', max_digits=5, decimal_places=1, blank=True, default='1')
|
||||
predict_work = models.DecimalField('预计工作量', default=0, max_digits=5, decimal_places=1, blank=False)
|
||||
evaluate_factor = models.DecimalField('折算系数', max_digits=5, decimal_places=1, blank=True, default='1') #Todo 小数点后2位
|
||||
maturity = models.CharField(
|
||||
verbose_name='成熟度',
|
||||
max_length=5,
|
||||
|
@ -28,17 +33,17 @@ class Todo(models.Model):
|
|||
('0%', '0%'),
|
||||
('10%', '10%'),
|
||||
('50%', '50%'),
|
||||
('70%', '70%'),
|
||||
('90%', '90%'),
|
||||
('100%', '100%')
|
||||
),
|
||||
blank=True,
|
||||
default='0%',
|
||||
)
|
||||
real_work = models.DecimalField('实际工作量', max_digits=5, decimal_places=1, blank=True, null=True)
|
||||
complete_note = models.TextField('完成情况说明', max_length=150, blank=True)
|
||||
real_work = models.DecimalField('实际工作量', default=0, max_digits=5, decimal_places=1, blank=False)
|
||||
complete_note = models.TextField('完成情况说明', max_length=500, blank=True)
|
||||
quality_mark = models.ForeignKey('users.QualityMark', on_delete=models.SET_NULL, blank=True, null=True,
|
||||
verbose_name='质量评价')
|
||||
attachment = models.FileField('交付物查看', blank=True)
|
||||
|
||||
def __str__(self):
|
||||
date = str(self.deadline)
|
||||
|
@ -50,6 +55,13 @@ class Todo(models.Model):
|
|||
verbose_name = '工作包'
|
||||
verbose_name_plural = '工作包'
|
||||
|
||||
# def save(self, *args, **kwargs):
|
||||
# super(Todo, self).save(*args, **kwargs)
|
||||
# print(request.HttpRequest)
|
||||
# # 直接保存报错 needs to have a value for field "id" before this many-to-many relationship can be used.
|
||||
# # self.sub_executor_count = int(self.sub_executor.count())
|
||||
# # Todo.objects.update(sub_executor_count=self.sub_executor.count())
|
||||
|
||||
@property
|
||||
def task_id(self):
|
||||
return self.related_task.task_id
|
||||
|
@ -74,6 +86,18 @@ class Todo(models.Model):
|
|||
def points(self):
|
||||
return int(self.predict_work * self.evaluate_factor)
|
||||
|
||||
@property
|
||||
def main_workload(self):
|
||||
return int(self.predict_work) * int(self.evaluate_factor)
|
||||
|
||||
@property
|
||||
def sub_workload(self):
|
||||
return self.predict_work * (1-int(self.evaluate_factor))/self.sub_executor.count
|
||||
|
||||
@classmethod
|
||||
def sub_member(cls):
|
||||
return cls.sub_executor.count
|
||||
|
||||
def list_sub_executor(self):
|
||||
return ', '.join([a.real_name for a in self.sub_executor.all()])
|
||||
list_sub_executor.short_description = '协办人'
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
from django.db import connection
|
||||
|
||||
def my_annotate():
|
||||
query1 = '''
|
||||
CREATE TEMPORARY TABLE work_cal AS
|
||||
SELECT tasks_todo_sub_executor.*, tasks_todo.*
|
||||
FROM tasks_todo_sub_executor
|
||||
INNER JOIN tasks_todo
|
||||
ON tasks_todo_sub_executor.todo_id = tasks_todo.id
|
||||
ORDER BY user_id;
|
||||
'''
|
||||
query2 = '''
|
||||
-- 计算每个工作包协办人数量并插入临时表
|
||||
UPDATE work_cal SET sub_executor_count = (
|
||||
SELECT COUNT(*) FROM tasks_todo_sub_executor
|
||||
WHERE work_cal.todo_id = tasks_todo_sub_executor.todo_id);
|
||||
'''
|
||||
query3 = '''
|
||||
SELECT exe_id, SUM(total_pre_work), SUM(total_real_work) FROM
|
||||
(
|
||||
-- 承办任务
|
||||
SELECT main_executor_id AS exe_id, SUM(predict_work * evaluate_factor) AS total_pre_work,
|
||||
SUM(real_work * tasks_todo.evaluate_factor) AS total_real_work
|
||||
FROM tasks_todo
|
||||
GROUP BY exe_id
|
||||
UNION ALL
|
||||
-- 协办任务
|
||||
SELECT user_id AS exe_id, SUM(predict_work * (1 -evaluate_factor) / sub_executor_count) AS total_pre_work,
|
||||
SUM(real_work * (1 - evaluate_factor) / sub_executor_count) AS total_real_work
|
||||
FROM work_cal
|
||||
GROUP BY exe_id
|
||||
) AS init
|
||||
GROUP BY exe_id
|
||||
'''
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(query1)
|
||||
cursor.execute(query2)
|
||||
cursor.execute(query3)
|
||||
raw = cursor.fetchall()
|
||||
return raw
|
|
@ -0,0 +1,16 @@
|
|||
from import_export import resources
|
||||
|
||||
from .models import Todo, Task
|
||||
|
||||
|
||||
class TodoResources(resources.ModelResource):
|
||||
class Meta:
|
||||
model = Todo
|
||||
fields = ('todo_topic', 'todo_note', 'deadline', 'duty_group__name', 'main_executor__real_name',
|
||||
'sub_executor__real_name', 'sub_executor_count', 'related_task', 'predict_work', 'evaluate_factor',
|
||||
'maturity', 'real_work', 'complete_note', 'quality_mark')
|
||||
|
||||
|
||||
class TaskResources(resources.ModelResource):
|
||||
class Meta:
|
||||
model = Task
|
|
@ -7,11 +7,18 @@ register = template.Library()
|
|||
|
||||
|
||||
@register.filter(name='quarter_cate')
|
||||
def quarter_cate(value, quarter):
|
||||
def quarter_cate(value, year_quarter):
|
||||
year_now = datetime.now().strftime('%Y')
|
||||
month = value.deadline.strftime('%m')
|
||||
year = value.deadline.strftime('%Y')
|
||||
month = int(month)
|
||||
quarter = int(quarter)
|
||||
|
||||
# year = int(year)
|
||||
# year_now = int(year) 不知道为什么,如果转整数会把2021和2022认为相同
|
||||
# print(quarter)
|
||||
req_year = str(year_quarter[1])
|
||||
quarter = int(year_quarter[0])
|
||||
# 可能造成性能损失,每次数据库会调出符合“当年”的任务或工作包的全部任务下工作包,并逐个判断
|
||||
if year == req_year:
|
||||
if quarter == 1 and 1 <= month <= 3:
|
||||
return str(value) + ' '
|
||||
|
||||
|
@ -26,6 +33,9 @@ def quarter_cate(value, quarter):
|
|||
|
||||
else:
|
||||
return ''
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter(name='last_month')
|
||||
def last_month(value):
|
||||
|
@ -34,6 +44,7 @@ def last_month(value):
|
|||
last_month = last_date.strftime('%Y/%m')
|
||||
return last_month
|
||||
|
||||
|
||||
@register.filter(name='next_month')
|
||||
def next_month(value):
|
||||
curent_date = datetime.strptime(value, '%Y年%m月')
|
||||
|
@ -41,7 +52,28 @@ def next_month(value):
|
|||
next_month = next_date.strftime('%Y/%m')
|
||||
return next_month
|
||||
|
||||
|
||||
@register.filter(name='this_month')
|
||||
def this_month(value):
|
||||
curent_date = datetime.strptime(value, '%Y年%m月')
|
||||
return curent_date.strftime('%m')
|
||||
|
||||
|
||||
@register.filter(name='last_year')
|
||||
def last_year(value):
|
||||
curent_year = value[1]
|
||||
last_year = curent_year - 1
|
||||
return last_year
|
||||
|
||||
|
||||
@register.filter(name='next_year')
|
||||
def next_year(value):
|
||||
curent_year = value[1]
|
||||
next_year = curent_year + 1
|
||||
return next_year
|
||||
|
||||
|
||||
@register.filter(name='this_year')
|
||||
def this_year(value):
|
||||
curent_year = value[1]
|
||||
return curent_year
|
|
@ -1,3 +1,212 @@
|
|||
import decimal
|
||||
|
||||
from django.core import serializers
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db.models.functions import TruncMonth
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
from django.db.models import Sum, F, FloatField, Count, Q
|
||||
|
||||
from django.utils import timezone
|
||||
from django.views import View
|
||||
|
||||
from apps.users.models import User
|
||||
from apps.tasks.models import Todo
|
||||
import pandas as pd
|
||||
from collections import defaultdict, Counter, ChainMap
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
# 用于按用户名合并任务,会对指定字段进行累加,类似GROUP BY(SUM),但会丢失无需计算的部分,因此之前需要单独构建姓名字典
|
||||
def solve(dataset, group_by_key, sum_value_keys):
|
||||
dic = defaultdict(Counter)
|
||||
for item in dataset:
|
||||
key = item[group_by_key]
|
||||
vals = {k: item[k] for k in sum_value_keys}
|
||||
dic[key].update(vals)
|
||||
return dic
|
||||
|
||||
def cal_method(main_list, sub_list, user_name):
|
||||
# print(main_list, sub_list, user_name)
|
||||
|
||||
# sub_credit = sub_list
|
||||
# main_credit = main_list
|
||||
|
||||
total_data = []
|
||||
season = 1
|
||||
# 分别计算每个季度的工作量、评价
|
||||
for main_credit, sub_credit in zip(main_list, sub_list):
|
||||
# TODO 对于完成质量的核算,先对评价求和,再除以已评价的承办任务数
|
||||
# for i in main_credit:
|
||||
# print(i['main_executor__quality_mark__mark_value__mark_value'])
|
||||
# print(main_credit)
|
||||
quality_dict = {}
|
||||
for i in main_credit:
|
||||
quality_dict[i['username']] = []
|
||||
if i['main_executor__quality_mark__mark_value__mark_value'] != None:
|
||||
quality_dict[i['username']].append(i['main_executor__quality_mark__mark_value__mark_value'])
|
||||
|
||||
for key, value in quality_dict.items():
|
||||
if value:
|
||||
quality_dict[key] = sum(value) / len(value)
|
||||
# print(quality_dict)
|
||||
|
||||
main_credit = solve(main_credit, 'username', ['main_pre_cal', 'main_real_cal', 'main_count', 'main_executor__evaluate_factor'])
|
||||
main_credit = dict(main_credit)
|
||||
sub_credit = solve(sub_credit, 'username', ['sub_pre_cal', 'sub_real_cal', 'sub_count'])
|
||||
sub_credit = dict(sub_credit)
|
||||
|
||||
# 按用户名合并承办与协办任务字典
|
||||
total_credit = deepcopy(main_credit)
|
||||
for key in sub_credit.keys():
|
||||
if key in total_credit:
|
||||
total_credit[key].update(sub_credit[key])
|
||||
else:
|
||||
total_credit[key] = sub_credit[key]
|
||||
|
||||
# print(total_credit['admin']['sub_pre_cal'])
|
||||
# 根据字典内容,计算总工作量
|
||||
for key, value in total_credit.items():
|
||||
# print(value)
|
||||
value['pre_cal'] = value['sub_pre_cal'] + value['main_pre_cal']
|
||||
value['real_cal'] = value['sub_real_cal'] + value['main_real_cal']
|
||||
value['real_name'] = user_name[key]
|
||||
value['season'] = season
|
||||
|
||||
# 由于不计算协办任务,quality_dict会有空值情况
|
||||
try:
|
||||
value['quality'] = quality_dict[key]
|
||||
except:
|
||||
value['quality'] = 0
|
||||
|
||||
for value in total_credit.values():
|
||||
# print(value)
|
||||
real_cal_season = "real_cal_" + str(season)
|
||||
value[real_cal_season] = value.pop("real_cal")
|
||||
quality_season = "quality_" + str(season)
|
||||
value[quality_season] = value.pop("quality")
|
||||
# print(total_credit)
|
||||
total_data.append(total_credit)
|
||||
# print(season)
|
||||
season += 1
|
||||
|
||||
# new_credit = []
|
||||
# for item in total_credit:
|
||||
# for key, value in item.items():
|
||||
# print(total_data, season)
|
||||
dd = defaultdict(list)
|
||||
for d in total_data: # you can list as many input dicts as you want here
|
||||
for key, value in d.items():
|
||||
dd[key].append(value)
|
||||
# print(dd)
|
||||
return dd
|
||||
|
||||
|
||||
class TestView(View):
|
||||
def get(self, request, year=2021, month=7):
|
||||
user_info = User.objects.filter(department=request.user.department) \
|
||||
.values_list('username', 'real_name')
|
||||
user_name = {}
|
||||
for user in user_info:
|
||||
user_name[user[0]] = user[1]
|
||||
|
||||
work_cal = User.objects.filter(department=request.user.department, main_executor__deadline__year=2021) \
|
||||
.order_by('main_executor__deadline').values('main_executor__todo_topic', 'main_executor__deadline')
|
||||
|
||||
# 以用户表查询,按用户列出所参与承办、协办任务,并在之后按用户名分组合并。
|
||||
main_credit = User.objects.filter(department=request.user.department,
|
||||
main_executor__deadline__year=year) \
|
||||
.annotate(main_count=Count('main_executor')) \
|
||||
.order_by('username') \
|
||||
.values('username', 'main_executor', 'main_executor__predict_work', 'main_executor__real_work',
|
||||
'main_executor__evaluate_factor', 'main_executor__maturity', 'main_executor__quality_mark__mark_value__mark_value', 'main_executor__deadline', 'main_count') # 这里的annotate不知会不会有问题
|
||||
|
||||
sub_count = Todo.objects.filter(sub_executor__department=request.user.department,
|
||||
deadline__year=year) \
|
||||
.annotate(sub_count=Count('sub_executor')).values('id', 'sub_count')
|
||||
sub_credit = User.objects.filter(department=request.user.department,
|
||||
sub_executor__deadline__year=year) \
|
||||
.order_by('username') \
|
||||
.values('username', 'real_name', 'sub_executor', 'sub_executor__predict_work', 'sub_executor__real_work',
|
||||
'sub_executor__evaluate_factor', 'sub_executor__maturity', 'sub_executor__quality_mark__mark_value__mark_value', 'sub_executor__deadline')
|
||||
|
||||
# 构建工作包id对应协办人人数的字典
|
||||
sub_exe_count = {}
|
||||
for i in sub_count:
|
||||
key = i['id']
|
||||
value = i['sub_count']
|
||||
sub_exe_count[key] = value
|
||||
# print(sub_exe_count)
|
||||
|
||||
# 计算每个承办任务的预计、实际工作量,并插入字典
|
||||
for i in main_credit:
|
||||
# 将成熟度由百分数转小数,以便其后与其他变量计算 eg. 50% -> 0.5
|
||||
i['main_executor__maturity'] = decimal.Decimal(float(i['main_executor__maturity'].strip('%')) / 100)
|
||||
# print(i['main_executor__maturity'])
|
||||
i['main_pre_cal'] = i['main_executor__predict_work'] * i['main_executor__evaluate_factor'] * i['main_executor__maturity']
|
||||
i['main_real_cal'] = i['main_executor__real_work'] * i['main_executor__evaluate_factor'] * i['main_executor__maturity']
|
||||
# print(i)
|
||||
# print(str(i['sub_executor']))
|
||||
|
||||
# 将协办任务对应的人数插入,计算每个协办任务的预计、实际工作量,并插入字典
|
||||
for i in sub_credit:
|
||||
sub_todo_id = i['sub_executor']
|
||||
i['sub_exe_count'] = sub_exe_count[sub_todo_id]
|
||||
i['sub_executor__maturity'] = decimal.Decimal(float(i['sub_executor__maturity'].strip('%')) / 100)
|
||||
i['sub_pre_cal'] = i['sub_executor__predict_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count'] * i['sub_executor__maturity']
|
||||
i['sub_real_cal'] = i['sub_executor__real_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count'] * i['sub_executor__maturity']
|
||||
i['sub_count'] = 1 # 用于帮助累加程序计算每个用户的协办任务数量, sub_exe_count会返回此协办任务的协办人数,在累加时导致计算错误
|
||||
# print(i)
|
||||
# print(str(i['sub_executor']))
|
||||
|
||||
|
||||
|
||||
main_Q1st, main_Q2nd, main_Q3th, main_Q4th = [], [], [], []
|
||||
for i in main_credit:
|
||||
# print(i['main_executor__deadline'].month)
|
||||
deadline_month = i['main_executor__deadline'].month
|
||||
if 1 <= deadline_month <= 3:
|
||||
main_Q1st.append(i)
|
||||
elif 4 <= deadline_month <= 6:
|
||||
main_Q2nd.append(i)
|
||||
elif 7 <= deadline_month <= 9:
|
||||
main_Q3th.append(i)
|
||||
elif 10 <= deadline_month <= 12:
|
||||
main_Q4th.append(i)
|
||||
# print(Q1st, Q2nd, Q3th, Q4th)
|
||||
|
||||
sub_Q1st, sub_Q2nd, sub_Q3th, sub_Q4th = [], [], [], []
|
||||
for i in sub_credit:
|
||||
# print(i['main_executor__deadline'].month)
|
||||
deadline_month = i['sub_executor__deadline'].month
|
||||
if 1 <= deadline_month <= 3:
|
||||
sub_Q1st.append(i)
|
||||
elif 4 <= deadline_month <= 6:
|
||||
sub_Q2nd.append(i)
|
||||
elif 7 <= deadline_month <= 9:
|
||||
sub_Q3th.append(i)
|
||||
elif 10 <= deadline_month <= 12:
|
||||
sub_Q4th.append(i)
|
||||
|
||||
main_list = [main_Q1st, main_Q2nd, main_Q3th, main_Q4th]
|
||||
sub_list = [sub_Q1st, sub_Q2nd, sub_Q3th, sub_Q4th]
|
||||
result = cal_method(main_list, sub_list, user_name)
|
||||
print(result)
|
||||
stat = {}
|
||||
for key, value in result.items():
|
||||
stat[key] = {}
|
||||
for j in value:
|
||||
stat[key]['real_name'] = j['real_name']
|
||||
season = str(j['season'])
|
||||
real_cal_season = 'real_cal_' + season
|
||||
quality_season = 'quality_' + season
|
||||
stat[key][real_cal_season] = j[real_cal_season]
|
||||
stat[key][quality_season] = j[quality_season]
|
||||
print(j)
|
||||
print(stat)
|
||||
|
||||
return HttpResponse(stat)
|
||||
# return render(request, 'tasks/index.html', context)
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
from django.urls import path
|
||||
from apps.tasks import views
|
||||
from django.urls import path, include
|
||||
# import debug_toolbar
|
||||
from apps.tasks import views, tests
|
||||
from TasksManager import settings
|
||||
|
||||
app_name = 'tasks'
|
||||
urlpatterns = [
|
||||
path('', views.IndexView.as_view(), name='index'),
|
||||
path('<int:year>/<int:month>/', views.IndexView.as_view(), name='index_month'),
|
||||
path('test/', tests.TestView.as_view(), name='test'),
|
||||
path('login/', views.UserLoginView.as_view(), name='login'),
|
||||
path('logout/', views.UserLogoutView.as_view(), name='logout'),
|
||||
path('todolist/', views.TodoListView.as_view(), name='todolist'),
|
||||
|
@ -12,5 +16,13 @@ urlpatterns = [
|
|||
path('group_todolist/<int:year>/<int:month>/', views.GroupTodoList.as_view(), name='group_todolist_month'),
|
||||
path('todo/<int:pk>/', views.TodoEntryView.as_view(), name='todo_detail'),
|
||||
path('tasklist/', views.TaskListView.as_view(), name='tasklist'),
|
||||
path('tasklist/<int:year>/', views.TaskListView.as_view(), name='tasklist_year'),
|
||||
path('about/', views.AboutView.as_view(), name='about'),
|
||||
]
|
||||
#
|
||||
# if settings.DEBUG:
|
||||
# import debug_toolbar
|
||||
# urlpatterns = [
|
||||
# path('__debug__/', include(debug_toolbar.urls)),
|
||||
# ] + urlpatterns
|
||||
|
||||
|
|
|
@ -1,28 +1,362 @@
|
|||
import re
|
||||
from collections import defaultdict, Counter
|
||||
from copy import deepcopy
|
||||
|
||||
from django.contrib import auth, messages
|
||||
from django.contrib.auth import logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Sum, F
|
||||
from django.db.models import Sum, F, FloatField, Count, Q
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
import django.utils.timezone as timezone
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.views import View
|
||||
from django.db import connection
|
||||
# Create your views here.
|
||||
from apps.tasks.models import Todo, Task
|
||||
from apps.tasks.forms import TodoForm
|
||||
from apps.users.models import User
|
||||
from . import my_query
|
||||
from functools import reduce
|
||||
import pandas as pd
|
||||
|
||||
import decimal
|
||||
|
||||
|
||||
class IndexView(View):
|
||||
@method_decorator(login_required)
|
||||
def get(self, request):
|
||||
users = User.objects.filter(department=request.user.department)
|
||||
# points = []
|
||||
# point = User.objects.all()
|
||||
# for i in point:
|
||||
# points.append(i)
|
||||
# points = User.objects.annotate(a=F('main_executor__evaluate_factor'))
|
||||
points = User.objects.annotate(a=Sum(F('main_executor__predict_work') * F('main_executor__evaluate_factor') + F('sub_executor__predict_work') * F('sub_executor__evaluate_factor')))
|
||||
context = {'users': users, 'points': points}
|
||||
def get(self, request, year=None, month=None):
|
||||
# 搞成了!!! 当月份更改时自动变更首页显示的月份! 其实就是用的上面那个链接的方法,之前不知道为啥没去用
|
||||
# https://stackoverflow.com/questions/63072235/django-localdate-doesnt-return-correct-date
|
||||
# https://stackoverflow.com/questions/13225890/django-default-timezone-now-saves-records-using-old-time
|
||||
# https://stackoverflow.com/questions/38237777/django-timezone-now-vs-timezone-now
|
||||
if year is None:
|
||||
year = timezone.now().year
|
||||
if month is None:
|
||||
month = timezone.now().month
|
||||
#TODO 部门不用部门下用户累加
|
||||
# 建立username和真实姓名的对应字典,并在工作量计算完成后插入结果集
|
||||
user_info = User.objects.filter(department=request.user.department)\
|
||||
.values_list('username', 'real_name')
|
||||
user_name = {}
|
||||
for user in user_info:
|
||||
user_name[user[0]] = user[1]
|
||||
# print(user_name)
|
||||
|
||||
# 以用户表查询,按用户列出所参与承办、协办任务,并在之后按用户名分组合并。
|
||||
main_credit = User.objects.filter(department=request.user.department,
|
||||
main_executor__deadline__year=year, main_executor__deadline__month=month)\
|
||||
.annotate(main_count=Count('main_executor'))\
|
||||
.order_by('username')\
|
||||
.values('username', 'main_executor', 'main_executor__predict_work', 'main_executor__real_work',
|
||||
'main_executor__evaluate_factor', 'main_count') # 这里的annotate不知会不会有问题
|
||||
|
||||
sub_count = Todo.objects.filter(sub_executor__department=request.user.department,
|
||||
deadline__year=year, deadline__month=month) \
|
||||
.annotate(sub_count=Count('sub_executor')).values('id', 'sub_count')
|
||||
sub_credit = User.objects.filter(department=request.user.department,
|
||||
sub_executor__deadline__year=year, sub_executor__deadline__month=month) \
|
||||
.order_by('username') \
|
||||
.values('username', 'real_name', 'sub_executor', 'sub_executor__predict_work', 'sub_executor__real_work',
|
||||
'sub_executor__evaluate_factor')
|
||||
|
||||
# 构建工作包id对应协办人人数的字典
|
||||
sub_exe_count = {}
|
||||
for i in sub_count:
|
||||
key = i['id']
|
||||
value = i['sub_count']
|
||||
sub_exe_count[key] = value
|
||||
# print(sub_exe_count)
|
||||
|
||||
# 计算每个承办任务的预计、实际工作量,并插入字典
|
||||
for i in main_credit:
|
||||
i['main_pre_cal'] = i['main_executor__predict_work'] * i['main_executor__evaluate_factor']
|
||||
i['main_real_cal'] = i['main_executor__real_work'] * i['main_executor__evaluate_factor']
|
||||
# print(i)
|
||||
# print(str(i['sub_executor']))
|
||||
|
||||
# 将协办任务对应的人数插入,计算每个协办任务的预计、实际工作量,并插入字典
|
||||
for i in sub_credit:
|
||||
sub_todo_id = i['sub_executor']
|
||||
i['sub_exe_count'] = sub_exe_count[sub_todo_id]
|
||||
i['sub_pre_cal'] = i['sub_executor__predict_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count']
|
||||
i['sub_real_cal'] = i['sub_executor__real_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count']
|
||||
i['sub_count'] = 1 # 用于帮助累加程序计算每个用户的协办任务数量, sub_exe_count会返回此协办任务的协办人数,在累加时导致计算错误
|
||||
# print(i)
|
||||
# print(str(i['sub_executor']))
|
||||
|
||||
# 用于按用户名合并任务,会对指定字段进行累加,类似GROUP BY(SUM),但会丢失无需计算的部分,因此之前需要单独构建姓名字典
|
||||
def solve(dataset, group_by_key, sum_value_keys):
|
||||
dic = defaultdict(Counter)
|
||||
for item in dataset:
|
||||
key = item[group_by_key]
|
||||
vals = {k: item[k] for k in sum_value_keys}
|
||||
dic[key].update(vals)
|
||||
return dic
|
||||
main_credit = solve(main_credit, 'username', ['main_pre_cal', 'main_real_cal', 'main_count'])
|
||||
main_credit = dict(main_credit)
|
||||
sub_credit = solve(sub_credit, 'username', ['sub_pre_cal', 'sub_real_cal', 'sub_count'])
|
||||
sub_credit = dict(sub_credit)
|
||||
|
||||
# 按用户名合并承办与协办任务字典
|
||||
total_credit = deepcopy(main_credit)
|
||||
for key in sub_credit.keys():
|
||||
if key in total_credit:
|
||||
total_credit[key].update(sub_credit[key])
|
||||
else:
|
||||
total_credit[key] = sub_credit[key]
|
||||
|
||||
# print(total_credit['admin']['sub_pre_cal'])
|
||||
# 根据字典内容,计算总工作量
|
||||
for key, value in total_credit.items():
|
||||
# print(value)
|
||||
value['pre_cal'] = value['sub_pre_cal'] + value['main_pre_cal']
|
||||
value['real_cal'] = value['sub_real_cal'] + value['main_real_cal']
|
||||
value['real_name'] = user_name[key]
|
||||
# total_credit = dict(main_credit.items() + sub_credit.items())
|
||||
# for value in sub_credit.values():
|
||||
# dict(value)
|
||||
# print(sub_credit)
|
||||
#
|
||||
# new_pair = {}
|
||||
# for doc, tab in sub_credit.items():
|
||||
# new_pair[doc] = {}
|
||||
# for word, freq in tab.items():
|
||||
# new_pair[doc][word] = freq
|
||||
# print(new_pair)
|
||||
# return HttpResponse(str(main_credit)+str(sub_credit))
|
||||
|
||||
# 若total_credit为空,不进行以下操作,避免err
|
||||
if total_credit:
|
||||
# 暂时解决当该用户当月无任务时,total_credit中不包含该用户的用户名,导致Key Error
|
||||
try:
|
||||
current_user = total_credit[request.user.username]
|
||||
except:
|
||||
current_user = {}
|
||||
|
||||
# 累加该部门各个用户的工作量,计算部门工作量
|
||||
department_cal = {}
|
||||
temp_pre = []
|
||||
depart_pre, depart_real, depart_count = 0, 0, 0
|
||||
for username, value in total_credit.items():
|
||||
print(username)
|
||||
print(value['pre_cal'])
|
||||
depart_pre = depart_pre + value['pre_cal']
|
||||
depart_real = depart_real + value['real_cal']
|
||||
depart_count = depart_count + value['main_count']
|
||||
temp_pre.append(value['pre_cal'])
|
||||
department_cal['pre_cal'] = depart_pre
|
||||
department_cal['real_cal'] = depart_real
|
||||
department_cal['depart_count'] = depart_count
|
||||
department_cal['pre_avg'] = department_cal['pre_cal'] / len(total_credit)
|
||||
department_cal['real_avg'] = department_cal['real_cal'] / len(total_credit)
|
||||
else:
|
||||
department_cal, current_user = {}, {}
|
||||
|
||||
|
||||
# 计算实际工作量、完成质量
|
||||
def cal_method(main_list, sub_list, user_name):
|
||||
# print(main_list, sub_list, user_name)
|
||||
|
||||
# sub_credit = sub_list
|
||||
# main_credit = main_list
|
||||
|
||||
total_data = []
|
||||
season = 1
|
||||
# 分别计算每个季度的工作量、评价
|
||||
for main_credit, sub_credit in zip(main_list, sub_list):
|
||||
# TODO 对于完成质量的核算,先对评价求和,再除以已评价的承办任务数
|
||||
# for i in main_credit:
|
||||
# print(i['main_executor__quality_mark__mark_value__mark_value'])
|
||||
# print(main_credit)
|
||||
quality_dict = {}
|
||||
for i in main_credit:
|
||||
# 先赋值0,避免前端显示个[]
|
||||
quality_dict[i['username']] = []
|
||||
if i['main_executor__quality_mark__mark_value__mark_value'] != None:
|
||||
quality_dict[i['username']].append(i['main_executor__quality_mark__mark_value__mark_value'])
|
||||
|
||||
# TEST
|
||||
for i in sub_credit:
|
||||
# 先赋值0,避免前端显示个[]
|
||||
quality_dict[i['username']] = []
|
||||
if i['sub_executor__quality_mark__mark_value__mark_value'] != None:
|
||||
quality_dict[i['username']].append(i['sub_executor__quality_mark__mark_value__mark_value'])
|
||||
|
||||
|
||||
for key, value in quality_dict.items():
|
||||
if value:
|
||||
quality_dict[key] = sum(value) / len(value)
|
||||
# print(quality_dict)
|
||||
|
||||
main_credit = solve(main_credit, 'username',
|
||||
['main_pre_cal', 'main_real_cal', 'main_count', 'main_executor__evaluate_factor'])
|
||||
main_credit = dict(main_credit)
|
||||
sub_credit = solve(sub_credit, 'username', ['sub_pre_cal', 'sub_real_cal', 'sub_count'])
|
||||
sub_credit = dict(sub_credit)
|
||||
|
||||
# 按用户名合并承办与协办任务字典
|
||||
total_credit = deepcopy(main_credit)
|
||||
for key in sub_credit.keys():
|
||||
if key in total_credit:
|
||||
total_credit[key].update(sub_credit[key])
|
||||
else:
|
||||
total_credit[key] = sub_credit[key]
|
||||
|
||||
# print(total_credit['admin']['sub_pre_cal'])
|
||||
# 根据字典内容,计算总工作量
|
||||
for key, value in total_credit.items():
|
||||
# print(value)
|
||||
value['pre_cal'] = value['sub_pre_cal'] + value['main_pre_cal']
|
||||
value['real_cal'] = value['sub_real_cal'] + value['main_real_cal']
|
||||
value['real_name'] = user_name[key]
|
||||
value['season'] = season
|
||||
|
||||
# 由于不计算协办任务,quality_dict会有空值情况
|
||||
try:
|
||||
value['quality'] = quality_dict[key]
|
||||
except:
|
||||
value['quality'] = 0
|
||||
|
||||
for value in total_credit.values():
|
||||
# print(value)
|
||||
real_cal_season = "real_cal_" + str(season)
|
||||
value[real_cal_season] = value.pop("real_cal")
|
||||
quality_season = "quality_" + str(season)
|
||||
value[quality_season] = value.pop("quality")
|
||||
# print(total_credit)
|
||||
total_data.append(total_credit)
|
||||
# print(season)
|
||||
season += 1
|
||||
|
||||
# new_credit = []
|
||||
# for item in total_credit:
|
||||
# for key, value in item.items():
|
||||
# print(total_data, season)
|
||||
dd = defaultdict(list)
|
||||
for d in total_data: # you can list as many input dicts as you want here
|
||||
for key, value in d.items():
|
||||
dd[key].append(value)
|
||||
# print(dd)
|
||||
return dd
|
||||
user_info = User.objects.filter(department=request.user.department) \
|
||||
.values_list('username', 'real_name')
|
||||
user_name = {}
|
||||
for user in user_info:
|
||||
user_name[user[0]] = user[1]
|
||||
|
||||
work_cal = User.objects.filter(department=request.user.department, main_executor__deadline__year=2021) \
|
||||
.order_by('main_executor__deadline').values('main_executor__todo_topic', 'main_executor__deadline')
|
||||
|
||||
# 以用户表查询,按用户列出所参与承办、协办任务,并在之后按用户名分组合并。
|
||||
main_credit = User.objects.filter(department=request.user.department,
|
||||
main_executor__deadline__year=year) \
|
||||
.annotate(main_count=Count('main_executor')) \
|
||||
.order_by('username') \
|
||||
.values('username', 'main_executor', 'main_executor__predict_work', 'main_executor__real_work',
|
||||
'main_executor__evaluate_factor', 'main_executor__maturity', 'main_executor__quality_mark__mark_value__mark_value', 'main_executor__deadline', 'main_count') # 这里的annotate不知会不会有问题
|
||||
|
||||
sub_count = Todo.objects.filter(sub_executor__department=request.user.department,
|
||||
deadline__year=year) \
|
||||
.annotate(sub_count=Count('sub_executor')).values('id', 'sub_count')
|
||||
sub_credit = User.objects.filter(department=request.user.department,
|
||||
sub_executor__deadline__year=year) \
|
||||
.order_by('username') \
|
||||
.values('username', 'real_name', 'sub_executor', 'sub_executor__predict_work', 'sub_executor__real_work',
|
||||
'sub_executor__evaluate_factor', 'sub_executor__maturity', 'sub_executor__quality_mark__mark_value__mark_value', 'sub_executor__deadline')
|
||||
|
||||
# 构建工作包id对应协办人人数的字典
|
||||
sub_exe_count = {}
|
||||
for i in sub_count:
|
||||
key = i['id']
|
||||
value = i['sub_count']
|
||||
sub_exe_count[key] = value
|
||||
# print(sub_exe_count)
|
||||
|
||||
# 计算每个承办任务的预计、实际工作量、成熟度,并插入字典
|
||||
for i in main_credit:
|
||||
# 将成熟度由百分数转小数,以便其后与其他变量计算 eg. 50% -> 0.5
|
||||
print('ad', i['main_executor__maturity'])
|
||||
# 临时补丁,解决用户将成熟度设置为空的问题,后面的协办任务也改了,记得改回去
|
||||
# TODO 数据库中设置成熟度为非空
|
||||
try:
|
||||
i['main_executor__maturity'] = decimal.Decimal(float(i['main_executor__maturity'].strip('%')) / 100)
|
||||
except:
|
||||
i['main_executor__maturity'] = decimal.Decimal(float(0) / 100)
|
||||
# print(i['main_executor__maturity'])
|
||||
i['main_pre_cal'] = i['main_executor__predict_work'] * i['main_executor__evaluate_factor'] * i['main_executor__maturity']
|
||||
i['main_real_cal'] = i['main_executor__real_work'] * i['main_executor__evaluate_factor'] * i['main_executor__maturity']
|
||||
# print(i)
|
||||
# print(str(i['sub_executor']))
|
||||
|
||||
# 将协办任务对应的人数插入,计算每个协办任务的预计、实际工作量,并插入字典
|
||||
for i in sub_credit:
|
||||
sub_todo_id = i['sub_executor']
|
||||
i['sub_exe_count'] = sub_exe_count[sub_todo_id]
|
||||
try:
|
||||
i['sub_executor__maturity'] = decimal.Decimal(float(i['sub_executor__maturity'].strip('%')) / 100)
|
||||
except:
|
||||
i['sub_executor__maturity'] = decimal.Decimal(float(0) / 100)
|
||||
i['sub_pre_cal'] = i['sub_executor__predict_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count'] * i['sub_executor__maturity']
|
||||
i['sub_real_cal'] = i['sub_executor__real_work'] * (1 - i['sub_executor__evaluate_factor']) / i['sub_exe_count'] * i['sub_executor__maturity']
|
||||
i['sub_count'] = 1 # 用于帮助累加程序计算每个用户的协办任务数量, sub_exe_count会返回此协办任务的协办人数,在累加时导致计算错误
|
||||
# print(i)
|
||||
# print(str(i['sub_executor']))
|
||||
|
||||
|
||||
|
||||
main_Q1st, main_Q2nd, main_Q3th, main_Q4th = [], [], [], []
|
||||
for i in main_credit:
|
||||
# print(i['main_executor__deadline'].month)
|
||||
deadline_month = i['main_executor__deadline'].month
|
||||
if 1 <= deadline_month <= 3:
|
||||
main_Q1st.append(i)
|
||||
elif 4 <= deadline_month <= 6:
|
||||
main_Q2nd.append(i)
|
||||
elif 7 <= deadline_month <= 9:
|
||||
main_Q3th.append(i)
|
||||
elif 10 <= deadline_month <= 12:
|
||||
main_Q4th.append(i)
|
||||
# print(Q1st, Q2nd, Q3th, Q4th)
|
||||
|
||||
sub_Q1st, sub_Q2nd, sub_Q3th, sub_Q4th = [], [], [], []
|
||||
for i in sub_credit:
|
||||
# print(i['main_executor__deadline'].month)
|
||||
deadline_month = i['sub_executor__deadline'].month
|
||||
if 1 <= deadline_month <= 3:
|
||||
sub_Q1st.append(i)
|
||||
elif 4 <= deadline_month <= 6:
|
||||
sub_Q2nd.append(i)
|
||||
elif 7 <= deadline_month <= 9:
|
||||
sub_Q3th.append(i)
|
||||
elif 10 <= deadline_month <= 12:
|
||||
sub_Q4th.append(i)
|
||||
|
||||
main_list = [main_Q1st, main_Q2nd, main_Q3th, main_Q4th]
|
||||
sub_list = [sub_Q1st, sub_Q2nd, sub_Q3th, sub_Q4th]
|
||||
stat_result = cal_method(main_list, sub_list, user_name)
|
||||
|
||||
stat = {}
|
||||
for key, value in stat_result.items():
|
||||
stat[key] = {}
|
||||
for j in value:
|
||||
stat[key]['real_name'] = j['real_name']
|
||||
season = str(j['season'])
|
||||
real_cal_season = 'real_cal_' + season
|
||||
quality_season = 'quality_' + season
|
||||
stat[key][real_cal_season] = j[real_cal_season]
|
||||
stat[key][quality_season] = j[quality_season]
|
||||
print(stat_result)
|
||||
|
||||
# return HttpResponse(result.items())
|
||||
# 为页面提供日期信息
|
||||
date = str(year) + '年' + str(month) + '月'
|
||||
|
||||
# return HttpResponse(str(total_credit) + '\n' + str(department_cal))
|
||||
context = {'date': date, 'users_data': total_credit, 'department_cal': department_cal,
|
||||
'current_user': current_user, 'stat': stat}
|
||||
return render(request, 'tasks/index.html', context)
|
||||
|
||||
|
||||
|
@ -32,7 +366,8 @@ class TodoListView(View):
|
|||
my_todo = Todo.objects.filter(main_executor=request.user, deadline__year=year, deadline__month=month)
|
||||
my_sub_todo = Todo.objects.filter(sub_executor=request.user, deadline__year=year, deadline__month=month)
|
||||
date = str(year) + '年' + str(month) + '月'
|
||||
context = {'my_todo': my_todo, 'my_sub_todo': my_sub_todo, 'date': date}
|
||||
current_path = request.get_full_path()
|
||||
context = {'my_todo': my_todo, 'my_sub_todo': my_sub_todo, 'date': date, 'current_path': current_path}
|
||||
return render(request, 'tasks/todolist.html', context)
|
||||
|
||||
|
||||
|
@ -42,17 +377,25 @@ class GroupTodoList(View):
|
|||
group_todo = Todo.objects.filter(main_executor__department=request.user.department, deadline__year=year,
|
||||
deadline__month=month).order_by('related_task_id', 'deadline')
|
||||
date = str(year) + '年' + str(month) + '月'
|
||||
context = {'group_todo': group_todo, 'date': date}
|
||||
current_path = request.get_full_path()
|
||||
context = {'group_todo': group_todo, 'date': date, 'current_path': current_path}
|
||||
return render(request, 'tasks/group_todolist.html', context)
|
||||
|
||||
|
||||
class TaskListView(View):
|
||||
@method_decorator(login_required)
|
||||
def get(self, request):
|
||||
tasks = Task.objects.filter().order_by('task_id')
|
||||
context = {'tasks': tasks}
|
||||
def get(self, request, year=timezone.now().year): # TODO 把timezone.now().year写在后面要用year替换的地方是否可以解决
|
||||
tasks = Task.objects.filter(department=request.user.department, deadline__year=year).order_by('task_id')\
|
||||
| Task.objects.filter(department=request.user.department, related_task__deadline__year=year).order_by('task_id')
|
||||
tasks = tasks.distinct()
|
||||
# tasks = Task.objects.filter(Q(department=request.user.department), Q(deadline__year=year) | Q(related_task__deadline__year=year)).order_by('task_id')
|
||||
# 使用‘或’,找出工作包/年度任务的截止日期在今年的年度任务。后面还要做一个筛选,以达到只显示本年度的工作包
|
||||
year_quarter = {'1': [1, year], '2': [2, year], '3': [3, year], '4': [4, year]}
|
||||
|
||||
context = {'tasks': tasks, 'year_quarter': year_quarter}
|
||||
return render(request, 'tasks/tasklist.html', context)
|
||||
|
||||
|
||||
class UserLoginView(View):
|
||||
def get(self, request):
|
||||
return render(request, 'tasks/login.html')
|
||||
|
@ -68,6 +411,7 @@ class UserLoginView(View):
|
|||
else:
|
||||
return redirect('tasks:index')
|
||||
|
||||
|
||||
class UserLogoutView(View):
|
||||
def get(self, request):
|
||||
logout(request)
|
||||
|
@ -84,8 +428,12 @@ class TodoEntryView(View):
|
|||
def post(self, request, pk):
|
||||
todo_detail = Todo.objects.get(id=pk)
|
||||
form = TodoForm(instance=todo_detail, data=request.POST)
|
||||
redirect_to = request.GET.get('next')
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
if url_has_allowed_host_and_scheme(redirect_to, None):
|
||||
return redirect(redirect_to)
|
||||
else:
|
||||
return redirect('tasks:todolist')
|
||||
# return redirect('tasks:todo_detail', pk=pk)
|
||||
|
||||
|
|
|
@ -23,14 +23,24 @@ class MyUserAdmin(UserAdmin):
|
|||
class MyGroupAdmin(GroupAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class DepartmentAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class TaskPropertyAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'task_property', 'own_department'
|
||||
)
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(TaskPropertyAdmin, self).get_queryset(request)
|
||||
if request.user.is_superuser:
|
||||
return qs
|
||||
else:
|
||||
return qs.filter(own_department=request.user.department)
|
||||
|
||||
|
||||
class QualityMarkAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'mark_name',
|
||||
|
@ -40,9 +50,27 @@ class QualityMarkAdmin(admin.ModelAdmin):
|
|||
return self.mark_value
|
||||
mark_value.short_description = 'ss'
|
||||
|
||||
# # 仅显示当前部门的任务属性,除非为超管
|
||||
# def get_queryset(self, request):
|
||||
# qs = super(QualityMarkAdmin, self).get_queryset(request)
|
||||
# if request.user.is_superuser:
|
||||
# return qs
|
||||
# else:
|
||||
# return qs.filter(department=request.user.department)
|
||||
|
||||
|
||||
class MarkValueAdmin(admin.ModelAdmin):
|
||||
# def get_queryset(self, request):
|
||||
# qs = super(MarkValueAdmin, self).get_queryset(request)
|
||||
# if request.user.is_superuser:
|
||||
# return qs
|
||||
# else:
|
||||
# return qs.filter(department=request.user.department)
|
||||
pass
|
||||
|
||||
admin.site.register(models.User, MyUserAdmin)
|
||||
admin.site.register(models.MyGroup, MyGroupAdmin)
|
||||
admin.site.register(models.MarkValue)
|
||||
admin.site.register(models.MarkValue, MarkValueAdmin)
|
||||
admin.site.register(models.Department)
|
||||
admin.site.register(models.QualityMark, QualityMarkAdmin)
|
||||
admin.site.register(models.TaskProperty, TaskPropertyAdmin)
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.db.models import Avg, Sum, F, Value
|
|||
class User(AbstractUser):
|
||||
real_name = models.CharField(max_length=150, verbose_name='姓名')
|
||||
staff_id = models.CharField(max_length=150, verbose_name='工号')
|
||||
department = models.ForeignKey('Department', on_delete=models.SET_NULL, null=True, blank=True)
|
||||
department = models.ForeignKey('Department', related_name='member', on_delete=models.SET_NULL, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '用户'
|
||||
|
@ -45,6 +45,10 @@ class Department(models.Model):
|
|||
verbose_name = '部门'
|
||||
verbose_name_plural = '部门'
|
||||
|
||||
@property
|
||||
def get_user_number(self):
|
||||
return self.member.count()
|
||||
|
||||
|
||||
class MarkValue(models.Model):
|
||||
mark_value = models.DecimalField('评价等级考核系数', max_digits=3, decimal_places=2)
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
version: "3"
|
||||
services:
|
||||
app:
|
||||
restart: always
|
||||
build: . # '点'代表当前目录
|
||||
command: "python3 manage.py runserver 0.0.0.0:8000"
|
||||
volumes:
|
||||
- .:/code
|
||||
ports:
|
||||
- "8000:8000"
|
|
@ -0,0 +1,7 @@
|
|||
import { defineClientConfig } from '@vuepress/client'
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ app, router, siteData }) {},
|
||||
setup() {},
|
||||
rootComponents: [],
|
||||
})
|
|
@ -0,0 +1,37 @@
|
|||
// module.exports = {
|
||||
// lang: 'zh-CN',
|
||||
// title: 'TasksManager',
|
||||
// description: '超好用的任务分配管理系统',
|
||||
// }
|
||||
//
|
||||
import {defaultTheme, defineUserConfig} from 'vuepress'
|
||||
|
||||
export default defineUserConfig({
|
||||
lang: 'zh-CN',
|
||||
title: 'TasksManager',
|
||||
description: '超好用的任务分配管理系统',
|
||||
|
||||
theme: defaultTheme({
|
||||
navbar: [
|
||||
{
|
||||
text: '指南',
|
||||
link: '/guide/',
|
||||
},
|
||||
{
|
||||
text: '部署',
|
||||
link: '/install/',
|
||||
}
|
||||
],
|
||||
sidebar: "auto",
|
||||
repo: 'https://github.com/raiots/TasksManager',
|
||||
repoLabel: '✨Github',
|
||||
docsDir: 'docs',
|
||||
docsBranch: 'master',
|
||||
lastUpdatedText: "📑 最后更新",
|
||||
contributorsText: "💕 参与贡献",
|
||||
editLinkText: "🖊️ 编辑本文",
|
||||
notFound: ["👻 页面不存在"],
|
||||
|
||||
}),
|
||||
})
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
home: true
|
||||
heroText: TasksManager
|
||||
tagline: 超好用的任务分配管理工具❤️
|
||||
actions:
|
||||
- text: 项目简介
|
||||
link: /guide/
|
||||
type: primary
|
||||
- text: 快速部署
|
||||
link: /install/
|
||||
type: secondary
|
||||
|
||||
features:
|
||||
- title: 任务分发
|
||||
details: TasksManager 管理员能够以年度任务为主线,向用户发送待办任务
|
||||
- title: 任务提交
|
||||
details: 用户可以查看自己的待办任务、并提交完成度与完成情况说明,帮助管理员掌握部门工作情况
|
||||
- title: 评价考核
|
||||
details: TasksManager 会对用户完成任务数量与质量进行综合评价,并直观显示于首页
|
||||
---
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
sidebar: auto
|
||||
|
||||
---
|
||||
|
||||
TasksManager 是一个由Django开发的任务分发系统
|
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
sidebar: auto
|
||||
navbar: True
|
||||
---
|
||||
|
||||
# 部署
|
||||
|
||||
你可以使用两种方式部署 TasksManager,这里推荐使用 Docker compose 的方式进行部署。
|
||||
|
||||
## Docker Compose 部署
|
||||
|
||||
> Docker Compose 是一个用于在使用Compose 文件格式定义的 Docker 上运行多容器应用程序的工具。
|
||||
|
||||
### 安装
|
||||
|
||||
拉取 TaskManager 程序源码
|
||||
|
||||
```shell
|
||||
git clone https://github.com/raiots/TasksManager.git
|
||||
```
|
||||
|
||||
### 启动
|
||||
使用 cd 命令进入程序文件夹后启动程序:
|
||||
|
||||
```shell
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
TasksManager 将会运行在服务器的 8000 端口,在浏览器中打开 http://ip地址:8000 即可访问
|
||||
|
||||
## 使用源码手动部署
|
||||
|
||||
> 程序使用 Python3.8 开发,请提前配置 Python 环境
|
||||
|
||||
### 安装
|
||||
|
||||
使用 git 下载 TasksManager 源码:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/raiots/TasksManager.git
|
||||
cd TasksManager
|
||||
```
|
||||
|
||||
创建并激活 Python 虚拟环境
|
||||
|
||||
```shell
|
||||
python -m venv venv
|
||||
.\venv\Scripts\activate.sh
|
||||
```
|
||||
|
||||
安装 TasksManager 依赖
|
||||
|
||||
```shell
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 启动
|
||||
|
||||
```shell
|
||||
python3 manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
TasksManager 将会运行在服务器的 8000 端口,在浏览器中打开 http://ip地址:8000 即可访问
|
||||
|
||||
## 使用 Nginx 配置反向代理
|
||||
|
||||
```
|
||||
server
|
||||
{
|
||||
listen 443 ssl http2;
|
||||
server_name your.domain.com ;
|
||||
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000; # 转发规则
|
||||
proxy_set_header Host $proxy_host; # 修改转发请求头,让8000端口的应用可以受到真实的请求
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"vuepress": "^2.0.0-beta.60"
|
||||
},
|
||||
"scripts": {
|
||||
"docs:dev": "vuepress dev docs",
|
||||
"docs:build": "vuepress build docs"
|
||||
}
|
||||
}
|
|
@ -1,7 +1,21 @@
|
|||
asgiref==3.3.1
|
||||
Django==3.1.5
|
||||
django-simpleui==2021.3
|
||||
defusedxml==0.7.1
|
||||
diff-match-patch==20200713
|
||||
Django==3.1.12
|
||||
django-import-export==2.5.0
|
||||
django-simpleui==2021.8.1
|
||||
et-xmlfile==1.0.1
|
||||
MarkupPy==1.14
|
||||
numpy==1.20.2
|
||||
odfpy==1.4.1
|
||||
openpyxl==3.0.7
|
||||
pandas==1.2.4
|
||||
python-dateutil==2.8.1
|
||||
pytz==2020.5
|
||||
PyYAML==5.4.1
|
||||
six==1.15.0
|
||||
sqlparse==0.4.1
|
||||
tablib==3.0.0
|
||||
xlrd==2.0.1
|
||||
xlwt==1.3.0
|
||||
django-debug-toolbar~=3.2.2
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 8.7 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -13,3 +13,8 @@
|
|||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.col-xs-1-5 {
|
||||
width: 20%;
|
||||
float: left;
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>ABOUT - Raiot</title>
|
||||
<link rel="stylesheet" href="{% static 'tasks/nes.min.font.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'tasks/nes.min.css' %}">
|
||||
<style>
|
||||
@charset "utf-8";
|
||||
@import url({% static 'tasks/about_font.css' %});
|
||||
|
@ -142,7 +142,7 @@
|
|||
<header class="">
|
||||
<div class="container">
|
||||
<div class="nav-brand">
|
||||
<a href="https://github.com/Anankke/SSPanel-Uim">
|
||||
<a href="#">
|
||||
<h1>STAFF</h1>
|
||||
</a>
|
||||
<p>© 2021 RAIOT</p>
|
||||
|
|
|
@ -9,7 +9,7 @@ scratch. This page gets rid of all links and provides the needed markup only.
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>任务管理系统</title>
|
||||
<title>任务管理工具</title>
|
||||
|
||||
<!-- Google Font: Source Sans Pro -->
|
||||
<link rel="stylesheet" href="{% static 'tasks/dist/css/googlefont.css' %}">
|
||||
|
@ -32,7 +32,7 @@ scratch. This page gets rid of all links and provides the needed markup only.
|
|||
<div class="container-xl">
|
||||
<a href="{% url 'tasks:index' %}" class="navbar-brand">
|
||||
<img src="{% static 'tasks/dist/img/AdminLTELogo.png' %}" alt="AdminLTE Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
|
||||
<span class="brand-text font-weight-light">任务管理系统</span>
|
||||
<span class="brand-text font-weight-light">任务管理工具</span>
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler order-1" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
|
@ -53,10 +53,18 @@ scratch. This page gets rid of all links and provides the needed markup only.
|
|||
<a href="{% url 'tasks:tasklist' %}" class="nav-link">年度任务</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin" class="nav-link">编辑任务</a>
|
||||
<a href="/admin/#tasks/task" class="nav-link">编辑任务</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a id="dropdownSubMenu1" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle">管理域风险分析</a>
|
||||
<ul aria-labelledby="dropdownSubMenu1" class="dropdown-menu border-0 shadow">
|
||||
<li><a href="{% url 'tasks:todolist' %}" class="dropdown-item" style="font-size: 0.9rem"> 风险事件采集 </a></li>
|
||||
<li><a href="{% url 'tasks:group_todolist' %}" class="dropdown-item" style="font-size: 0.9rem">风险事件批量采集</a></li>
|
||||
<li><a href="{% url 'tasks:group_todolist' %}" class="dropdown-item" style="font-size: 0.9rem">可视化分析</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin" class="nav-link">系统配置</a>
|
||||
<a href="/admin/#users/user" class="nav-link">系统配置</a>
|
||||
</li>
|
||||
{# <li class="nav-item dropdown">#}
|
||||
{# <a id="dropdownSubMenu1" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="nav-link dropdown-toggle">统计与分析</a>#}
|
||||
|
@ -191,7 +199,7 @@ scratch. This page gets rid of all links and provides the needed markup only.
|
|||
<li class="nav-tabs">
|
||||
<a>欢迎,</a>
|
||||
<b style="color: cadetblue">{{ user.real_name }}</b>
|
||||
<a>。 本系统为非涉密应用系统,禁止处理秘密级及以上信息!!!</a>
|
||||
<a>。 本软件为非涉密应用工具,禁止处理秘密级及以上信息!!!</a>
|
||||
</li>
|
||||
<li class="navbar-tabs">
|
||||
<a href="{% url 'tasks:logout' %}"> 登出</a>
|
||||
|
|
|
@ -58,11 +58,11 @@
|
|||
<table class="table" style="word-break: break-all">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 16px; text-align:center; vertical-align: middle;">#</th>
|
||||
<th style="width: 16px; text-align:center; vertical-align: middle;">序号</th>
|
||||
<th style="width: 280px; text-align:center; vertical-align: middle;">工作事项</th>
|
||||
<th style="width: 100px; text-align:center; vertical-align: middle;">完成时间</th>
|
||||
<th style="width: 160px; text-align:center; vertical-align: middle;">工作要求及交付物</th>
|
||||
<th style="width: 50px; text-align:center; vertical-align: middle;">任务编号</th>
|
||||
<th style="width: 60px; text-align:center; vertical-align: middle;">任务编号</th>
|
||||
<th style="width: 120px; text-align:center; vertical-align: middle;">任务来源</th>
|
||||
<th style="width: 120px; text-align:center; vertical-align: middle;">承办单位</th>
|
||||
<th style="width: 120px;text-align:center; vertical-align: middle;">承/督办人</th>
|
||||
|
|
|
@ -8,12 +8,20 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>总览</h1>
|
||||
<h1>总览 ({{ date }}) </h1>
|
||||
{# <h1><font color="#FF0000">测试版,请勿用于生产环境!!!</font></h1>#}
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item active"><a href="#">主页</a></li>
|
||||
</ol>
|
||||
{# <ol class="breadcrumb float-sm-right">#}
|
||||
{# <li class="breadcrumb-item active"><a href="#">主页</a></li>#}
|
||||
{# </ol>#}
|
||||
<br>
|
||||
{% load taskfilter %}
|
||||
<ul class="pagination float-md-right">
|
||||
<li class="page-item"><a class="page-link" href="/{{ date|last_month }}">«</a></li>
|
||||
<li class="page-item"><a class="page-link"> {{ date|this_month }}月 </a></li>
|
||||
<li class="page-item"><a class="page-link" href="/{{ date|next_month }}">»</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
|
@ -23,14 +31,13 @@
|
|||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="col">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-info elevation-1"><i class="fas fa-tasks"></i></span>
|
||||
|
||||
<span class="info-box-icon bg-danger elevation-1"><i class="fas fa-tasks"></i></span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">承办任务数</span>
|
||||
<span class="info-box-text">部门总任务数</span>
|
||||
<span class="info-box-number">
|
||||
{{ request.user.main_executor.count }}
|
||||
{{ department_cal.depart_count|floatformat:'0'|default_if_none:'0' }}
|
||||
<small>个</small>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -39,13 +46,135 @@
|
|||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col-12 col-sm-6 col-md-3">
|
||||
<div class="col">
|
||||
<div class="info-box mb-3">
|
||||
<span class="info-box-icon bg-danger elevation-1"><i class="fas fa-tasks"></i></span>
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">部门预计总工作量</span>
|
||||
<span class="info-box-number">
|
||||
{{ department_cal.pre_cal|default_if_none:'0'|floatformat:'1' }}
|
||||
<small></small>
|
||||
</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col">
|
||||
<div class="info-box mb-3">
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">部门实际总工作量</span>
|
||||
<span class="info-box-number">
|
||||
{{ department_cal.real_cal|default_if_none:'0'|floatformat:'1' }}
|
||||
<small></small>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col">
|
||||
<div class="info-box mb-3">
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">部门预计平均工作量</span>
|
||||
<span class="info-box-number">
|
||||
{{ department_cal.pre_avg|default_if_none:'0'|floatformat:'1' }}
|
||||
<small></small>
|
||||
</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col">
|
||||
<div class="info-box mb-3">
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">部门实际平均工作量</span>
|
||||
<span class="info-box-number">
|
||||
{{ department_cal.real_avg|default_if_none:'0'|floatformat:'1' }}
|
||||
<small></small>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
|
||||
<!-- fix for small devices only -->
|
||||
<div class="clearfix hidden-md-up"></div>
|
||||
|
||||
{# <div class="col-12 col-sm-6 col-md-3">#}
|
||||
{# <div class="info-box mb-3">#}
|
||||
{# <span class="info-box-icon bg-success elevation-1"><i class="fas fa-shopping-cart"></i></span>#}
|
||||
{##}
|
||||
{# <div class="info-box-content">#}
|
||||
{# <span class="info-box-text">Sales</span>#}
|
||||
{# <span class="info-box-number">760</span>#}
|
||||
{# </div>#}
|
||||
{# <!-- /.info-box-content -->#}
|
||||
{# </div>#}
|
||||
{# <!-- /.info-box -->#}
|
||||
{# </div>#}
|
||||
{# <!-- /.col -->#}
|
||||
{# <div class="col-12 col-sm-6 col-md-3">#}
|
||||
{# <div class="info-box mb-3">#}
|
||||
{# <span class="info-box-icon bg-warning elevation-1"><i class="fas fa-users"></i></span>#}
|
||||
{##}
|
||||
{# <div class="info-box-content">#}
|
||||
{# <span class="info-box-text">New Members</span>#}
|
||||
{# <span class="info-box-number">2,000</span>#}
|
||||
{# </div>#}
|
||||
{# <!-- /.info-box-content -->#}
|
||||
{# </div>#}
|
||||
{# <!-- /.info-box -->#}
|
||||
{# </div>#}
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="info-box">
|
||||
<span class="info-box-icon bg-info elevation-1"><i class="fas fa-tasks"></i></span>
|
||||
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text"><b>个人任务数据统计:</b></span>
|
||||
{# <span class="info-box-number">#}
|
||||
{# {% for user in current_user %}{{ user.main_executor_count }}{% endfor %}#}
|
||||
{# <small>个</small>#}
|
||||
{# </span>#}
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="info-box">
|
||||
{# <span class="info-box-icon bg-info elevation-1"><i class="fas fa-tasks"></i></span>#}
|
||||
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">承办任务数</span>
|
||||
<span class="info-box-number">
|
||||
{{ current_user.main_count }}
|
||||
<small>个</small>
|
||||
</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col">
|
||||
<div class="info-box mb-3">
|
||||
{# <span class="info-box-icon bg-danger elevation-1"><i class="fas fa-tasks"></i></span>#}
|
||||
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">协办任务数</span>
|
||||
<span class="info-box-number">{{ user.sub_executor.count }}
|
||||
<span class="info-box-number">
|
||||
{{ current_user.sub_count }}
|
||||
<small>个</small>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -54,6 +183,34 @@
|
|||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col">
|
||||
<div class="info-box mb-3">
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">预计工作量</span>
|
||||
<span class="info-box-number">
|
||||
{{ current_user.pre_cal|floatformat:'1'|default_if_none:'0' }}
|
||||
<small></small>
|
||||
</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
<div class="col">
|
||||
<div class="info-box mb-3">
|
||||
<div class="info-box-content">
|
||||
<span class="info-box-text">实际工作量</span>
|
||||
<span class="info-box-number">
|
||||
{{ current_user.real_cal|floatformat:'1'|default_if_none:'0' }}
|
||||
<small></small>
|
||||
</span>
|
||||
</div>
|
||||
<!-- /.info-box-content -->
|
||||
</div>
|
||||
<!-- /.info-box -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
|
||||
<!-- fix for small devices only -->
|
||||
<div class="clearfix hidden-md-up"></div>
|
||||
|
@ -124,17 +281,80 @@
|
|||
<!-- /.col (MAIN) -->
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">部门工作统计表</h3>
|
||||
</div>
|
||||
{# TODO 更换为绩效考核表 分季度按人显示工作量及完成质量#}
|
||||
<!-- /.card-header -->
|
||||
<div class="card-body">
|
||||
<div id="example2_wrapper" class="dataTables_wrapper dt-bootstrap4"><div class="row"><div class="col-sm-12 col-md-6"></div><div class="col-sm-12 col-md-6"></div></div><div class="row"><div class="col-sm-12"><table id="example2" class="table table-bordered table-hover dataTable" role="grid" aria-describedby="example2_info">
|
||||
<thead>
|
||||
<tr role="row">
|
||||
<th class="sorting_asc" tabindex="0" aria-controls="example2" rowspan="2" colspan="1" aria-sort="ascending" aria-label="Rendering engine: activate to sort column descending">姓名</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="example2" rowspan="1" colspan="2" style="text-align: center">一季度</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="example2" rowspan="1" colspan="2" style="text-align: center">二季度</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="example2" rowspan="1" colspan="2" style="text-align: center">三季度</th>
|
||||
<th class="sorting" tabindex="0" aria-controls="example2" rowspan="1" colspan="2" style="text-align: center">四季度</th>
|
||||
</tr>
|
||||
<tr role="row">
|
||||
<th>实际工作量</th>
|
||||
<th>完成质量</th>
|
||||
<th>实际工作量</th>
|
||||
<th>完成质量</th>
|
||||
<th>实际工作量</th>
|
||||
<th>完成质量</th>
|
||||
<th>实际工作量</th>
|
||||
<th>完成质量</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in stat.values %}
|
||||
<tr role="row" class="odd">
|
||||
<td class="sorting_1">{{ user.real_name }}</td>
|
||||
<td>{{ user.real_cal_1|default_if_none:'0'|floatformat:'1' }}</td>
|
||||
<td>{{ user.quality_1|default_if_none:'0'|floatformat:'1' }}</td>
|
||||
<td>{{ user.real_cal_2|default_if_none:'0'|floatformat:'1' }}</td>
|
||||
<td>{{ user.quality_2|default_if_none:'0'|floatformat:'1' }}</td>
|
||||
<td>{{ user.real_cal_3|default_if_none:'0'|floatformat:'1' }}</td>
|
||||
<td>{{ user.quality_3|default_if_none:'0'|floatformat:'1' }}</td>
|
||||
<td>{{ user.real_cal_4|default_if_none:'0'|floatformat:'1' }}</td>
|
||||
<td>{{ user.quality_4|default_if_none:'0'|floatformat:'1' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{# <tfoot>#}
|
||||
{# <tr><th rowspan="1" colspan="1">Rendering engine</th><th rowspan="1" colspan="1">Browser</th><th rowspan="1" colspan="1">Platform(s)</th><th rowspan="1" colspan="1">Engine version</th><th rowspan="1" colspan="1">CSS grade</th></tr>#}
|
||||
{# </tfoot>#}
|
||||
</table></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
<!-- /.col -->
|
||||
</div>
|
||||
|
||||
</div><!-- /.container-fluid -->
|
||||
</section>
|
||||
<!-- /.content -->
|
||||
</div>
|
||||
|
||||
{# <div class="content-wrapper">#}
|
||||
{# {{ main_points }}#}
|
||||
{# <br>#}
|
||||
{# {{ sub_points }}#}
|
||||
{# <br>#}
|
||||
{# {{ points }}#}
|
||||
{# {{ points.3.credit }}#}
|
||||
{# {% for point in points.values %}'{{ point }}', {% endfor %}#}
|
||||
{# {% for point in points %}#}
|
||||
{# {{ point }}#}
|
||||
{# {{ point.a }}#}
|
||||
{# {% endfor %}#}
|
||||
|
||||
{##}
|
||||
{# {% for user in users %}#}
|
||||
{# {{ user }}#}
|
||||
{# {{ user.get_predict_work_count.total_predict_work }}#}
|
||||
|
@ -146,7 +366,7 @@
|
|||
{# {% endfor %}#}
|
||||
{# </br>#}
|
||||
{# {% endfor %}#}
|
||||
|
||||
{##}
|
||||
{# </div>#}
|
||||
{% endblock %}
|
||||
|
||||
|
@ -160,29 +380,51 @@
|
|||
* Here we will create a few charts using ChartJS
|
||||
*/
|
||||
var areaChartData = {
|
||||
labels : [{% for user in users %}'{{ user }}', {% endfor %}],
|
||||
labels : [{% for user in users_data.values %}'{{ user.real_name }}', {% endfor %}],
|
||||
datasets: [
|
||||
{
|
||||
label : '协办任务',
|
||||
backgroundColor : 'rgba(114,140,212,1)',
|
||||
borderColor : 'rgba(114,140,212,1)',
|
||||
pointRadius : false,
|
||||
pointColor : 'rgba(114,140,212,1)',
|
||||
pointStrokeColor : '#c1c7d1',
|
||||
pointHighlightFill : '#fff',
|
||||
pointHighlightStroke: 'rgba(220,220,220,1)',
|
||||
data : [{% for user in users_data.values %}'{{ user.sub_count }}', {% endfor %}]
|
||||
},
|
||||
{
|
||||
label : '承办任务',
|
||||
backgroundColor : 'rgba(60,141,188,0.9)',
|
||||
borderColor : 'rgba(60,141,188,0.8)',
|
||||
backgroundColor : 'rgba(23,162,184,0.9)',
|
||||
borderColor : 'rgba(100,98,204,0.8)',
|
||||
pointRadius : false,
|
||||
pointColor : '#3b8bba',
|
||||
pointStrokeColor : 'rgba(60,141,188,1)',
|
||||
pointHighlightFill : '#fff',
|
||||
pointHighlightStroke: 'rgba(60,141,188,1)',
|
||||
data : [{% for user in users %}'{{ user.main_executor.count }}', {% endfor %}]
|
||||
data : [{% for user in users_data.values %}'{{ user.main_count }}', {% endfor %}]
|
||||
},
|
||||
{
|
||||
label : '协办任务',
|
||||
backgroundColor : 'rgba(210, 214, 222, 1)',
|
||||
borderColor : 'rgba(210, 214, 222, 1)',
|
||||
label : '预计工作量',
|
||||
backgroundColor : 'rgba(210, 214, 222,1)',
|
||||
borderColor : 'rgba(210, 214, 222,1)',
|
||||
pointRadius : false,
|
||||
pointColor : 'rgba(210, 214, 222, 1)',
|
||||
pointStrokeColor : '#c1c7d1',
|
||||
pointColor : '#3b8bba',
|
||||
pointStrokeColor : 'rgba(60,141,188,1)',
|
||||
pointHighlightFill : '#fff',
|
||||
pointHighlightStroke: 'rgba(220,220,220,1)',
|
||||
data : [{% for user in users %}'{{ user.sub_executor.count }}', {% endfor %}]
|
||||
pointHighlightStroke: 'rgba(60,141,188,1)',
|
||||
data : [{% for user in users_data.values %}'{{ user.pre_cal|floatformat:'1' }}', {% endfor %}]
|
||||
},
|
||||
{
|
||||
label : '工作量',
|
||||
backgroundColor : 'rgba(253,199,101,0.9)',
|
||||
borderColor : 'rgba(253,199,101,0.8)',
|
||||
pointRadius : false,
|
||||
pointColor : '#3b8bba',
|
||||
pointStrokeColor : 'rgba(60,141,188,1)',
|
||||
pointHighlightFill : '#fff',
|
||||
pointHighlightStroke: 'rgba(60,141,188,1)',
|
||||
data : [{% for user in users_data.values %}'{{ user.real_cal|floatformat:'1' }}', {% endfor %}]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>任务管理系统</title>
|
||||
<title>任务管理工具</title>
|
||||
|
||||
<!-- Google Font: Source Sans Pro -->
|
||||
<link rel="stylesheet" href="{% static 'tasks/dist/css/googlefont.css' %}">
|
||||
|
@ -16,7 +16,7 @@
|
|||
<!-- Theme style -->
|
||||
<link rel="stylesheet" href="{% static 'tasks/dist/css/adminlte.min.css' %}">
|
||||
</head>
|
||||
<body class="hold-transition login-page">
|
||||
<body class="hold-transition login-page" style="background-image: url({% static 'tasks/images/peak_background.svg' %})">
|
||||
<div class="login-box">
|
||||
<!-- /.login-logo -->
|
||||
<div class="card card-outline card-primary">
|
||||
|
|
|
@ -33,6 +33,19 @@
|
|||
<!-- /.card-header -->
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-striped" style="word-break: break-all; overflow: auto" cellspacing="0">
|
||||
<div class="row">
|
||||
<div class="col-12" style="background-color: #f2f2f2">
|
||||
<button type="button" id="unfold" onclick="alert1()" class="btn btn-default float-left" style="display: block;"><i class="far fa-credit-card"></i> 展开</button>
|
||||
<button type="button" id="fold" onclick="alert1()" class="btn btn-default float-left" style="display: none;"><i class="far fa-credit-card"></i> 收起</button>
|
||||
<ul class="pagination float-md-right">
|
||||
{% load taskfilter %}
|
||||
<li class="page-item"><a class="page-link" href="/tasklist/{{ year_quarter.1|last_year }}">«</a></li>
|
||||
<li class="page-item"><a class="page-link"> {{ year_quarter.1|this_year }}年 </a></li>
|
||||
<li class="page-item"><a class="page-link" href="/tasklist/{{ year_quarter.1|next_year }}">»</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<thead>
|
||||
<tr style="background-color: #f2f2f2">
|
||||
<th style="width: 140px; text-align:center; vertical-align: middle;">任务属性</th>
|
||||
|
@ -80,16 +93,16 @@
|
|||
<td>{{ task.aim_value }}</td>
|
||||
{% load taskfilter %}
|
||||
<td style="background-color: #BFE7A8; padding: 0">
|
||||
<textarea class="text-none-border" style="background-color: #BFE7A8;">{% for todo in task.related_task.all %}{{ todo|quarter_cate:'1'|safe }}{% endfor %}</textarea>
|
||||
<textarea class="text-none-border" style="background-color: #BFE7A8;">{% for todo in task.related_task.all %}{{ todo|quarter_cate:year_quarter.1|safe }}{% endfor %}</textarea>
|
||||
</td>
|
||||
<td style="background-color: #F5CCB0; padding: 0">
|
||||
<textarea class="text-none-border" style="background-color: #F5CCB0">{% for todo in task.related_task.all %}{{ todo|quarter_cate:'2'|safe }}{% endfor %}</textarea>
|
||||
<textarea class="text-none-border" style="background-color: #F5CCB0">{% for todo in task.related_task.all %}{{ todo|quarter_cate:year_quarter.2|safe }}{% endfor %}</textarea>
|
||||
</td>
|
||||
<td style="background-color: #F7E4A8; padding: 0">
|
||||
<textarea class="text-none-border" style="background-color: #F7E4A8">{% for todo in task.related_task.all %}{{ todo|quarter_cate:'3'|safe }}{% endfor %}</textarea>
|
||||
<textarea class="text-none-border" style="background-color: #F7E4A8">{% for todo in task.related_task.all %}{{ todo|quarter_cate:year_quarter.3|safe }}{% endfor %}</textarea>
|
||||
</td>
|
||||
<td style="background-color: #83E1E3; padding: 0">
|
||||
<textarea class="text-none-border" style="background-color: #83E1E3">{% for todo in task.related_task.all %}{{ todo|quarter_cate:'4'|safe }}{% endfor %}</textarea>
|
||||
<textarea class="text-none-border" style="background-color: #83E1E3">{% for todo in task.related_task.all %}{{ todo|quarter_cate:year_quarter.4|safe }}{% endfor %}</textarea>
|
||||
</td>
|
||||
<td style="text-align:center;">{{ task.deadline | date:"Y.m" }}</td>
|
||||
<td style="text-align:center;">{{ task.duty_group }}</td>
|
||||
|
@ -120,5 +133,35 @@
|
|||
}).on('input', function () {
|
||||
setHeight(this);
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
|
||||
$('#unfold').click(function (element){
|
||||
var unfold = document.getElementById("unfold");
|
||||
var fold = document.getElementById("fold");
|
||||
unfold.style.display = "none";
|
||||
fold.style.display = "block";
|
||||
$(element).css({'height':'auto','overflow-y':'hidden'}).height(element.scrollHeight);
|
||||
$('textarea').each(function () {
|
||||
setHeight(this);
|
||||
}).on('input', function () {
|
||||
setHeight(this);
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
$('#fold').click(function (element){
|
||||
var unfold = document.getElementById("unfold");
|
||||
var fold = document.getElementById("fold");
|
||||
unfold.style.display = "block";
|
||||
fold.style.display = "none";
|
||||
location.reload();
|
||||
// TODO 暂时无法解决让textarea恢复原本高度的问题,使用刷新代替
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="container">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1 class="m-0"> 我的任务 <small>Example 3.0</small></h1>
|
||||
<h1 class="m-0"> 我的任务 <small></small></h1>
|
||||
</div><!-- /.col -->
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
|
@ -37,11 +37,15 @@
|
|||
<dl class="row">
|
||||
<dt class="col-sm-4">工作事项</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.todo_topic }}</dd>
|
||||
<dt class="col-sm-4">交付物</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.todo_note }}</dd>
|
||||
<dt class="col-sm-4">完成时间</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.deadline }}</dd>
|
||||
{# <dd class="col-sm-8 offset-sm-4">Donec id elit non mi porta gravida at eget metus.</dd>#}
|
||||
<dt class="col-sm-4">交付物</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.todo_note }}</dd>
|
||||
<dt class="col-sm-4">任务编号</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.task_id }}</dd>
|
||||
<dt class="col-sm-4">任务来源</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.task_origin }}</dd>
|
||||
<dt class="col-sm-4">责任单位</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.duty_group }}</dd>
|
||||
<dt class="col-sm-4">承/督办人</dt>
|
||||
|
@ -50,6 +54,12 @@
|
|||
<dd class="col-sm-8">{{ todo_detail.sub_executor.all|join:', ' }}</dd>
|
||||
<dt class="col-sm-4">预计工作量</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.predict_work }}</dd>
|
||||
<dt class="col-sm-4">折算系数</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.evaluate_factor }}</dd>
|
||||
<dt class="col-sm-4">实际工作量</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.real_work }}</dd>
|
||||
<dt class="col-sm-4">成熟度</dt>
|
||||
<dd class="col-sm-8">{{ todo_detail.maturity }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<!-- /.card-body -->
|
||||
|
@ -66,14 +76,34 @@
|
|||
<!-- form start -->
|
||||
<form action="" method="post">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">成熟度</label>
|
||||
{{ form.maturity }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">实际工作量</label>
|
||||
{{ form.real_work }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail">协办人</label>
|
||||
{{ form.sub_executor }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail">折算系数</label>
|
||||
{{ form.evaluate_factor }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="exampleInputPassword1">完成情况说明</label>
|
||||
{{ form.complete_note }}
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<h3 style="text-align: center; letter-spacing: 30px">我的承办</h3>
|
||||
<HR width="50%" color=#e2e4e6 SIZE=3>
|
||||
<tr>
|
||||
<th style="width: 70px; text-align:center; vertical-align: middle;">序号</th>
|
||||
<th style="width: 100px; text-align:center; vertical-align: middle;">序号</th>
|
||||
<th style="width: 300px; text-align:center; vertical-align: middle;">工作事项</th>
|
||||
<th style="width: 100px; text-align:center; vertical-align: middle;">完成时间</th>
|
||||
<th style="width: 240px; text-align:center; vertical-align: middle;">工作要求及交付物</th>
|
||||
|
@ -97,7 +97,7 @@
|
|||
</td>
|
||||
<td style="text-align: center"><textarea style="overflow:hidden; width: 100%" readonly="readonly">{{ todo.complete_note }}</textarea></td>
|
||||
<td style="text-align: center">{{ todo.quality_mark|default:'' }}</td>
|
||||
<td><a href="{% url 'tasks:todo_detail' pk=todo.id %}">填报</a> </td>
|
||||
<td><a href="{% url 'tasks:todo_detail' pk=todo.id %}?next={{ current_path }}">填报</a> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -150,7 +150,7 @@
|
|||
</td>
|
||||
<td style="text-align: center"><textarea style="overflow:hidden; width:100%" readonly="readonly">{{ todo.complete_note }}</textarea></td>
|
||||
<td style="text-align: center">{{ todo.quality_mark|default:'' }}</td>
|
||||
<td><a href="{% url 'tasks:todo_detail' pk=todo.id %}">填报</a> </td>
|
||||
<td><a href="{% url 'tasks:todo_detail' pk=todo.id %}?next={{ current_path }}">填报</a> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
|
Loading…
Reference in New Issue