diff --git a/server/graphql/v2/query/collection/TransactionsCollectionQuery.ts b/server/graphql/v2/query/collection/TransactionsCollectionQuery.ts index 9dcbd39d66e..679add0d466 100644 --- a/server/graphql/v2/query/collection/TransactionsCollectionQuery.ts +++ b/server/graphql/v2/query/collection/TransactionsCollectionQuery.ts @@ -6,6 +6,7 @@ import { cloneDeep, flatten, intersection, isEmpty, isNil, pick, uniq } from 'lo import type { Order as SequelizeOrder } from 'sequelize'; import { CollectiveType } from '../../../../constants/collectives'; +import { TransactionKind } from '../../../../constants/transaction-kind'; import cache, { memoize } from '../../../../lib/cache'; import { buildSearchConditions } from '../../../../lib/sql-search'; import { parseToBoolean } from '../../../../lib/utils'; @@ -45,6 +46,8 @@ const LEDGER_ORDERED_TRANSACTIONS_FIELDS = { ), }; +const { PLATFORM_TIP, HOST_FEE_SHARE } = TransactionKind; + export const getTransactionKindPriorityCase = tableName => ` CASE WHEN "${tableName}"."kind" IN ('CONTRIBUTION', 'EXPENSE', 'ADDED_FUNDS', 'BALANCE_TRANSFER', 'PREPAID_PAYMENT_METHOD') THEN 1 @@ -127,6 +130,10 @@ export const TransactionsCollectionArgs = { type: GraphQLString, description: 'The term to search', }, + hasDebt: { + type: GraphQLBoolean, + description: 'If true, return transactions with debt attached, if false transactions without debt attached.', + }, hasExpense: { type: GraphQLBoolean, description: 'Only return transactions with an Expense attached', @@ -497,6 +504,25 @@ export const TransactionsCollectionResolver = async ( where.push({ isRefund: args.isRefund }); } + if (args.hasDebt !== undefined) { + const hasDebtSubquery = `SELECT id FROM "Transactions" as "DebtTransaction" + WHERE "DebtTransaction"."TransactionGroup" = "Transaction"."TransactionGroup" + AND ("DebtTransaction".kind)::text = CONCAT(("Transaction"."kind")::text, '_DEBT') + AND "DebtTransaction"."type" != "Transaction"."type" + AND "DebtTransaction"."deletedAt" IS NULL`; + if (args.hasDebt === true) { + where.push({ kind: [PLATFORM_TIP, HOST_FEE_SHARE] }); // Need to be this kind to have debt + where.push(sequelize.literal(`EXISTS (${hasDebtSubquery})`)); + } else if (args.hasDebt === false) { + where.push( + sequelize.or( + { kind: { [Op.not]: [PLATFORM_TIP, HOST_FEE_SHARE] } }, // No Debt if not this kind + sequelize.literal(`NOT EXISTS (${hasDebtSubquery})`), + ), + ); + } + } + /* Ordering of transactions by - createdAt (rounded by a 10s interval): to treat very close timestamps as the same to defer ordering to transaction group, kind and type