fix: auto-increment cashflow transactions

This commit is contained in:
a.bouhuolia
2023-05-23 13:56:35 +02:00
parent 40ef02f215
commit 0c689459cb
33 changed files with 166 additions and 518 deletions

View File

@@ -0,0 +1,29 @@
// @ts-nocheck
import React from 'react';
import OwnerContributionFormFields from './OwnerContribution/OwnerContributionFormFields';
import OtherIncomeFormFields from './OtherIncome/OtherIncomeFormFields';
import TransferFromAccountFormFields from './TransferFromAccount/TransferFromAccountFormFields';
/**
*
* @param param0
* @returns
*/
export default function MoneyInContentFields({ accountType }) {
const handleTransactionType = () => {
switch (accountType) {
case 'owner_contribution':
return <OwnerContributionFormFields />;
case 'other_income':
return <OtherIncomeFormFields />;
case 'transfer_from_account':
return <TransferFromAccountFormFields />;
default:
break;
}
};
return <React.Fragment>{handleTransactionType()}</React.Fragment>;
}

View File

@@ -0,0 +1,25 @@
// @ts-nocheck
import React from 'react';
import { MoneyInDialogProvider } from './MoneyInDialogProvider';
import MoneyInForm from './MoneyInForm';
/**
* Money in dialog content.
*/
export default function MoneyInDialogContent({
// #ownProps
dialogName,
accountId,
accountType,
}) {
return (
<MoneyInDialogProvider
accountId={accountId}
accountType={accountType}
dialogName={dialogName}
>
<MoneyInForm />
</MoneyInDialogProvider>
);
}

View File

@@ -0,0 +1,92 @@
// @ts-nocheck
import React from 'react';
import { DialogContent } from '@/components';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
import {
useCreateCashflowTransaction,
useAccount,
useAccounts,
useBranches,
useCashflowAccounts,
useSettingCashFlow,
} from '@/hooks/query';
const MoneyInDialogContent = React.createContext();
/**
* Money in dialog provider.
*/
function MoneyInDialogProvider({
accountId,
accountType,
dialogName,
...props
}) {
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Fetches accounts list.
const { isFetching: isAccountsLoading, data: accounts } = useAccounts();
// Fetches the specific account details.
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
enabled: !!accountId,
});
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches({}, { enabled: isBranchFeatureCan });
// Fetch cash flow list .
const { data: cashflowAccounts, isLoading: isCashFlowAccountsLoading } =
useCashflowAccounts({}, { keepPreviousData: true });
const { mutateAsync: createCashflowTransactionMutate } =
useCreateCashflowTransaction();
// Handle fetching settings.
const { isLoading: isSettingsLoading } = useSettingCashFlow();
// Submit payload.
const [submitPayload, setSubmitPayload] = React.useState({});
// provider.
const provider = {
accounts,
account,
branches,
accountId,
accountType,
isAccountsLoading,
isBranchesSuccess,
cashflowAccounts,
submitPayload,
dialogName,
createCashflowTransactionMutate,
setSubmitPayload,
};
return (
<DialogContent
isLoading={
isAccountsLoading ||
isCashFlowAccountsLoading ||
isBranchesLoading ||
isSettingsLoading
}
>
<MoneyInDialogContent.Provider value={provider} {...props} />
</DialogContent>
);
}
const useMoneyInDailogContext = () => React.useContext(MoneyInDialogContent);
export { MoneyInDialogProvider, useMoneyInDailogContext };

View File

