@@ -119,35 +119,171 @@ export function streamToCSV (
119119 }
120120}
121121
122- const getUniquePrices = ( tempTxGroup : TransactionsWithPaybuttonsAndPrices [ ] , groupKey : string , currency : SupportedQuotesType ) : Set < number > => {
123- const uniquePrices : Set < number > = new Set ( )
124- const quoteId = QUOTE_IDS [ currency . toUpperCase ( ) ]
125- tempTxGroup
126- . forEach ( tx => {
122+ class TransactionGroupManager {
123+ private tempTxGroups : Record < string , TransactionsWithPaybuttonsAndPrices [ ] > = { }
124+
125+ constructor (
126+ private currency : SupportedQuotesType ,
127+ private timezone : string
128+ ) { }
129+
130+ addToGroup ( groupKey : string , tx : TransactionsWithPaybuttonsAndPrices ) : void {
131+ if ( this . tempTxGroups [ groupKey ] === undefined ) {
132+ this . tempTxGroups [ groupKey ] = [ ]
133+ }
134+ this . tempTxGroups [ groupKey ] . push ( tx )
135+ }
136+
137+ processAllGroups ( treatedPayments : TransactionFileData [ ] ) : void {
138+ Object . keys ( this . tempTxGroups ) . forEach ( key => {
139+ this . processGroup ( key , treatedPayments )
140+ } )
141+ }
142+
143+ processGroup ( groupKey : string , treatedPayments : TransactionFileData [ ] ) : void {
144+ const tempTxGroup = this . tempTxGroups [ groupKey ]
145+ if ( tempTxGroup === undefined || tempTxGroup . length === 0 ) return
146+
147+ if ( tempTxGroup . length === 1 ) {
148+ this . addSingleTransaction ( tempTxGroup [ 0 ] , groupKey , treatedPayments )
149+ } else {
150+ this . addCollapsedTransactionGroup ( tempTxGroup , groupKey , treatedPayments )
151+ }
152+
153+ this . tempTxGroups [ groupKey ] = [ ]
154+ }
155+
156+ private addSingleTransaction (
157+ tx : TransactionsWithPaybuttonsAndPrices ,
158+ groupKey : string ,
159+ treatedPayments : TransactionFileData [ ]
160+ ) : void {
161+ const { timestamp, hash, address, amount } = tx
162+ const values = getTransactionValue ( tx )
163+ const value = Number ( values [ this . currency ] )
164+ const rate = tx . prices . find ( p => p . price . quoteId === QUOTE_IDS [ this . currency . toUpperCase ( ) ] ) ! . price . value
165+ const buttonNames = this . extractButtonNamesFromGroupKey ( groupKey )
166+
167+ treatedPayments . push ( {
168+ amount,
169+ value,
170+ date : moment . tz ( timestamp * 1000 , this . timezone ) ,
171+ transactionId : hash ,
172+ rate,
173+ currency : this . currency ,
174+ address : address . address ,
175+ notes : buttonNames ,
176+ newtworkId : address . networkId
177+ } as TransactionFileData )
178+ }
179+
180+ private addCollapsedTransactionGroup (
181+ tempTxGroup : TransactionsWithPaybuttonsAndPrices [ ] ,
182+ groupKey : string ,
183+ treatedPayments : TransactionFileData [ ]
184+ ) : void {
185+ const totalAmount = tempTxGroup . reduce ( ( sum , p ) => sum + Number ( p . amount ) , 0 )
186+ const totalValue = tempTxGroup . reduce ( ( sum , p ) => sum + Number ( getTransactionValue ( p ) [ this . currency ] ) , 0 )
187+ const uniquePrices = this . getUniquePrices ( tempTxGroup , groupKey )
188+ const rate = new Prisma . Decimal ( uniquePrices . values ( ) . next ( ) . value as number )
189+ const buttonNames = this . extractButtonNamesFromGroupKey ( groupKey )
190+ const notes = `${ buttonNames } - ${ tempTxGroup . length . toString ( ) } transactions`
191+
192+ treatedPayments . push ( {
193+ amount : totalAmount ,
194+ value : totalValue ,
195+ date : moment . tz ( tempTxGroup [ 0 ] . timestamp * 1000 , this . timezone ) ,
196+ transactionId : DEFAULT_MULTI_VALUES_LINE_LABEL ,
197+ rate,
198+ currency : this . currency ,
199+ address : DEFAULT_MULTI_VALUES_LINE_LABEL ,
200+ newtworkId : tempTxGroup [ 0 ] . address . networkId ,
201+ notes
202+ } as TransactionFileData )
203+ }
204+
205+ private extractButtonNamesFromGroupKey ( groupKey : string ) : string {
206+ return groupKey . split ( '_' ) . slice ( 2 ) . join ( ';' )
207+ }
208+
209+ private getUniquePrices ( tempTxGroup : TransactionsWithPaybuttonsAndPrices [ ] , groupKey : string ) : Set < number > {
210+ const uniquePrices : Set < number > = new Set ( )
211+ const quoteId = QUOTE_IDS [ this . currency . toUpperCase ( ) ]
212+
213+ tempTxGroup . forEach ( tx => {
127214 const price = tx . prices . find ( p => p . price . quoteId === quoteId ) ! . price . value
128215 uniquePrices . add ( Number ( price ) )
129216 } )
130- if ( uniquePrices . size !== 1 ) {
217+
218+ if ( uniquePrices . size !== 1 ) {
219+ this . handlePriceValidationError ( tempTxGroup , groupKey , uniquePrices , quoteId )
220+ }
221+
222+ return uniquePrices
223+ }
224+
225+ private handlePriceValidationError (
226+ tempTxGroup : TransactionsWithPaybuttonsAndPrices [ ] ,
227+ groupKey : string ,
228+ uniquePrices : Set < number > ,
229+ quoteId : number
230+ ) : void {
131231 if ( uniquePrices . size > 1 ) {
132232 const nonUniquePrices = [ ...uniquePrices ]
133233 const txsForPrice : Record < number , string [ ] > = { }
134234 nonUniquePrices . forEach ( nonUniquePrice => {
135- txsForPrice [ nonUniquePrice ] = tempTxGroup . filter ( tx => nonUniquePrice === tx . prices . find ( p => p . price . quoteId === quoteId ) ! . price . value . toNumber ( ) ) . map ( tx => tx . id )
235+ txsForPrice [ nonUniquePrice ] = tempTxGroup
236+ . filter ( tx => nonUniquePrice === tx . prices . find ( p => p . price . quoteId === quoteId ) ! . price . value . toNumber ( ) )
237+ . map ( tx => tx . id )
136238 } )
137239 console . error ( 'ERROR WHEN TRYING TO COLLAPSE TXS INTO DIFFERENT PRICES:' , { txsForPrice, nonUniquePrices } )
138240 } else {
139241 console . error ( 'ERROR WHEN TRYING TO COLLAPSE TXS INTO DIFFERENT PRICES, NO PRICES FOR GROUP KEY' , { groupKey } )
140242 }
141243
142244 throw new Error (
143- RESPONSE_MESSAGES
144- . INVALID_PRICES_AMOUNT_FOR_TX_ON_CSV_CREATION_500 ( tempTxGroup . length ) . message
245+ RESPONSE_MESSAGES . INVALID_PRICES_AMOUNT_FOR_TX_ON_CSV_CREATION_500 ( tempTxGroup . length ) . message
145246 )
146247 }
147- return uniquePrices
148248}
149249
150- const collapsePaymentsPushTx = (
250+ const generateGroupKey = (
251+ tx : TransactionsWithPaybuttonsAndPrices ,
252+ timezone : string ,
253+ userId : string ,
254+ paybuttonId ?: string
255+ ) : string => {
256+ const { timestamp } = tx
257+ const dateKey = moment . tz ( timestamp * 1000 , timezone ) . format ( 'YYYY-MM-DD' )
258+ const dateKeyUTC = moment . utc ( timestamp * 1000 ) . format ( 'YYYY-MM-DD' )
259+ const buttonNamesKey = extractPaybuttonNames ( tx , userId , paybuttonId )
260+
261+ return `${ dateKey } _${ dateKeyUTC } _${ buttonNamesKey } `
262+ }
263+
264+ const extractPaybuttonNames = (
265+ tx : TransactionsWithPaybuttonsAndPrices ,
266+ userId : string ,
267+ paybuttonId ?: string
268+ ) : string => {
269+ const uniqueButtonNames = new Set (
270+ tx . address . paybuttons
271+ . filter ( pb => pb . paybutton . providerUserId === userId )
272+ . map ( pb => pb . paybutton . name )
273+ )
274+
275+ if ( uniqueButtonNames . size > 1 ) {
276+ if ( paybuttonId !== undefined ) {
277+ return tx . address . paybuttons . find ( pb => pb . paybutton . id === paybuttonId ) ?. paybutton . name ?? ''
278+ } else {
279+ return [ ...uniqueButtonNames ] . join ( '_' )
280+ }
281+ } else {
282+ return uniqueButtonNames . values ( ) . next ( ) . value ?? ''
283+ }
284+ }
285+
286+ const addSingleTransactionToResults = (
151287 tx : TransactionsWithPaybuttonsAndPrices ,
152288 groupKey : string ,
153289 currency : SupportedQuotesType ,
@@ -173,61 +309,6 @@ const collapsePaymentsPushTx = (
173309 } as TransactionFileData )
174310}
175311
176- const collapsePaymentsPushTempGroup = (
177- groupKey : string ,
178- tempTxGroups : Record < string , TransactionsWithPaybuttonsAndPrices [ ] > ,
179- currency : SupportedQuotesType ,
180- treatedPayments : TransactionFileData [ ] ,
181- timezone : string
182- ) : void => {
183- const tempTxGroup = tempTxGroups [ groupKey ]
184- if ( tempTxGroup === undefined || tempTxGroup . length === 0 ) return
185- if ( tempTxGroup . length === 1 ) {
186- collapsePaymentsPushTx ( tempTxGroup [ 0 ] , groupKey , currency , treatedPayments , timezone )
187- tempTxGroups [ groupKey ] = [ ]
188- return
189- }
190- const totalAmount = tempTxGroup . reduce ( ( sum , p ) => sum + Number ( p . amount ) , 0 )
191- const totalValue = tempTxGroup . reduce ( ( sum , p ) => sum + Number ( getTransactionValue ( p ) [ currency ] ) , 0 )
192- const uniquePrices = getUniquePrices ( tempTxGroup , groupKey , currency )
193- const rate = new Prisma . Decimal ( uniquePrices . values ( ) . next ( ) . value as number )
194- const buttonNames = groupKey . split ( '_' ) . slice ( 2 ) . join ( ';' )
195- const notes = `${ buttonNames } - ${ tempTxGroup . length . toString ( ) } transactions`
196-
197- treatedPayments . push ( {
198- amount : totalAmount ,
199- value : totalValue ,
200- date : moment . tz ( tempTxGroup [ 0 ] . timestamp * 1000 , timezone ) ,
201- transactionId : DEFAULT_MULTI_VALUES_LINE_LABEL ,
202- rate,
203- currency,
204- address : DEFAULT_MULTI_VALUES_LINE_LABEL ,
205- newtworkId : tempTxGroup [ 0 ] . address . networkId ,
206- notes
207- } as TransactionFileData )
208-
209- tempTxGroups [ groupKey ] = [ ]
210- }
211-
212- const getButtonNames = ( tx : TransactionsWithPaybuttonsAndPrices , userId : string , paybuttonId ?: string ) : string => {
213- let buttonNamesKey : string = ''
214- const uniqueButtonNames = new Set (
215- tx . address . paybuttons
216- . filter ( pb => pb . paybutton . providerUserId === userId )
217- . map ( pb => pb . paybutton . name )
218- )
219- if ( uniqueButtonNames . size > 1 ) {
220- if ( paybuttonId !== undefined ) {
221- buttonNamesKey = tx . address . paybuttons . find ( pb => pb . paybutton . id === paybuttonId ) ?. paybutton . name ?? ''
222- } else {
223- buttonNamesKey = [ ...uniqueButtonNames ] . join ( '_' )
224- }
225- } else {
226- buttonNamesKey = uniqueButtonNames . values ( ) . next ( ) . value ?? ''
227- }
228- return buttonNamesKey
229- }
230-
231312export const collapseSmallPayments = (
232313 payments : TransactionsWithPaybuttonsAndPrices [ ] ,
233314 currency : SupportedQuotesType ,
@@ -237,52 +318,52 @@ export const collapseSmallPayments = (
237318 paybuttonId ?: string
238319) : TransactionFileData [ ] => {
239320 const treatedPayments : TransactionFileData [ ] = [ ]
240- const tempTxGroups : Record < string , TransactionsWithPaybuttonsAndPrices [ ] > = { }
321+ const groupManager = new TransactionGroupManager ( currency , timezone )
241322
242323 payments . forEach ( ( tx : TransactionsWithPaybuttonsAndPrices , index : number ) => {
243324 const { timestamp } = tx
244325 const values = getTransactionValue ( tx )
245326 const value = Number ( values [ currency ] )
246- const dateKey = moment . tz ( timestamp * 1000 , timezone ) . format ( 'YYYY-MM-DD' )
247- const dateKeyUTC = moment . utc ( timestamp * 1000 ) . format ( 'YYYY-MM-DD' )
248- const buttonNamesKey = getButtonNames ( tx , userId , paybuttonId )
249-
250- const groupKey = `${ dateKey } _${ dateKeyUTC } _${ buttonNamesKey } `
251-
252- let nextGroupKey : string | null = ''
253- const nextPayment = payments [ index + 1 ]
254- if ( nextPayment !== undefined ) {
255- const nextDateKey = moment . tz ( nextPayment . timestamp * 1000 , timezone ) . format ( 'YYYY-MM-DD' )
256- const nextDateKeyUTC = moment . utc ( nextPayment . timestamp * 1000 ) . format ( 'YYYY-MM-DD' )
257- const nextButtonName = getButtonNames ( nextPayment , userId , paybuttonId )
327+ const groupKey = generateGroupKey ( tx , timezone , userId , paybuttonId )
258328
259- nextGroupKey = `${ nextDateKey } _${ nextDateKeyUTC } _${ nextButtonName } `
260- } else {
261- nextGroupKey = null
262- }
329+ const shouldProcessGroups = shouldProcessGroupsAtCurrentIndex (
330+ payments , index , timezone , userId , paybuttonId , groupKey
331+ )
263332
264333 if ( value < collapseThreshold ) {
265- if ( tempTxGroups [ groupKey ] === undefined ) tempTxGroups [ groupKey ] = [ ]
266- tempTxGroups [ groupKey ] . push ( tx )
334+ groupManager . addToGroup ( groupKey , tx )
267335 } else {
268- Object . keys ( tempTxGroups ) . forEach ( key => {
269- collapsePaymentsPushTempGroup ( key , tempTxGroups , currency , treatedPayments , timezone )
270- } )
271- collapsePaymentsPushTx ( tx , groupKey , currency , treatedPayments , timezone )
336+ groupManager . processAllGroups ( treatedPayments )
337+ addSingleTransactionToResults ( tx , groupKey , currency , treatedPayments , timezone )
272338 }
273339
274- if ( nextGroupKey !== groupKey ) {
275- collapsePaymentsPushTempGroup ( groupKey , tempTxGroups , currency , treatedPayments , timezone )
340+ if ( shouldProcessGroups ) {
341+ groupManager . processGroup ( groupKey , treatedPayments )
276342 }
277343 } )
278344
279- Object . keys ( tempTxGroups ) . forEach ( key => {
280- collapsePaymentsPushTempGroup ( key , tempTxGroups , currency , treatedPayments , timezone )
281- } )
345+ groupManager . processAllGroups ( treatedPayments )
282346
283347 return treatedPayments
284348}
285349
350+ const shouldProcessGroupsAtCurrentIndex = (
351+ payments : TransactionsWithPaybuttonsAndPrices [ ] ,
352+ currentIndex : number ,
353+ timezone : string ,
354+ userId : string ,
355+ paybuttonId : string | undefined ,
356+ currentGroupKey : string
357+ ) : boolean => {
358+ const nextPayment = payments [ currentIndex + 1 ]
359+ if ( nextPayment === undefined ) {
360+ return false // No next payment, will be handled at the end
361+ }
362+
363+ const nextGroupKey = generateGroupKey ( nextPayment , timezone , userId , paybuttonId )
364+ return nextGroupKey !== currentGroupKey
365+ }
366+
286367const sortPaymentsByNetworkId = ( payments : TransactionsWithPaybuttonsAndPrices [ ] ) : TransactionsWithPaybuttonsAndPrices [ ] => {
287368 const groupedByNetworkIdPayments = payments . reduce < Record < number , TransactionsWithPaybuttonsAndPrices [ ] > > ( ( acc , transaction ) => {
288369 const networkId = transaction . address . networkId
0 commit comments