From 65bfaa9f51397c41ae4780ba8535b25f70e3911b Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Thu, 28 May 2026 18:12:40 -0700 Subject: [PATCH] Cancel signature requests when a contract ends When UpdateUser sets a contract end date that is already in the past, the user can no longer fulfill outstanding signature requests. Delete their still-pending requests as part of the same update so they stop appearing as awaiting signatures. Signed-off-by: Bryan Frimin --- e2e/console/user_test.go | 74 +++++++++++++++++++++++++++++++++ pkg/iam/organization_service.go | 10 ++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/e2e/console/user_test.go b/e2e/console/user_test.go index c4617786a..5218a640e 100644 --- a/e2e/console/user_test.go +++ b/e2e/console/user_test.go @@ -16,6 +16,7 @@ package console_test import ( "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -398,6 +399,79 @@ func TestUser_DeactivateUserCancelsSignatureRequests(t *testing.T) { assertRequestedSignatureCount(t, owner, documentVersionID, 0) } +func TestUser_EndedContractCancelsSignatureRequests(t *testing.T) { + t.Parallel() + owner := testutil.NewClient(t, testutil.RoleOwner) + signer := testutil.NewClientInOrg(t, testutil.RoleViewer, owner) + + docID, _ := createTestDocument(t, owner) + approveTestDocument(t, owner, docID) + + var versionResult struct { + Node struct { + Versions struct { + Edges []struct { + Node struct { + ID string `json:"id"` + } `json:"node"` + } `json:"edges"` + } `json:"versions"` + } `json:"node"` + } + + err := owner.Execute(` + query($id: ID!) { + node(id: $id) { + ... on Document { + versions(first: 1, orderBy: { field: CREATED_AT, direction: DESC }) { + edges { node { id } } + } + } + } + } + `, map[string]any{"id": docID}, &versionResult) + require.NoError(t, err) + require.NotEmpty(t, versionResult.Node.Versions.Edges) + + documentVersionID := versionResult.Node.Versions.Edges[0].Node.ID + signerProfileID := signer.GetProfileID().String() + + _, err = owner.Do(` + mutation($input: RequestSignatureInput!) { + requestSignature(input: $input) { + documentVersionSignatureEdge { node { id } } + } + } + `, map[string]any{ + "input": map[string]any{ + "documentVersionId": documentVersionID, + "signatoryId": signerProfileID, + }, + }) + require.NoError(t, err) + + assertRequestedSignatureCount(t, owner, documentVersionID, 1) + + contractEndDate := time.Now().Add(-24 * time.Hour).Format(time.RFC3339) + _, err = owner.DoConnect(` + mutation($input: UpdateUserInput!) { + updateUser(input: $input) { + profile { id } + } + } + `, map[string]any{ + "input": map[string]any{ + "id": signerProfileID, + "fullName": "Ended Contract Signer", + "additionalEmailAddresses": []string{}, + "contractEndDate": contractEndDate, + }, + }) + require.NoError(t, err) + + assertRequestedSignatureCount(t, owner, documentVersionID, 0) +} + func assertRequestedSignatureCount( t *testing.T, owner *testutil.Client, diff --git a/pkg/iam/organization_service.go b/pkg/iam/organization_service.go index 500043366..78eaf1a07 100644 --- a/pkg/iam/organization_service.go +++ b/pkg/iam/organization_service.go @@ -1102,12 +1102,20 @@ func (s *OrganizationService) UpdateUser(ctx context.Context, req *UpdateUserReq profile.ContractEndDate = *req.ContractEndDate } - profile.UpdatedAt = time.Now() + now := time.Now() + profile.UpdatedAt = now if err := profile.Update(ctx, conn, scope); err != nil { return fmt.Errorf("cannot update profile: %w", err) } + if profile.ContractEndDate != nil && profile.ContractEndDate.Before(now) { + signatures := &coredata.DocumentVersionSignatures{} + if err := signatures.DeleteRequestedBySignatory(ctx, conn, scope, profile.ID); err != nil { + return fmt.Errorf("cannot delete requested signatures: %w", err) + } + } + membership := &coredata.Membership{} var webhookPayload *webhooktypes.User