@@ -0,0 +1,77 @@
// @ts-nocheck
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from '@/components';
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { compose } from '@/utils';
/**
* Money in floating actions.
*/
function MoneyInFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting, submitForm } = useFormikContext();
// money in dialog context.
const { dialogName, setSubmitPayload, submitPayload } =
useMoneyInDailogContext();
// handle submit as draft button click.
const handleSubmitDraftBtnClick = (event) => {
setSubmitPayload({ publish: false });
submitForm();
};
// Handle submit button click.
const handleSubmittBtnClick = (event) => {
setSubmitPayload({ publish: true });
submitForm();
};
// Handle close button click.
const handleCloseBtnClick = (event) => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button
disabled={isSubmitting}
onClick={handleCloseBtnClick}
style={{ minWidth: '75px' }}
>
<T id={'close'} />
</Button>
{/* <Button
disabled={isSubmitting}
loading={isSubmitting && !submitPayload.publish}
style={{ minWidth: '75px' }}
type="submit"
onClick={handleSubmitDraftBtnClick}
>
{<T id={'save_as_draft'} />}
</Button> */}
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
loading={isSubmitting && submitPayload.publish}
style={{ minWidth: '75px' }}
type="submit"
onClick={handleSubmittBtnClick}
>
{<T id={'save_and_publish'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(MoneyInFloatingActions);

View File

@@ -0,0 +1,24 @@
// @ts-nocheck
import * as Yup from 'yup';
import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from '@/constants/dataTypes';
const Schema = Yup.object().shape({
date: Yup.date().required().label(intl.get('date')),
amount: Yup.number().required().label(intl.get('amount')),
transaction_number: Yup.string(),
transaction_type: Yup.string().required(),
reference_no: Yup.string(),
credit_account_id: Yup.number().required(),
cashflow_account_id: Yup.string().required(),
branch_id: Yup.string(),
exchange_rate: Yup.number(),
description: Yup.string()
.min(3)
.max(DATATYPES_LENGTH.TEXT)
.label(intl.get('description')),
publish: Yup.boolean(),
});
export const CreateMoneyInFormSchema = Schema;

View File

@@ -0,0 +1,118 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import moment from 'moment';
import { Intent } from '@blueprintjs/core';
import { Formik } from 'formik';
import { omit } from 'lodash';
import '@/style/pages/CashFlow/CashflowTransactionForm.scss';
import { AppToaster } from '@/components';
import MoneyInFormContent from './MoneyInFormContent';
import { CreateMoneyInFormSchema } from './MoneyInForm.schema';
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
import withSettings from '@/containers/Settings/withSettings';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
import { compose, transactionNumber } from '@/utils';
const defaultInitialValues = {
date: moment(new Date()).format('YYYY-MM-DD'),
amount: '',
transaction_number: '',
transaction_type: '',
reference_no: '',
cashflow_account_id: '',
credit_account_id: '',
currency_code: '',
description: '',
branch_id: '',
publish: '',
exchange_rate: 1,
};
function MoneyInForm({
// #withDialogActions
closeDialog,
// #withCurrentOrganization
organization: { base_currency },
// #withSettings
transactionNextNumber,
transactionNumberPrefix,
transactionIncrementMode,
}) {
const {
dialogName,
accountId,
accountType,
createCashflowTransactionMutate,
submitPayload,
} = useMoneyInDailogContext();
// transaction number.
const transactionNo = transactionNumber(
transactionNumberPrefix,
transactionNextNumber,
);
// Initial form values.
const initialValues = {
...defaultInitialValues,
currency_code: base_currency,
transaction_type: accountType,
...(transactionIncrementMode && {
transaction_number: transactionNo,
}),
cashflow_account_id: accountId,
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = {
...omit(values, ['currency_code']),
publish: true,
};
setSubmitting(true);
createCashflowTransactionMutate(form)
.then(() => {
closeDialog(dialogName);
AppToaster.show({
message: intl.get('cash_flow_transaction_success_message'),
intent: Intent.SUCCESS,
});
})
.finally(() => {
setSubmitting(false);
});
};
return (
<div>
<Formik
validationSchema={CreateMoneyInFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<MoneyInFormContent />
</Formik>
</div>
);
}
export default compose(
withDialogActions,
withCurrentOrganization(),
withSettings(({ cashflowSetting }) => ({
transactionNextNumber: cashflowSetting?.nextNumber,
transactionNumberPrefix: cashflowSetting?.numberPrefix,
transactionIncrementMode: cashflowSetting?.autoIncrement,
})),
)(MoneyInForm);

View File

@@ -0,0 +1,22 @@
// @ts-nocheck
import React from 'react';
import { Form } from 'formik';
import MoneyInFormFields from './MoneyInFormFields';
import MoneyInFormDialog from './MoneyInFormDialog';
import MoneyInFloatingActions from './MoneyInFloatingActions';
import { MoneyInOutSyncIncrementSettingsToForm } from '../_components';
/**
* Money In form content.
*/
export default function MoneyInFormContent() {
return (
<Form>
<MoneyInFormFields />
<MoneyInFormDialog />
<MoneyInFloatingActions />
<MoneyInOutSyncIncrementSettingsToForm />
</Form>
);
}

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import React from 'react';
import { useFormikContext } from 'formik';
import TransactionNumberDialog from '@/containers/Dialogs/TransactionNumberDialog';
/**
* Moneny in / transaction number form dialog.
*/
export default function MoneyInFormDialog() {
const { setFieldValue } = useFormikContext();
// Update the form once the transaction number form submit confirm.
const handleTransactionNumberFormConfirm = (settings) => {
setFieldValue('transaction_number', settings.transactionNumber);
setFieldValue('transaction_number_manually', settings.transactionNumber);
};
return (
<React.Fragment>
<TransactionNumberDialog
dialogName={'transaction-number-form'}
onConfirm={handleTransactionNumberFormConfirm}
/>
</React.Fragment>
);
}

View File

@@ -0,0 +1,30 @@
// @ts-nocheck
import React from 'react';
import { useFormikContext } from 'formik';
import { Classes } from '@blueprintjs/core';
import { If } from '@/components';
import MoneyInContentFields from './MoneyInContentFields';
import TransactionTypeFields from './TransactionTypeFields';
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
/**
* Money in form fields.
*/
function MoneyInFormFields() {
const { values } = useFormikContext();
// Money in dialog context.
const { accountId } = useMoneyInDailogContext();
return (
<div className={Classes.DIALOG_BODY}>
<If condition={!accountId}>
<TransactionTypeFields />
</If>
<MoneyInContentFields accountType={values.transaction_type} />
</div>
);
}
export default MoneyInFormFields;

View File

@@ -0,0 +1,224 @@
// @ts-nocheck
import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FieldRequiredHint,
Col,
Row,
If,
FeatureCan,
BranchSelect,
BranchSelectButton,
ExchangeRateMutedField,
} from '@/components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from '@/hooks';
import { CLASSES, ACCOUNT_TYPE, Features } from '@/constants';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
} from '@/utils';
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import {
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import { MoneyInOutTransactionNoField } from '../../_components';
/**
* Other income form fields.
*/
export default function OtherIncomeFormFields() {
// Money in dialog context.
const { accounts, account, branches } = useMoneyInDailogContext();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
const isForeigAccount = useForeignAccount();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<React.Fragment>
<FeatureCan feature={Features.Branches}>
<Row>
<Col xs={5}>
<FormGroup
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FormGroup>
</Col>
</Row>
<BranchRowDivider />
</FeatureCan>
<Row>
<Col xs={5}>
{/*------------ Date -----------*/}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
minimal={true}
className={classNames(CLASSES.FILL, 'form-group--date')}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{
position: Position.BOTTOM,
minimal: true,
}}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Transaction number -----------*/}
<MoneyInOutTransactionNoField />
</Col>
</Row>
{/*------------ amount -----------*/}
<FastField name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup>
</FormGroup>
)}
</FastField>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row>
<Col xs={5}>
{/*------------ other income account -----------*/}
<FastField name={'credit_account_id'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cash_flow_transaction.other_income_account'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="credit_account_id" />}
className={'form-group--credit_account_id'}
>
<AccountsSuggestField
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('credit_account_id', id)
}
filterByTypes={[
ACCOUNT_TYPE.INCOME,
ACCOUNT_TYPE.OTHER_INCOME,
]}
inputProps={{
intent: inputIntent({ error, touched }),
}}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Reference -----------*/}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment>
);
}

