diff --git a/src/plugins/sberbank-by/__tests__/converters/transactions/filterDuplicates.test.js b/src/plugins/sberbank-by/__tests__/converters/transactions/filterDuplicates.test.js index 4068d27f7..cdfe265a2 100644 --- a/src/plugins/sberbank-by/__tests__/converters/transactions/filterDuplicates.test.js +++ b/src/plugins/sberbank-by/__tests__/converters/transactions/filterDuplicates.test.js @@ -109,6 +109,79 @@ describe('filterDuplicates', () => { comment: 'Открытие' } ] + ], + [ + [ + { + sourceSystem: 4, + eventId: 'hold-event', + contractId: '10957794', + cardPAN: '911238******4812', + cardId: '1771310095', + eventDate: 1712911020000, + transactionCode: '01000R', + transactionName: 'SOU Sber Bank > Minsk BLR Терминал: WWL90902 RRN:410383389811 AuthCode:602242', + merchantId: '0802082', + merchantPlace: 'SOU Sber Bank > Minsk BLR', + transactionSum: -50, + transactionCurrency: '933', + commissionSum: 0, + commissionCurrency: '933', + rnnCode: '410383389811', + authorizationCode: '602242', + eventStatus: 0, + payAvailable: false + }, + { + sourceSystem: 1, + eventId: 'posted-event', + contractId: '10957794', + contractCurrency: '933', + cardPAN: '911238******4812', + cardId: '1771310095', + eventDate: 1712911022000, + processingDate: 1712914622000, + transactionType: -1, + transactionCode: '205', + transactionName: 'Покупка SOU Sber Bank', + operationCode: null, + merchantId: '0802082', + merchantName: 'SOU Sber Bank', + merchantPlace: 'Minsk', + transactionSum: 50, + transactionCurrency: '933', + accountSum: 50, + commissionSum: 0, + commissionCurrency: '933', + rnnCode: '410383389811', + authorizationCode: '602242', + eventStatus: 1, + payAvailable: false + } + ], + { + 10957794: { + id: 'account-1', + instrument: 'BYN' + } + }, + [ + { + hold: false, + date: new Date('2024-04-12T08:37:02.000Z'), + movements: [ + { + id: 'posted-event', + account: { id: 'account-1' }, + invoice: null, + sum: -50, + fee: 0 + } + ], + merchant: null, + comment: 'Покупка SOU Sber Bank' + } + ] ] ])('converts filter duplicates transactions', (apiTransactions, accountsByContractNumber, transactions) => { expect(convertTransactions(apiTransactions, accountsByContractNumber)).toEqual(transactions) diff --git a/src/plugins/sberbank-by/converters.js b/src/plugins/sberbank-by/converters.js index 656f14ae9..bdb02c6e8 100644 --- a/src/plugins/sberbank-by/converters.js +++ b/src/plugins/sberbank-by/converters.js @@ -1,4 +1,4 @@ -import { keyBy, sortBy, uniqBy } from 'lodash' +import { keyBy, sortBy } from 'lodash' import codeToCurrency from '../../common/codeToCurrencyLookup' import { toISODateString } from '../../common/dateUtils' import { getIntervalBetweenDates } from '../../common/momentDateUtils' @@ -101,21 +101,84 @@ function parseMetalInstrument (code) { } } +function getApiTransactionDedupKey (apiTransaction) { + const rrn = apiTransaction.rnnCode || apiTransaction.souRnnCode || '' + const authorizationCode = apiTransaction.authorizationCode && apiTransaction.authorizationCode !== '000000' + ? apiTransaction.authorizationCode + : '' + const dateKey = rrn || authorizationCode + ? toISODateString(new Date(apiTransaction.eventDate)) + : String(apiTransaction.eventDate) + return [ + apiTransaction.contractId, + apiTransaction.cardId || apiTransaction.cardPAN || '', + dateKey, + Math.abs(apiTransaction.transactionSum), + apiTransaction.transactionCurrency || '', + rrn, + authorizationCode + ].join('_') +} + +function shouldReplaceApiTransaction (currentTransaction, nextTransaction) { + if (currentTransaction.eventStatus === 0 && nextTransaction.eventStatus !== 0) { + return true + } + if (!currentTransaction.processingDate && nextTransaction.processingDate) { + return true + } + return false +} + +function deduplicateApiTransactions (apiTransactions) { + const uniqueTransactions = [] + const transactionIndexesByKey = {} + for (const apiTransaction of sortBy(apiTransactions, apiTransaction => apiTransaction.transactionName?.match(/Перевод/))) { + const key = getApiTransactionDedupKey(apiTransaction) + const index = transactionIndexesByKey[key] + if (index === undefined) { + transactionIndexesByKey[key] = uniqueTransactions.length + uniqueTransactions.push(apiTransaction) + continue + } + if (shouldReplaceApiTransaction(uniqueTransactions[index], apiTransaction)) { + uniqueTransactions[index] = apiTransaction + } + } + return uniqueTransactions +} + +function getTransactionDedupKey (transaction) { + const movement = transaction.movements?.[0] + if (!movement) { + return null + } + return `${movement.account.id}_${transaction.date.getTime()}_${Math.abs(movement.sum)}` +} + +function shouldReplaceTransaction (currentTransaction, nextTransaction) { + return currentTransaction.hold && !nextTransaction.hold +} + export function convertTransactions (apiTransactions, accountsByContractNumber) { - const adjustedApiTransactions = uniqBy( - sortBy(apiTransactions, apiTransaction => apiTransaction.transactionName?.match(/Перевод/)), - apiTransaction => `${apiTransaction.contractId}_${apiTransaction.eventDate}_${Math.abs(apiTransaction.transactionSum)}_${apiTransaction.rnnCode}_${apiTransaction.authorizationCode}`) + const adjustedApiTransactions = deduplicateApiTransactions(apiTransactions) const transactions = [] - const transactionIds = {} + const transactionIndexes = {} for (const transaction of adjustedApiTransactions) { if (accountsByContractNumber[transaction.contractId]) { const answer = convertTransaction(transaction, accountsByContractNumber[transaction.contractId]) if (answer !== null) { - const key = answer.movements[0].account.id + '_' + answer.date + '_' + Math.abs(answer.movements[0].sum) - if (transactionIds[key]) { // - } else { - transactionIds[key] = true + const key = getTransactionDedupKey(answer) + if (key === null) { + transactions.push(answer) + continue + } + const index = transactionIndexes[key] + if (index === undefined) { + transactionIndexes[key] = transactions.length transactions.push(answer) + } else if (shouldReplaceTransaction(transactions[index], answer)) { + transactions[index] = answer } } }