Skip to content

Commit

Permalink
Add split distribution (#2151)
Browse files Browse the repository at this point in the history
* Add split distribution feature

* Add upcoming release notes

* Fix tests

* Fix remaining test

* Disable distribute button when all transactions are filled

* Add canDistributeRemainder

---------

Co-authored-by: Matiss Janis Aboltins <[email protected]>
  • Loading branch information
NikxDa and MatissJanis authored Jan 13, 2024
1 parent dccad90 commit 44a5199
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1349,7 +1349,7 @@ const Transaction = memo(function Transaction(props) {
);
});

function TransactionError({ error, isDeposit, onAddSplit, style }) {
function TransactionError({ error, isDeposit, onAddSplit, onDistributeRemainder, style, canDistributeRemainder }) {
switch (error.type) {
case 'SplitTransactionError':
if (error.version === 1) {
Expand All @@ -1372,10 +1372,20 @@ function TransactionError({ error, isDeposit, onAddSplit, style }) {
</Text>
</Text>
<View style={{ flex: 1 }} />
<Button
type="normal"
style={{ marginLeft: 15 }}
onClick={onDistributeRemainder}
data-testid="distribute-split-button"
disabled={!canDistributeRemainder}
>
Distribute
</Button>
<Button
type="primary"
style={{ marginLeft: 15, padding: '4px 10px' }}
style={{ marginLeft: 10, padding: '4px 10px' }}
onClick={onAddSplit}
data-testid="add-split-button"
>
Add Split
</Button>
Expand Down Expand Up @@ -1433,6 +1443,7 @@ function NewTransaction({
onSave,
onAdd,
onAddSplit,
onDistributeRemainder,
onManagePayees,
onCreatePayee,
onNavigateToTransferAccount,
Expand All @@ -1442,6 +1453,8 @@ function NewTransaction({
const error = transactions[0].error;
const isDeposit = transactions[0].amount > 0;

const emptyChildTransactions = transactions.filter(t => t.parent_id === transactions[0].id && t.amount === 0)

return (
<View
style={{
Expand Down Expand Up @@ -1508,6 +1521,8 @@ function NewTransaction({
error={error}
isDeposit={isDeposit}
onAddSplit={() => onAddSplit(transactions[0].id)}
onDistributeRemainder={() => onDistributeRemainder(transactions[0].id)}
canDistributeRemainder={emptyChildTransactions.length > 0}
/>
) : (
<Button
Expand Down Expand Up @@ -1597,14 +1612,21 @@ function TransactionTableInner({
? (parent && parent.error) || trans.error
: trans.error;

const hasSplitError =
(!expanded || isLastChild(transactions, index)) &&
error &&
error.type === 'SplitTransactionError'

const emptyChildTransactions = transactions.filter(
t => t.parent_id === (trans.is_parent ? trans.id : trans.parent_id) && t.amount === 0
)

return (
<>
{(!expanded || isLastChild(transactions, index)) &&
error &&
error.type === 'SplitTransactionError' && (
{hasSplitError && (
<Tooltip
position="bottom-right"
width={250}
width={350}
forceTop={position}
forceLayout={true}
style={{ transform: 'translate(-5px, 2px)' }}
Expand All @@ -1613,6 +1635,8 @@ function TransactionTableInner({
error={error}
isDeposit={isChildDeposit}
onAddSplit={() => props.onAddSplit(trans.id)}
onDistributeRemainder={() => props.onDistributeRemainder(trans.id)}
canDistributeRemainder={emptyChildTransactions.length > 0}
/>
</Tooltip>
)}
Expand Down Expand Up @@ -1706,6 +1730,7 @@ function TransactionTableInner({
onCreatePayee={props.onCreatePayee}
onNavigateToTransferAccount={onNavigateToTransferAccount}
onNavigateToSchedule={onNavigateToSchedule}
onDistributeRemainder={props.onDistributeRemainder}
balance={
props.transactions?.length > 0
? props.balances?.[props.transactions[0]?.id]?.balance
Expand Down Expand Up @@ -2083,6 +2108,62 @@ export const TransactionTable = forwardRef((props, ref) => {
[props.onAddSplit],
);

const onDistributeRemainder = useCallback(
async id => {
const { transactions, tableNavigator, newTransactions } =
latestState.current;

const targetTransactions = isTemporaryId(id) ? newTransactions : transactions
const transaction = targetTransactions.find(t => t.id === id);

const parentTransaction = transaction.is_parent ? transaction : targetTransactions.find(
t => t.id === transaction.parent_id
)

const siblingTransactions = targetTransactions.filter(
t => t.parent_id === (transaction.is_parent ? transaction.id : transaction.parent_id)
)

const emptyTransactions = siblingTransactions.filter(t => t.amount === 0);

const remainingAmount =
parentTransaction.amount -
siblingTransactions.reduce((acc, t) => acc + t.amount, 0);

const amountPerTransaction = Math.floor(
remainingAmount / emptyTransactions.length,
);
let remainingCents =
remainingAmount - amountPerTransaction * emptyTransactions.length;

let amounts = new Array(emptyTransactions.length).fill(
amountPerTransaction,
);

for (let amountIndex in amounts) {
if (remainingCents === 0) break;

amounts[amountIndex] += 1;
remainingCents--;
}

if (isTemporaryId(id)) {
newNavigator.onEdit(null)
} else {
tableNavigator.onEdit(null)
}

for (const transactionIndex in emptyTransactions) {
await onSave({
...emptyTransactions[transactionIndex],
amount: amounts[transactionIndex],
});
}
},
[latestState],
);


function onCloseAddTransaction() {
setNewTransactions(
makeTemporaryTransactions(
Expand Down Expand Up @@ -2113,6 +2194,7 @@ export const TransactionTable = forwardRef((props, ref) => {
onCheckEnter={onCheckEnter}
onAddTemporary={onAddTemporary}
onAddSplit={onAddSplit}
onDistributeRemainder={onDistributeRemainder}
onCloseAddTransaction={onCloseAddTransaction}
onToggleSplit={onToggleSplit}
newTransactions={newTransactions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ describe('Transactions', () => {
await waitForAutocomplete();

await userEvent.click(
container.querySelector('[data-testid="transaction-error"] button'),
container.querySelector('[data-testid="add-split-button"]'),
);

input = await editNewField(container, 'debit', 1);
Expand Down Expand Up @@ -794,6 +794,7 @@ describe('Transactions', () => {
expect(getTransactions().length).toBe(5);
await userEvent.click(splitButton);
await waitForAutocomplete();

expect(getTransactions().length).toBe(6);
expect(getTransactions()[0].is_parent).toBe(true);
expect(getTransactions()[1].is_child).toBe(true);
Expand All @@ -816,7 +817,7 @@ describe('Transactions', () => {

// Add another split transaction and make sure everything is
// updated properly
await userEvent.click(toolbar.querySelector('button'));
await userEvent.click(toolbar.querySelector('[data-testid="add-split-button"]'));
expect(getTransactions().length).toBe(7);
expect(getTransactions()[2].amount).toBe(0);
expectErrorToExist(getTransactions().slice(0, 3));
Expand Down Expand Up @@ -910,7 +911,7 @@ describe('Transactions', () => {
await userEvent.click(splitButton);
await waitForAutocomplete();
await userEvent.click(
container.querySelector('[data-testid="transaction-error"] button'),
container.querySelector('[data-testid="add-split-button"]'),
);
expect(getTransactions().length).toBe(7);

Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/2151.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Features
authors: [NikxDa]
---

Add "Distribute" button to distribute remaining split amount across empty splits.

0 comments on commit 44a5199

Please sign in to comment.