View File

@@ -0,0 +1,223 @@
// @ts-nocheck
import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FieldRequiredHint,
Col,
Row,
If,
ExchangeRateMutedField,
BranchSelect,
BranchSelectButton,
FeatureCan,
} from '@/components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from '@/hooks';
import { ACCOUNT_TYPE, CLASSES, Features } from '@/constants';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
} from '@/utils';
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import {
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../../MoneyInDialog/utils';
import { MoneyInOutTransactionNoField } from '../../_components';
/**
/**
* Owner contribution form fields.
*/
export default function OwnerContributionFormFields() {
// Money in dialog context.
const { accounts, account, branches } = useMoneyInDailogContext();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
const isForeigAccount = useForeignAccount();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<React.Fragment>
<FeatureCan feature={Features.Branches}>
<Row>
<Col xs={5}>
<FormGroup
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FormGroup>
</Col>
</Row>
<BranchRowDivider />
</FeatureCan>
<Row>
<Col xs={5}>
{/*------------ Date -----------*/}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
minimal={true}
className={classNames(CLASSES.FILL, 'form-group--date')}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{
position: Position.BOTTOM,
minimal: true,
}}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Transaction number -----------*/}
<MoneyInOutTransactionNoField />
</Col>
</Row>
{/*------------ amount -----------*/}
<Field name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={account?.currency_code} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup>
</FormGroup>
)}
</Field>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row>
<Col xs={5}>
{/*------------ equity account -----------*/}
<FastField name={'credit_account_id'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cash_flow_transaction.label_equity_account'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="credit_account_id" />}
className={'form-group--credit_account_id'}
>
<AccountsSuggestField
accounts={accounts}
onAccountSelected={(account) => {
form.setFieldValue('credit_account_id', account.id);
form.setFieldValue('currency_code', account.currency_code);
}}
filterByTypes={ACCOUNT_TYPE.EQUITY}
inputProps={{
intent: inputIntent({ error, touched }),
}}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Reference -----------*/}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment>
);
}

View File

@@ -0,0 +1,96 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { FormGroup } from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
FieldRequiredHint,
ListSelect,
Col,
Row,
} from '@/components';
import { inputIntent } from '@/utils';
import { CLASSES, getAddMoneyInOptions } from '@/constants';
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
/**
* Transaction type fields.
*/
export default function TransactionTypeFields() {
// Money in dialog context.
const { cashflowAccounts } = useMoneyInDailogContext();
// Retrieves the add money in button options.
const addMoneyInOptions = useMemo(() => getAddMoneyInOptions(), []);
return (
<div className="trasnaction-type-fileds">
<Row>
<Col xs={5}>
{/*------------ Current account -----------*/}
<FastField name={'cashflow_account_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cash_flow_transaction.label_current_account'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="cashflow_account_id" />}
minimal={true}
className={classNames(
CLASSES.FILL,
'form-group--cashflow_account_id',
)}
>
<AccountsSuggestField
accounts={cashflowAccounts}
onAccountSelected={({ id }) =>
form.setFieldValue('cashflow_account_id', id)
}
inputProps={{
intent: inputIntent({ error, touched }),
}}
/>
</FormGroup>
)}
</FastField>
{/*------------ Transaction type -----------*/}
</Col>
<Col xs={5}>
<Field name={'transaction_type'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'transaction_type'} />}
labelInfo={<FieldRequiredHint />}
helperText={<ErrorMessage name="transaction_type" />}
intent={inputIntent({ error, touched })}
className={classNames(
CLASSES.FILL,
'form-group--transaction_type',
)}
>
<ListSelect
items={addMoneyInOptions}
onItemSelect={(type) => {
setFieldValue('transaction_type', type.value);
}}
filterable={false}
selectedItem={value}
selectedItemProp={'value'}
textProp={'name'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</Field>
</Col>
</Row>
</div>
);
}

View File

