Skip to content

Commit 638940a

Browse files
committed
fix(backend): 我的待办区分处理和协助页面 #8750
1 parent daf5063 commit 638940a

File tree

8 files changed

+110
-37
lines changed

8 files changed

+110
-37
lines changed

dbm-ui/backend/configuration/models/dba.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,11 @@ def get_biz_db_type_admins(cls, bk_biz_id: int, db_type: str) -> List[str]:
6060
if db_type == admin["db_type"]:
6161
return admin["users"] or DEFAULT_DB_ADMINISTRATORS
6262
return DEFAULT_DB_ADMINISTRATORS
63+
64+
@classmethod
65+
def get_dba_for_db_type(cls, bk_biz_id: int, db_type: str) -> List[str]:
66+
"""获取主dba、备dba、二线dba人员"""
67+
dba_list = cls.list_biz_admins(bk_biz_id)
68+
dba_content = next((dba for dba in dba_list if dba["db_type"] == db_type), {"users": []})
69+
users = dba_content.get("users", [])
70+
return users[:1], users[1:2], users[2:]

dbm-ui/backend/ticket/filters.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class TicketListFilter(filters.FilterSet):
2424
cluster = filters.CharFilter(field_name="cluster", method="filter_cluster", label=_("集群域名"))
2525
todo = filters.CharFilter(field_name="todo", method="filter_todo", label=_("代办状态"))
2626
ordering = filters.CharFilter(field_name="ordering", method="order_ticket", label=_("排序字段"))
27+
is_assist = filters.BooleanFilter(field_name="is_assist", method="filter_is_assist", label=_("是否协助"))
2728

2829
class Meta:
2930
model = Ticket
@@ -47,11 +48,21 @@ def filter_ids(self, queryset, name, value):
4748
def filter_todo(self, queryset, name, value):
4849
user = self.request.user.username
4950
if value == "running":
50-
todo_filter = Q(todo_of_ticket__operators__contains=user, todo_of_ticket__status__in=TODO_RUNNING_STATUS)
51+
todo_filter = Q(
52+
Q(todo_of_ticket__operators__contains=user) | Q(todo_of_ticket__helpers__contains=user),
53+
todo_of_ticket__status__in=TODO_RUNNING_STATUS,
54+
)
5155
else:
5256
todo_filter = Q(todo_of_ticket__done_by=user)
5357
return queryset.filter(todo_filter).distinct()
5458

59+
def filter_is_assist(self, queryset, name, value):
60+
user = self.request.user.username
61+
# 根据 value 的值选择不同的字段
62+
field = "helpers" if value else "operators"
63+
todo_filter = Q(**{f"todo_of_ticket__{field}__contains": user}, todo_of_ticket__status__in=TODO_RUNNING_STATUS)
64+
return queryset.filter(todo_filter).distinct()
65+
5566
def filter_status(self, queryset, name, value):
5667
status = value.split(",")
5768
status_filter = Q()

dbm-ui/backend/ticket/handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
from backend.ticket.exceptions import TicketFlowsConfigException
4242
from backend.ticket.flow_manager.manager import TicketFlowManager
4343
from backend.ticket.models import Flow, Ticket, TicketFlowsConfig, Todo
44-
from backend.ticket.serializers import TodoSerializer
4544
from backend.ticket.todos import BaseTodoContext, TodoActorFactory
4645
from backend.ticket.todos.itsm_todo import ItsmTodoContext
4746

