@@ -2,17 +2,43 @@ import { Command } from 'commander';
22import { client } from '../lib/api-client.js' ;
33import { outputJson } from '../lib/output.js' ;
44import { YnabCliError } from '../lib/errors.js' ;
5+ import dayjs from 'dayjs' ;
56import {
67 amountToMilliunits ,
78 applyTransactionFilters ,
89 applyFieldSelection ,
10+ summarizeTransactions ,
11+ findTransferCandidates ,
912 type TransactionLike ,
13+ type SummaryTransaction ,
1014} from '../lib/utils.js' ;
1115import { withErrorHandling , requireConfirmation , buildUpdateObject } from '../lib/command-utils.js' ;
1216import { validateTransactionSplits , validateBatchUpdates } from '../lib/schemas.js' ;
1317import { parseDate , todayDate } from '../lib/dates.js' ;
1418import type { CommandOptions } from '../types/index.js' ;
1519
20+ async function fetchTransactions ( options : {
21+ budget ?: string ;
22+ account ?: string ;
23+ category ?: string ;
24+ payee ?: string ;
25+ since ?: string ;
26+ type ?: string ;
27+ lastKnowledge ?: number ;
28+ } ) {
29+ const params = {
30+ budgetId : options . budget ,
31+ sinceDate : options . since ? parseDate ( options . since ) : undefined ,
32+ type : options . type ,
33+ lastKnowledgeOfServer : options . lastKnowledge ,
34+ } ;
35+
36+ if ( options . account ) return client . getTransactionsByAccount ( options . account , params ) ;
37+ if ( options . category ) return client . getTransactionsByCategory ( options . category , params ) ;
38+ if ( options . payee ) return client . getTransactionsByPayee ( options . payee , params ) ;
39+ return client . getTransactions ( params ) ;
40+ }
41+
1642interface TransactionOptions {
1743 account ?: string ;
1844 date ?: string ;
@@ -70,6 +96,8 @@ export function createTransactionsCommand(): Command {
7096 '--fields <fields>' ,
7197 'Comma-separated list of fields to include (e.g., id,date,amount,memo)'
7298 )
99+ . option ( '--last-knowledge <number>' , 'Last server knowledge for delta requests. When used, output includes server_knowledge.' , parseInt )
100+ . option ( '--limit <number>' , 'Maximum number of transactions to return' , parseInt )
73101 . action (
74102 withErrorHandling (
75103 async (
@@ -86,35 +114,32 @@ export function createTransactionsCommand(): Command {
86114 minAmount ?: number ;
87115 maxAmount ?: number ;
88116 fields ?: string ;
117+ lastKnowledge ?: number ;
118+ limit ?: number ;
89119 } & CommandOptions
90120 ) => {
91- const params = {
92- budgetId : options . budget ,
93- sinceDate : options . since ? parseDate ( options . since ) : undefined ,
94- type : options . type ,
95- } ;
96-
97- const result = options . account
98- ? await client . getTransactionsByAccount ( options . account , params )
99- : options . category
100- ? await client . getTransactionsByCategory ( options . category , params )
101- : options . payee
102- ? await client . getTransactionsByPayee ( options . payee , params )
103- : await client . getTransactions ( params ) ;
104-
121+ const result = await fetchTransactions ( options ) ;
105122 const transactions = result ?. transactions || [ ] ;
106123
107- const filtered = applyTransactionFilters ( transactions as TransactionLike [ ] , {
124+ let filtered = applyTransactionFilters ( transactions as TransactionLike [ ] , {
108125 until : options . until ? parseDate ( options . until ) : undefined ,
109126 approved : options . approved ,
110127 status : options . status ,
111128 minAmount : options . minAmount ,
112129 maxAmount : options . maxAmount ,
113130 } ) ;
114131
132+ if ( options . limit && options . limit > 0 ) {
133+ filtered = filtered . slice ( 0 , options . limit ) ;
134+ }
135+
115136 const selected = applyFieldSelection ( filtered , options . fields ) ;
116137
117- outputJson ( selected ) ;
138+ if ( options . lastKnowledge !== undefined ) {
139+ outputJson ( { transactions : selected , server_knowledge : result ?. server_knowledge } ) ;
140+ } else {
141+ outputJson ( selected ) ;
142+ }
118143 }
119144 )
120145 ) ;
@@ -435,5 +460,102 @@ export function createTransactionsCommand(): Command {
435460 )
436461 ) ;
437462
463+ cmd
464+ . command ( 'summary' )
465+ . description ( 'Summarize transactions with aggregate counts by payee, category, and status' )
466+ . option ( '-b, --budget <id>' , 'Budget ID' )
467+ . option ( '--account <id>' , 'Filter by account ID' )
468+ . option ( '--category <id>' , 'Filter by category ID' )
469+ . option ( '--payee <id>' , 'Filter by payee ID' )
470+ . option ( '--since <date>' , 'Filter transactions since date' )
471+ . option ( '--until <date>' , 'Filter transactions until date' )
472+ . option ( '--type <type>' , 'Filter by transaction type' )
473+ . option ( '--approved <value>' , 'Filter by approval status: true or false' )
474+ . option (
475+ '--status <statuses>' ,
476+ 'Filter by cleared status: cleared, uncleared, reconciled (comma-separated)'
477+ )
478+ . option ( '--min-amount <amount>' , 'Minimum amount in currency units' , parseFloat )
479+ . option ( '--max-amount <amount>' , 'Maximum amount in currency units' , parseFloat )
480+ . option ( '--top <number>' , 'Limit payee/category breakdowns to top N entries' , parseInt )
481+ . action (
482+ withErrorHandling (
483+ async (
484+ options : {
485+ budget ?: string ;
486+ account ?: string ;
487+ category ?: string ;
488+ payee ?: string ;
489+ since ?: string ;
490+ until ?: string ;
491+ type ?: string ;
492+ approved ?: string ;
493+ status ?: string ;
494+ minAmount ?: number ;
495+ maxAmount ?: number ;
496+ top ?: number ;
497+ } & CommandOptions
498+ ) => {
499+ const result = await fetchTransactions ( options ) ;
500+ const transactions = result ?. transactions || [ ] ;
501+
502+ const filtered = applyTransactionFilters ( transactions as TransactionLike [ ] , {
503+ until : options . until ? parseDate ( options . until ) : undefined ,
504+ approved : options . approved ,
505+ status : options . status ,
506+ minAmount : options . minAmount ,
507+ maxAmount : options . maxAmount ,
508+ } ) ;
509+
510+ const summary = summarizeTransactions (
511+ filtered as SummaryTransaction [ ] ,
512+ options . top ? { top : options . top } : undefined
513+ ) ;
514+ outputJson ( summary ) ;
515+ }
516+ )
517+ ) ;
518+
519+ cmd
520+ . command ( 'find-transfers' )
521+ . description ( 'Find candidate transfer matches for a transaction across accounts' )
522+ . argument ( '<id>' , 'Transaction ID' )
523+ . option ( '-b, --budget <id>' , 'Budget ID' )
524+ . option ( '--days <number>' , 'Maximum date difference in days (default: 3)' , parseInt )
525+ . option ( '--since <date>' , 'Search transactions since date (defaults to source date minus --days)' )
526+ . action (
527+ withErrorHandling (
528+ async (
529+ id : string ,
530+ options : {
531+ budget ?: string ;
532+ days ?: number ;
533+ since ?: string ;
534+ } & CommandOptions
535+ ) => {
536+ const maxDays = options . days ?? 3 ;
537+ const source = await client . getTransaction ( id , options . budget ) ;
538+
539+ const sinceDate = options . since
540+ ? parseDate ( options . since )
541+ : dayjs ( source . date ) . subtract ( maxDays , 'day' ) . format ( 'YYYY-MM-DD' ) ;
542+
543+ const result = await client . getTransactions ( {
544+ budgetId : options . budget ,
545+ sinceDate,
546+ } ) ;
547+
548+ const allTransactions = result ?. transactions || [ ] ;
549+ const candidates = findTransferCandidates (
550+ source as SummaryTransaction ,
551+ allTransactions as SummaryTransaction [ ] ,
552+ { maxDays }
553+ ) ;
554+
555+ outputJson ( { source, candidates } ) ;
556+ }
557+ )
558+ ) ;
559+
438560 return cmd ;
439561}
0 commit comments