@@ -0,0 +1,227 @@
// @ts-nocheck
import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import { DateInput } from '@blueprintjs/datetime';
import {
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FieldRequiredHint,
Col,
Row,
If,
ExchangeRateMutedField,
FeatureCan,
BranchSelect,
BranchSelectButton,
} from '@/components';
import { useAutofocus } from '@/hooks';
import { CLASSES, ACCOUNT_TYPE, Features } from '@/constants';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
} from '@/utils';
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import {
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../../MoneyInDialog/utils';
import { MoneyInOutTransactionNoField } from '../../_components';
/**
* Transfer from account form fields.
*/
export default function TransferFromAccountFormFields() {
// Money in dialog context.
const { accounts, account, branches } = useMoneyInDailogContext();
const isForeigAccount = useForeignAccount();
const amountFieldRef = useAutofocus();
const { values } = useFormikContext();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<React.Fragment>
<FeatureCan feature={Features.Branches}>
<Row>
<Col xs={5}>
<FormGroup
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FormGroup>
</Col>
</Row>
<BranchRowDivider />
</FeatureCan>
<Row>
<Col xs={5}>
{/*------------ Date -----------*/}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
minimal={true}
className={classNames(CLASSES.FILL, 'form-group--date')}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{
position: Position.BOTTOM,
minimal: true,
}}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Transaction number -----------*/}
<MoneyInOutTransactionNoField />
</Col>
</Row>
{/*------------ amount -----------*/}
<FastField name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup>
</FormGroup>
)}
</FastField>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row>
<Col xs={5}>
{/*------------ transfer from account -----------*/}
<FastField name={'credit_account_id'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={
<T id={'cash_flow_transaction.label_transfer_from_account'} />
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="credit_account_id" />}
className={'form-group--credit_account_id'}
>
<AccountsSuggestField
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('credit_account_id', id)
}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CREDIT_CARD,
]}
inputProps={{
intent: inputIntent({ error, touched }),
}}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Reference -----------*/}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment>
);
}

View File

@@ -0,0 +1,40 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Dialog, DialogSuspense } from '@/components';
import withDialogRedux from '@/components/DialogReduxConnect';
import { compose } from '@/utils';
const MoneyInDialogContent = React.lazy(() => import('./MoneyInDialogContent'));
/**
* Money In dialog.
*/
function MoneyInDialog({
dialogName,
payload = { account_type: null, account_id: null, account_name: '' },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={intl.get('cash_flow_transaction.money_in', {
value: payload.account_name,
})}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
className={'dialog--money-in'}
>
<DialogSuspense>
<MoneyInDialogContent
dialogName={dialogName}
accountId={payload.account_id}
accountType={payload.account_type}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(MoneyInDialog);

View File

@@ -0,0 +1,48 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { useFormikContext } from 'formik';
import { transactionNumber } from '@/utils';
import { isEqual, isNull, first } from 'lodash';
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
export const useObserveTransactionNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
React.useEffect(() => {
const TransactionNo = transactionNumber(prefix, nextNumber);
setFieldValue('transacttion_numner', TransactionNo);
}, [setFieldValue, prefix, nextNumber]);
};
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useMoneyInDailogContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};
export const useForeignAccount = () => {
const { values } = useFormikContext();
const { account } = useMoneyInDailogContext();
return (
!isEqual(account.currency_code, values.currency_code) &&
!isNull(account.currency_code)
);
};
export const BranchRowDivider = styled.div`
height: 1px;
background: #ebf1f6;
margin-bottom: 15px;
`;

View File

@@ -0,0 +1,26 @@
// @ts-nocheck
import React from 'react';
import OtherExpnseFormFields from './OtherExpense/OtherExpnseFormFields';
import OwnerDrawingsFormFields from './OwnerDrawings/OwnerDrawingsFormFields';
import TransferToAccountFormFields from './TransferToAccount/TransferToAccountFormFields';
function MoneyOutContentFields({ accountType }) {
const handleTransactionType = () => {
switch (accountType) {
case 'OwnerDrawing':
return <OwnerDrawingsFormFields />;
case 'other_expense':
return <OtherExpnseFormFields />;
case 'transfer_to_account':
return <TransferToAccountFormFields />;
default:
break;
}
};
return <React.Fragment>{handleTransactionType()}</React.Fragment>;
}
export default MoneyOutContentFields;

View File

@@ -0,0 +1,24 @@
// @ts-nocheck
import React from 'react';
import { MoneyOutProvider } from './MoneyOutDialogProvider';
import MoneyOutForm from './MoneyOutForm';
/**
* Money out dailog content.
*/
export default function MoneyOutDialogContent({
// #ownProps
dialogName,
accountId,
accountType,
}) {
return (
<MoneyOutProvider
accountId={accountId}
accountType={accountType}
dialogName={dialogName}
>
<MoneyOutForm />
</MoneyOutProvider>
);
}

View File