@@ -293,6 +292,7 @@ def batch_process_todo(cls, user, action, operations):
293292
@param action 动作
294293
@param operations: todo列表,每个item包含todo id和params
295294
"""
295+
from backend.ticket.serializers import TodoSerializer
296296

297297
results = []
298298
for operation in operations:
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 3.2.25 on 2025-01-03 02:28
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
("ticket", "0012_alter_ticket_remark"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="todo",
15+
name="helpers",
16+
field=models.JSONField(default=list, verbose_name="协助人"),
17+
),
18+
]

dbm-ui/backend/ticket/models/todo.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,44 @@ def exist_unfinished(self):
3131
def get_operators(self, todo_type, ticket, operators):
3232
# 获得提单人,dba,业务协助人. TODO: 后续还会细分主、备、二线DBA,以及明确区分协助人角色
3333
creator = [ticket.creator]
34-
dba = DBAdministrator.get_biz_db_type_admins(ticket.bk_biz_id, ticket.group)
34+
# dba = DBAdministrator.get_biz_db_type_admins(ticket.bk_biz_id, ticket.group)
35+
dba, second_dba, other_dba = DBAdministrator.get_dba_for_db_type(ticket.bk_biz_id, ticket.group)
3536
biz_helpers = BizSettings.get_assistance(ticket.bk_biz_id)
3637

3738
# 构造单据状态与处理人之间的对应关系
3839
# - 审批中:提单人可撤销,dba可处理,
3940
# 考虑某些单据审批人是特定配置(数据导出 -- 运维审批),所以从ItsmBuilder获得审批人
40-
# - 待执行:提单人 + 单据协助人
41-
# - 待继续:dba + 提单人 + 单据协助人
42-
# - 待补货:dba + 提单人 + 单据协助人
43-
# - 已失败:dba + 提单人 + 单据协助人
41+
# - 待执行:operators[提单人] + helpers[单据协助人]
42+
# - 待继续:operators[提单人 + dba] + helpers[单据协助人 + second_dba + other_dba]
43+
# - 待补货:operators[提单人 + dba] + helpers[单据协助人 + second_dba + other_dba]
44+
# - 已失败:operators[提单人 + dba] + helpers[单据协助人 + second_dba + other_dba]
4445
itsm_builder = BuilderFactory.get_builder_cls(ticket.ticket_type).itsm_flow_builder(ticket)
46+
itsm_operators = itsm_builder.get_approvers().split(",")
4547
todo_operators_map = {
46-
TodoType.ITSM: itsm_builder.get_approvers().split(","),
47-
TodoType.APPROVE: creator + biz_helpers,
48-
TodoType.INNER_APPROVE: dba + creator + biz_helpers,
49-
TodoType.RESOURCE_REPLENISH: dba + creator + biz_helpers,
50-
TodoType.INNER_FAILED: dba + creator + biz_helpers,
48+
TodoType.ITSM: itsm_operators[:1],
49+
TodoType.APPROVE: creator,
50+
TodoType.INNER_APPROVE: creator + dba,
51+
TodoType.RESOURCE_REPLENISH: creator + dba,
52+
TodoType.INNER_FAILED: creator + dba,
53+
}
54+
todo_helpers_map = {
55+
TodoType.ITSM: itsm_operators[1:],
56+
TodoType.APPROVE: biz_helpers,
57+
TodoType.INNER_APPROVE: biz_helpers + second_dba + other_dba,
58+
TodoType.RESOURCE_REPLENISH: biz_helpers + second_dba + other_dba,
59+
TodoType.INNER_FAILED: biz_helpers + second_dba + other_dba,
5160
}
5261
# 按照顺序去重
5362
operators = list(dict.fromkeys(operators + todo_operators_map.get(todo_type, [])))
54-
return operators
63+
helpers = [item for item in todo_helpers_map.get(todo_type, []) if item not in operators]
64+
return creator, biz_helpers, helpers, operators
5565

5666
def create(self, **kwargs):
57-
operators = self.get_operators(kwargs["type"], kwargs["ticket"], kwargs.get("operators", []))
67+
creator, biz_helpers, helpers, operators = self.get_operators(
68+
kwargs["type"], kwargs["ticket"], kwargs.get("operators", [])
69+
)
5870
kwargs["operators"] = operators
71+
kwargs["helpers"] = helpers
5972
todo = super().create(**kwargs)
6073
return todo
6174

@@ -69,6 +82,7 @@ class Todo(AuditedModel):
6982
flow = models.ForeignKey("Flow", help_text=_("关联流程任务"), related_name="todo_of_flow", on_delete=models.CASCADE)
7083
ticket = models.ForeignKey("Ticket", help_text=_("关联工单"), related_name="todo_of_ticket", on_delete=models.CASCADE)
7184
operators = models.JSONField(_("待办人"), default=list)
85+
helpers = models.JSONField(_("协助人"), default=list)
7286
type = models.CharField(
7387
_("待办类型"),
7488
choices=TodoType.get_choices(),

dbm-ui/backend/ticket/serializers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class TicketSerializer(AuditedSerializer, serializers.ModelSerializer):
9797
details = TicketDetailsSerializer(help_text=_("单据详情"))
9898
# 额外补充展示字段
9999
todo_operators = serializers.SerializerMethodField(help_text=_("处理人列表"))
100+
todo_helpers = serializers.SerializerMethodField(help_text=_("协助人列表"))
100101
status = serializers.SerializerMethodField(help_text=_("状态"), read_only=True)
101102
status_display = serializers.SerializerMethodField(help_text=_("状态名称"))
102103
ticket_type_display = serializers.SerializerMethodField(help_text=_("单据类型名称"))
@@ -126,8 +127,12 @@ def get_todo_operators(self, obj):
126127
obj.running_todos = [todo for todo in obj.todo_of_ticket.all() if todo.status == TodoStatus.TODO]
127128
return obj.running_todos[0].operators if obj.running_todos else []
128129

130+
def get_todo_helpers(self, obj):
131+
obj.running_todo_helpers = [todo for todo in obj.todo_of_ticket.all() if todo.status == TodoStatus.TODO]
132+
return obj.running_todo_helpers[0].helpers if obj.running_todo_helpers else []
133+
129134
def get_status(self, obj):
130-
if obj.status == TicketStatus.RUNNING and obj.running_todos:
135+
if obj.status == TicketStatus.RUNNING and (obj.running_todos or obj.running_todo_helpers):
131136
obj.status = TicketStatus.INNER_TODO
132137
return obj.status
133138

@@ -231,7 +236,6 @@ class TodoSerializer(serializers.ModelSerializer):
231236
单据序列化
232237
"""
233238

