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() { public get categorizeCashflowTransactionValidationSchema() {
return [ return [
check('date').exists().isISO8601().toDate(), check('date').exists().isISO8601().toDate(),
oneOf([ check('credit_account_id').exists().isInt().toInt(),
check('to_account_id').exists().isInt().toInt(),
check('from_account_id').exists().isInt().toInt(),
]),
check('transaction_number').optional(), check('transaction_number').optional(),
check('transaction_type').exists(), check('transaction_type').exists(),
check('reference_no').optional(), check('reference_no').optional(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,30 +2,55 @@
import React from 'react'; import React from 'react';
import { DrawerHeaderContent, DrawerLoading } from '@/components'; import { DrawerHeaderContent, DrawerLoading } from '@/components';
import { DRAWERS } from '@/constants/drawers'; 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(); const CategorizeTransactionBootContext = React.createContext();
/** /**
* Estimate detail provider. * Categorize transcation boot.
*/ */
function CategorizeTransactionBoot({ uncategorizedTransactionId, ...props }) { 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 { const {
data: uncategorizedTransaction, data: uncategorizedTransaction,
isLoading: isUncategorizedTransactionLoading, isLoading: isUncategorizedTransactionLoading,
} = useUncategorizedTransaction(uncategorizedTransactionId); } = useUncategorizedTransaction(uncategorizedTransactionId);
const provider = { const provider = {
uncategorizedTransactionId,
uncategorizedTransaction, uncategorizedTransaction,
isUncategorizedTransactionLoading, isUncategorizedTransactionLoading,
branches,
accounts,
isBranchesLoading,
isAccountsLoading,
}; };
const isLoading =
isBranchesLoading || isUncategorizedTransactionLoading || isAccountsLoading;
return ( return (
<DrawerLoading loading={isUncategorizedTransactionLoading}> <DrawerLoading loading={isLoading}>
<DrawerHeaderContent <DrawerHeaderContent
name={DRAWERS.CATEGORIZE_TRANSACTION} name={DRAWERS.CATEGORIZE_TRANSACTION}
title={'Categorize Transaction'} title={'Categorize Transaction'}
subTitle={''}
/> />
<CategorizeTransactionBootContext.Provider value={provider} {...props} /> <CategorizeTransactionBootContext.Provider value={provider} {...props} />
</DrawerLoading> </DrawerLoading>

View File

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

View File

@@ -1,7 +1,14 @@
// @ts-nocheck // @ts-nocheck
import * as Yup from 'yup'; 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 CreateCategorizeTransactionSchema = Schema;
export const EditCategorizeTransactionSchema = Schema;

View File

@@ -1,34 +1,57 @@
// @ts-nocheck // @ts-nocheck
import { Formik, Form } from 'formik'; import { Formik, Form } from 'formik';
import styled from 'styled-components';
import withDialogActions from '@/containers/Dialog/withDialogActions'; import withDialogActions from '@/containers/Dialog/withDialogActions';
import { import { CreateCategorizeTransactionSchema } from './CategorizeTransactionForm.schema';
EditCategorizeTransactionSchema,
CreateCategorizeTransactionSchema,
} from './CategorizeTransactionForm.schema';
import { compose, transformToForm } from '@/utils';
import { CategorizeTransactionFormContent } from './CategorizeTransactionFormContent'; import { CategorizeTransactionFormContent } from './CategorizeTransactionFormContent';
import { CategorizeTransactionFormFooter } from './CategorizeTransactionFormFooter'; import { CategorizeTransactionFormFooter } from './CategorizeTransactionFormFooter';
import { useCategorizeTransaction } from '@/hooks/query';
// Default initial form values. import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot';
const defaultInitialValues = {}; 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. * Categorize cashflow transaction form dialog content.
*/ */
function CategorizeTransactionFormRoot({ function CategorizeTransactionFormRoot({
// #withDialogActions // #withDrawerActions
closeDialog, closeDrawer,
}) { }) {
const isNewMode = true; const { uncategorizedTransactionId, uncategorizedTransaction } =
useCategorizeTransactionBoot();
// Form validation schema in create and edit mode. const { mutateAsync: categorizeTransaction } = useCategorizeTransaction();
const validationSchema = isNewMode
? CreateCategorizeTransactionSchema
: EditCategorizeTransactionSchema;
// Callbacks handles form submit. // 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. // Form initial values in create and edit mode.
const initialValues = { const initialValues = {
...defaultInitialValues, ...defaultInitialValues,
@@ -37,23 +60,37 @@ function CategorizeTransactionFormRoot({
* values such as `notes` come back from the API as null, so remove those * values such as `notes` come back from the API as null, so remove those
* as well. * as well.
*/ */
...transformToForm({}, defaultInitialValues), ...transformToCategorizeForm(uncategorizedTransaction),
}; };
return ( return (
<Formik <DivRoot>
validationSchema={validationSchema} <Formik
initialValues={initialValues} validationSchema={CreateCategorizeTransactionSchema}
onSubmit={handleFormSubmit} initialValues={initialValues}
> onSubmit={handleFormSubmit}
<Form> >
<CategorizeTransactionFormContent /> <Form>
<CategorizeTransactionFormFooter /> <CategorizeTransactionFormContent />
</Form> <CategorizeTransactionFormFooter />
</Formik> </Form>
</Formik>
</DivRoot>
); );
} }
export const CategorizeTransactionForm = compose(withDialogActions)( export const CategorizeTransactionForm = compose(withDrawerActions)(
CategorizeTransactionFormRoot, 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 // @ts-nocheck
import { Position } from '@blueprintjs/core'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { import { FormGroup } from '@blueprintjs/core';
AccountsSelect, import { FFormGroup, FSelect, FSuggest } from '@/components';
FDateInput, import { getAddMoneyInOptions, getAddMoneyOutOptions } from '@/constants';
FFormGroup, import { useFormikContext } from 'formik';
FInputGroup, import { useCategorizeTransactionBoot } from './CategorizeTransactionBoot';
FSelect,
FSuggest,
FTextArea,
} from '@/components';
import { getAddMoneyInOptions } from '@/constants';
// Retrieves the add money in button options. // 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() { export function CategorizeTransactionFormContent() {
const { uncategorizedTransaction } = useCategorizeTransactionBoot();
const transactionTypes = uncategorizedTransaction?.is_deposit_transaction
? MoneyInOptions
: MoneyOutOptions;
return ( return (
<> <>
<Title>$22,583.00</Title> <FormGroup label={'Amount'} inline>
<Title>{uncategorizedTransaction.formatted_amount}</Title>
</FormGroup>
<FFormGroup name={'category'} label={'Category'} fastField inline> <FFormGroup name={'category'} label={'Category'} fastField inline>
<FSuggest <FSelect
name={'transaction_type'} name={'transactionType'}
items={AddMoneyInOptions} items={transactionTypes}
popoverProps={{ minimal: true }} popoverProps={{ minimal: true }}
valueAccessor={'value'} valueAccessor={'value'}
textAccessor={'name'} textAccessor={'name'}
@@ -33,58 +41,56 @@ export function CategorizeTransactionFormContent() {
/> />
</FFormGroup> </FFormGroup>
<FFormGroup name={'date'} label={'Date'} fastField inline> <CategorizeTransactionFormSubContent />
<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>
</> </>
); );
} }
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 { 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 ( return (
<div> <Root>
<div className={Classes.DRAWER_FOOTER}> <div className={Classes.DRAWER_FOOTER}>
<Button <Group spacing={10}>
intent={Intent.PRIMARY} <Button
// loading={isSubmitting} intent={Intent.PRIMARY}
style={{ minWidth: '85px' }} loading={isSubmitting}
type="submit" style={{ minWidth: '75px' }}
> type="submit"
Save >
</Button> Save
</Button>
<Button <Button
// disabled={isSubmitting} disabled={isSubmitting}
// onClick={onClose} onClick={handleClose}
style={{ minWidth: '75px' }} style={{ minWidth: '75px' }}
> >
Close Close
</Button> </Button>
</Group>
</div> </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_TRANSACTIONS_INFINITY',
CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY: CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY:
'CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY', 'CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY',
CASHFLOW_UNCAATEGORIZED_TRANSACTION: 'CASHFLOW_UNCAATEGORIZED_TRANSACTION', CASHFLOW_UNCAATEGORIZED_TRANSACTION: 'CASHFLOW_UNCAATEGORIZED_TRANSACTION',
}; };