@@ -0,0 +1,87 @@
// @ts-nocheck
import React from 'react';
import { DialogContent } from '@/components';
import { Features } from '@/constants';
import { useFeatureCan } from '@/hooks/state';
import {
useAccounts,
useAccount,
useBranches,
useCreateCashflowTransaction,
useCashflowAccounts,
useSettingCashFlow,
} from '@/hooks/query';
const MoneyInDialogContent = React.createContext();
/**
* Money out dialog provider.
*/
function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) {
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Fetches accounts list.
const { isLoading: isAccountsLoading, data: accounts } = useAccounts();
// Fetches the specific account details.
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
enabled: !!accountId,
});
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches({}, { enabled: isBranchFeatureCan });
// Fetch cash flow list .
const { data: cashflowAccounts, isLoading: isCashFlowAccountsLoading } =
useCashflowAccounts({}, { keepPreviousData: true });
const { mutateAsync: createCashflowTransactionMutate } =
useCreateCashflowTransaction();
// Handle fetching settings.
const { isLoading: isSettingsLoading } = useSettingCashFlow();
// Submit payload.
const [submitPayload, setSubmitPayload] = React.useState({});
// provider.
const provider = {
accounts,
account,
accountId,
accountType,
branches,
isAccountsLoading,
isBranchesSuccess,
cashflowAccounts,
submitPayload,
dialogName,
createCashflowTransactionMutate,
setSubmitPayload,
};
return (
<DialogContent
isLoading={
isAccountsLoading ||
isCashFlowAccountsLoading ||
isBranchesLoading ||
isSettingsLoading
}
>
<MoneyInDialogContent.Provider value={provider} {...props} />
</DialogContent>
);
}
const useMoneyOutDialogContext = () => React.useContext(MoneyInDialogContent);
export { MoneyOutProvider, useMoneyOutDialogContext };

View File

