Fix signature count mismatch with signatures tab on documents

The badge on a document version showed signatures filtered by
activeContract: true, while the signatures tab fetched signatures with
no filter and listed people filtered by contractEnded: false and
state: ACTIVE. The two views disagreed both when a signer's contract had
ended and when a signer was deactivated while still under contract.

Add a state: ProfileState field to DocumentVersionSignatureFilter
alongside the existing activeContract filter, so the signature query
can mirror the same predicates as the people query. Pass
{ activeContract: true, state: ACTIVE } from the badge, the document
list item, and the signatures tab fragment. The same filter is now
evaluated on both the count and the list.

Threaded through the console and MCP resolvers, the MCP spec, and the
n8n getAllSignatures operation.

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-05-22 10:55:31 +02:00
parent 63e8c85f66
commit 83445c6e34
9 changed files with 56 additions and 12 deletions

View File

@@ -40,10 +40,10 @@ export const documentLayoutQuery = graphql`
...DocumentTitleFormFragment ...DocumentTitleFormFragment
...DocumentActionsDropdown_versionFragment ...DocumentActionsDropdown_versionFragment
...DocumentDetailsCard_versionFragment ...DocumentDetailsCard_versionFragment
signatures(first: 0 filter: { activeContract: true }) { signatures(first: 0 filter: { activeContract: true, state: ACTIVE }) {
totalCount totalCount
} }
signedSignatures: signatures(first: 0 filter: { states: [SIGNED], activeContract: true }) { signedSignatures: signatures(first: 0 filter: { states: [SIGNED], activeContract: true, state: ACTIVE }) {
totalCount totalCount
} }
approvalQuorums(first: 1, orderBy: { field: CREATED_AT, direction: DESC }) { approvalQuorums(first: 1, orderBy: { field: CREATED_AT, direction: DESC }) {
@@ -85,10 +85,10 @@ export const documentLayoutQuery = graphql`
...DocumentTitleFormFragment ...DocumentTitleFormFragment
...DocumentActionsDropdown_versionFragment ...DocumentActionsDropdown_versionFragment
...DocumentDetailsCard_versionFragment ...DocumentDetailsCard_versionFragment
signatures(first: 0 filter: { activeContract: true }) { signatures(first: 0 filter: { activeContract: true, state: ACTIVE }) {
totalCount totalCount
} }
signedSignatures: signatures(first: 0 filter: { states: [SIGNED], activeContract: true }) { signedSignatures: signatures(first: 0 filter: { states: [SIGNED], activeContract: true, state: ACTIVE }) {
totalCount totalCount
} }
approvalQuorums(first: 1, orderBy: { field: CREATED_AT, direction: DESC }) { approvalQuorums(first: 1, orderBy: { field: CREATED_AT, direction: DESC }) {

View File

@@ -54,10 +54,10 @@ const fragment = graphql`
} }
} }
} }
signatures(first: 0 filter: { activeContract: true }) { signatures(first: 0 filter: { activeContract: true, state: ACTIVE }) {
totalCount totalCount
} }
signedSignatures: signatures(first: 0 filter: { states: [SIGNED], activeContract: true }) { signedSignatures: signatures(first: 0 filter: { states: [SIGNED], activeContract: true, state: ACTIVE }) {
totalCount totalCount
} }
} }

View File

@@ -29,7 +29,7 @@ const versionFragment = graphql`
@argumentDefinitions( @argumentDefinitions(
count: { type: "Int", defaultValue: 1000 } count: { type: "Int", defaultValue: 1000 }
cursor: { type: "CursorKey" } cursor: { type: "CursorKey" }
signatureFilter: { type: "DocumentVersionSignatureFilter" } signatureFilter: { type: "DocumentVersionSignatureFilter", defaultValue: { activeContract: true, state: ACTIVE } }
) { ) {
...DocumentSignaturePlaceholder_versionFragment ...DocumentSignaturePlaceholder_versionFragment
signatures(first: $count, after: $cursor, filter: $signatureFilter) signatures(first: $count, after: $cursor, filter: $signatureFilter)
@@ -102,8 +102,11 @@ export function DocumentSignatureList(props: {
return; return;
} }
const filter const filter = {
= selectedStates.length > 0 ? { states: selectedStates } : null; activeContract: true,
state: "ACTIVE" as const,
...(selectedStates.length > 0 ? { states: selectedStates } : {}),
};
refetch({ signatureFilter: filter }); refetch({ signatureFilter: filter });
}, [selectedStates, refetch]); }, [selectedStates, refetch]);

View File