234-
operators = serializers.JSONField(help_text=_("待办人列表"))
235239
cost_time = serializers.SerializerMethodField(help_text=_("耗时"))
236240

237241
def get_cost_time(self, obj):

dbm-ui/backend/ticket/todos/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def process(self, username, action, params):
6363
return
6464
# 允许超级用户和操作人确认
6565
is_superuser = User.objects.get(username=username).is_superuser and self.allow_superuser_process
66-
if not is_superuser and username not in self.todo.operators:
66+
if not is_superuser and username not in self.todo.operators + self.todo.helpers:
6767
raise TodoWrongOperatorException(_("{}不在处理人: {}中,无法处理").format(username, self.todo.operators))
6868

6969
# 执行确认操作

dbm-ui/backend/ticket/views.py

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,12 @@ def _get_self_manage_tickets(cls, user):
154154
Q(group=manage.db_type) & Q(bk_biz_id=manage.bk_biz_id) if manage.bk_biz_id else Q(group=manage.db_type)
155155
for manage in DBAdministrator.objects.filter(users__contains=user.username)
156156
]
157-
ticket_filter = Q(creator=user.username) | reduce(operator.or_, manage_filters or [Q()])
158-
return Ticket.objects.filter(ticket_filter)
157+
# 除了user管理的单据合集,处理人及协助人也能管理自己的单据
158+
todo_filters = Q(
159+
Q(todo_of_ticket__operators__contains=user.username) | Q(todo_of_ticket__helpers__contains=user.username)
160+
)
161+
ticket_filter = Q(creator=user.username) | todo_filters | reduce(operator.or_, manage_filters or [Q()])
162+
return Ticket.objects.filter(ticket_filter).prefetch_related("todo_of_ticket")
159163

160164
def filter_queryset(self, queryset):
161165
"""filter_class可能导致预取的todo失效,这里重新取一次"""
@@ -446,29 +450,43 @@ def get_tickets_count(self, request, *args, **kwargs):
446450
"""
447451
user = request.user.username
448452
tickets = self._get_self_manage_tickets(request.user)
449-
count_map = {count_type: 0 for count_type in CountType.get_values()}
453+
exclude_values = {"MY_APPROVE", "SELF_MANAGE", "DONE"}
454+
455+
# 初始化 count_map
456+
def initialize_count_map():
457+
return {count_type: 0 for count_type in CountType.get_values() if count_type not in exclude_values}
450458

459+
results = {}
460+
461+
# 通用的函数来计算待办和协助状态
462+
def calculate_status_count(field_name, relation_name):
463+
status_counts = (
464+
tickets.filter(
465+
status__in=TICKET_TODO_STATUS_SET,
466+
**{f"{relation_name}__{field_name}__contains": user},
467+
**{f"{relation_name}__status__in": TODO_RUNNING_STATUS},
468+
)
469+
.distinct()
470+
.values_list("status", flat=True)
471+
)
472+
count_map = initialize_count_map()
473+
for sts, count in Counter(status_counts).items():
474+
sts = CountType.INNER_TODO.value if sts == "RUNNING" else sts
475+
count_map[sts] = count
476+
return count_map
477+
478+
# 计算我的代办
479+
results["Pending"] = calculate_status_count("operators", "todo_of_ticket")
480+
# 计算我的协助
481+
results["to_help"] = calculate_status_count("helpers", "todo_of_ticket")
451482
# 我负责的业务
452-
count_map[CountType.SELF_MANAGE] = tickets.count()
483+
results[CountType.SELF_MANAGE] = tickets.count()
453484
# 我的申请
454-
count_map[CountType.MY_APPROVE] = tickets.filter(creator=user).count()
455-
# 我的代办
456-
todo_status = (
457-
tickets.filter(
458-
status__in=TICKET_TODO_STATUS_SET,
459-
todo_of_ticket__operators__contains=user,
460-
todo_of_ticket__status__in=TODO_RUNNING_STATUS,
461-
)
462-
.distinct()
463-
.values_list("status", flat=True)
464-
)
465-
for sts, count in Counter(todo_status).items():
466-
sts = CountType.INNER_TODO.value if sts == "RUNNING" else sts
467-
count_map[sts] = count
485+
results[CountType.MY_APPROVE] = tickets.filter(creator=user).count()
468486
# 我的已办
469-
count_map[CountType.DONE] = tickets.filter(todo_of_ticket__done_by=user).count()
487+
results[CountType.DONE] = tickets.filter(todo_of_ticket__done_by=user).count()
470488

471-
return Response(count_map)
489+
return Response(results)
472490

473491
@common_swagger_auto_schema(
474492
operation_summary=_("查询集群变更单据事件"),

0 commit comments

Comments
 (0)