@@ -0,0 +1,69 @@
// @ts-nocheck
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from '@/components';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { compose } from '@/utils';
/**
* Money out floating actions.
*/
function MoneyOutFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting, submitForm } = useFormikContext();
// money in dialog context.
const { dialogName, setSubmitPayload, submitPayload } =
useMoneyOutDialogContext();
// handle submit as draft button click.
const handleSubmitDraftBtnClick = (event) => {
setSubmitPayload({ publish: false });
submitForm();
};
// Handle submit button click.
const handleSubmittBtnClick = (event) => {
setSubmitPayload({ publish: true });
submitForm();
};
// Handle close button click.
const handleCloseBtnClick = (event) => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button
disabled={isSubmitting}
onClick={handleCloseBtnClick}
style={{ minWidth: '75px' }}
>
<T id={'close'} />
</Button>
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
loading={isSubmitting && submitPayload.publish}
style={{ minWidth: '75px' }}
type="submit"
onClick={handleSubmittBtnClick}
>
{<T id={'save_and_publish'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(MoneyOutFloatingActions);

View File

@@ -0,0 +1,23 @@
// @ts-nocheck
import * as Yup from 'yup';
import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from '@/constants/dataTypes';
const Schema = Yup.object().shape({
date: Yup.date().required().label(intl.get('date')),
amount: Yup.number().required().label(intl.get('amount')),
transaction_number: Yup.string(),
transaction_type: Yup.string().required(),
reference_no: Yup.string(),
credit_account_id: Yup.number().required(),
cashflow_account_id: Yup.string().required(),
branch_id: Yup.string(),
exchange_rate: Yup.number(),
description: Yup.string()
.min(3)
.max(DATATYPES_LENGTH.TEXT)
.label(intl.get('description')),
publish: Yup.boolean(),
});
export const CreateMoneyOutSchema = Schema;

View File

@@ -0,0 +1,115 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import moment from 'moment';
import { Intent } from '@blueprintjs/core';
import { Formik } from 'formik';
import { omit } from 'lodash';
import '@/style/pages/CashFlow/CashflowTransactionForm.scss';
import { AppToaster } from '@/components';
import MoneyOutFormContent from './MoneyOutFormContent';
import { CreateMoneyOutSchema } from './MoneyOutForm.schema';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
import withSettings from '@/containers/Settings/withSettings';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
import { compose, transactionNumber } from '@/utils';
const defaultInitialValues = {
date: moment(new Date()).format('YYYY-MM-DD'),
amount: '',
transaction_number: '',
transaction_type: '',
reference_no: '',
cashflow_account_id: '',
credit_account_id: '',
description: '',
publish: '',
exchange_rate: 1,
};
function MoneyOutForm({
// #withDialogActions
closeDialog,
// #withCurrentOrganization
organization: { base_currency },
// #withSettings
transactionNextNumber,
transactionNumberPrefix,
transactionIncrementMode,
}) {
const {
dialogName,
accountId,
accountType,
createCashflowTransactionMutate,
submitPayload,
} = useMoneyOutDialogContext();
// transaction number.
const transactionNo = transactionNumber(
transactionNumberPrefix,
transactionNextNumber,
);
// Initial form values.
const initialValues = {
...defaultInitialValues,
currency_code: base_currency,
transaction_type: accountType,
...(transactionIncrementMode && {
transaction_number: transactionNo,
}),
cashflow_account_id: accountId,
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = {
...omit(values, ['currency_code']),
publish: true,
};
setSubmitting(true);
createCashflowTransactionMutate(form)
.then(() => {
closeDialog(dialogName);
AppToaster.show({
message: intl.get('cash_flow_transaction_success_message'),
intent: Intent.SUCCESS,
});
})
.finally(() => {
setSubmitting(false);
});
};
return (
<div>
<Formik
validationSchema={CreateMoneyOutSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<MoneyOutFormContent />
</Formik>
</div>
);
}
export default compose(
withDialogActions,
withCurrentOrganization(),
withSettings(({ cashflowSetting }) => ({
transactionNextNumber: cashflowSetting?.nextNumber,
transactionNumberPrefix: cashflowSetting?.numberPrefix,
transactionIncrementMode: cashflowSetting?.autoIncrement,
})),
)(MoneyOutForm);

View File

@@ -0,0 +1,22 @@
// @ts-nocheck
import React from 'react';
import { Form } from 'formik';
import MoneyOutFormFields from './MoneyOutFormFields';
import MoneyOutFormDialog from './MoneyOutFormDialog';
import MoneyOutFloatingActions from './MoneyOutFloatingActions';
import { MoneyInOutSyncIncrementSettingsToForm } from '../_components';
/**
* Money out form content.
*/
export default function MoneyOutFormContent() {
return (
<Form>
<MoneyOutFormFields />
<MoneyOutFormDialog />
<MoneyOutFloatingActions />
<MoneyInOutSyncIncrementSettingsToForm />
</Form>
);
}

View File

@@ -0,0 +1,28 @@
// @ts-nocheck
import React from 'react';
import { useFormikContext } from 'formik';
import TransactionNumberDialog from '@/containers/Dialogs/TransactionNumberDialog';
/**
* Money out form dialog.
*/
export default function MoneyOutFormDialog() {
const { setFieldValue } = useFormikContext();
// Update the form once the transaction number form submit confirm.
const handleTransactionNumberFormConfirm = ({
incrementNumber,
manually,
}) => {
setFieldValue('transaction_number', incrementNumber || '');
setFieldValue('transaction_number_manually', manually);
};
return (
<React.Fragment>
<TransactionNumberDialog
dialogName={'transaction-number-form'}
onConfirm={handleTransactionNumberFormConfirm}
/>
</React.Fragment>
);
}

View File

@@ -0,0 +1,30 @@
// @ts-nocheck
import React from 'react';
import { useFormikContext } from 'formik';
import { Classes } from '@blueprintjs/core';
import { If } from '@/components';
import MoneyOutContentFields from './MoneyOutContentFields';
import TransactionTypeFields from './TransactionTypeFields';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
/**
* Money out form fields.
*/
function MoneyOutFormFields() {
// Money in dialog context.
const { accountId } = useMoneyOutDialogContext();
const { values } = useFormikContext();
return (
<div className={Classes.DIALOG_BODY}>
<If condition={!accountId}>
<TransactionTypeFields />
</If>
<MoneyOutContentFields accountType={values.transaction_type} />
</div>
);
}
export default MoneyOutFormFields;

View File

@@ -0,0 +1,226 @@
// @ts-nocheck
import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FieldRequiredHint,
Col,
Row,
If,
FeatureCan,
BranchSelect,
BranchSelectButton,
ExchangeRateMutedField,
} from '@/components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from '@/hooks';
import { Features, ACCOUNT_TYPE } from '@/constants';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
} from '@/utils';
import { CLASSES } from '@/constants/classes';
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import {
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import { MoneyInOutTransactionNoField } from '../../_components';
/**
* Other expense form fields.
*/
export default function OtherExpnseFormFields() {
// Money in dialog context.
const { accounts, account, branches } = useMoneyOutDialogContext();
const isForeigAccount = useForeignAccount();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<React.Fragment>
<FeatureCan feature={Features.Branches}>
<Row>
<Col xs={5}>
<FormGroup
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FormGroup>
</Col>
</Row>
<BranchRowDivider />
</FeatureCan>
<Row>
<Col xs={5}>
{/*------------ Date -----------*/}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
minimal={true}
className={classNames(CLASSES.FILL, 'form-group--date')}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{
position: Position.BOTTOM,
minimal: true,
}}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Transaction number -----------*/}
<MoneyInOutTransactionNoField />
</Col>
</Row>
{/*------------ amount -----------*/}
<FastField name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup>
</FormGroup>
)}
</FastField>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values.currency_code}
toCurrency={account.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row>
<Col xs={5}>
{/*------------ other expense account -----------*/}
<FastField name={'credit_account_id'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cash_flow_transaction.label_expense_account'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="credit_account_id" />}
className={'form-group--credit_account_id'}
>
<AccountsSuggestField
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('credit_account_id', id)
}
filterByTypes={[
ACCOUNT_TYPE.EXPENSE,
ACCOUNT_TYPE.OTHER_EXPENSE,
]}
inputProps={{
intent: inputIntent({ error, touched }),
}}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Reference -----------*/}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment>
);
}

View File