@@ -91,6 +91,18 @@ export const description: INodeProperties[] = [
default: false, default: false,
description: 'Whether to filter by active contract status', description: 'Whether to filter by active contract status',
}, },
{
displayName: 'Profile State',
name: 'state',
type: 'options',
default: '',
description: 'Filter by signatory profile state',
options: [
{ name: 'Any', value: '' },
{ name: 'Active', value: 'ACTIVE' },
{ name: 'Inactive', value: 'INACTIVE' },
],
},
], ],
}, },
]; ];
@@ -107,6 +119,7 @@ export async function execute(
const filter: IDataObject = {}; const filter: IDataObject = {};
if ((filters.states as string[])?.length) filter.states = filters.states; if ((filters.states as string[])?.length) filter.states = filters.states;
if (filters.activeContract !== undefined) filter.activeContract = filters.activeContract; if (filters.activeContract !== undefined) filter.activeContract = filters.activeContract;
if (filters.state) filter.state = filters.state;
const hasFilter = Object.keys(filter).length > 0; const hasFilter = Object.keys(filter).length > 0;

View File

@@ -22,13 +22,15 @@ type (
DocumentVersionSignatureFilter struct { DocumentVersionSignatureFilter struct {
states DocumentVersionSignatureStates states DocumentVersionSignatureStates
activeContract *bool activeContract *bool
state *ProfileState
} }
) )
func NewDocumentVersionSignatureFilter(states []DocumentVersionSignatureState, activeContract *bool) *DocumentVersionSignatureFilter { func NewDocumentVersionSignatureFilter(states []DocumentVersionSignatureState, activeContract *bool, state *ProfileState) *DocumentVersionSignatureFilter {
return &DocumentVersionSignatureFilter{ return &DocumentVersionSignatureFilter{
states: DocumentVersionSignatureStates(states), states: DocumentVersionSignatureStates(states),
activeContract: activeContract, activeContract: activeContract,
state: state,
} }
} }
@@ -36,6 +38,7 @@ func (f *DocumentVersionSignatureFilter) SQLArguments() pgx.StrictNamedArgs {
return pgx.StrictNamedArgs{ return pgx.StrictNamedArgs{
"states": f.states, "states": f.states,
"active_contract": f.activeContract, "active_contract": f.activeContract,
"profile_state": f.state,
} }
} }
@@ -70,5 +73,16 @@ func (f *DocumentVersionSignatureFilter) SQLFragment() string {
) )
) )
END END
AND
CASE
WHEN @profile_state::text IS NULL
THEN TRUE
ELSE EXISTS (
SELECT 1
FROM iam_membership_profiles p
WHERE p.id = signed_by_profile_id
AND p.state = @profile_state::membership_state
)
END
)` )`
} }

View File

@@ -279,6 +279,7 @@ func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.Doc
var ( var (
signatureStates []coredata.DocumentVersionSignatureState signatureStates []coredata.DocumentVersionSignatureState
activeContract *bool activeContract *bool
profileState *coredata.ProfileState
) )
if filter != nil { if filter != nil {
@@ -289,9 +290,13 @@ func (r *documentVersionResolver) Signatures(ctx context.Context, obj *types.Doc
if filter.ActiveContract != nil { if filter.ActiveContract != nil {
activeContract = filter.ActiveContract activeContract = filter.ActiveContract
} }
if filter.State != nil {
profileState = filter.State
}
} }
signatureFilter := coredata.NewDocumentVersionSignatureFilter(signatureStates, activeContract) signatureFilter := coredata.NewDocumentVersionSignatureFilter(signatureStates, activeContract, profileState)
cursor := types.NewCursor(first, after, last, before, pageOrderBy) cursor := types.NewCursor(first, after, last, before, pageOrderBy)

View File

@@ -247,6 +247,7 @@ input DocumentVersionSignatureOrder {
input DocumentVersionSignatureFilter { input DocumentVersionSignatureFilter {
states: [DocumentVersionSignatureState!] states: [DocumentVersionSignatureState!]
activeContract: Boolean activeContract: Boolean
state: ProfileState
} }
input DocumentVersionApprovalQuorumOrder { input DocumentVersionApprovalQuorumOrder {

View File

@@ -2188,6 +2188,7 @@ func (r *Resolver) ListDocumentVersionSignaturesTool(ctx context.Context, req *m
var ( var (
signatureStates []coredata.DocumentVersionSignatureState signatureStates []coredata.DocumentVersionSignatureState
activeContract *bool activeContract *bool
profileState *coredata.ProfileState
) )
if input.Filter != nil { if input.Filter != nil {
@@ -2198,9 +2199,13 @@ func (r *Resolver) ListDocumentVersionSignaturesTool(ctx context.Context, req *m
if input.Filter.ActiveContract != nil { if input.Filter.ActiveContract != nil {
activeContract = input.Filter.ActiveContract activeContract = input.Filter.ActiveContract
} }
if input.Filter.State != nil {
profileState = input.Filter.State
}
} }
signatureFilter := coredata.NewDocumentVersionSignatureFilter(signatureStates, activeContract) signatureFilter := coredata.NewDocumentVersionSignatureFilter(signatureStates, activeContract, profileState)
page, err := prb.Documents.ListSignatures(ctx, scope, input.DocumentVersionID, cursor, signatureFilter) page, err := prb.Documents.ListSignatures(ctx, scope, input.DocumentVersionID, cursor, signatureFilter)
if err != nil { if err != nil {

View File

@@ -6322,6 +6322,9 @@ components:
active_contract: active_contract:
type: boolean type: boolean
description: Signatory contract status description: Signatory contract status
state:
$ref: "#/components/schemas/ProfileState"
description: Signatory profile state
ListDocumentVersionSignaturesOutput: ListDocumentVersionSignaturesOutput:
type: object type: object