feat: wip categorize the cashflow transactions

This commit is contained in:
Ahmed Bouhuolia
2024-03-06 22:15:31 +02:00
parent a17b4f6a8a
commit d87d674aba
27 changed files with 768 additions and 144 deletions

View File

@@ -80,10 +80,7 @@ export default class NewCashflowTransactionController extends BaseController {
public get categorizeCashflowTransactionValidationSchema() {
return [
check('date').exists().isISO8601().toDate(),
oneOf([
check('to_account_id').exists().isInt().toInt(),
check('from_account_id').exists().isInt().toInt(),
]),
check('credit_account_id').exists().isInt().toInt(),
check('transaction_number').optional(),
check('transaction_type').exists(),
check('reference_no').optional(),

View File

@@ -235,8 +235,7 @@ export interface ICashflowTransactionSchema {
export interface ICashflowTransactionInput extends ICashflowTransactionSchema {}
export interface ICategorizeCashflowTransactioDTO {
fromAccountId: number;
toAccountId: number;
creditAccountId: number;
referenceNo: string;
transactionNumber: string;
transactionType: string;

View File

@@ -318,7 +318,7 @@ export default class Account extends mixin(TenantModel, [
to: 'uncategorized_cashflow_transactions.accountId',
},
filter: (query) => {
query.filter('categorized', false);
query.where('categorized', false);
},
},
};

View File

@@ -1,6 +1,6 @@
/* eslint-disable global-require */
import TenantModel from 'models/TenantModel';
import { Model } from 'objection';
import { Model, ModelOptions, QueryContext } from 'objection';
import Account from './Account';
export default class UncategorizedCashflowTransaction extends TenantModel {
@@ -95,6 +95,19 @@ export default class UncategorizedCashflowTransaction extends TenantModel {
.increment('uncategorized_transactions', 1);
}
public async $afterUpdate(
opt: ModelOptions,
queryContext: QueryContext
): void | Promise<any> {
await super.$afterUpdate(opt, queryContext);
if (this.id && this.categorized) {
await Account.query(queryContext.transaction)
.findById(this.accountId)
.decrement('uncategorized_transactions', 1);
}
}
/**
*
* @param queryContext
@@ -102,7 +115,7 @@ export default class UncategorizedCashflowTransaction extends TenantModel {
public async $afterDelete(queryContext) {
await super.$afterDelete(queryContext);
await Account.query()
await Account.query(queryContext.transaction)
.findById(this.accountId)
.decrement('uncategorized_transactions', 1);
}

View File

@@ -79,13 +79,14 @@ export class CategorizeCashflowTransaction {
cashflowTransactionDTO
);
// Updates the uncategorized transaction as categorized.
await UncategorizedCashflowTransaction.query(trx)
.findById(uncategorizedTransactionId)
.patch({
await UncategorizedCashflowTransaction.query(trx).patchAndFetchById(
uncategorizedTransactionId,
{
categorized: true,
categorizeRefType: 'CashflowTransaction',
categorizeRefId: cashflowTransaction.id,
});
}
);
// Triggers `onCashflowTransactionCategorized` event.
await this.eventPublisher.emitAsync(
events.cashflow.onTransactionCategorized,

View File

@@ -31,6 +31,7 @@ export default class GetCashflowTransactionsService {
.withGraphFetched('entries.cashflowAccount')
.withGraphFetched('entries.creditAccount')
.withGraphFetched('transactions.account')
.orderBy('date', 'DESC')
.throwIfNotFound();
this.throwErrorCashflowTranscationNotFound(cashflowTransaction);

View File

@@ -24,7 +24,8 @@ export class GetUncategorizedTransactions {
.where('accountId', accountId)
.where('categorized', false)
.withGraphFetched('account')
.pagination(0, 10);
.orderBy('date', 'DESC')
.pagination(0, 1000);
const data = await this.transformer.transform(
tenantId,

View File

@@ -8,6 +8,7 @@ export class UncategorizedTransactionTransformer extends Transformer {
*/
public includeAttributes = (): string[] => {
return [
'formattedAmount',
'formattedDate',
'formattetDepositAmount',
'formattedWithdrawalAmount',
@@ -23,6 +24,17 @@ export class UncategorizedTransactionTransformer extends Transformer {
return this.formatDate(transaction.date);
}
/**
* Formatted amount.
* @param transaction
* @returns {string}
*/
public formattedAmount(transaction) {
return formatNumber(transaction.amount, {
currencyCode: transaction.currencyCode,
});
}
/**
* Formatted deposit amount.
* @param transaction

View File

@@ -55,7 +55,7 @@ export const transformCategorizeTransToCashflow = (
referenceNo: categorizeDTO.referenceNo || uncategorizeModel.referenceNo,
description: categorizeDTO.description || uncategorizeModel.description,
cashflowAccountId: uncategorizeModel.accountId,
creditAccountId: categorizeDTO.fromAccountId || categorizeDTO.toAccountId,
creditAccountId: categorizeDTO.creditAccountId,
exchangeRate: categorizeDTO.exchangeRate || 1,
currencyCode: uncategorizeModel.currencyCode,
amount: uncategorizeModel.amount,

View File

@@ -1,5 +1,6 @@
// @ts-nocheck
import React from 'react';
import clsx from 'classnames';
import { Classes } from '@blueprintjs/core';
import { LoadingIndicator } from '../Indicator';
@@ -11,8 +12,8 @@ export function DrawerLoading({ loading, mount = false, children }) {
);
}
export function DrawerBody({ children }) {
return <div className={Classes.DRAWER_BODY}>{children}</div>;
export function DrawerBody({ children, className }) {
return <div className={clsx(Classes.DRAWER_BODY, className)}>{children}</div>;
}
export * from './DrawerActionsBar';

View File

@@ -26,12 +26,12 @@ const SelectButton = styled(Button)`
position: relative;
padding-right: 30px;
&.bp4-small{
&.bp4-small {
padding-right: 24px;
}
&:not(.is-selected):not([class*='bp4-intent-']):not(.bp4-minimal) {
color: #5c7080;
color: #8f99a8;
}
&:after {
content: '';

View File

@@ -2,30 +2,55 @@
import React from 'react';
import { DrawerHeaderContent, DrawerLoading } from '@/components';
import { DRAWERS } from '@/constants/drawers';
import { useUncategorizedTransaction } from '@/hooks/query';
import {
useAccounts,
useBranches,
useUncategorizedTransaction,
} from '@/hooks/query';
import { useFeatureCan } from '@/hooks/state';
import { Features } from '@/constants';
const CategorizeTransactionBootContext = React.createContext();
/**
* Estimate detail provider.
* Categorize transcation boot.
*/
function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) {
// Detarmines whether the feature is enabled.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Fetches accounts list.
const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
// Fetches the branches list.
const { data: branches, isLoading: isBranchesLoading } = useBranches(
{},
{ enabled: isBranchFeatureCan },
);
// Retrieves the uncategorized transaction.
const {
data: uncategorizedTransaction,
isLoading: isUncategorizedTransactionLoading,
} = useUncategorizedTransaction(uncategorizedTransactionId);
const provider = {
uncategorizedTransactionId,
uncategorizedTransaction,
isUncategorizedTransactionLoading,
branches,
accounts,
isBranchesLoading,
isAccountsLoading,
};
const isLoading =
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
return (
<DrawerLoading loading={isUncategorizedTransactionLoading}>
<DrawerLoading loading={isLoading}>
<DrawerHeaderContent
name={DRAWERS.CATEGORIZE_TRANSACTION}
title={'Categorize Transaction'}
subTitle={''}
/>
<CategorizeTransactionBootContext.Provider value={provider} {...props} />
</DrawerLoading>

View File

@@ -1,4 +1,5 @@
// @ts-nocheck
import styled from 'styled-components';
import { DrawerBody } from '@/components';
import { CategorizeTransactionBoot } from './CategorizeTransactionBoot';
import { CategorizeTransactionForm } from './CategorizeTransactionForm';
@@ -10,9 +11,14 @@ export default function CategorizeTransactionContent({
<CategorizeTransactionBoot
uncategorizedTransactionId={uncategorizedTransactionId}
>
<DrawerBody>
<CategorizeTransactionDrawerBody>
<CategorizeTransactionForm />
</DrawerBody>
</CategorizeTransactionDrawerBody>
</CategorizeTransactionBoot>
);
}
export const CategorizeTransactionDrawerBody = styled(DrawerBody)`
padding: 20px;
background-color: #fff;
`;

View File

@@ -1,7 +1,14 @@
// @ts-nocheck
import * as Yup from 'yup';
const Schema = Yup.object().shape({});
const Schema = Yup.object().shape({
amount: Yup.string().required().label('Amount'),
exchangeRate: Yup.string().required().label('Exchange rate'),
transactionType: Yup.string().required().label('Transaction type'),
date: Yup.string().required().label('Date'),
creditAccountId: Yup.string().required().label('Credit account'),
referenceNo: Yup.string().optional().label('Reference No.'),
description: Yup.string().optional().label('Description'),
});
export const CreateCategorizeTransactionSchema = Schema;
export const EditCategorizeTransactionSchema = Schema;

View File

@@ -1,34 +1,57 @@
// @ts-nocheck
import { Formik, Form } from 'formik';
import styled from 'styled-components';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import {
EditCategorizeTransactionSchema,
CreateCategorizeTransactionSchema,
} from './CategorizeTransactionForm.schema';
import { compose, transformToForm } from '@/utils';
import { CreateCategorizeTransactionSchema } from './CategorizeTransactionForm.schema';
import { CategorizeTransactionFormContent } from './CategorizeTransactionFormContent';
import { CategorizeTransactionFormFooter } from './CategorizeTransactionFormFooter';
// Default initial form values.
const defaultInitialValues = {};
import { useCategorizeTransaction } from '@/hooks/query';
import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot';
import { DRAWERS } from '@/constants/drawers';
import {
transformToCategorizeForm,
defaultInitialValues,
tranformToRequest,
} from './_utils';
import { compose } from '@/utils';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { AppToaster } from '@/components';
import { Intent } from '@blueprintjs/core';
/**
* Categorize cashflow transaction form dialog content.
*/
function CategorizeTransactionFormRoot({
// #withDialogActions
closeDialog,
// #withDrawerActions
closeDrawer,
}) {
const isNewMode = true;
// Form validation schema in create and edit mode.
const validationSchema = isNewMode
? CreateCategorizeTransactionSchema
: EditCategorizeTransactionSchema;
const { uncategorizedTransactionId, uncategorizedTransaction } =
useCategorizeTransactionBoot();
const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
// Callbacks handles form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {};
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const transformedValues = tranformToRequest(values);
setSubmitting(true);
categorizeTransaction([uncategorizedTransactionId, transformedValues])
.then(() => {
setSubmitting(false);
closeDrawer(DRAWERS.CATEGORIZE_TRANSACTION);
AppToaster.show({
message: 'The uncategorized transaction has been categorized.',
intent: Intent.SUCCESS,
});
})
.catch(() => {
setSubmitting(false);
AppToaster.show({
message: 'Something went wrong!',
intent: Intent.DANGER,
});
});
};
// Form initial values in create and edit mode.
const initialValues = {
...defaultInitialValues,
@@ -37,23 +60,37 @@ function CategorizeTransactionFormRoot({
* values such as `notes` come back from the API as null, so remove those
* as well.
*/
...transformToForm({}, defaultInitialValues),
...transformToCategorizeForm(uncategorizedTransaction),
};
return (
<Formik
validationSchema={validationSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<Form>
<CategorizeTransactionFormContent />
<CategorizeTransactionFormFooter />
</Form>
</Formik>
<DivRoot>
<Formik
validationSchema={CreateCategorizeTransactionSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<Form>
<CategorizeTransactionFormContent />
<CategorizeTransactionFormFooter />
</Form>
</Formik>
</DivRoot>
);
}
export const CategorizeTransactionForm = compose(withDialogActions)(
export const CategorizeTransactionForm = compose(withDrawerActions)(
CategorizeTransactionFormRoot,
);
const DivRoot = styled.div`
.bp4-form-group .bp4-form-content {
flex: 1 0;
}
.bp4-form-group .bp4-label {
width: 140px;
}
.bp4-form-group {
margin-bottom: 18px;
}
`;

View File

@@ -1,31 +1,39 @@
// @ts-nocheck
import { Position } from '@blueprintjs/core';
import React from 'react';
import styled from 'styled-components';
import {
AccountsSelect,
FDateInput,
FFormGroup,
FInputGroup,
FSelect,
FSuggest,
FTextArea,
} from '@/components';
import { getAddMoneyInOptions } from '@/constants';
import { FormGroup } from '@blueprintjs/core';
import { FFormGroup, FSelect, FSuggest } from '@/components';
import { getAddMoneyInOptions, getAddMoneyOutOptions } from '@/constants';
import { useFormikContext } from 'formik';
import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot';
// Retrieves the add money in button options.
const AddMoneyInOptions = getAddMoneyInOptions();
const MoneyInOptions = getAddMoneyInOptions();
const MoneyOutOptions = getAddMoneyOutOptions();
const Title = styled('h3')``;
const Title = styled('h3')`
font-size: 20px;
font-weight: 400;
color: #cd4246;
`;
export function CategorizeTransactionFormContent() {
const { uncategorizedTransaction } = useCategorizeTransactionBoot();
const transactionTypes = uncategorizedTransaction?.is_deposit_transaction
? MoneyInOptions
: MoneyOutOptions;
return (
<>
<Title>$22,583.00</Title>
<FormGroup label={'Amount'} inline>
<Title>{uncategorizedTransaction.formatted_amount}</Title>
</FormGroup>
<FFormGroup name={'category'} label={'Category'} fastField inline>
<FSuggest
name={'transaction_type'}
items={AddMoneyInOptions}
<FSelect
name={'transactionType'}
items={transactionTypes}
popoverProps={{ minimal: true }}
valueAccessor={'value'}
textAccessor={'name'}
@@ -33,58 +41,56 @@ export function CategorizeTransactionFormContent() {
/>
</FFormGroup>
<FFormGroup name={'date'} label={'Date'} fastField inline>
<FDateInput
name={'date'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
/>
</FFormGroup>
<FFormGroup
name={'from_account_id'}
label={'From Account'}
fastField={true}
inline
>
<AccountsSelect
name={'from_account_id'}
items={[]}
fastField={true}
fill={true}
allowCreate={true}
/>
</FFormGroup>
<FFormGroup
name={'toAccountId'}
label={'To Account'}
fastField={true}
inline
>
<AccountsSelect
name={'to_account_id'}
items={[]}
fastField={true}
fill={true}
allowCreate={true}
/>
</FFormGroup>
<FFormGroup name={'referenceNo'} label={'Reference No.'} fastField inline>
<FInputGroup name={'reference_no'} fill />
</FFormGroup>
<FFormGroup name={'description'} label={'Description'} fastField inline>
<FTextArea
name={'description'}
growVertically={true}
large={true}
fill={true}
/>
</FFormGroup>
<CategorizeTransactionFormSubContent />
</>
);
}
const CategorizeTransactionOtherIncome = React.lazy(
() => import('./MoneyIn/CategorizeTransactionOtherIncome'),
);
const CategorizeTransactionOwnerContribution = React.lazy(
() => import('./MoneyIn/CategorizeTransactionOwnerContribution'),
);
const CategorizeTransactionTransferFrom = React.lazy(
() => import('./MoneyIn/CategorizeTransactionTransferFrom'),
);
const CategorizeTransactionOtherExpense = React.lazy(
() => import('./MoneyOut/CategorizeTransactionOtherExpense'),
);
const CategorizeTransactionToAccount = React.lazy(
() => import('./MoneyOut/CategorizeTransactionToAccount'),
);
const CategorizeTransactionOwnerDrawings = React.lazy(
() => import('./MoneyOut/CategorizeTransactionOwnerDrawings'),
);
function CategorizeTransactionFormSubContent() {
const { values } = useFormikContext();
// Other expense.
if (values.transactionType === 'other_expense') {
return <CategorizeTransactionOtherExpense />;
// Owner contribution.
} else if (values.transactionType === 'owner_contribution') {
return <CategorizeTransactionOwnerContribution />;
// Other Income.
} else if (values.transactionType === 'other_income') {
return <CategorizeTransactionOtherIncome />;
// Transfer from account.
} else if (values.transactionType === 'transfer_from_account') {
return <CategorizeTransactionTransferFrom />;
// Transfer to account.
} else if (values.transactionType === 'transfer_to_account') {
return <CategorizeTransactionToAccount />;
// Owner drawings.
} else if (values.transactionType === 'OwnerDrawing') {
return <CategorizeTransactionOwnerDrawings />;
}
return null;
}

View File

@@ -1,26 +1,56 @@
// @ts-nocheck
import * as R from 'ramda';
import { Button, Classes, Intent } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import styled from 'styled-components';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { DRAWERS } from '@/constants/drawers';
import { Group } from '@/components';
function CategorizeTransactionFormFooterRoot({
// #withDrawerActions
closeDrawer,
}) {
const { isSubmitting } = useFormikContext();
const handleClose = () => {
closeDrawer(DRAWERS.CATEGORIZE_TRANSACTION);
};
export function CategorizeTransactionFormFooter() {
return (
<div>
<Root>
<div className={Classes.DRAWER_FOOTER}>
<Button
intent={Intent.PRIMARY}
// loading={isSubmitting}
style={{ minWidth: '85px' }}
type="submit"
>
Save
</Button>
<Group spacing={10}>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
>
Save
</Button>
<Button
// disabled={isSubmitting}
// onClick={onClose}
style={{ minWidth: '75px' }}
>
Close
</Button>
<Button
disabled={isSubmitting}
onClick={handleClose}
style={{ minWidth: '75px' }}
>
Close
</Button>
</Group>
</div>
</div>
</Root>
);
}
export const CategorizeTransactionFormFooter = R.compose(withDrawerActions)(
CategorizeTransactionFormFooterRoot,
);
const Root = styled.div`
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: #fff;
`;

View File

@@ -0,0 +1,73 @@
// @ts-nocheck
import { Position } from '@blueprintjs/core';
import {
AccountsSelect,
FDateInput,
FFormGroup,
FInputGroup,
FTextArea,
} from '@/components';
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
export default function CategorizeTransactionOtherIncome() {
const { accounts } = useCategorizeTransactionBoot();
return (
<>
<FFormGroup name={'date'} label={'Date'} fastField inline>
<FDateInput
name={'date'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
/>
</FFormGroup>
<FFormGroup
name={'debitAccountId'}
label={'To Account'}
fastField={true}
inline
>
<AccountsSelect
name={'debitAccountId'}
items={accounts}
fastField
fill
allowCreate
disabled
/>
</FFormGroup>
<FFormGroup
name={'creditAccountId'}
label={'Income Account'}
fastField
inline
>
<AccountsSelect
name={'creditAccountId'}
items={accounts}
filterByRootTypes={['income']}
fastField
fill
allowCreate
/>
</FFormGroup>
<FFormGroup name={'referenceNo'} label={'Reference No.'} fastField inline>
<FInputGroup name={'reference_no'} fill />
</FFormGroup>
<FFormGroup name={'description'} label={'Description'} fastField inline>
<FTextArea
name={'description'}
growVertically={true}
large={true}
fill={true}
/>
</FFormGroup>
</>
);
}

View File

@@ -0,0 +1,68 @@
// @ts-nocheck
import { Position } from '@blueprintjs/core';
import {
AccountsSelect,
FDateInput,
FFormGroup,
FInputGroup,
FTextArea,
} from '@/components';
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
export default function CategorizeTransactionOwnerContribution() {
const { accounts } = useCategorizeTransactionBoot();
return (
<>
<FFormGroup name={'date'} label={'Date'} fastField inline>
<FDateInput
name={'date'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
/>
</FFormGroup>
<FFormGroup
name={'debitAccountId'}
label={'From Account'}
fastField
inline
>
<AccountsSelect
name={'debitAccountId'}
items={accounts}
fastField
fill
allowCreate
disabled
/>
</FFormGroup>
<FFormGroup
name={'creditAccountId'}
label={'Equity Account'}
fastField
inline
>
<AccountsSelect
name={'creditAccountId'}
items={accounts}
filterByRootTypes={['equity']}
fastField
fill
allowCreate
/>
</FFormGroup>
<FFormGroup name={'referenceNo'} label={'Reference No.'} fastField inline>
<FInputGroup name={'reference_no'} fill />
</FFormGroup>
<FFormGroup name={'description'} label={'Description'} fastField inline>
<FTextArea name={'description'} growVertically large fill />
</FFormGroup>
</>
);
}

View File

@@ -0,0 +1,73 @@
// @ts-nocheck
import { Position } from '@blueprintjs/core';
import {
AccountsSelect,
FDateInput,
FFormGroup,
FInputGroup,
FTextArea,
} from '@/components';
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
export default function CategorizeTransactionTransferFrom() {
const { accounts } = useCategorizeTransactionBoot();
return (
<>
<FFormGroup name={'date'} label={'Date'} fastField inline>
<FDateInput
name={'date'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
/>
</FFormGroup>
<FFormGroup
name={'debitAccountId'}
label={'From Account'}
fastField
inline
>
<AccountsSelect
name={'debitAccountId'}
items={accounts}
fastField
fill
allowCreate
disabled
/>
</FFormGroup>
<FFormGroup
name={'creditAccountId'}
label={'To Account'}
fastField
inline
>
<AccountsSelect
name={'to_account_id'}
items={accounts}
filterByRootTypes={['asset']}
fastField
fill
allowCreate
/>
</FFormGroup>
<FFormGroup name={'referenceNo'} label={'Reference No.'} fastField inline>
<FInputGroup name={'reference_no'} fill />
</FFormGroup>
<FFormGroup name={'description'} label={'Description'} fastField inline>
<FTextArea
name={'description'}
growVertically={true}
large={true}
fill={true}
/>
</FFormGroup>
</>
);
}

View File

@@ -0,0 +1,73 @@
// @ts-nocheck
import { Position } from '@blueprintjs/core';
import {
AccountsSelect,
FDateInput,
FFormGroup,
FInputGroup,
FTextArea,
} from '@/components';
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
export default function CategorizeTransactionOtherExpense() {
const { accounts } = useCategorizeTransactionBoot();
return (
<>
<FFormGroup name={'date'} label={'Date'} fastField inline>
<FDateInput
name={'date'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
/>
</FFormGroup>
<FFormGroup
name={'debitAccountId'}
label={'Payment Account'}
fastField={true}
inline
>
<AccountsSelect
name={'debitAccountId'}
items={accounts}
fastField
fill
allowCreate
disabled
/>
</FFormGroup>
<FFormGroup
name={'creditAccountId'}
label={'Expense Account'}
fastField={true}
inline
>
<AccountsSelect
name={'creditAccountId'}
items={accounts}
filterByRootTypes={['expense']}
fastField
fill
allowCreate
/>
</FFormGroup>
<FFormGroup name={'referenceNo'} label={'Reference No.'} fastField inline>
<FInputGroup name={'reference_no'} fill />
</FFormGroup>
<FFormGroup name={'description'} label={'Description'} fastField inline>
<FTextArea
name={'description'}
growVertically={true}
large={true}
fill={true}
/>
</FFormGroup>
</>
);
}

View File

@@ -0,0 +1,73 @@
// @ts-nocheck
import { Position } from '@blueprintjs/core';
import {
AccountsSelect,
FDateInput,
FFormGroup,
FInputGroup,
FTextArea,
} from '@/components';
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
export default function CategorizeTransactionOwnerDrawings() {
const { accounts } = useCategorizeTransactionBoot();
return (
<>
<FFormGroup name={'date'} label={'Date'} fastField inline>
<FDateInput
name={'date'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
/>
</FFormGroup>
<FFormGroup
name={'debitAccountId'}
label={'Debit Account'}
fastField
inline
>
<AccountsSelect
name={'debitAccountId'}
items={accounts}
fastField
fill
allowCreate
disabled
/>
</FFormGroup>
<FFormGroup
name={'creditAccountId'}
label={'Equity Account'}
fastField
inline
>
<AccountsSelect
name={'creditAccountId'}
items={accounts}
filterByRootTypes={['equity']}
fastField
fill
allowCreate
/>
</FFormGroup>
<FFormGroup name={'referenceNo'} label={'Reference No.'} fastField inline>
<FInputGroup name={'reference_no'} fill />
</FFormGroup>
<FFormGroup name={'description'} label={'Description'} fastField inline>
<FTextArea
name={'description'}
growVertically={true}
large={true}
fill={true}
/>
</FFormGroup>
</>
);
}

View File

@@ -0,0 +1,73 @@
// @ts-nocheck
import { Position } from '@blueprintjs/core';
import {
AccountsSelect,
FDateInput,
FFormGroup,
FInputGroup,
FTextArea,
} from '@/components';
import { useCategorizeTransactionBoot } from '../CategorizeTransactionBoot';
export default function CategorizeTransactionToAccount() {
const { accounts } = useCategorizeTransactionBoot();
return (
<>
<FFormGroup name={'date'} label={'Date'} fastField inline>
<FDateInput
name={'date'}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
/>
</FFormGroup>
<FFormGroup
name={'debitAccountId'}
label={'From Account'}
fastField
inline
>
<AccountsSelect
name={'debitAccountId'}
items={accounts}
fastField
fill
allowCreate
disabled
/>
</FFormGroup>
<FFormGroup
name={'creditAccountId'}
label={'To Account'}
fastField
inline
>
<AccountsSelect
name={'creditAccountId'}
items={accounts}
filterByRootTypes={['assset']}
fastField
fill
allowCreate
/>
</FFormGroup>
<FFormGroup name={'referenceNo'} label={'Reference No.'} fastField inline>
<FInputGroup name={'reference_no'} fill />
</FFormGroup>
<FFormGroup name={'description'} label={'Description'} fastField inline>
<FTextArea
name={'description'}
growVertically={true}
large={true}
fill={true}
/>
</FFormGroup>
</>
);
}

View File

@@ -0,0 +1,31 @@
// @ts-nocheck
import { transformToForm, transfromToSnakeCase } from '@/utils';
// Default initial form values.
export const defaultInitialValues = {
amount: '',
date: '',
creditAccountId: '',
debitAccountId: '',
exchangeRate: '1',
transactionType: '',
referenceNo: '',
description: '',
};
export const transformToCategorizeForm = (uncategorizedTransaction) => {
const defaultValues = {
debitAccountId: uncategorizedTransaction.account_id,
transactionType: uncategorizedTransaction.is_deposit_transaction
? 'other_income'
: 'other_expense',
amount: uncategorizedTransaction.amount,
date: uncategorizedTransaction.date,
};
return transformToForm(defaultValues, defaultInitialValues);
};
export const tranformToRequest = (formValues) => {
return transfromToSnakeCase(formValues);
};

View File

@@ -0,0 +1 @@
export * from './CategorizeTransactionDrawer';

View File

@@ -231,3 +231,27 @@ export function useUncategorizedTransaction(
},
);
}
/**
* Categorize the cashflow transaction.
*/
export function useCategorizeTransaction(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
([id, values]) =>
apiRequest.post(`cashflow/transactions/${id}/categorize`, values),
{
onSuccess: (res, id) => {
// Invalidate queries.
commonInvalidateQueries(queryClient);
queryClient.invalidateQueries(t.CASHFLOW_UNCAATEGORIZED_TRANSACTION);
queryClient.invalidateQueries(
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
);
},
...props,
},
);
}

View File

@@ -202,7 +202,6 @@ const CASH_FLOW_ACCOUNTS = {
'CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY',
CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY:
'CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY',
CASHFLOW_UNCAATEGORIZED_TRANSACTION: 'CASHFLOW_UNCAATEGORIZED_TRANSACTION',
};