@@ -0,0 +1,220 @@
// @ts-nocheck
import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import { DateInput } from '@blueprintjs/datetime';
import {
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FieldRequiredHint,
If,
Col,
Row,
FeatureCan,
BranchSelect,
BranchSelectButton,
ExchangeRateMutedField,
} from '@/components';
import { useAutofocus } from '@/hooks';
import { CLASSES, Features, ACCOUNT_TYPE } from '@/constants';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
} from '@/utils';
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import {
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../../MoneyOutDialog/utils';
import { MoneyInOutTransactionNoField } from '../../_components';
/**
* Owner drawings form fields.
*/
export default function OwnerDrawingsFormFields() {
// Money out dialog context.
const { accounts, account, branches } = useMoneyOutDialogContext();
const { values } = useFormikContext();
const isForeigAccount = useForeignAccount();
const amountFieldRef = useAutofocus();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<React.Fragment>
<FeatureCan feature={Features.Branches}>
<Row>
<Col xs={5}>
<FormGroup
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FormGroup>
</Col>
</Row>
<BranchRowDivider />
</FeatureCan>
<Row>
<Col xs={5}>
{/*------------ Date -----------*/}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
minimal={true}
className={classNames(CLASSES.FILL, 'form-group--date')}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{
position: Position.BOTTOM,
minimal: true,
}}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Transaction number -----------*/}
<MoneyInOutTransactionNoField />
</Col>
</Row>
{/*------------ amount -----------*/}
<Field name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={(ref) => (amountFieldRef.current = ref)}
intent={inputIntent({ error, touched })}
/>
</ControlGroup>
</FormGroup>
)}
</Field>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values?.currency_code}
toCurrency={account?.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row>
<Col xs={5}>
{/*------------ equitty account -----------*/}
<FastField name={'credit_account_id'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cash_flow_transaction.label_equity_account'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="credit_account_id" />}
className={'form-group--credit_account_id'}
>
<AccountsSuggestField
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('credit_account_id', id)
}
filterByTypes={ACCOUNT_TYPE.EQUITY}
inputProps={{
intent: inputIntent({ error, touched }),
}}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Reference -----------*/}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment>
);
}

View File

@@ -0,0 +1,99 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { FormGroup } from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
FieldRequiredHint,
ListSelect,
Col,
Row,
} from '@/components';
import { inputIntent } from '@/utils';
import { CLASSES } from '@/constants/classes';
import { getAddMoneyOutOptions } from '@/constants/cashflowOptions';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
/**
* Transaction type fields.
*/
function TransactionTypeFields() {
// Money in dialog context.
const { cashflowAccounts } = useMoneyOutDialogContext();
const addMoneyOutOptions = useMemo(() => getAddMoneyOutOptions(), []);
return (
<div className="trasnaction-type-fileds">
<Row>
<Col xs={5}>
{/*------------ Current account -----------*/}
<FastField name={'cashflow_account_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cash_flow_transaction.label_current_account'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="cashflow_account_id" />}
minimal={true}
className={classNames(
CLASSES.FILL,
'form-group--cashflow_account_id',
)}
>
<AccountsSuggestField
accounts={cashflowAccounts}
onAccountSelected={({ id }) =>
form.setFieldValue('cashflow_account_id', id)
}
inputProps={{
intent: inputIntent({ error, touched }),
}}
/>
</FormGroup>
)}
</FastField>
{/*------------ Transaction type -----------*/}
</Col>
<Col xs={5}>
<Field name={'transaction_type'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'transaction_type'} />}
labelInfo={<FieldRequiredHint />}
helperText={<ErrorMessage name="transaction_type" />}
intent={inputIntent({ error, touched })}
className={classNames(
CLASSES.FILL,
'form-group--transaction_type',
)}
>
<ListSelect
items={addMoneyOutOptions}
onItemSelect={(type) => {
setFieldValue('transaction_type', type.value);
}}
filterable={false}
selectedItem={value}
selectedItemProp={'value'}
textProp={'name'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</Field>
</Col>
</Row>
</div>
);
}
export default TransactionTypeFields;

View File

@@ -0,0 +1,233 @@
// @ts-nocheck
import React from 'react';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FieldRequiredHint,
Icon,
Col,
Row,
If,
InputPrependButton,
FeatureCan,
BranchSelect,
BranchSelectButton,
ExchangeRateMutedField,
} from '@/components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from '@/hooks';
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
compose,
} from '@/utils';
import { Features } from '@/constants';
import { CLASSES } from '@/constants/classes';
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import {
useObserveTransactionNoSettings,
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import withSettings from '@/containers/Settings/withSettings';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { MoneyInOutTransactionNoField } from '../../_components';
/**
* Transfer to account form fields.
*/
export default function TransferToAccountFormFields() {
// Money in dialog context.
const { accounts, account, branches } = useMoneyOutDialogContext();
const { values } = useFormikContext();
const isForeigAccount = useForeignAccount();
const accountRef = useAutofocus();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<React.Fragment>
<FeatureCan feature={Features.Branches}>
<Row>
<Col xs={5}>
<FormGroup
label={<T id={'branch'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FormGroup>
</Col>
</Row>
<BranchRowDivider />
</FeatureCan>
<Row>
<Col xs={5}>
{/*------------ Date -----------*/}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
minimal={true}
className={classNames(CLASSES.FILL, 'form-group--date')}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{
position: Position.BOTTOM,
minimal: true,
}}
intent={inputIntent({ error, touched })}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Transaction number -----------*/}
<MoneyInOutTransactionNoField />
</Col>
</Row>
{/*------------ amount -----------*/}
<FastField name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'amount'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
inputRef={accountRef}
intent={inputIntent({ error, touched })}
/>
</ControlGroup>
</FormGroup>
)}
</FastField>
<If condition={isForeigAccount}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={values?.currency_code}
toCurrency={account?.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row>
<Col xs={5}>
{/*------------ transfer from account -----------*/}
<FastField name={'credit_account_id'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={
<T id={'cash_flow_transaction.label_transfer_to_account'} />
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="credit_account_id" />}
className={'form-group--credit_account_id'}
>
<AccountsSuggestField
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('credit_account_id', id)
}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CREDIT_CARD,
]}
inputProps={{
intent: inputIntent({ error, touched }),
}}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/*------------ Reference -----------*/}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
className={'form-group--reference-no'}
>
<InputGroup
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
>
<TextArea
growVertically={true}
large={true}
intent={inputIntent({ error, touched })}
{...field}
/>
</FormGroup>
)}
</FastField>
</React.Fragment>
);
}

