-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement transactions unlinking
- Loading branch information
Showing
9 changed files
with
278 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
src/controllers/transactions.controller/unlink-transfer-transactions.e2e.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import { TRANSACTION_TRANSFER_NATURE, TRANSACTION_TYPES } from 'shared-types'; | ||
import * as helpers from '@tests/helpers'; | ||
import { faker } from '@faker-js/faker'; | ||
|
||
describe('Unlink transfer transactions', () => { | ||
it('unlink system transactions', async () => { | ||
// Firstly create two transfer transactions | ||
const accountA = await helpers.createAccount({ raw: true }); | ||
const accountB = await helpers.createAccount({ raw: true }); | ||
|
||
await helpers.createTransaction({ | ||
payload: { | ||
...helpers.buildTransactionPayload({ accountId: accountA.id }), | ||
transferNature: TRANSACTION_TRANSFER_NATURE.common_transfer, | ||
destinationAmount: faker.number.int({ max: 1000 }) * 1000, | ||
destinationAccountId: accountB.id, | ||
}, | ||
raw: true, | ||
}); | ||
|
||
await helpers.createTransaction({ | ||
payload: { | ||
...helpers.buildTransactionPayload({ accountId: accountA.id }), | ||
transferNature: TRANSACTION_TRANSFER_NATURE.common_transfer, | ||
destinationAmount: faker.number.int({ max: 1000 }) * 1000, | ||
destinationAccountId: accountB.id, | ||
}, | ||
raw: true, | ||
}); | ||
|
||
// Now unlink them | ||
const transactions = await helpers.getTransactions({ raw: true }); | ||
const transferIds = transactions.map((item) => item.transferId); | ||
|
||
const updatedTransactions = await helpers.unlinkTransferTransactions({ | ||
transferIds, | ||
raw: true, | ||
}); | ||
|
||
// Test that now they're unlinked and not transfer anymore | ||
updatedTransactions.forEach((tx) => { | ||
const oppositeTx = transactions.find((item) => item.id === tx.id); | ||
|
||
expect(tx).toEqual({ | ||
...oppositeTx, | ||
transferId: null, | ||
transferNature: TRANSACTION_TRANSFER_NATURE.not_transfer, | ||
}); | ||
}); | ||
}); | ||
it('unlink external transactions', async () => { | ||
// Firstly create external expense + income | ||
await helpers.monobank.pair(); | ||
const { transactions } = await helpers.monobank.mockTransactions(); | ||
const expenseExternalTx = transactions.find( | ||
(item) => item.transactionType === TRANSACTION_TYPES.expense, | ||
); | ||
const incomeExternalTx = transactions.find( | ||
(item) => item.transactionType === TRANSACTION_TYPES.income, | ||
); | ||
|
||
// Now create system expense + income | ||
const accountA = await helpers.createAccount({ raw: true }); | ||
const accountB = await helpers.createAccount({ raw: true }); | ||
|
||
const [expenseSystemTx] = await helpers.createTransaction({ | ||
payload: { | ||
...helpers.buildTransactionPayload({ accountId: accountA.id }), | ||
transactionType: TRANSACTION_TYPES.expense, | ||
}, | ||
raw: true, | ||
}); | ||
|
||
const [incomeSystemTx] = await helpers.createTransaction({ | ||
payload: { | ||
...helpers.buildTransactionPayload({ accountId: accountB.id }), | ||
transactionType: TRANSACTION_TYPES.income, | ||
}, | ||
raw: true, | ||
}); | ||
|
||
// Now link 1 external with 1 system for each type | ||
const updatedA = await helpers.updateTransaction({ | ||
id: expenseExternalTx.id, | ||
payload: { | ||
transferNature: TRANSACTION_TRANSFER_NATURE.common_transfer, | ||
destinationTransactionId: incomeSystemTx.id, | ||
}, | ||
raw: true, | ||
}); | ||
const updatedB = await helpers.updateTransaction({ | ||
id: incomeExternalTx.id, | ||
payload: { | ||
transferNature: TRANSACTION_TRANSFER_NATURE.common_transfer, | ||
destinationTransactionId: expenseSystemTx.id, | ||
}, | ||
raw: true, | ||
}); | ||
|
||
// Test that after updation only transfer-related fields were changed for each | ||
// transaction | ||
expect(expenseExternalTx).toEqual({ | ||
...updatedA[0], | ||
transferNature: expect.toBeAnythingOrNull(), | ||
transferId: expect.toBeAnythingOrNull(), | ||
}); | ||
expect(incomeSystemTx).toEqual({ | ||
...updatedA[1], | ||
transferNature: expect.toBeAnythingOrNull(), | ||
transferId: expect.toBeAnythingOrNull(), | ||
}); | ||
expect(incomeExternalTx).toEqual({ | ||
...updatedB[0], | ||
transferNature: expect.toBeAnythingOrNull(), | ||
transferId: expect.toBeAnythingOrNull(), | ||
}); | ||
expect(expenseSystemTx).toEqual({ | ||
...updatedB[1], | ||
transferNature: expect.toBeAnythingOrNull(), | ||
transferId: expect.toBeAnythingOrNull(), | ||
}); | ||
|
||
// Now unlink all of them | ||
const transferIds = [...updatedA, ...updatedB].map((t) => t.transferId); | ||
|
||
const result = await helpers.unlinkTransferTransactions({ | ||
transferIds, | ||
raw: true, | ||
}); | ||
|
||
// After unlinking check that transactions now are COMPLETELY SAME | ||
[ | ||
expenseExternalTx, | ||
incomeExternalTx, | ||
expenseSystemTx, | ||
incomeSystemTx, | ||
].forEach((tx) => { | ||
expect(result.find((t) => t.id === tx.id)).toEqual(tx); | ||
}); | ||
}); | ||
}); |
32 changes: 32 additions & 0 deletions
32
src/controllers/transactions.controller/unlink-transfer-transactions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { API_RESPONSE_STATUS, endpointsTypes } from 'shared-types'; | ||
import { CustomResponse } from '@common/types'; | ||
import { errorHandler } from '@controllers/helpers'; | ||
import * as transactionsService from '@services/transactions'; | ||
import { ValidationError } from '@js/errors'; | ||
|
||
export const unlinkTransferTransactions = async (req, res: CustomResponse) => { | ||
try { | ||
const { transferIds }: endpointsTypes.UnlinkTransferTransactionsBody = | ||
req.body; | ||
const { id: userId } = req.user; | ||
|
||
if (!transferIds || !Array.isArray(transferIds)) { | ||
throw new ValidationError({ | ||
message: | ||
'"transferIds" field is required and should be an array if transferIds.', | ||
}); | ||
} | ||
|
||
const data = await transactionsService.unlinkTransferTransactions({ | ||
userId, | ||
transferIds: [...new Set(transferIds)], | ||
}); | ||
|
||
return res | ||
.status(200) | ||
.json({ status: API_RESPONSE_STATUS.success, response: data }); | ||
} catch (err) { | ||
console.log('ERR', err); | ||
errorHandler(res, err); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { TRANSACTION_TRANSFER_NATURE } from 'shared-types'; | ||
import { Op, Transaction } from 'sequelize'; | ||
import { connection } from '@models/index'; | ||
import { GenericSequelizeModelAttributes } from '@common/types'; | ||
import { logger } from '@js/utils/logger'; | ||
import * as Transactions from '@models/Transactions.model'; | ||
|
||
export const unlinkTransferTransactions = async ( | ||
payload: { userId: number; transferIds: string[] }, | ||
attributes: GenericSequelizeModelAttributes = {}, | ||
): Promise<Transactions.default[]> => { | ||
const isTxPassedFromAbove = attributes.transaction !== undefined; | ||
const transaction: Transaction = | ||
attributes.transaction ?? (await connection.sequelize.transaction()); | ||
|
||
try { | ||
const transactions = await Transactions.getTransactionsByArrayOfField( | ||
{ | ||
userId: payload.userId, | ||
fieldName: 'transferId', | ||
fieldValues: payload.transferIds, | ||
}, | ||
{ transaction }, | ||
); | ||
|
||
const txIds = transactions.map((t) => t.id); | ||
await Transactions.default.update( | ||
{ | ||
transferId: null, | ||
transferNature: TRANSACTION_TRANSFER_NATURE.not_transfer, | ||
}, | ||
{ | ||
where: { | ||
userId: payload.userId, | ||
id: { [Op.in]: txIds }, | ||
}, | ||
transaction, | ||
}, | ||
); | ||
|
||
const updatedTxs = await Transactions.getTransactionsByArrayOfField( | ||
{ | ||
userId: payload.userId, | ||
fieldName: 'id', | ||
fieldValues: txIds, | ||
}, | ||
{ transaction }, | ||
); | ||
|
||
if (!isTxPassedFromAbove) { | ||
await transaction.commit(); | ||
} | ||
|
||
return updatedTxs; | ||
} catch (err) { | ||
logger.error(err); | ||
if (!isTxPassedFromAbove) { | ||
await transaction.rollback(); | ||
} | ||
throw err; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters