feat: project group can be belonged to many departments
This commit is contained in:
@@ -312,10 +312,11 @@ class EmailNotificationMiddleware(MiddlewareMixin):
|
||||
notification_data = {
|
||||
'id': project_group_data.get('id', ''),
|
||||
'name': project_group_data.get('name', ''),
|
||||
'department': project_group_data.get('department', ''),
|
||||
'department_name': project_group_data.get('department_name', ''),
|
||||
'departments': project_group_data.get('departments', ''),
|
||||
'department_names': project_group_data.get('department_names', ''),
|
||||
'departments_info': project_group_data.get('departments_info', ''),
|
||||
'description': project_group_data.get('description', ''),
|
||||
'timestamp': project_group_data.get('updated_at', ''),
|
||||
'timestamp': project_group_data.get('created_at', ''),
|
||||
'operation_path': request.path,
|
||||
'operation_method': request.method
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ LABEL_MAP = {
|
||||
'grade_major': '年级专业',
|
||||
'project_group': '项目组',
|
||||
'project_group_name': '项目组',
|
||||
'department_names': '所属部门',
|
||||
'position': '职位',
|
||||
'start_date': '开始日期',
|
||||
'end_date': '结束日期',
|
||||
@@ -91,7 +92,15 @@ def _format_value(value):
|
||||
return str(value)
|
||||
if isinstance(value, bool):
|
||||
return '是' if value else '否'
|
||||
if isinstance(value, (list, dict)):
|
||||
if isinstance(value, list):
|
||||
# 对于列表,优先显示为逗号分隔的字符串
|
||||
if all(isinstance(item, str) for item in value):
|
||||
return ', '.join(value)
|
||||
try:
|
||||
return json.dumps(value, ensure_ascii=False)
|
||||
except Exception:
|
||||
return str(value)
|
||||
if isinstance(value, dict):
|
||||
try:
|
||||
return json.dumps(value, ensure_ascii=False)
|
||||
except Exception:
|
||||
|
||||
@@ -6,12 +6,18 @@ from .models import Personnel, ProjectGroup
|
||||
@admin.register(ProjectGroup)
|
||||
class ProjectGroupAdmin(admin.ModelAdmin):
|
||||
"""项目组管理"""
|
||||
list_display = ['name', 'department', 'description', 'created_at']
|
||||
list_filter = ['department', 'created_at']
|
||||
list_display = ['name', 'get_departments', 'description', 'created_at']
|
||||
list_filter = ['departments', 'created_at']
|
||||
search_fields = ['name', 'description']
|
||||
ordering = ['department', 'name']
|
||||
ordering = ['name']
|
||||
filter_horizontal = ['departments']
|
||||
|
||||
def get_departments(self, obj):
|
||||
"""获取部门列表显示"""
|
||||
return ", ".join([dept.name for dept in obj.departments.all()])
|
||||
get_departments.short_description = '所属部门'
|
||||
|
||||
|
||||
import django_filters
|
||||
@admin.register(Personnel)
|
||||
class PersonnelAdmin(admin.ModelAdmin):
|
||||
"""人员信息管理"""
|
||||
@@ -51,68 +57,3 @@ class PersonnelAdmin(admin.ModelAdmin):
|
||||
def get_queryset(self, request):
|
||||
"""优化查询"""
|
||||
return super().get_queryset(request).select_related('department', 'project_group')
|
||||
from .models import Personnel, ProjectGroup
|
||||
from finance.models import Department
|
||||
|
||||
|
||||
class PersonnelFilter(django_filters.FilterSet):
|
||||
"""人员信息过滤器"""
|
||||
|
||||
# 基本筛选
|
||||
department = django_filters.ModelChoiceFilter(
|
||||
queryset=Department.objects.all(),
|
||||
field_name='department',
|
||||
label='部门'
|
||||
)
|
||||
|
||||
project_group = django_filters.ModelChoiceFilter(
|
||||
queryset=ProjectGroup.objects.all(),
|
||||
field_name='project_group',
|
||||
label='项目组'
|
||||
)
|
||||
|
||||
position = django_filters.CharFilter(
|
||||
field_name='position',
|
||||
lookup_expr='icontains',
|
||||
label='职位'
|
||||
)
|
||||
|
||||
gender = django_filters.ChoiceFilter(
|
||||
choices=Personnel.GENDER_CHOICES,
|
||||
field_name='gender',
|
||||
label='性别'
|
||||
)
|
||||
|
||||
is_active = django_filters.BooleanFilter(
|
||||
field_name='is_active',
|
||||
label='在职状态'
|
||||
)
|
||||
|
||||
# 日期范围筛选
|
||||
start_date_from = django_filters.DateFilter(
|
||||
field_name='start_date',
|
||||
lookup_expr='gte',
|
||||
label='任职开始时间(从)'
|
||||
)
|
||||
|
||||
start_date_to = django_filters.DateFilter(
|
||||
field_name='start_date',
|
||||
lookup_expr='lte',
|
||||
label='任职开始时间(到)'
|
||||
)
|
||||
|
||||
end_date_from = django_filters.DateFilter(
|
||||
field_name='end_date',
|
||||
lookup_expr='gte',
|
||||
label='任职结束时间(从)'
|
||||
)
|
||||
|
||||
end_date_to = django_filters.DateFilter(
|
||||
field_name='end_date',
|
||||
lookup_expr='lte',
|
||||
label='任职结束时间(到)'
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Personnel
|
||||
fields = ['department', 'project_group', 'position', 'gender', 'is_active']
|
||||
|
||||
@@ -6,17 +6,23 @@ from finance.models import Department
|
||||
class ProjectGroup(models.Model):
|
||||
"""项目组模型"""
|
||||
name = models.CharField(max_length=100, verbose_name="项目组名称")
|
||||
department = models.ForeignKey(Department, on_delete=models.CASCADE, verbose_name="所属部门")
|
||||
departments = models.ManyToManyField(Department, verbose_name="所属部门", related_name="project_groups")
|
||||
description = models.TextField(blank=True, null=True, verbose_name="项目组描述")
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "项目组"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ['department', 'name']
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.department.name} - {self.name}"
|
||||
department_names = ", ".join([dept.name for dept in self.departments.all()])
|
||||
return f"{self.name} ({department_names})" if department_names else self.name
|
||||
|
||||
@property
|
||||
def department_names(self):
|
||||
"""返回所属部门名称列表"""
|
||||
return [dept.name for dept in self.departments.all()]
|
||||
|
||||
|
||||
class Personnel(models.Model):
|
||||
|
||||
@@ -6,13 +6,18 @@ from .models import Personnel, ProjectGroup
|
||||
|
||||
class ProjectGroupSerializer(serializers.ModelSerializer):
|
||||
"""项目组序列化器"""
|
||||
department_name = serializers.CharField(source='department.name', read_only=True)
|
||||
department_names = serializers.ListField(read_only=True)
|
||||
departments_info = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = ProjectGroup
|
||||
fields = ['id', 'name', 'department', 'department_name', 'description', 'created_at']
|
||||
fields = ['id', 'name', 'departments', 'department_names', 'departments_info', 'description', 'created_at']
|
||||
read_only_fields = ['created_at']
|
||||
|
||||
def get_departments_info(self, obj):
|
||||
"""获取部门详细信息"""
|
||||
return [{'id': dept.id, 'name': dept.name} for dept in obj.departments.all()]
|
||||
|
||||
|
||||
class PersonnelReadSerializer(serializers.ModelSerializer):
|
||||
"""人员信息读取序列化器"""
|
||||
@@ -45,11 +50,13 @@ class PersonnelWriteSerializer(serializers.ModelSerializer):
|
||||
]
|
||||
|
||||
def validate_project_group(self, value):
|
||||
"""验证项目组是否属于选定的部门"""
|
||||
"""验证项目组是否包含选定的部门"""
|
||||
if value and hasattr(self, 'initial_data'):
|
||||
department_id = self.initial_data.get('department')
|
||||
if department_id and value.department_id != int(department_id):
|
||||
raise serializers.ValidationError("项目组必须属于选定的部门")
|
||||
if department_id:
|
||||
# 检查项目组的部门列表中是否包含选定的部门
|
||||
if not value.departments.filter(id=int(department_id)).exists():
|
||||
raise serializers.ValidationError("项目组必须包含选定的部门")
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
|
||||
@@ -197,16 +197,16 @@ class ProjectGroupViewSet(viewsets.ModelViewSet):
|
||||
queryset = ProjectGroup.objects.all()
|
||||
serializer_class = ProjectGroupSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['department']
|
||||
filterset_fields = ['departments']
|
||||
search_fields = ['name', 'description']
|
||||
ordering = ['department', 'name']
|
||||
ordering = ['name']
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def by_department(self, request):
|
||||
"""按部门获取项目组"""
|
||||
department_id = request.query_params.get('department_id')
|
||||
if department_id:
|
||||
project_groups = ProjectGroup.objects.filter(department_id=department_id)
|
||||
project_groups = ProjectGroup.objects.filter(departments__id=department_id).distinct()
|
||||
serializer = self.get_serializer(project_groups, many=True)
|
||||
return Response(serializer.data)
|
||||
return Response([])
|
||||
@@ -221,7 +221,7 @@ class ProjectGroupViewSet(viewsets.ModelViewSet):
|
||||
project_group_data = {
|
||||
'id': project_group.id,
|
||||
'name': "[已删除]" + project_group.name,
|
||||
'department_name': project_group.department.name if project_group.department else '',
|
||||
'department_names': project_group.department_names,
|
||||
'description': project_group.description,
|
||||
'created_at': str(project_group.created_at),
|
||||
'operation_path': request.path,
|
||||
@@ -267,13 +267,3 @@ class ProjectGroupViewSet(viewsets.ModelViewSet):
|
||||
else:
|
||||
ip = request.META.get('REMOTE_ADDR')
|
||||
return ip
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def by_department(self, request):
|
||||
"""按部门获取项目组"""
|
||||
department_id = request.query_params.get('department_id')
|
||||
if department_id:
|
||||
project_groups = ProjectGroup.objects.filter(department_id=department_id)
|
||||
serializer = self.get_serializer(project_groups, many=True)
|
||||
return Response(serializer.data)
|
||||
return Response([])
|
||||
|
||||
@@ -54,7 +54,13 @@
|
||||
|
||||
<el-table :data="filteredProjectGroups" style="width: 100%" v-loading="projectGroupLoading">
|
||||
<el-table-column prop="name" label="项目组名称" />
|
||||
<el-table-column prop="department_name" label="所属部门" />
|
||||
<el-table-column label="所属部门" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-tag v-for="dept in scope.row.departments_info" :key="dept.id" size="small" style="margin-right: 5px;">
|
||||
{{ dept.name }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="描述" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
@@ -86,8 +92,8 @@
|
||||
<el-form-item label="项目组名称" prop="name">
|
||||
<el-input v-model="projectGroupForm.name" placeholder="请输入项目组名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属部门" prop="department">
|
||||
<el-select v-model="projectGroupForm.department" placeholder="请选择所属部门">
|
||||
<el-form-item label="所属部门" prop="departments">
|
||||
<el-select v-model="projectGroupForm.departments" placeholder="请选择所属部门" multiple>
|
||||
<el-option
|
||||
v-for="dept in departments"
|
||||
:key="dept.id"
|
||||
@@ -152,14 +158,14 @@ export default {
|
||||
projectGroupForm: {
|
||||
id: null,
|
||||
name: '',
|
||||
department: '',
|
||||
departments: [],
|
||||
description: ''
|
||||
},
|
||||
projectGroupRules: {
|
||||
name: [
|
||||
{ required: true, message: '请输入项目组名称', trigger: 'blur' }
|
||||
],
|
||||
department: [
|
||||
departments: [
|
||||
{ required: true, message: '请选择所属部门', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
@@ -170,7 +176,9 @@ export default {
|
||||
if (!this.departmentFilter) {
|
||||
return this.projectGroups
|
||||
}
|
||||
return this.projectGroups.filter(group => group.department === parseInt(this.departmentFilter))
|
||||
return this.projectGroups.filter(group =>
|
||||
group.departments && group.departments.includes(parseInt(this.departmentFilter))
|
||||
)
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
@@ -270,13 +278,16 @@ export default {
|
||||
|
||||
// 项目组管理方法
|
||||
showAddProjectGroupDialog() {
|
||||
this.projectGroupForm = { id: null, name: '', department: '', description: '' }
|
||||
this.projectGroupForm = { id: null, name: '', departments: [], description: '' }
|
||||
this.projectGroupDialogTitle = '添加项目组'
|
||||
this.projectGroupDialogVisible = true
|
||||
},
|
||||
|
||||
editProjectGroup(projectGroup) {
|
||||
this.projectGroupForm = { ...projectGroup }
|
||||
this.projectGroupForm = {
|
||||
...projectGroup,
|
||||
departments: projectGroup.departments || []
|
||||
}
|
||||
this.projectGroupDialogTitle = '编辑项目组'
|
||||
this.projectGroupDialogVisible = true
|
||||
},
|
||||
|
||||
@@ -209,7 +209,9 @@ export default {
|
||||
return []
|
||||
}
|
||||
const departmentId = parseInt(this.form.department)
|
||||
return this.projectGroups.filter(group => group.department === departmentId)
|
||||
return this.projectGroups.filter(group =>
|
||||
group.departments && group.departments.includes(departmentId)
|
||||
)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
Reference in New Issue
Block a user