View File

@@ -0,0 +1,43 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Dialog, DialogSuspense } from '@/components';
import withDialogRedux from '@/components/DialogReduxConnect';
import { compose } from 'redux';
const MoneyOutDialogContent = React.lazy(() =>
import('./MoneyOutDialogContent'),
);
/**
* Money out dialog.
*/
function MoneyOutDialog({
dialogName,
payload = { account_type: null, account_id: null, account_name: '' },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={intl.get('cash_flow_transaction.money_out', {
value: payload.account_name,
})}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
className={'dialog--money-out'}
>
<DialogSuspense>
<MoneyOutDialogContent
dialogName={dialogName}
accountId={payload.account_id}
accountType={payload.account_type}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(MoneyOutDialog);

View File

@@ -0,0 +1,47 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { useFormikContext } from 'formik';
import { transactionNumber } from '@/utils';
import { first, isEqual, isNull } from 'lodash';
import { useMoneyOutDialogContext } from './MoneyOutDialogProvider';
export const useObserveTransactionNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
React.useEffect(() => {
const TransactionNo = transactionNumber(prefix, nextNumber);
setFieldValue('transacttion_numner', TransactionNo);
}, [setFieldValue, prefix, nextNumber]);
};
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useMoneyOutDialogContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};
export const useForeignAccount = () => {
const { values } = useFormikContext();
const { account } = useMoneyOutDialogContext();
return (
!isEqual(account.currency_code, values.currency_code) &&
!isNull(account.currency_code)
);
};
export const BranchRowDivider = styled.div`
height: 1px;
background: #ebf1f6;
margin-bottom: 15px;
`;

View File

@@ -0,0 +1,127 @@
// @ts-nocheck
import React from 'react';
import { useFormikContext } from 'formik';
import { InputGroup, Position, ControlGroup } from '@blueprintjs/core';
import * as R from 'ramda';
import {
FFormGroup,
Icon,
InputPrependButton,
FormattedMessage as T,
} from '@/components';
import { useUpdateEffect } from '@/hooks';
import withSettings from '@/containers/Settings/withSettings';
import withDialogActions from '@/containers/Dialog/withDialogActions';
/**
* Syncs cashflow auto-increment settings to the form once update.
*/
export const MoneyInOutSyncIncrementSettingsToForm = R.compose(
withDialogActions,
withSettings(({ cashflowSetting }) => ({
transactionAutoIncrement: cashflowSetting?.autoIncrement,
transactionNextNumber: cashflowSetting?.nextNumber,
transactionNumberPrefix: cashflowSetting?.numberPrefix,
})),
)(
({
// #withSettings
transactionAutoIncrement,
transactionNextNumber,
transactionNumberPrefix,
}) => {
const { setFieldValue } = useFormikContext();
useUpdateEffect(() => {
// Do not update if the invoice auto-increment is disabled.
if (!transactionAutoIncrement) return null;
const transactionNumber = transactionNumber(
transactionNumberPrefix,
transactionNextNumber,
);
setFieldValue('transaction_number', transactionNumber);
}, [setFieldValue, transactionNumberPrefix, transactionNextNumber]);
return null;
},
);
/**
* Money In/Out transaction number field.
*/
export const MoneyInOutTransactionNoField = R.compose(
withDialogActions,
withSettings(({ cashflowSetting }) => ({
transactionAutoIncrement: cashflowSetting?.autoIncrement,
transactionNextNumber: cashflowSetting?.nextNumber,
transactionNumberPrefix: cashflowSetting?.numberPrefix,
})),
)(
({
// #withDialogActions
openDialog,
// #withSettings
transactionAutoIncrement,
}) => {
const { values, setFieldValue } = useFormikContext();
// Handle tranaction number changing.
const handleTransactionNumberChange = () => {
openDialog('transaction-number-form');
};
// Handle transaction no. field blur.
const handleTransactionNoBlur = (event) => {
const newValue = event.target.value;
if (values.transaction_number !== newValue && transactionAutoIncrement) {
openDialog('transaction-number-form', {
initialFormValues: {
onceManualNumber: newValue,
incrementMode: 'manual-transaction',
},
});
}
if (!transactionAutoIncrement) {
setFieldValue('transaction_number', values.transaction_number);
setFieldValue('transaction_number_manually', values.transaction_number);
}
};
return (
<FFormGroup
name={'transaction_number'}
label={<T id={'transaction_number'} />}
>
<ControlGroup fill={true}>
<InputGroup
minimal={true}
value={values.transaction_number}
asyncControl={true}
onBlur={handleTransactionNoBlur}
/>
<InputPrependButton
buttonProps={{
onClick: handleTransactionNumberChange,
icon: <Icon icon={'settings-18'} />,
}}
tooltip={true}
tooltipProps={{
content: (
<T
id={
'cash_flow.setting_your_auto_generated_transaction_number'
}
/>
),
position: Position.BOTTOM_LEFT,
}}
/>
</ControlGroup>
</FFormGroup>
);
},
);