diff --git a/src/dispatch/case/models.py b/src/dispatch/case/models.py index 6014bb9451e1..44e0d92aaf09 100644 --- a/src/dispatch/case/models.py +++ b/src/dispatch/case/models.py @@ -176,6 +176,10 @@ class Case(Base, TimeStampMixin, ProjectMixin): ticket = relationship("Ticket", uselist=False, backref="case", cascade="all, delete-orphan") + # Foreign key to individual who resolved + resolved_by_id = Column(Integer, ForeignKey("individual_contact.id")) + resolved_by = relationship("IndividualContact", foreign_keys=[resolved_by_id]) + # resources case_costs = relationship( "CaseCost", @@ -325,6 +329,7 @@ class CaseBase(DispatchBase): description: str | None = None resolution: str | None = None resolution_reason: CaseResolutionReason | None = None + resolved_by: IndividualContactRead | None = None status: CaseStatus | None = None visibility: Visibility | None = None diff --git a/src/dispatch/case/service.py b/src/dispatch/case/service.py index f7006a10e081..9e62195d97d6 100644 --- a/src/dispatch/case/service.py +++ b/src/dispatch/case/service.py @@ -388,6 +388,14 @@ def update(*, db_session, case: Case, case_in: CaseUpdate, current_user: Dispatc case_id=case.id, ) + if case.status == CaseStatus.closed: + individual = individual_service.get_or_create( + db_session=db_session, + email=current_user.email, + project=case.project, + ) + case.resolved_by = individual + if case.visibility != case_in.visibility: case.visibility = case_in.visibility diff --git a/src/dispatch/database/revisions/tenant/versions/2025-08-01_4649b11b683f.py b/src/dispatch/database/revisions/tenant/versions/2025-08-01_4649b11b683f.py new file mode 100644 index 000000000000..a44a9fbd1d41 --- /dev/null +++ b/src/dispatch/database/revisions/tenant/versions/2025-08-01_4649b11b683f.py @@ -0,0 +1,33 @@ +"""Add resolved_by to case table + +Revision ID: 4649b11b683f +Revises: 408118048599 +Create Date: 2025-08-01 14:11:04.276577 + +""" + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "4649b11b683f" +down_revision = "408118048599" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("case", sa.Column("resolved_by_id", sa.Integer(), nullable=True)) + op.create_foreign_key( + "fk_case_resolved_by_id", "case", "individual_contact", ["resolved_by_id"], ["id"] + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint("fk_case_resolved_by_id", "case", type_="foreignkey") + op.drop_column("case", "resolved_by_id") + # ### end Alembic commands ### diff --git a/src/dispatch/plugins/dispatch_slack/case/messages.py b/src/dispatch/plugins/dispatch_slack/case/messages.py index cfda32077d73..84f422c8874a 100644 --- a/src/dispatch/plugins/dispatch_slack/case/messages.py +++ b/src/dispatch/plugins/dispatch_slack/case/messages.py @@ -147,18 +147,22 @@ def create_case_message(case: Case, channel_id: str) -> list[Block]: Section( text=f"*Resolution description* \n {case.resolution}"[:MAX_SECTION_TEXT_LENGTH] ), - Actions( - elements=[ - Button( - text="Re-open", - action_id=CaseNotificationActions.reopen, - style="primary", - value=button_metadata, - ) - ] - ), ] ) + if case.resolved_by: + blocks.append(Section(text=f"*Resolved by* \n {case.resolved_by.individual.email}")) + blocks.append( + Actions( + elements=[ + Button( + text="Re-open", + action_id=CaseNotificationActions.reopen, + style="primary", + value=button_metadata, + ) + ] + ), + ) else: action_buttons = [ Button( diff --git a/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue b/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue index 3ce037f86e5b..d0928aefa3a3 100644 --- a/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue +++ b/src/dispatch/static/dispatch/src/case/CaseAttributesDrawer.vue @@ -104,6 +104,27 @@ const handleResolutionUpdate = (newResolution) => { + + +
Resolved By
+
+ +
+ mdi-account-check + + {{ modelValue.resolved_by?.name || "Not specified" }} + +
+
+
+
Priority
diff --git a/src/dispatch/static/dispatch/src/case/DetailsTab.vue b/src/dispatch/static/dispatch/src/case/DetailsTab.vue index f0686233d221..c93b18abbe66 100644 --- a/src/dispatch/static/dispatch/src/case/DetailsTab.vue +++ b/src/dispatch/static/dispatch/src/case/DetailsTab.vue @@ -24,42 +24,63 @@ />
- -