Merge branch 'develop' into main

This commit is contained in:
a.bouhuolia
2022-03-24 15:32:30 +02:00
757 changed files with 26203 additions and 1575 deletions

View File

@@ -13,13 +13,13 @@ const Schema = Yup.object().shape({
.min(1)
.max(DATATYPES_LENGTH.STRING)
.label(intl.get('journal_type')),
date: Yup.date()
.required()
.label(intl.get('date')),
date: Yup.date().required().label(intl.get('date')),
currency_code: Yup.string().max(3),
publish: Yup.boolean(),
branch_id: Yup.string(),
reference: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING),
description: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
exchange_rate: Yup.number(),
entries: Yup.array().of(
Yup.object().shape({
credit: Yup.number().nullable(),

View File

@@ -10,7 +10,7 @@ import { useMakeJournalFormContext } from './MakeJournalProvider';
* Make journal entries field.
*/
export default function MakeJournalEntriesField() {
const { accounts, contacts } = useMakeJournalFormContext();
const { accounts, contacts ,branches } = useMakeJournalFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
@@ -18,6 +18,7 @@ export default function MakeJournalEntriesField() {
name={'entries'}
contacts={contacts}
accounts={accounts}
branches={branches}
shouldUpdate={entriesFieldShouldUpdate}
>
{({

View File

@@ -17,6 +17,7 @@ import MakeJournalFormFloatingActions from './MakeJournalFormFloatingActions';
import MakeJournalEntriesField from './MakeJournalEntriesField';
import MakeJournalFormFooter from './MakeJournalFormFooter';
import MakeJournalFormDialogs from './MakeJournalFormDialogs';
import MakeJournalFormTopBar from './MakeJournalFormTopBar';
import withSettings from 'containers/Settings/withSettings';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
@@ -58,6 +59,7 @@ function MakeJournalEntriesForm({
journalNumberPrefix,
journalNextNumber,
);
// Form initial values.
const initialValues = useMemo(
() => ({
@@ -68,12 +70,12 @@ function MakeJournalEntriesForm({
: {
...defaultManualJournal,
...(journalAutoIncrement && {
journal_number: defaultTo(journalNumber, ''),
journal_number: journalNumber,
}),
currency_code: base_currency,
}),
}),
[manualJournal, base_currency, journalNumber],
[manualJournal, base_currency, journalNumber, journalAutoIncrement],
);
// Handle the form submiting.
@@ -107,7 +109,7 @@ function MakeJournalEntriesForm({
return;
}
const form = {
...omit(values, ['journal_number', 'journal_number_manually']),
...omit(values, ['journal_number_manually']),
...(values.journal_number_manually && {
journal_number: values.journal_number,
}),
@@ -169,6 +171,7 @@ function MakeJournalEntriesForm({
onSubmit={handleSubmit}
>
<Form>
<MakeJournalFormTopBar />
<MakeJournalEntriesHeader />
<MakeJournalEntriesField />
<MakeJournalFormFooter />
@@ -185,7 +188,7 @@ function MakeJournalEntriesForm({
export default compose(
withMediaActions,
withSettings(({ manualJournalsSettings }) => ({
journalNextNumber: parseInt(manualJournalsSettings?.nextNumber, 10),
journalNextNumber: manualJournalsSettings?.nextNumber,
journalNumberPrefix: manualJournalsSettings?.numberPrefix,
journalAutoIncrement: manualJournalsSettings?.autoIncrement,
})),

View File

@@ -28,6 +28,7 @@ import {
} from 'components';
import withSettings from 'containers/Settings/withSettings';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import { JournalExchangeRateInputField } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions';
import {
currenciesFieldShouldUpdate,
@@ -52,14 +53,14 @@ function MakeJournalEntriesHeader({
// Handle journal number change.
const handleJournalNumberChange = () => {
openDialog('journal-number-form', {});
openDialog('journal-number-form');
};
// Handle journal number blur.
const handleJournalNoBlur = (form, field) => (event) => {
const newValue = event.target.value;
if (field.value !== newValue) {
if (field.value !== newValue && journalAutoIncrement) {
openDialog('journal-number-form', {
initialFormValues: {
manualTransactionNo: newValue,
@@ -201,13 +202,19 @@ function MakeJournalEntriesHeader({
selectedCurrencyCode={value}
onCurrencySelected={(currencyItem) => {
form.setFieldValue('currency_code', currencyItem.currency_code);
form.setFieldValue('exchange_rate', '');
}}
defaultSelectText={value}
disabled={true}
/>
</FormGroup>
)}
</FastField>
{/* ----------- Exchange rate ----------- */}
<JournalExchangeRateInputField
name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }}
/>
</div>
);
}

View File

@@ -11,7 +11,7 @@ import { MakeJournalProvider } from './MakeJournalProvider';
*/
export default function MakeJournalEntriesPage() {
const { id: journalId } = useParams();
return (
<MakeJournalProvider journalId={journalId}>
<MakeJournalEntriesForm />

View File

@@ -21,15 +21,16 @@ export default function MakeJournalEntriesTable({
entries,
defaultEntry,
error,
initialLinesNumber = 4,
minLinesNumber = 4,
initialLinesNumber = 1,
minLinesNumber = 1,
currencyCode,
}) {
const { accounts, contacts } = useMakeJournalFormContext();
const { accounts, contacts, branches } = useMakeJournalFormContext();
// Memorized data table columns.
const columns = useJournalTableEntriesColumns();
// Handles update datatable data.
const handleUpdateData = (rowIndex, columnId, value) => {
const newRows = compose(
@@ -62,13 +63,13 @@ export default function MakeJournalEntriesTable({
data={entries}
sticky={true}
totalRow={true}
footer={true}
payload={{
accounts,
errors: error,
updateData: handleUpdateData,
removeRow: handleRemoveRow,
contacts,
branches,
autoFocus: ['account_id', 0],
currencyCode,
}}

View File

@@ -1,44 +1,29 @@
import React from 'react';
import { FastField } from 'formik';
import classNames from 'classnames';
import styled from 'styled-components';
import { CLASSES } from 'common/classes';
import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { Postbox, ErrorMessage, Row, Col } from 'components';
import Dragzone from 'components/Dragzone';
import { inputIntent } from 'utils';
import { Row, Col, Paper } from 'components';
import { MakeJournalFormFooterLeft } from './MakeJournalFormFooterLeft';
import { MakeJournalFormFooterRight } from './MakeJournalFormFooterRight';
export default function MakeJournalFormFooter() {
return (
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
<Postbox title={<T id={'journal_details'} />} defaultOpen={false}>
<MakeJournalFooterPaper>
<Row>
<Col md={8}>
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="description" />}
fill={true}
>
<TextArea fill={true} {...field} />
</FormGroup>
)}
</FastField>
<MakeJournalFormFooterLeft />
</Col>
<Col md={4}>
<Dragzone
initialFiles={[]}
// onDrop={handleDropFiles}
// onDeleteFile={handleDeleteFile}
hint={<T id={'attachments_maximum'} />}
/>
<MakeJournalFormFooterRight />
</Col>
</Row>
</Postbox>
</MakeJournalFooterPaper>
</div>
);
}
const MakeJournalFooterPaper = styled(Paper)`
padding: 20px;
`;

View File

@@ -0,0 +1,32 @@
import React from 'react';
import styled from 'styled-components';
import { FFormGroup, FEditableText, FormattedMessage as T } from 'components';
export function MakeJournalFormFooterLeft() {
return (
<React.Fragment>
{/* --------- Description --------- */}
<DescriptionFormGroup
label={<T id={'description'} />}
name={'description'}
>
<FEditableText
name={'description'}
placeholder={<T id={'make_jorunal.decscrption.placeholder'} />}
/>
</DescriptionFormGroup>
</React.Fragment>
);
}
const DescriptionFormGroup = styled(FFormGroup)`
&.bp3-form-group {
.bp3-label {
font-size: 12px;
margin-bottom: 12px;
}
.bp3-form-content {
margin-left: 10px;
}
}
`;

View File

@@ -0,0 +1,34 @@
import React from 'react';
import styled from 'styled-components';
import {
T,
TotalLines,
TotalLine,
TotalLineBorderStyle,
TotalLineTextStyle,
} from 'components';
import { useJournalTotals } from './utils';
export function MakeJournalFormFooterRight() {
const { formattedSubtotal, formattedTotal } = useJournalTotals();
return (
<MakeJouranlTotalLines>
<TotalLine
title={<T id={'make_journal.label.subtotal'} />}
value={formattedSubtotal}
borderStyle={TotalLineBorderStyle.None}
/>
<TotalLine
title={<T id={'make_journal.label.total'} />}
value={formattedTotal}
textStyle={TotalLineTextStyle.Bold}
/>
</MakeJouranlTotalLines>
);
}
const MakeJouranlTotalLines = styled(TotalLines)`
width: 100%;
color: #555555;
`;

View File

@@ -0,0 +1,68 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Button, Alignment, NavbarGroup, Classes } from '@blueprintjs/core';
import styled from 'styled-components';
import { useSetPrimaryBranchToForm } from './utils';
import { useFeatureCan } from 'hooks/state';
import {
Icon,
BranchSelect,
FeatureCan,
FormTopbar,
DetailsBarSkeletonBase,
} from 'components';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import { Features } from 'common';
/**
* Make journal form topbar.
* @returns
*/
export default function MakeJournalFormTopBar() {
// Features guard.
const { featureCan } = useFeatureCan();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
// Can't display the navigation bar if branches feature is not enabled.
if (!featureCan(Features.Branches)) {
return null;
}
return (
<FormTopbar>
<NavbarGroup align={Alignment.LEFT}>
<FeatureCan feature={Features.Branches}>
<MakeJournalFormSelectBranch />
</FeatureCan>
</NavbarGroup>
</FormTopbar>
);
}
function MakeJournalFormSelectBranch() {
// Invoice form context.
const { branches, isBranchesLoading } = useMakeJournalFormContext();
return isBranchesLoading ? (
<DetailsBarSkeletonBase className={Classes.SKELETON} />
) : (
<BranchSelect
name={'branch_id'}
branches={branches}
input={MakeJournalBranchSelectButton}
popoverProps={{ minimal: true }}
/>
);
}
function MakeJournalBranchSelectButton({ label }) {
return (
<Button
text={intl.get('make_journal.branch_button.label', { label })}
minimal={true}
small={true}
icon={<Icon icon={'branch-16'} iconSize={16} />}
/>
);
}

View File

@@ -1,4 +1,7 @@
import React, { createContext, useState } from 'react';
import { isEqual, isUndefined } from 'lodash';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import {
useAccounts,
@@ -8,7 +11,8 @@ import {
useCreateJournal,
useEditJournal,
useSettings,
useSettingsManualJournals
useBranches,
useSettingsManualJournals,
} from 'hooks/query';
const MakeJournalFormContext = createContext();
@@ -16,15 +20,17 @@ const MakeJournalFormContext = createContext();
/**
* Make journal form provider.
*/
function MakeJournalProvider({ journalId, ...props }) {
function MakeJournalProvider({ journalId, query, ...props }) {
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Load the accounts list.
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
// Load the customers list.
const {
data: contacts,
isLoading: isContactsLoading,
} = useAutoCompleteContacts();
const { data: contacts, isLoading: isContactsLoading } =
useAutoCompleteContacts();
// Load the currencies list.
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
@@ -43,15 +49,27 @@ function MakeJournalProvider({ journalId, ...props }) {
// Loading the journal settings.
const { isLoading: isSettingsLoading } = useSettingsManualJournals();
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches(query, { enabled: isBranchFeatureCan });
// Submit form payload.
const [submitPayload, setSubmitPayload] = useState({});
// Determines whether the warehouse and branches are loading.
const isFeatureLoading = isBranchesLoading;
const provider = {
accounts,
contacts,
currencies,
manualJournal,
branches,
createJournalMutate,
editJournalMutate,
@@ -59,12 +77,13 @@ function MakeJournalProvider({ journalId, ...props }) {
isContactsLoading,
isCurrenciesLoading,
isJournalLoading,
isFeatureLoading,
isSettingsLoading,
isBranchesSuccess,
isNewMode: !journalId,
submitPayload,
setSubmitPayload
setSubmitPayload,
};
return (
@@ -73,7 +92,7 @@ function MakeJournalProvider({ journalId, ...props }) {
isJournalLoading ||
isAccountsLoading ||
isCurrenciesLoading ||
isContactsLoading ||
isContactsLoading ||
isSettingsLoading
}
name={'make-journal-page'}

View File

@@ -1,15 +1,23 @@
import React from 'react';
import { Intent, Position, Button, Tooltip } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { Icon, Money, Hint } from 'components';
import { Menu, MenuItem, Position, Button } from '@blueprintjs/core';
import { Popover2 } from '@blueprintjs/popover2';
import { useFormikContext } from 'formik';
import intl from 'react-intl-universal';
import { ExchangeRateInputGroup, Icon, Hint, FormattedMessage as T } from 'components';
import {
AccountsListFieldCell,
MoneyFieldCell,
InputGroupCell,
ContactsListFieldCell,
BranchesListFieldCell,
} from 'components/DataTableCells';
import { safeSumBy } from 'utils';
import { CellType, Features, Align } from 'common';
import { useFeatureCan } from 'hooks/state';
import { useCurrentOrganization } from 'hooks/state';
import { useJournalIsForeign } from './utils';
/**
* Contact header cell.
@@ -40,42 +48,6 @@ export function DebitHeaderCell({ payload: { currencyCode } }) {
return intl.get('debit_currency', { currency: currencyCode });
}
/**
* Account footer cell.
*/
function AccountFooterCell({ payload: { currencyCode } }) {
return (
<span>
{intl.get('total_currency', { currency: currencyCode })}
</span>
);
}
/**
* Total credit table footer cell.
*/
function TotalCreditFooterCell({ payload: { currencyCode }, rows }) {
const credit = safeSumBy(rows, 'original.credit');
return (
<span>
<Money amount={credit} currency={currencyCode} />
</span>
);
}
/**
* Total debit table footer cell.
*/
function TotalDebitFooterCell({ payload: { currencyCode }, rows }) {
const debit = safeSumBy(rows, 'original.debit');
return (
<span>
<Money amount={debit} currency={currencyCode} />
</span>
);
}
/**
* Actions cell renderer.
*/
@@ -86,95 +58,120 @@ export const ActionsCellRenderer = ({
data,
payload,
}) => {
const onClickRemoveRole = () => {
const handleClickRemoveRole = () => {
payload.removeRow(index);
};
const exampleMenu = (
<Menu>
<MenuItem onClick={handleClickRemoveRole} text="Remove line" />
</Menu>
);
return (
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
<Popover2 content={exampleMenu} placement="left-start">
<Button
icon={<Icon icon="times-circle" iconSize={14} />}
icon={<Icon icon={'more-13'} iconSize={13} />}
iconSize={14}
className="ml2"
className="m12"
minimal={true}
intent={Intent.DANGER}
onClick={onClickRemoveRole}
/>
</Tooltip>
</Popover2>
);
};
ActionsCellRenderer.cellType = CellType.Button;
/**
* Retrieve columns of make journal entries table.
*/
export const useJournalTableEntriesColumns = () => {
const { featureCan } = useFeatureCan();
return React.useMemo(
() => [
{
Header: '#',
accessor: 'index',
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
className: 'index',
width: 40,
disableResizing: true,
disableSortBy: true,
sticky: 'left',
},
{
Header: intl.get('account'),
id: 'account_id',
accessor: 'account_id',
Cell: AccountsListFieldCell,
Footer: AccountFooterCell,
className: 'account',
disableSortBy: true,
width: 160,
fieldProps: { allowCreate: true }
fieldProps: { allowCreate: true },
},
{
Header: CreditHeaderCell,
accessor: 'credit',
Cell: MoneyFieldCell,
Footer: TotalCreditFooterCell,
className: 'credit',
disableSortBy: true,
width: 100,
align: Align.Right,
},
{
Header: DebitHeaderCell,
accessor: 'debit',
Cell: MoneyFieldCell,
Footer: TotalDebitFooterCell,
className: 'debit',
disableSortBy: true,
width: 100,
align: Align.Right,
},
{
Header: ContactHeaderCell,
id: 'contact_id',
accessor: 'contact_id',
Cell: ContactsListFieldCell,
className: 'contact',
disableSortBy: true,
width: 120,
},
...(featureCan(Features.Branches)
? [
{
Header: intl.get('branch'),
id: 'branch_id',
accessor: 'branch_id',
Cell: BranchesListFieldCell,
disableSortBy: true,
width: 120,
},
]
: []),
{
Header: intl.get('note'),
accessor: 'note',
Cell: InputGroupCell,
disableSortBy: true,
className: 'note',
width: 200,
},
{
Header: '',
accessor: 'action',
Cell: ActionsCellRenderer,
className: 'actions',
disableSortBy: true,
disableResizing: true,
width: 45,
align: Align.Center,
},
],
[],
);
};
/**
* Journal exchange rate input field.
* @returns {JSX.Element}
*/
export function JournalExchangeRateInputField({ ...props }) {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
const isForeignJouranl = useJournalIsForeign();
// Can't continue if the customer is not foreign.
if (!isForeignJouranl) {
return null;
}
return (
<ExchangeRateInputGroup
fromCurrency={values.currency_code}
toCurrency={currentOrganization.base_currency}
{...props}
/>
);
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Intent } from '@blueprintjs/core';
import { sumBy, setWith, toSafeInteger, get } from 'lodash';
import { sumBy, setWith, toSafeInteger, get, first } from 'lodash';
import moment from 'moment';
import * as R from 'ramda';
import {
@@ -9,11 +9,15 @@ import {
repeatValue,
transformToForm,
defaultFastFieldShouldUpdate,
ensureEntriesHasEmptyLine
ensureEntriesHasEmptyLine,
formattedAmount,
safeSumBy,
} from 'utils';
import { AppToaster } from 'components';
import intl from 'react-intl-universal';
import { useFormikContext } from 'formik';
import { useMakeJournalFormContext } from './MakeJournalProvider';
import { useCurrentOrganization } from 'hooks/state';
const ERROR = {
JOURNAL_NUMBER_ALREADY_EXISTS: 'JOURNAL.NUMBER.ALREADY.EXISTS',
@@ -26,25 +30,30 @@ const ERROR = {
ENTRIES_SHOULD_ASSIGN_WITH_CONTACT: 'ENTRIES_SHOULD_ASSIGN_WITH_CONTACT',
};
export const MIN_LINES_NUMBER = 4;
export const MIN_LINES_NUMBER = 1;
export const DEFAULT_LINES_NUMBER = 1;
export const defaultEntry = {
account_id: '',
credit: '',
debit: '',
contact_id: '',
branch_id: '',
note: '',
};
export const defaultManualJournal = {
journal_number: '',
journal_number_manually: false,
journal_type: 'Journal',
date: moment(new Date()).format('YYYY-MM-DD'),
description: '',
reference: '',
currency_code: '',
publish: '',
entries: [...repeatValue(defaultEntry, 4)],
branch_id: '',
exchange_rate: 1,
entries: [...repeatValue(defaultEntry, DEFAULT_LINES_NUMBER)],
};
// Transform to edit form.
@@ -179,6 +188,7 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.contacts !== oldProps.contacts ||
newProps.branches !== oldProps.branches ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
@@ -192,3 +202,63 @@ export const currenciesFieldShouldUpdate = (newProps, oldProps) => {
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useMakeJournalFormContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};
/**
* Retreives the Journal totals.
*/
export const useJournalTotals = () => {
const {
values: { entries, currency_code: currencyCode },
} = useFormikContext();
// Retrieves the invoice entries total.
const totalCredit = safeSumBy(entries, 'credit');
const totalDebit = safeSumBy(entries, 'debit');
const total = Math.max(totalCredit, totalDebit);
// Retrieves the formatted total money.
const formattedTotal = React.useMemo(
() => formattedAmount(total, currencyCode),
[total, currencyCode],
);
// Retrieves the formatted subtotal.
const formattedSubtotal = React.useMemo(
() => formattedAmount(total, currencyCode, { money: false }),
[total, currencyCode],
);
return {
formattedTotal,
formattedSubtotal,
};
};
/**
* Detarmines whether the expenses has foreign .
* @returns {boolean}
*/
export const useJournalIsForeign = () => {
const { values } = useFormikContext();
const currentOrganization = useCurrentOrganization();
const isForeignJournal = React.useMemo(
() => values.currency_code !== currentOrganization.base_currency,
[values.currency_code, currentOrganization.base_currency],
);
return isForeignJournal;
};

View File

@@ -0,0 +1,79 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useDeleteBranch } from 'hooks/query';
import { handleDeleteErrors } from '../../Preferences/Branches/utils';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Branch delete alert.
*/
function BranchDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { branchId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: deleteBranch, isLoading } = useDeleteBranch();
// Handle cancel delete alert.
const handleCancelDelete = () => {
closeAlert(name);
};
// Handle confirm delete branch.
const handleConfirmDeleteBranch = () => {
deleteBranch(branchId)
.then(() => {
AppToaster.show({
message: intl.get('branch.alert.delete_message'),
intent: Intent.SUCCESS,
});
})
.catch(
({
response: {
data: { errors },
},
}) => {
handleDeleteErrors(errors);
},
)
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelDelete}
onConfirm={handleConfirmDeleteBranch}
loading={isLoading}
>
<p>
<FormattedHTMLMessage id={'branch.once_delete_this_branch'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(BranchDeleteAlert);

View File

@@ -0,0 +1,70 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { useMarkBranchAsPrimary } from 'hooks/query';
import { AppToaster } from 'components';
import withAlertActions from 'containers/Alert/withAlertActions';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import { compose } from 'utils';
/**
* branch mark primary alert.
*/
function BranchMarkPrimaryAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { branchId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: markPrimaryBranchMutate, isLoading } =
useMarkBranchAsPrimary();
// Handle cancel mark primary alert.
const handleCancelMarkPrimaryAlert = () => {
closeAlert(name);
};
// andle cancel mark primary confirm.
const handleConfirmMarkPrimaryBranch = () => {
markPrimaryBranchMutate(branchId)
.then(() => {
AppToaster.show({
message: intl.get('branch.alert.mark_primary_message'),
intent: Intent.SUCCESS,
});
closeAlert(name);
})
.catch((error) => {
closeAlert(name);
});
};
return (
<Alert
// cancelButtonText={<T id={'cancel'} />}
// confirmButtonText={<T id={'make_primary'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleCancelMarkPrimaryAlert}
onConfirm={handleConfirmMarkPrimaryBranch}
loading={isLoading}
>
<p>
<T id={'branch.alert.are_you_sure_you_want_to_make'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(BranchMarkPrimaryAlert);

View File

@@ -0,0 +1,80 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useDeleteWarehouse } from 'hooks/query';
import { handleDeleteErrors } from '../../Preferences/Warehouses/utils';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Warehouse delete alert
* @returns
*/
function WarehouseDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { warehouseId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: deleteWarehouseMutate, isLoading } =
useDeleteWarehouse();
// handle cancel delete warehouse alert.
const handleCancelDeleteAlert = () => {
closeAlert(name);
};
// handleConfirm delete invoice
const handleConfirmWarehouseDelete = () => {
deleteWarehouseMutate(warehouseId)
.then(() => {
AppToaster.show({
message: intl.get('warehouse.alert.delete_message'),
intent: Intent.SUCCESS,
});
})
.catch(
({
response: {
data: { errors },
},
}) => {
handleDeleteErrors(errors);
},
)
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelDeleteAlert}
onConfirm={handleConfirmWarehouseDelete}
loading={isLoading}
>
<p>
<FormattedHTMLMessage id={'warehouse.once_delete_this_warehouse'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(WarehouseDeleteAlert);

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { useTransferredWarehouseTransfer } from 'hooks/query';
import { AppToaster } from 'components';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* warehouse transfer transferred alert.
* @returns
*/
function TransferredWarehouseTransferAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { warehouseTransferId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: transferredWarehouseTransferMutate, isLoading } =
useTransferredWarehouseTransfer();
// handle cancel alert.
const handleCancelAlert = () => {
closeAlert(name);
};
// Handle confirm alert.
const handleConfirmTransferred = () => {
transferredWarehouseTransferMutate(warehouseTransferId)
.then(() => {
AppToaster.show({
message: intl.get('warehouse_transfer.alert.transferred_warehouse'),
intent: Intent.SUCCESS,
});
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'deliver'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleCancelAlert}
onConfirm={handleConfirmTransferred}
loading={isLoading}
>
<p>
<T id={'warehouse_transfer.alert.are_you_sure_you_want_to_deliver'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(TransferredWarehouseTransferAlert);

View File

@@ -0,0 +1,70 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { useMarkWarehouseAsPrimary } from 'hooks/query';
import { AppToaster } from 'components';
import withAlertActions from 'containers/Alert/withAlertActions';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import { compose } from 'utils';
/**
* warehouse mark primary alert.
*/
function WarehouseMarkPrimaryAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { warehouseId },
// #withAlertActions
closeAlert,
}) {
// const { mutateAsync: markPrimaryWarehouseMutate, isLoading } =
// useMarkWarehouseAsPrimary();
// Handle cancel mark primary alert.
const handleCancelMarkPrimaryAlert = () => {
closeAlert(name);
};
// andle cancel mark primary confirm.
const handleConfirmMarkPrimaryWarehouse = () => {
markPrimaryWarehouseMutate(warehouseId)
.then(() => {
AppToaster.show({
message: intl.get('warehouse.alert.mark_primary_message'),
intent: Intent.SUCCESS,
});
closeAlert(name);
})
.catch((error) => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'make_primary'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleCancelMarkPrimaryAlert}
onConfirm={handleConfirmMarkPrimaryWarehouse}
loading={isLoading}
>
<p>
<T id={'warehouse.alert.are_you_sure_you_want_to_make'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(WarehouseMarkPrimaryAlert);

View File

@@ -0,0 +1,85 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useDeleteWarehouseTransfer } from 'hooks/query';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { compose } from 'utils';
/**
* Warehouse transfer delete alert
* @returns
*/
function WarehouseTransferDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { warehouseTransferId },
// #withAlertActions
closeAlert,
// #withDrawerActions
closeDrawer,
}) {
const { mutateAsync: deleteWarehouseTransferMutate, isLoading } =
useDeleteWarehouseTransfer();
// handle cancel delete warehouse alert.
const handleCancelDeleteAlert = () => {
closeAlert(name);
};
// handleConfirm delete warehouse transfer.
const handleConfirmWarehouseTransferDelete = () => {
deleteWarehouseTransferMutate(warehouseTransferId)
.then(() => {
AppToaster.show({
message: intl.get('warehouse_transfer.alert.delete_message'),
intent: Intent.SUCCESS,
});
closeDrawer('warehouse-transfer-detail-drawer');
})
.catch(
({
response: {
data: { errors },
},
}) => {},
)
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelDeleteAlert}
onConfirm={handleConfirmWarehouseTransferDelete}
loading={isLoading}
>
<p>
<FormattedHTMLMessage
id={'warehouse_transfer.once_delete_this_warehouse_transfer'}
/>
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
withDrawerActions,
)(WarehouseTransferDeleteAlert);

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { useInitiateWarehouseTransfer } from 'hooks/query';
import { AppToaster } from 'components';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* warehouse transfer initiate alert.
* @returns
*/
function WarehouseTransferInitiateAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { warehouseTransferId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: initialWarehouseTransferMutate, isLoading } =
useInitiateWarehouseTransfer();
// handle cancel alert.
const handleCancelAlert = () => {
closeAlert(name);
};
// Handle confirm alert.
const handleConfirmInitiated = () => {
initialWarehouseTransferMutate(warehouseTransferId)
.then(() => {
AppToaster.show({
message: intl.get('warehouse_transfer.alert.initiate_warehouse'),
intent: Intent.SUCCESS,
});
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'warehouse_transfer.label.initiate'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleCancelAlert}
onConfirm={handleConfirmInitiated}
loading={isLoading}
>
<p>
<T id={'warehouse_transfer.alert.are_you_sure_you_want_to_initate'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(WarehouseTransferInitiateAlert);

View File

@@ -19,7 +19,10 @@ import CurrenciesAlerts from '../Preferences/Currencies/CurrenciesAlerts';
import RolesAlerts from '../Preferences/Users/Roles/RolesAlerts';
import CreditNotesAlerts from '../Sales/CreditNotes/CreditNotesAlerts';
import VendorCreditNotesAlerts from '../Purchases/CreditNotes/VendorCreditNotesAlerts';
import TransactionsLockingAlerts from '../TransactionsLocking/TransactionsLockingAlerts'
import TransactionsLockingAlerts from '../TransactionsLocking/TransactionsLockingAlerts';
import WarehousesAlerts from '../Preferences/Warehouses/WarehousesAlerts';
import WarehousesTransfersAlerts from '../WarehouseTransfers/WarehousesTransfersAlerts';
import BranchesAlerts from '../Preferences/Branches/BranchesAlerts';
export default [
...AccountsAlerts,
@@ -43,5 +46,8 @@ export default [
...RolesAlerts,
...CreditNotesAlerts,
...VendorCreditNotesAlerts,
...TransactionsLockingAlerts
...TransactionsLockingAlerts,
...WarehousesAlerts,
...WarehousesTransfersAlerts,
...BranchesAlerts,
];

View File

@@ -3,32 +3,33 @@ import classNames from 'classnames';
import { FormGroup, Position, Classes, ControlGroup } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FastField, ErrorMessage } from 'formik';
import { FFormGroup } from '../../../components/Forms';
import moment from 'moment';
import { Features } from 'common';
import {
MoneyInputGroup,
InputPrependText,
CurrencySelectList,
BranchSelect,
BranchSelectButton,
FeatureCan,
Row,
Col,
} from 'components';
import { FormattedMessage as T } from 'components';
import { useCustomerFormContext } from './CustomerFormProvider';
import {
momentFormatter,
tansformDateValue,
inputIntent,
} from 'utils';
import { useSetPrimaryBranchToForm } from './utils';
import { momentFormatter, tansformDateValue, inputIntent } from 'utils';
/**
* Customer financial panel.
*/
export default function CustomerFinancialPanel() {
const {
currencies,
customerId
} = useCustomerFormContext();
const { currencies, customerId, branches } = useCustomerFormContext();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={'tab-panel--financial'}>
@@ -62,12 +63,7 @@ export default function CustomerFinancialPanel() {
{/*------------ Opening balance -----------*/}
<FastField name={'opening_balance'}>
{({
form,
field,
field: { value },
meta: { error, touched },
}) => (
{({ form, field, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'opening_balance'} />}
className={classNames(
@@ -92,6 +88,23 @@ export default function CustomerFinancialPanel() {
)}
</FastField>
{/*------------ Opening branch -----------*/}
<FeatureCan feature={Features.Branches}>
<FFormGroup
label={<T id={'customer.label.opening_branch'} />}
name={'opening_balance_branch_id'}
inline={true}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'opening_balance_branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FFormGroup>
</FeatureCan>
{/*------------ Currency -----------*/}
<FastField name={'currency_code'}>
{({ form, field: { value }, meta: { error, touched } }) => (
@@ -110,7 +123,6 @@ export default function CustomerFinancialPanel() {
onCurrencySelected={(currency) => {
form.setFieldValue('currency_code', currency.currency_code);
}}
disabled={true}
/>
</FormGroup>
)}

View File

@@ -42,6 +42,7 @@ const Schema = Yup.object().shape({
opening_balance: Yup.number().nullable(),
currency_code: Yup.string(),
opening_balance_at: Yup.date(),
opening_balance_branch_id: Yup.string(),
});
export const CreateCustomerForm = Schema;

View File

@@ -6,15 +6,21 @@ import {
useCreateCustomer,
useEditCustomer,
useContact,
useBranches,
} from 'hooks/query';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
const CustomerFormContext = createContext();
function CustomerFormProvider({ customerId, ...props }) {
function CustomerFormProvider({ query, customerId, ...props }) {
const { state } = useLocation();
const contactId = state?.action;
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Handle fetch customer details.
const { data: customer, isLoading: isCustomerLoading } = useCustomer(
customerId,
@@ -28,6 +34,13 @@ function CustomerFormProvider({ customerId, ...props }) {
// Handle fetch Currencies data table
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches(query, { enabled: isBranchFeatureCan });
// Form submit payload.
const [submitPayload, setSubmitPayload] = useState({});
@@ -38,18 +51,20 @@ function CustomerFormProvider({ customerId, ...props }) {
const isNewMode = contactId || !customerId;
const isFormLoading =
isCustomerLoading || isCurrenciesLoading || isContactLoading;
isCustomerLoading || isCurrenciesLoading || isBranchesLoading;
const provider = {
customerId,
customer,
currencies,
branches,
contactDuplicate,
submitPayload,
isNewMode,
isCustomerLoading,
isCurrenciesLoading,
isBranchesSuccess,
isFormLoading,
setSubmitPayload,

View File

@@ -1,5 +1,9 @@
import React from 'react';
import moment from 'moment';
import { useFormikContext } from 'formik';
import { first } from 'lodash';
import { useCustomerFormContext } from './CustomerFormProvider';
export const defaultInitialValues = {
customer_type: 'business',
@@ -35,4 +39,20 @@ export const defaultInitialValues = {
opening_balance: '',
currency_code: '',
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
opening_balance_branch_id: '',
};
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useCustomerFormContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('opening_balance_branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};

View File

@@ -25,6 +25,7 @@ const defaultInitialValues = {
name: '',
code: '',
description: '',
currency_code:'',
subaccount: false,
};

View File

@@ -17,12 +17,14 @@ import {
Hint,
AccountsSelectList,
AccountsTypesSelect,
CurrencySelect,
} from 'components';
import withAccounts from 'containers/Accounts/withAccounts';
import { inputIntent } from 'utils';
import { compose } from 'redux';
import { useAutofocus } from 'hooks';
import { FOREIGN_CURRENCY_ACCOUNTS } from '../../../common/accountTypes';
import { useAccountDialogContext } from './AccountDialogProvider';
/**
@@ -37,7 +39,7 @@ function AccountFormDialogFields({
const accountNameFieldRef = useAutofocus();
// Account form context.
const { accounts, accountsTypes } = useAccountDialogContext();
const { accounts, accountsTypes, currencies } = useAccountDialogContext();
return (
<Form>
@@ -58,6 +60,7 @@ function AccountFormDialogFields({
defaultSelectText={<T id={'select_account_type'} />}
onTypeSelected={(accountType) => {
form.setFieldValue('account_type', accountType.key);
form.setFieldValue('currency_code', '');
}}
disabled={
action === 'edit' ||
@@ -143,6 +146,7 @@ function AccountFormDialogFields({
)}
inline={true}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="parent_account_id" />}
>
<AccountsSelectList
accounts={accounts}
@@ -159,6 +163,24 @@ function AccountFormDialogFields({
</FastField>
</If>
<If condition={FOREIGN_CURRENCY_ACCOUNTS.includes(values.account_type)}>
{/*------------ Currency -----------*/}
<FastField name={'currency_code'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'currency'} />}
className={classNames('form-group--select-list', Classes.FILL)}
inline={true}
>
<CurrencySelect
name={'currency_code'}
currencies={currencies}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
</If>
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup

View File

@@ -3,6 +3,7 @@ import { DialogContent } from 'components';
import {
useCreateAccount,
useAccountsTypes,
useCurrencies,
useAccount,
useAccounts,
useEditAccount,
@@ -30,16 +31,17 @@ function AccountDialogProvider({
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
// Fetches accounts types.
const {
data: accountsTypes,
isLoading: isAccountsTypesLoading,
} = useAccountsTypes();
const { data: accountsTypes, isLoading: isAccountsTypesLoading } =
useAccountsTypes();
// Fetches the specific account details.
const { data: account, isLoading: isAccountLoading } = useAccount(accountId, {
enabled: !!accountId,
});
// Handle fetch Currencies data table
const { data: currencies, isLoading: isCurrenciesLoading } = useCurrencies();
const isNewMode = !accountId;
// Provider payload.
@@ -49,6 +51,7 @@ function AccountDialogProvider({
parentAccountId,
action,
accountType,
currencies,
createAccountMutate,
editAccountMutate,
@@ -57,11 +60,15 @@ function AccountDialogProvider({
account,
isAccountsLoading,
isNewMode
isCurrenciesLoading,
isNewMode,
};
const isLoading =
isAccountsLoading || isAccountsTypesLoading || isAccountLoading;
isAccountsLoading ||
isAccountsTypesLoading ||
isAccountLoading ||
isCurrenciesLoading;
return (
<DialogContent isLoading={isLoading}>

View File

@@ -10,6 +10,13 @@ export const transformApiErrors = (errors) => {
if (errors.find((e) => e.type === 'ACCOUNT.NAME.NOT.UNIQUE')) {
fields.name = intl.get('account_name_is_already_used');
}
if (
errors.find((e) => e.type === 'ACCOUNT_CURRENCY_NOT_SAME_PARENT_ACCOUNT')
) {
fields.parent_account_id = intl.get(
'accounts.error.account_currency_not_same_parent_account',
);
}
return fields;
};

View File

@@ -37,7 +37,6 @@ function BadDebtForm({
// Initial form values
const initialValues = {
...defaultInitialValues,
currency_code: base_currency,
amount: invoice.due_amount,
};

View File

@@ -30,7 +30,7 @@ import { useBadDebtContext } from './BadDebtFormProvider';
function BadDebtFormFields() {
const amountfieldRef = useAutofocus();
const { accounts } = useBadDebtContext();
const { accounts ,invoice } = useBadDebtContext();
return (
<div className={Classes.DIALOG_BODY}>
@@ -55,7 +55,7 @@ function BadDebtFormFields() {
helperText={<ErrorMessage name="amount" />}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<InputPrependText text={invoice.currency_code} />
<MoneyInputGroup
value={value}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import BranchActivateForm from './BranchActivateForm';
import { BranchActivateFormProvider } from './BranchActivateFormProvider';
export default function BranchActivateDialogContent({
// #ownProps
dialogName,
}) {
return (
<BranchActivateFormProvider dialogName={dialogName}>
<BranchActivateForm />
</BranchActivateFormProvider>
);
}

View File

@@ -0,0 +1,64 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useBranchActivateContext } from './BranchActivateFormProvider';
import BranchActivateFormContent from './BranchActivateFormContent';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Branch activate form.
*/
function BranchActivateForm({
// #withDialogActions
closeDialog,
}) {
const { activateBranches, dialogName } = useBranchActivateContext();
// Initial form values
const initialValues = {};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = {
...values,
};
setSubmitting(true);
// Handle request response success.
const onSuccess = (response) => {
AppToaster.show({
message: intl.get('branch_activate.dialog_success_message'),
intent: Intent.SUCCESS,
});
closeDialog(dialogName);
};
// Handle request response errors.
const onError = ({
response: {
data: { errors },
},
}) => {
if (errors) {
}
setSubmitting(false);
};
activateBranches(form).then(onSuccess).catch(onError);
};
return (
<Formik
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={BranchActivateFormContent}
/>
);
}
export default compose(withDialogActions)(BranchActivateForm);

View File

@@ -0,0 +1,26 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Form } from 'formik';
import { Classes } from '@blueprintjs/core';
import BranchActivateFormFloatingActions from './BranchActivateFormFloatingActions';
/**
* Branch activate form content.
*/
export default function BranchActivateFormContent() {
return (
<Form>
<div className={Classes.DIALOG_BODY}>
<p class="paragraph">
{intl.getHTML('branch_activate.dialog_paragraph')}
</p>
<ul class="paragraph list">
<li>{intl.get('branch_activate.dialog_paragraph.line_1')}</li>
<li>{intl.get('branch_activate.dialog_paragraph.line_2')}</li>
</ul>
</div>
<BranchActivateFormFloatingActions />
</Form>
);
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from 'components';
import { useBranchActivateContext } from './BranchActivateFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* branch activate form floating actions.
*/
function BranchActivateFormFloatingActions({
// #withDialogActions
closeDialog,
}) {
// branch activate dialog context.
const { dialogName } = useBranchActivateContext();
// Formik context.
const { isSubmitting } = useFormikContext();
// Handle close button click.
const handleCancelBtnClick = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleCancelBtnClick} style={{ minWidth: '85px' }}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '95px' }}
type="submit"
>
{<T id={'branches.activate_button'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(BranchActivateFormFloatingActions);

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { DialogContent } from 'components';
import { useActivateBranches } from 'hooks/query';
const BranchActivateContext = React.createContext();
/**
* Branch activate form provider.
*/
function BranchActivateFormProvider({ dialogName, ...props }) {
const { mutateAsync: activateBranches, isLoading } = useActivateBranches();
// State provider.
const provider = {
activateBranches,
dialogName,
};
return (
<DialogContent isLoading={isLoading}>
<BranchActivateContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useBranchActivateContext = () => React.useContext(BranchActivateContext);
export { BranchActivateFormProvider, useBranchActivateContext };

View File

@@ -0,0 +1,32 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import { Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'utils';
const BranchActivateDialogContent = React.lazy(() =>
import('./BranchActivateDialogContent'),
);
/**
* Branch activate dialog.
*/
function BranchActivateDialog({ dialogName, payload: {}, isOpen }) {
return (
<Dialog
name={dialogName}
title={<T id={'branch_activate.dialog.label'} />}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
className={'dialog--branch-activate'}
>
<DialogSuspense>
<BranchActivateDialogContent dialogName={dialogName} />
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(BranchActivateDialog);

View File

@@ -0,0 +1,82 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { CreateBranchFormSchema } from './BranchForm.schema';
import { transformErrors } from './utils';
import BranchFormContent from './BranchFormContent';
import { useBranchFormContext } from './BranchFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, transformToForm } from 'utils';
const defaultInitialValues = {
name: '',
code: '',
address: '',
phone_number: '',
email: '',
website: '',
city: '',
country: '',
};
function BranchForm({
// #withDialogActions
closeDialog,
}) {
const { dialogName, branch, branchId, createBranchMutate, editBranchMutate } =
useBranchFormContext();
// Initial form values.
const initialValues = {
...defaultInitialValues,
...transformToForm(branch, defaultInitialValues),
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = { ...values };
// Handle request response success.
const onSuccess = (response) => {
AppToaster.show({
message: intl.get('branch.dialog.success_message'),
intent: Intent.SUCCESS,
});
closeDialog(dialogName);
};
// Handle request response errors.
const onError = ({
response: {
data: { errors },
},
}) => {
if (errors) {
}
transformErrors(errors, { setErrors });
setSubmitting(false);
};
if (branchId) {
editBranchMutate([branchId, form]).then(onSuccess).catch(onError);
} else {
createBranchMutate(form).then(onSuccess).catch(onError);
}
};
return (
<Formik
validationSchema={CreateBranchFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={BranchFormContent}
/>
);
}
export default compose(withDialogActions)(BranchForm);

View File

@@ -0,0 +1,16 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from 'common/dataTypes';
const Schema = Yup.object().shape({
name: Yup.string().required().label(intl.get('branch_name')),
code: Yup.string().trim().min(0).max(DATATYPES_LENGTH.STRING),
address: Yup.string().trim(),
city: Yup.string().trim(),
country: Yup.string().trim(),
website: Yup.string().url().nullable(),
phone_number: Yup.number(),
email: Yup.string().email().nullable(),
});
export const CreateBranchFormSchema = Schema;

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { Form } from 'formik';
import BranchFormFields from './BranchFormFields';
import BranchFormFloatingActions from './BranchFormFloatingActions';
/**
* Branch form content.
*/
export default function BranchFormContent() {
return (
<Form>
<BranchFormFields />
<BranchFormFloatingActions />
</Form>
);
}

View File

@@ -0,0 +1,21 @@
import React from 'react';
import '../../../style/pages/Branches/BranchFormDialog.scss';
import { BranchFormProvider } from './BranchFormProvider';
import BranchForm from './BranchForm';
/**
* Branch form dialog content.
*/
export default function BranchFormDialogContent({
// #ownProps
dialogName,
branchId,
}) {
return (
<BranchFormProvider branchId={branchId} dialogName={dialogName}>
<BranchForm />
</BranchFormProvider>
);
}

View File

@@ -0,0 +1,153 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FastField, ErrorMessage, Field } from 'formik';
import styled from 'styled-components';
import {
Classes,
FormGroup,
InputGroup,
ControlGroup,
Position,
} from '@blueprintjs/core';
import { inputIntent } from 'utils';
import { FieldRequiredHint, Col, Row, FormattedMessage as T } from 'components';
import { useBranchFormContext } from './BranchFormProvider';
/**
* Branch form dialog fields.
*/
function BranchFormFields() {
return (
<div className={Classes.DIALOG_BODY}>
{/*------------ Branch Name -----------*/}
<FastField name={'name'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'branch.dialog.label.branch_name'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="branch_name" />}
className={'form-group--branch_name'}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Branch Code -----------*/}
<FastField name={'code'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'branch.dialog.label.branch_code'} />}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="code" />}
className={'form-group--branch_name'}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Branch Address -----------*/}
<FastField name={'address'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={intl.get('branch.dialog.label.branch_address')}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="address" />}
className={'form-group--branch_address'}
>
<InputGroup
intent={inputIntent({ error, touched })}
placeholder={intl.get('branch.dialog.label.address_1')}
{...field}
/>
</FormGroup>
)}
</FastField>
<BranchAddressWrap>
{/*------------ Branch Address City & Country-----------*/}
<FormGroup
inline={true}
className={'form-group--branch_address'}
helperText={<ErrorMessage name="branch_address_2" />}
>
<ControlGroup>
<FastField name={'city'}>
{({ field, meta: { error, touched } }) => (
<InputGroup
intent={inputIntent({ error, touched })}
placeholder={intl.get('branch.dialog.label.city')}
{...field}
/>
)}
</FastField>
<FastField name={'country'}>
{({ field, meta: { error, touched } }) => (
<InputGroup
intent={inputIntent({ error, touched })}
placeholder={intl.get('branch.dialog.label.country')}
{...field}
/>
)}
</FastField>
</ControlGroup>
</FormGroup>
</BranchAddressWrap>
{/*------------ Phone Number -----------*/}
<FastField name={'phone_number'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={intl.get('branch.dialog.label.phone_number')}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="phone_number" />}
className={'form-group--phone_number'}
>
<InputGroup placeholder={'https://'} {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Email -----------*/}
<FastField name={'email'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={intl.get('branch.dialog.label.email')}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="email" />}
className={'form-group--email'}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Website -----------*/}
<FastField name={'website'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={intl.get('branch.dialog.label.website')}
intent={inputIntent({ error, touched })}
inline={true}
helperText={<ErrorMessage name="email" />}
className={'form-group--website'}
>
<InputGroup intent={inputIntent({ error, touched })} {...field} />
</FormGroup>
)}
</FastField>
</div>
);
}
export default BranchFormFields;
const BranchAddressWrap = styled.div`
margin-left: 160px;
`;

View File

@@ -0,0 +1,46 @@
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from 'components';
import { useBranchFormContext } from './BranchFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Branch form floating actions.
*/
function BranchFormFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting } = useFormikContext();
const { dialogName } = useBranchFormContext();
// Handle close button click.
const handleCancelBtnClick = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleCancelBtnClick} style={{ minWidth: '85px' }}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '95px' }}
type="submit"
>
{<T id={'save'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(BranchFormFloatingActions);

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { DialogContent } from 'components';
import { useCreateBranch, useEditBranch, useBranch } from 'hooks/query';
const BranchFormContext = React.createContext();
/**
* Branch form dialog provider.
*/
function BranchFormProvider({ dialogName, branchId, ...props }) {
// Create and edit warehouse mutations.
const { mutateAsync: createBranchMutate } = useCreateBranch();
const { mutateAsync: editBranchMutate } = useEditBranch();
// Handle fetch branch detail.
const { data: branch, isLoading: isBranchLoading } = useBranch(branchId, {
enabled: !!branchId,
});
// State provider.
const provider = {
dialogName,
branch,
branchId,
createBranchMutate,
editBranchMutate,
};
return (
<DialogContent isLoading={isBranchLoading}>
<BranchFormContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useBranchFormContext = () => React.useContext(BranchFormContext);
export { BranchFormProvider, useBranchFormContext };

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import { Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'utils';
const BranchFormDialogContent = React.lazy(() =>
import('./BranchFormDialogContent'),
);
/**
* Branch form form dialog.
*/
function BranchFormDialog({
dialogName,
payload: { branchId, action },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={
action === 'edit' ? (
<T id={'branch.dialog.label_edit_branch'} />
) : (
<T id={'branch.dialog.label_new_branch'} />
)
}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
className={'dialog--branch-form'}
>
<DialogSuspense>
<BranchFormDialogContent dialogName={dialogName} branchId={branchId} />
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(BranchFormDialog);

View File

@@ -0,0 +1,14 @@
import intl from 'react-intl-universal';
/**
* Transformes the response errors types.
*/
export const transformErrors = (errors, { setErrors }) => {
if (errors.find((error) => error.type === 'BRANCH_CODE_NOT_UNIQUE')) {
setErrors({
code: intl.get('branche.error.warehouse_code_not_unique'),
});
}
}

View File

@@ -0,0 +1,25 @@
import React from 'react';
import 'style/pages/CustomerOpeningBalance/CustomerOpeningBalance.scss';
import CustomerOpeningBalanceForm from './CustomerOpeningBalanceForm';
import { CustomerOpeningBalanceFormProvider } from './CustomerOpeningBalanceFormProvider';
/**
* Customer opening balance dialog content.
* @returns
*/
export default function CustomerOpeningBalanceDialogContent({
// #ownProps
dialogName,
customerId,
}) {
return (
<CustomerOpeningBalanceFormProvider
customerId={customerId}
dialogName={dialogName}
>
<CustomerOpeningBalanceForm />
</CustomerOpeningBalanceFormProvider>
);
}

View File

@@ -0,0 +1,115 @@
import React from 'react';
import { Classes, Position, FormGroup, ControlGroup } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { isEqual } from 'lodash';
import { FastField, useFormikContext } from 'formik';
import { momentFormatter, tansformDateValue, handleDateChange } from 'utils';
import { Features } from 'common';
import classNames from 'classnames';
import {
If,
Icon,
FormattedMessage as T,
ExchangeRateMutedField,
BranchSelect,
BranchSelectButton,
FeatureCan,
InputPrependText,
} from 'components';
import { FMoneyInputGroup, FFormGroup } from '../../../components/Forms';
import { useCustomerOpeningBalanceContext } from './CustomerOpeningBalanceFormProvider';
import { useSetPrimaryBranchToForm } from './utils';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
/**
* Customer Opening balance fields.
* @returns
*/
function CustomerOpeningBalanceFields({
// #withCurrentOrganization
organization: { base_currency },
}) {
// Formik context.
const { values } = useFormikContext();
const { branches, customer } = useCustomerOpeningBalanceContext();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={Classes.DIALOG_BODY}>
{/*------------ Opening balance -----------*/}
<FFormGroup
name={'opening_balance'}
label={<T id={'customer_opening_balance.label.opening_balance'} />}
>
<ControlGroup>
<InputPrependText text={customer.currency_code} />
<FMoneyInputGroup
name={'opening_balance'}
allowDecimals={true}
allowNegativeValue={true}
/>
</ControlGroup>
</FFormGroup>
{/*------------ Opening balance at -----------*/}
<FastField name={'opening_balance_at'}>
{({ form, field: { value } }) => (
<FormGroup
label={
<T id={'customer_opening_balance.label.opening_balance_at'} />
}
className={Classes.FILL}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('opening_balance_at', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<If condition={!isEqual(base_currency, customer.currency_code)}>
{/*------------ Opening balance exchange rate -----------*/}
<ExchangeRateMutedField
name={'opening_balance_exchange_rate'}
fromCurrency={base_currency}
toCurrency={customer.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.opening_balance_at}
exchangeRate={values.opening_balance_exchange_rate}
/>
</If>
{/*------------ Opening balance branch id -----------*/}
<FeatureCan feature={Features.Branches}>
<FFormGroup
label={<T id={'branch'} />}
name={'opening_balance_branch_id'}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'opening_balance_branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FFormGroup>
</FeatureCan>
</div>
);
}
export default compose(withCurrentOrganization())(CustomerOpeningBalanceFields);

View File

@@ -0,0 +1,83 @@
import React from 'react';
import moment from 'moment';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import { defaultTo } from 'lodash';
import { AppToaster } from 'components';
import { CreateCustomerOpeningBalanceFormSchema } from './CustomerOpeningBalanceForm.schema';
import { useCustomerOpeningBalanceContext } from './CustomerOpeningBalanceFormProvider';
import CustomerOpeningBalanceFormContent from './CustomerOpeningBalanceFormContent';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
const defaultInitialValues = {
opening_balance: '0',
opening_balance_branch_id: '',
opening_balance_exchange_rate: 1,
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
};
/**
* Customer Opening balance form.
* @returns
*/
function CustomerOpeningBalanceForm({
// #withDialogActions
closeDialog,
}) {
const { dialogName, customer, editCustomerOpeningBalanceMutate } =
useCustomerOpeningBalanceContext();
// Initial form values
const initialValues = {
...defaultInitialValues,
...customer,
opening_balance: defaultTo(customer.opening_balance, ''),
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const formValues = {
...values,
};
// Handle request response success.
const onSuccess = (response) => {
AppToaster.show({
message: intl.get('customer_opening_balance.success_message'),
intent: Intent.SUCCESS,
});
closeDialog(dialogName);
};
// Handle request response errors.
const onError = ({
response: {
data: { errors },
},
}) => {
if (errors) {
}
setSubmitting(false);
};
editCustomerOpeningBalanceMutate([customer.id, formValues])
.then(onSuccess)
.catch(onError);
};
return (
<Formik
validationSchema={CreateCustomerOpeningBalanceFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={CustomerOpeningBalanceFormContent}
/>
);
}
export default compose(withDialogActions)(CustomerOpeningBalanceForm);

View File

@@ -0,0 +1,10 @@
import * as Yup from 'yup';
const Schema = Yup.object().shape({
opening_balance_branch_id: Yup.string(),
opening_balance: Yup.number().nullable(),
opening_balance_at: Yup.date(),
opening_balance_exchange_rate: Yup.number(),
});
export const CreateCustomerOpeningBalanceFormSchema = Schema;

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { Form } from 'formik';
import CustomerOpeningBalanceFields from './CustomerOpeningBalanceFields';
import CustomerOpeningBalanceFormFloatingActions from './CustomerOpeningBalanceFormFloatingActions';
/**
* Customer Opening balance form content.
* @returns
*/
export default function CustomerOpeningBalanceFormContent() {
return (
<Form>
<CustomerOpeningBalanceFields />
<CustomerOpeningBalanceFormFloatingActions />
</Form>
);
}

View File

@@ -0,0 +1,50 @@
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from 'components';
import { useCustomerOpeningBalanceContext } from './CustomerOpeningBalanceFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Customer Opening balance floating actions.
* @returns
*/
function CustomerOpeningBalanceFormFloatingActions({
// #withDialogActions
closeDialog,
}) {
// dialog context.
const { dialogName } = useCustomerOpeningBalanceContext();
// Formik context.
const { isSubmitting } = useFormikContext();
// Handle close button click.
const handleCancelBtnClick = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
>
{<T id={'edit'} />}
</Button>
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
<T id={'cancel'} />
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(
CustomerOpeningBalanceFormFloatingActions,
);

View File

@@ -0,0 +1,65 @@
import React from 'react';
import { DialogContent } from 'components';
import {
useBranches,
useCustomer,
useEditCustomerOpeningBalance,
} from 'hooks/query';
import { useFeatureCan } from 'hooks/state';
import { Features } from 'common';
import { transfromCustomertoForm } from './utils';
const CustomerOpeningBalanceContext = React.createContext();
/**
* Customer opening balance provider.
* @returns
*/
function CustomerOpeningBalanceFormProvider({
query,
customerId,
dialogName,
...props
}) {
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
const { mutateAsync: editCustomerOpeningBalanceMutate } =
useEditCustomerOpeningBalance();
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches(query, { enabled: isBranchFeatureCan });
// Handle fetch customer details.
const { data: customer, isLoading: isCustomerLoading } = useCustomer(
customerId,
{ enabled: !!customerId },
);
// State provider.
const provider = {
branches,
customer: transfromCustomertoForm(customer),
isBranchesSuccess,
isBranchesLoading,
dialogName,
editCustomerOpeningBalanceMutate,
};
return (
<DialogContent isLoading={isBranchesLoading || isCustomerLoading}>
<CustomerOpeningBalanceContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useCustomerOpeningBalanceContext = () =>
React.useContext(CustomerOpeningBalanceContext);
export { CustomerOpeningBalanceFormProvider, useCustomerOpeningBalanceContext };

View File

@@ -0,0 +1,40 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import { Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'redux';
const CustomerOpeningBalanceDialogContent = React.lazy(() =>
import('./CustomerOpeningBalanceDialogContent'),
);
/**
* Customer opening balance dialog.
* @returns
*/
function CustomerOpeningBalanceDialog({
dialogName,
payload: { customerId },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={<T id={'customer_opening_balance.label'} />}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
className={'dialog--customer-opening-balance'}
>
<DialogSuspense>
<CustomerOpeningBalanceDialogContent
customerId={customerId}
dialogName={dialogName}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(CustomerOpeningBalanceDialog);

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { first, pick } from 'lodash';
import { useCustomerOpeningBalanceContext } from './CustomerOpeningBalanceFormProvider';
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useCustomerOpeningBalanceContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('opening_balance_branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};
export function transfromCustomertoForm(values) {
return {
...pick(values, [
'id',
'opening_balance',
'opening_balance_exchange_rate',
'currency_code',
]),
};
}

View File

@@ -27,6 +27,8 @@ const defaultInitialValues = {
reference_no: '',
quantity_on_hand: '',
publish: '',
branch_id: '',
warehouse_id: '',
};
/**
@@ -36,13 +38,8 @@ function InventoryAdjustmentForm({
// #withDialogActions
closeDialog,
}) {
const {
dialogName,
item,
itemId,
submitPayload,
createInventoryAdjMutate,
} = useInventoryAdjContext();
const { dialogName, item, itemId, submitPayload, createInventoryAdjMutate } =
useInventoryAdjContext();
// Initial form values.
const initialValues = {
@@ -63,7 +60,9 @@ function InventoryAdjustmentForm({
closeDialog(dialogName);
AppToaster.show({
message: intl.get('the_adjustment_transaction_has_been_created_successfully'),
message: intl.get(
'the_adjustment_transaction_has_been_created_successfully',
),
intent: Intent.SUCCESS,
});
})

View File

@@ -23,6 +23,8 @@ const Schema = Yup.object().shape({
reference_no: Yup.string(),
new_quantity: Yup.number().required(),
publish: Yup.boolean(),
branch_id: Yup.string(),
warehouse_id: Yup.string(),
});
export const CreateInventoryAdjustmentFormSchema = Schema;

View File

@@ -1,4 +1,5 @@
import React from 'react';
import styled from 'styled-components';
import { FastField, ErrorMessage, Field } from 'formik';
import {
Classes,
@@ -12,7 +13,17 @@ import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from 'hooks';
import { ListSelect, FieldRequiredHint, Col, Row } from 'components';
import {
ListSelect,
FieldRequiredHint,
Col,
Row,
FeatureCan,
BranchSelect,
WarehouseSelect,
BranchSelectButton,
WarehouseSelectButton,
} from 'components';
import {
inputIntent,
momentFormatter,
@@ -21,26 +32,76 @@ import {
toSafeNumber,
} from 'utils';
import { CLASSES } from 'common/classes';
import { Features } from 'common';
import adjustmentType from 'common/adjustmentType';
import AccountsSuggestField from 'components/AccountsSuggestField';
import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider';
import { diffQuantity } from './utils';
import {
diffQuantity,
useSetPrimaryBranchToForm,
useSetPrimaryWarehouseToForm,
} from './utils';
import { useFeatureCan } from 'hooks/state';
import InventoryAdjustmentQuantityFields from './InventoryAdjustmentQuantityFields';
/**
* Inventory adjustment form dialogs fields.
*/
export default function InventoryAdjustmentFormDialogFields() {
// Features guard.
const { featureCan } = useFeatureCan();
const dateFieldRef = useAutofocus();
// Inventory adjustment dialog context.
const { accounts } = useInventoryAdjContext();
const { accounts, branches, warehouses } = useInventoryAdjContext();
// Intl context.
// Sets the primary warehouse to form.
useSetPrimaryWarehouseToForm();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={Classes.DIALOG_BODY}>
<Row>
<FeatureCan feature={Features.Branches}>
<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>
</FeatureCan>
<FeatureCan feature={Features.Warehouses}>
<Col xs={5}>
<FormGroup
label={<T id={'warehouse'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<WarehouseSelect
name={'warehouse_id'}
warehouses={warehouses}
input={WarehouseSelectButton}
popoverProps={{ minimal: true }}
/>
</FormGroup>
</Col>
</FeatureCan>
</Row>
{featureCan(Features.Warehouses) && featureCan(Features.Branches) && (
<FeatureRowDivider />
)}
<Row>
<Col xs={5}>
{/*------------ Date -----------*/}
@@ -173,3 +234,9 @@ export default function InventoryAdjustmentFormDialogFields() {
</div>
);
}
export const FeatureRowDivider = styled.div`
height: 2px;
background: #e9e9e9;
margin-bottom: 15px;
`;

View File

@@ -1,8 +1,12 @@
import React, { useState, createContext } from 'react';
import { DialogContent } from 'components';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
import {
useItem,
useAccounts,
useBranches,
useWarehouses,
useCreateInventoryAdjustment,
} from 'hooks/query';
@@ -12,29 +16,59 @@ const InventoryAdjustmentContext = createContext();
* Inventory adjustment dialog provider.
*/
function InventoryAdjustmentFormProvider({ itemId, dialogName, ...props }) {
// Features guard.
const { featureCan } = useFeatureCan();
const isWarehouseFeatureCan = featureCan(Features.Warehouses);
const isBranchFeatureCan = featureCan(Features.Branches);
// Fetches accounts list.
const { isFetching: isAccountsLoading, data: accounts } = useAccounts();
// Fetches the item details.
const { isFetching: isItemLoading, data: item } = useItem(itemId);
// Fetch warehouses list.
const {
mutateAsync: createInventoryAdjMutate,
} = useCreateInventoryAdjustment();
data: warehouses,
isLoading: isWarehouesLoading,
isSuccess: isWarehousesSuccess,
} = useWarehouses({}, { enabled: isWarehouseFeatureCan });
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches({}, { enabled: isBranchFeatureCan });
const { mutateAsync: createInventoryAdjMutate } =
useCreateInventoryAdjustment();
// Submit payload.
const [submitPayload, setSubmitPayload] = useState({});
// Determines whether the warehouse and branches are loading.
const isFeatureLoading = isWarehouesLoading || isBranchesLoading;
// State provider.
const provider = {
itemId,
isAccountsLoading,
accounts,
isItemLoading,
item,
submitPayload,
itemId,
branches,
warehouses,
accounts,
dialogName,
submitPayload,
isBranchesSuccess,
isWarehousesSuccess,
isAccountsLoading,
isItemLoading,
isFeatureLoading,
isWarehouesLoading,
isBranchesLoading,
createInventoryAdjMutate,
setSubmitPayload,
};
@@ -46,6 +80,7 @@ function InventoryAdjustmentFormProvider({ itemId, dialogName, ...props }) {
);
}
const useInventoryAdjContext = () => React.useContext(InventoryAdjustmentContext);
const useInventoryAdjContext = () =>
React.useContext(InventoryAdjustmentContext);
export { InventoryAdjustmentFormProvider, useInventoryAdjContext };

View File

@@ -1,3 +1,7 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { useInventoryAdjContext } from './InventoryAdjustmentFormProvider';
import { first } from 'lodash';
export const decrementQuantity = (newQuantity, quantityOnHand) => {
return quantityOnHand - newQuantity;
@@ -12,3 +16,34 @@ export const diffQuantity = (newQuantity, quantityOnHand, type) => {
? decrementQuantity(newQuantity, quantityOnHand)
: incrementQuantity(newQuantity, quantityOnHand);
};
export const useSetPrimaryWarehouseToForm = () => {
const { setFieldValue } = useFormikContext();
const { warehouses, isWarehousesSuccess } = useInventoryAdjContext();
React.useEffect(() => {
if (isWarehousesSuccess) {
const primaryWarehouse =
warehouses.find((b) => b.primary) || first(warehouses);
if (primaryWarehouse) {
setFieldValue('warehouse_id', primaryWarehouse.id);
}
}
}, [isWarehousesSuccess, setFieldValue, warehouses]);
};
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useInventoryAdjContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};

View File

@@ -1,4 +1,5 @@
import React, { useCallback } from 'react';
import intl from 'react-intl-universal';
import { DialogContent } from 'components';
import { useSaveSettings, useSettingsManualJournals } from 'hooks/query';
@@ -12,7 +13,7 @@ import {
transformSettingsToForm,
} from 'containers/JournalNumber/utils';
import 'style/pages/ManualJournal/JournalNumberDialog.scss'
import 'style/pages/ManualJournal/JournalNumberDialog.scss';
/**
* Journal number dialog's content.
@@ -28,16 +29,14 @@ function JournalNumberDialogContent({
// #ownProps
onConfirm,
initialValues
initialValues,
}) {
const { isLoading: isSettingsLoading } = useSettingsManualJournals();
const { mutateAsync: saveSettingsMutate } = useSaveSettings();
const [referenceFormValues, setReferenceFormValues] = React.useState(null);
// Handle the form submit.
// Handle the form submit.
const handleSubmitForm = (values, { setSubmitting }) => {
// Transformes the form values to settings to save it.
const options = transformFormToSettings(values, 'manual_journals');
// Handle success.
const handleSuccess = () => {
setSubmitting(false);
@@ -52,6 +51,9 @@ function JournalNumberDialogContent({
handleSuccess();
return;
}
// Transformes the form values to settings to save it.
const options = transformFormToSettings(values, 'manual_journals');
saveSettingsMutate({ options }).then(handleSuccess).catch(handleErrors);
};
@@ -59,6 +61,17 @@ function JournalNumberDialogContent({
closeDialog('journal-number-form');
}, [closeDialog]);
// Handle form change.
const handleChange = (values) => {
setReferenceFormValues(values);
};
// Description.
const description =
referenceFormValues?.incrementMode === 'auto'
? intl.get('manual_journals.auto_increment.auto')
: intl.get('manual_journals.auto_increment.manually');
return (
<DialogContent isLoading={isSettingsLoading}>
<ReferenceNumberForm
@@ -71,7 +84,9 @@ function JournalNumberDialogContent({
...initialValues,
}}
onSubmit={handleSubmitForm}
description={description}
onClose={handleClose}
onChange={handleChange}
/>
</DialogContent>
);
@@ -84,4 +99,4 @@ export default compose(
numberPrefix: manualJournalsSettings?.numberPrefix,
autoIncrement: manualJournalsSettings?.autoIncrement,
})),
)(JournalNumberDialogContent);
)(JournalNumberDialogContent);

View File

@@ -1,8 +1,12 @@
import React from 'react';
import { DialogContent } from 'components';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
import {
useCreateCashflowTransaction,
useAccount,
useAccounts,
useBranches,
useCashflowAccounts,
useSettingCashFlow,
} from 'hooks/query';
@@ -18,9 +22,24 @@ function MoneyInDialogProvider({
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 });
@@ -37,9 +56,12 @@ function MoneyInDialogProvider({
// provider.
const provider = {
accounts,
account,
branches,
accountId,
accountType,
isAccountsLoading,
isBranchesSuccess,
cashflowAccounts,
@@ -53,7 +75,10 @@ function MoneyInDialogProvider({
return (
<DialogContent
isLoading={
isAccountsLoading || isCashFlowAccountsLoading || isSettingsLoading
isAccountsLoading ||
isCashFlowAccountsLoading ||
isBranchesLoading ||
isSettingsLoading
}
>
<MoneyInDialogContent.Provider value={provider} {...props} />

View File

@@ -48,7 +48,7 @@ function MoneyInFloatingActions({
>
<T id={'close'} />
</Button>
<Button
{/* <Button
disabled={isSubmitting}
loading={isSubmitting && !submitPayload.publish}
style={{ minWidth: '75px' }}
@@ -56,7 +56,7 @@ function MoneyInFloatingActions({
onClick={handleSubmitDraftBtnClick}
>
{<T id={'save_as_draft'} />}
</Button>
</Button> */}
<Button
intent={Intent.PRIMARY}

View File

@@ -28,8 +28,11 @@ const defaultInitialValues = {
reference_no: '',
cashflow_account_id: '',
credit_account_id: '',
currency_code: '',
description: '',
branch_id: '',
publish: '',
exchange_rate: 1,
};
function MoneyInForm({

View File

@@ -10,11 +10,14 @@ const Schema = Yup.object().shape({
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(),
publish: Yup.boolean(),
});
export const CreateMoneyInFormSchema = Schema;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
@@ -7,6 +7,7 @@ import {
TextArea,
Position,
ControlGroup,
Button,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
@@ -18,7 +19,12 @@ import {
Icon,
Col,
Row,
If,
FeatureCan,
BranchSelect,
BranchSelectButton,
InputPrependButton,
ExchangeRateMutedField,
} from 'components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from 'hooks';
@@ -32,8 +38,14 @@ import {
compose,
} from 'utils';
import { CLASSES } from 'common/classes';
import { Features } from 'common';
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import { useObserveTransactionNoSettings } from '../utils';
import {
useObserveTransactionNoSettings,
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -50,10 +62,14 @@ function OtherIncomeFormFields({
transactionNextNumber,
}) {
// Money in dialog context.
const { accounts } = useMoneyInDailogContext();
const { accounts, account, branches } = useMoneyInDailogContext();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
const isForeigAccount = useForeignAccount();
// Handle tranaction number changing.
const handleTransactionNumberChange = () => {
openDialog('transaction-number-form');
@@ -79,8 +95,30 @@ function OtherIncomeFormFields({
transactionNextNumber,
);
// 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 -----------*/}
@@ -165,7 +203,7 @@ function OtherIncomeFormFields({
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
@@ -181,6 +219,17 @@ function OtherIncomeFormFields({
)}
</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 -----------*/}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
@@ -9,6 +9,7 @@ import {
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import {
FormattedMessage as T,
AccountsSuggestField,
@@ -18,7 +19,12 @@ import {
Icon,
Col,
Row,
If,
InputPrependButton,
ExchangeRateMutedField,
BranchSelect,
BranchSelectButton,
FeatureCan,
} from 'components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from 'hooks';
@@ -30,9 +36,15 @@ import {
handleDateChange,
compose,
} from 'utils';
import { Features } from 'common';
import { CLASSES } from 'common/classes';
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import { useObserveTransactionNoSettings } from '../utils';
import {
useObserveTransactionNoSettings,
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -50,10 +62,14 @@ function OwnerContributionFormFields({
transactionNextNumber,
}) {
// Money in dialog context.
const { accounts } = useMoneyInDailogContext();
const { accounts, account, branches } = useMoneyInDailogContext();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
const isForeigAccount = useForeignAccount();
// Handle tranaction number changing.
const handleTransactionNumberChange = () => {
openDialog('transaction-number-form');
@@ -79,8 +95,29 @@ function OwnerContributionFormFields({
transactionNextNumber,
);
// 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 -----------*/}
@@ -151,7 +188,7 @@ function OwnerContributionFormFields({
</Col>
</Row>
{/*------------ amount -----------*/}
<FastField name={'amount'}>
<Field name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
@@ -165,7 +202,7 @@ function OwnerContributionFormFields({
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<InputPrependText text={account?.currency_code} />
<MoneyInputGroup
value={value}
@@ -179,8 +216,18 @@ function OwnerContributionFormFields({
</ControlGroup>
</FormGroup>
)}
</FastField>
</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 -----------*/}
@@ -195,9 +242,10 @@ function OwnerContributionFormFields({
>
<AccountsSuggestField
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('credit_account_id', id)
}
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 }),
@@ -226,7 +274,6 @@ function OwnerContributionFormFields({
</FastField>
</Col>
</Row>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
@@ -18,8 +18,14 @@ import {
Icon,
Col,
Row,
If,
InputPrependButton,
ExchangeRateMutedField,
FeatureCan,
BranchSelect,
BranchSelectButton,
} from 'components';
import { Features } from 'common';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from 'hooks';
import { ACCOUNT_TYPE } from 'common/accountTypes';
@@ -33,7 +39,12 @@ import {
} from 'utils';
import { CLASSES } from 'common/classes';
import { useMoneyInDailogContext } from '../MoneyInDialogProvider';
import { useObserveTransactionNoSettings } from '../utils';
import {
useObserveTransactionNoSettings,
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -50,10 +61,13 @@ function TransferFromAccountFormFields({
transactionNextNumber,
}) {
// Money in dialog context.
const { accounts } = useMoneyInDailogContext();
const { accounts, account, branches } = useMoneyInDailogContext();
const isForeigAccount = useForeignAccount();
const amountFieldRef = useAutofocus();
const { values } = useFormikContext();
// Handle tranaction number changing.
const handleTransactionNumberChange = () => {
openDialog('transaction-number-form');
@@ -73,6 +87,9 @@ function TransferFromAccountFormFields({
}
};
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
// Syncs transaction number settings with form.
useObserveTransactionNoSettings(
transactionNumberPrefix,
@@ -80,6 +97,24 @@ function TransferFromAccountFormFields({
);
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 -----------*/}
@@ -164,7 +199,7 @@ function TransferFromAccountFormFields({
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
@@ -179,7 +214,17 @@ function TransferFromAccountFormFields({
</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 -----------*/}

View File

@@ -1,6 +1,10 @@
import React from 'react';
import styled from 'styled-components';
import { useFormikContext } from 'formik';
import { transactionNumber } from 'utils';
import { isEqual, isUndefined, isNull, first } from 'lodash';
import { useMoneyInDailogContext } from './MoneyInDialogProvider';
export const useObserveTransactionNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
@@ -10,3 +14,34 @@ export const useObserveTransactionNoSettings = (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

@@ -1,7 +1,11 @@
import React from 'react';
import { DialogContent } from 'components';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
import {
useAccounts,
useAccount,
useBranches,
useCreateCashflowTransaction,
useCashflowAccounts,
useSettingCashFlow,
@@ -13,9 +17,25 @@ 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 });
@@ -32,9 +52,12 @@ function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) {
// provider.
const provider = {
accounts,
account,
accountId,
accountType,
branches,
isAccountsLoading,
isBranchesSuccess,
cashflowAccounts,
@@ -48,7 +71,10 @@ function MoneyOutProvider({ accountId, accountType, dialogName, ...props }) {
return (
<DialogContent
isLoading={
isAccountsLoading || isCashFlowAccountsLoading || isSettingsLoading
isAccountsLoading ||
isCashFlowAccountsLoading ||
isBranchesLoading ||
isSettingsLoading
}
>
<MoneyInDialogContent.Provider value={provider} {...props} />

View File

@@ -50,7 +50,7 @@ function MoneyOutFloatingActions({
>
<T id={'close'} />
</Button>
<Button
{/* <Button
disabled={isSubmitting}
loading={isSubmitting && !submitPayload.publish}
style={{ minWidth: '75px' }}
@@ -58,7 +58,7 @@ function MoneyOutFloatingActions({
onClick={handleSubmitDraftBtnClick}
>
{<T id={'save_as_draft'} />}
</Button>
</Button> */}
<Button
intent={Intent.PRIMARY}

View File

@@ -30,6 +30,7 @@ const defaultInitialValues = {
credit_account_id: '',
description: '',
publish: '',
exchange_rate: 1,
};
function MoneyOutForm({

View File

@@ -10,11 +10,13 @@ const Schema = Yup.object().shape({
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(),
publish: Yup.boolean(),
});
export const CreateMoneyOutSchema = Schema;

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
@@ -18,11 +18,17 @@ import {
Icon,
Col,
Row,
If,
InputPrependButton,
FeatureCan,
BranchSelect,
BranchSelectButton,
ExchangeRateMutedField,
} from 'components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from 'hooks';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { Features } from 'common';
import {
inputIntent,
@@ -33,7 +39,12 @@ import {
} from 'utils';
import { CLASSES } from 'common/classes';
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import { useObserveTransactionNoSettings } from '../utils';
import {
useObserveTransactionNoSettings,
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -50,7 +61,10 @@ function OtherExpnseFormFields({
transactionNextNumber,
}) {
// Money in dialog context.
const { accounts } = useMoneyOutDialogContext();
const { accounts, account, branches } = useMoneyOutDialogContext();
const isForeigAccount = useForeignAccount();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
@@ -79,8 +93,29 @@ function OtherExpnseFormFields({
transactionNextNumber,
);
// 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 -----------*/}
@@ -165,7 +200,7 @@ function OtherExpnseFormFields({
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
@@ -180,7 +215,17 @@ function OtherExpnseFormFields({
</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 -----------*/}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
@@ -17,12 +17,18 @@ import {
FieldRequiredHint,
InputPrependButton,
Icon,
If,
Col,
Row,
FeatureCan,
BranchSelect,
BranchSelectButton,
ExchangeRateMutedField,
} from 'components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from 'hooks';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { Features } from 'common';
import {
inputIntent,
momentFormatter,
@@ -32,7 +38,12 @@ import {
} from 'utils';
import { CLASSES } from 'common/classes';
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import { useObserveTransactionNoSettings } from '../utils';
import {
useObserveTransactionNoSettings,
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -49,7 +60,9 @@ function OwnerDrawingsFormFields({
transactionNextNumber,
}) {
// Money out dialog context.
const { accounts } = useMoneyOutDialogContext();
const { accounts, account, branches } = useMoneyOutDialogContext();
const { values } = useFormikContext();
const isForeigAccount = useForeignAccount();
const amountFieldRef = useAutofocus();
@@ -78,8 +91,29 @@ function OwnerDrawingsFormFields({
transactionNextNumber,
);
// 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 -----------*/}
@@ -164,7 +198,7 @@ function OwnerDrawingsFormFields({
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
@@ -180,6 +214,17 @@ function OwnerDrawingsFormFields({
)}
</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 -----------*/}

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { FastField, Field, ErrorMessage } from 'formik';
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
@@ -18,7 +18,12 @@ import {
Icon,
Col,
Row,
If,
InputPrependButton,
FeatureCan,
BranchSelect,
BranchSelectButton,
ExchangeRateMutedField,
} from 'components';
import { DateInput } from '@blueprintjs/datetime';
import { useAutofocus } from 'hooks';
@@ -31,9 +36,15 @@ import {
handleDateChange,
compose,
} from 'utils';
import { Features } from 'common';
import { CLASSES } from 'common/classes';
import { useMoneyOutDialogContext } from '../MoneyOutDialogProvider';
import { useObserveTransactionNoSettings } from '../utils';
import {
useObserveTransactionNoSettings,
useSetPrimaryBranchToForm,
useForeignAccount,
BranchRowDivider,
} from '../utils';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -50,7 +61,9 @@ function TransferToAccountFormFields({
transactionNextNumber,
}) {
// Money in dialog context.
const { accounts } = useMoneyOutDialogContext();
const { accounts, account, branches } = useMoneyOutDialogContext();
const { values } = useFormikContext();
const isForeigAccount = useForeignAccount();
const accountRef = useAutofocus();
@@ -79,8 +92,29 @@ function TransferToAccountFormFields({
transactionNextNumber,
);
// 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 -----------*/}
@@ -165,7 +199,7 @@ function TransferToAccountFormFields({
className={'form-group--amount'}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<InputPrependText text={account.currency_code} />
<MoneyInputGroup
value={value}
@@ -180,7 +214,17 @@ function TransferToAccountFormFields({
</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 -----------*/}

View File

@@ -1,6 +1,10 @@
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();
@@ -10,3 +14,33 @@ export const useObserveTransactionNoSettings = (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

@@ -18,6 +18,8 @@ const Schema = Yup.object().shape({
.label(intl.get('payment_account_')),
reference: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
// statement: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
branch_id: Yup.string(),
exchange_rate: Yup.number(),
entries: Yup.array().of(
Yup.object().shape({
payment_amount: Yup.number().nullable(),

View File

@@ -1,6 +1,8 @@
import React from 'react';
import { FastField, ErrorMessage } from 'formik';
import styled from 'styled-components';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { FormattedMessage as T } from 'components';
import { isEqual } from 'lodash';
import intl from 'react-intl-universal';
import {
Classes,
@@ -16,11 +18,17 @@ import { CLASSES } from 'common/classes';
import { DateInput } from '@blueprintjs/datetime';
import { FieldRequiredHint, Col, Row } from 'components';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { Features } from 'common';
import {
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
Icon,
If,
FeatureCan,
ExchangeRateMutedField,
BranchSelect,
BranchSelectButton,
} from 'components';
import {
inputIntent,
@@ -28,20 +36,49 @@ import {
tansformDateValue,
handleDateChange,
} from 'utils';
import { useSetPrimaryBranchToForm, useForeignAccount } from './utils';
import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
/**
* Quick payment made form fields.
*/
export default function QuickPaymentMadeFormFields() {
const { accounts } = useQuickPaymentMadeContext();
function QuickPaymentMadeFormFields({
// #withCurrentOrganization
organization: { base_currency },
}) {
const { accounts, branches, baseCurrency } = useQuickPaymentMadeContext();
// Intl context.
const { values } = useFormikContext();
const paymentMadeFieldRef = useAutofocus();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={Classes.DIALOG_BODY}>
<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}>
{/* ------------- Vendor name ------------- */}
@@ -114,6 +151,18 @@ export default function QuickPaymentMadeFormFields() {
</FormGroup>
)}
</FastField>
<If condition={!isEqual(base_currency, values.currency_code)}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={base_currency}
toCurrency={values.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.payment_date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row>
<Col xs={5}>
{/* ------------- Payment date ------------- */}
@@ -206,3 +255,11 @@ export default function QuickPaymentMadeFormFields() {
</div>
);
}
export default compose(withCurrentOrganization())(QuickPaymentMadeFormFields);
export const BranchRowDivider = styled.div`
height: 1px;
background: #ebf1f6;
margin-bottom: 15px;
`;

View File

@@ -1,6 +1,13 @@
import React from 'react';
import { DialogContent } from 'components';
import { useBill, useAccounts, useCreatePaymentMade } from 'hooks/query';
import {
useBill,
useAccounts,
useBranches,
useCreatePaymentMade,
} from 'hooks/query';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
import { pick } from 'lodash';
@@ -9,7 +16,11 @@ const QuickPaymentMadeContext = React.createContext();
/**
* Quick payment made dialog provider.
*/
function QuickPaymentMadeFormProvider({ billId, dialogName, ...props }) {
function QuickPaymentMadeFormProvider({ query, billId, dialogName, ...props }) {
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Handle fetch bill details.
const { isLoading: isBillLoading, data: bill } = useBill(billId, {
enabled: !!billId,
@@ -21,6 +32,13 @@ function QuickPaymentMadeFormProvider({ billId, dialogName, ...props }) {
// Create payment made mutations.
const { mutateAsync: createPaymentMadeMutate } = useCreatePaymentMade();
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches(query, { enabled: isBranchFeatureCan });
// State provider.
const provider = {
bill: {
@@ -29,12 +47,16 @@ function QuickPaymentMadeFormProvider({ billId, dialogName, ...props }) {
payment_amount: bill?.due_amount,
},
accounts,
branches,
dialogName,
createPaymentMadeMutate,
isBranchesSuccess,
};
return (
<DialogContent isLoading={isAccountsLoading || isBillLoading}>
<DialogContent
isLoading={isAccountsLoading || isBillLoading || isBranchesLoading}
>
<QuickPaymentMadeContext.Provider value={provider} {...props} />
</DialogContent>
);

View File

@@ -1,5 +1,11 @@
import React from 'react';
import moment from 'moment';
import intl from 'react-intl-universal';
import { first, isEqual } from 'lodash';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useFormikContext } from 'formik';
import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider';
// Default initial values of payment made.
export const defaultPaymentMade = {
@@ -9,6 +15,8 @@ export const defaultPaymentMade = {
reference: '',
payment_number: '',
// statement: '',
exchange_rate: 1,
branch_id: '',
entries: [{ bill_id: '', payment_amount: '' }],
};
@@ -16,10 +24,7 @@ export const transformErrors = (errors, { setFieldError }) => {
const getError = (errorType) => errors.find((e) => e.type === errorType);
if (getError('PAYMENT.NUMBER.NOT.UNIQUE')) {
setFieldError(
'payment_number',
intl.get('payment_number_is_not_unique'),
);
setFieldError('payment_number', intl.get('payment_number_is_not_unique'));
}
if (getError('INVALID_BILL_PAYMENT_AMOUNT')) {
setFieldError(
@@ -27,4 +32,25 @@ export const transformErrors = (errors, { setFieldError }) => {
intl.get('the_payment_amount_bigger_than_invoice_due_amount'),
);
}
if (getError('WITHDRAWAL_ACCOUNT_CURRENCY_INVALID')) {
AppToaster.show({
message: intl.get('payment_made.error.withdrawal_account_currency_invalid'),
intent: Intent.DANGER,
});
}
};
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useQuickPaymentMadeContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};

View File

@@ -3,22 +3,20 @@ import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from 'common/dataTypes';
const Schema = Yup.object().shape({
customer_id: Yup.string()
.label(intl.get('customer_name_'))
.required(),
customer_id: Yup.string().label(intl.get('customer_name_')).required(),
payment_receive_no: Yup.string()
.required()
.nullable()
.max(DATATYPES_LENGTH.STRING)
.label(intl.get('payment_receive_no_')),
payment_date: Yup.date()
.required()
.label(intl.get('payment_date_')),
payment_date: Yup.date().required().label(intl.get('payment_date_')),
deposit_account_id: Yup.number()
.required()
.label(intl.get('deposit_account_')),
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
// statement: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
branch_id: Yup.string(),
exchange_rate: Yup.number(),
entries: Yup.array().of(
Yup.object().shape({
payment_amount: Yup.number().nullable(),

View File

@@ -1,8 +1,10 @@
import React from 'react';
import { FastField, ErrorMessage } from 'formik';
import styled from 'styled-components';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { useAutofocus } from 'hooks';
import { isEqual } from 'lodash';
import {
Classes,
FormGroup,
@@ -20,32 +22,66 @@ import {
InputPrependText,
MoneyInputGroup,
Icon,
If,
FeatureCan,
ExchangeRateMutedField,
BranchSelect,
BranchSelectButton,
} from 'components';
import { Features } from 'common';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
compose
compose,
} from 'utils';
import { useSetPrimaryBranchToForm } from './utils';
import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import withSettings from 'containers/Settings/withSettings';
/**
* Quick payment receive form fields.
*/
function QuickPaymentReceiveFormFields({
paymentReceiveAutoIncrement
paymentReceiveAutoIncrement,
// #withCurrentOrganization
organization: { base_currency },
}) {
const { accounts } = useQuickPaymentReceiveContext();
const { accounts, branches, baseCurrency } = useQuickPaymentReceiveContext();
// Intl context.
const { values } = useFormikContext();
const paymentReceiveFieldRef = useAutofocus();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={Classes.DIALOG_BODY}>
<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}>
{/* ------------- Customer name ------------- */}
@@ -120,6 +156,19 @@ function QuickPaymentReceiveFormFields({
</FormGroup>
)}
</FastField>
<If condition={!isEqual(base_currency, values.currency_code)}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={base_currency}
toCurrency={values.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.payment_date}
exchangeRate={values.exchange_rate}
/>
</If>
<Row>
<Col xs={5}>
{/* ------------- Payment date ------------- */}
@@ -199,6 +248,7 @@ function QuickPaymentReceiveFormFields({
</FormGroup>
)}
</FastField>
{/* --------- Statement --------- */}
<FastField name={'statement'}>
{({ form, field, meta: { error, touched } }) => (
@@ -218,4 +268,11 @@ export default compose(
withSettings(({ paymentReceiveSettings }) => ({
paymentReceiveAutoIncrement: paymentReceiveSettings?.autoIncrement,
})),
)(QuickPaymentReceiveFormFields)
withCurrentOrganization(),
)(QuickPaymentReceiveFormFields);
export const BranchRowDivider = styled.div`
height: 1px;
background: #ebf1f6;
margin-bottom: 15px;
`;

View File

@@ -1,14 +1,31 @@
import React, { useContext, createContext } from 'react';
import { pick } from 'lodash';
import { DialogContent } from 'components';
import { useAccounts, useInvoice, useSettingsPaymentReceives, useCreatePaymentReceive } from 'hooks/query';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
import {
useAccounts,
useInvoice,
useBranches,
useSettingsPaymentReceives,
useCreatePaymentReceive,
} from 'hooks/query';
const QuickPaymentReceiveContext = createContext();
/**
* Quick payment receive dialog provider.
*/
function QuickPaymentReceiveFormProvider({ invoiceId, dialogName, ...props }) {
function QuickPaymentReceiveFormProvider({
query,
invoiceId,
dialogName,
baseCurrency,
...props
}) {
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Handle fetch accounts data.
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
@@ -22,9 +39,17 @@ function QuickPaymentReceiveFormProvider({ invoiceId, dialogName, ...props }) {
// Fetch payment made settings.
const { isLoading: isSettingsLoading } = useSettingsPaymentReceives();
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches(query, { enabled: isBranchFeatureCan });
// State provider.
const provider = {
accounts,
branches,
invoice: {
...pick(invoice, ['id', 'due_amount', 'customer', 'currency_code']),
customer_id: invoice?.customer?.display_name,
@@ -32,13 +57,16 @@ function QuickPaymentReceiveFormProvider({ invoiceId, dialogName, ...props }) {
},
isAccountsLoading,
isSettingsLoading,
isBranchesSuccess,
dialogName,
baseCurrency,
createPaymentReceiveMutate,
};
return (
<DialogContent isLoading={isAccountsLoading || isInvoiceLoading}>
<DialogContent
isLoading={isAccountsLoading || isInvoiceLoading || isBranchesLoading}
>
<QuickPaymentReceiveContext.Provider value={provider} {...props} />
</DialogContent>
);

View File

@@ -1,5 +1,12 @@
import React from 'react';
import moment from 'moment';
import intl from 'react-intl-universal';
import { first } from 'lodash';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useFormikContext } from 'formik';
import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider';
export const defaultInitialValues = {
customer_id: '',
@@ -8,6 +15,8 @@ export const defaultInitialValues = {
payment_date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '',
// statement: '',
exchange_rate: 1,
branch_id: '',
entries: [{ invoice_id: '', payment_amount: '' }],
};
@@ -32,4 +41,25 @@ export const transformErrors = (errors, { setFieldError }) => {
intl.get('the_payment_amount_bigger_than_invoice_due_amount'),
);
}
if (getError('PAYMENT_ACCOUNT_CURRENCY_INVALID')) {
AppToaster.show({
message: intl.get('payment_Receive.error.payment_account_currency_invalid'),
intent: Intent.DANGER,
});
}
};
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useQuickPaymentReceiveContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};

View File

@@ -19,6 +19,7 @@ const defaultInitialValues = {
reference_no: '',
description: '',
amount: '',
exchange_rate: 1,
};
/**

View File

@@ -8,5 +8,6 @@ const Schema = Yup.object().shape({
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
from_account_id: Yup.number().required().label(intl.get('deposit_account_')),
description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
exchange_rate: Yup.number(),
});
export const CreateRefundCreditNoteFormSchema = Schema;

View File

@@ -1,6 +1,8 @@
import React from 'react';
import styled from 'styled-components';
import intl from 'react-intl-universal';
import { FastField, ErrorMessage } from 'formik';
import { isEqual } from 'lodash';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
@@ -11,14 +13,22 @@ import {
} from '@blueprintjs/core';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { Features } from 'common';
import { DateInput } from '@blueprintjs/datetime';
import {
Icon,
Col,
Row,
If,
FieldRequiredHint,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FormattedMessage as T,
ExchangeRateMutedField,
BranchSelect,
BranchSelectButton,
FeatureCan,
} from 'components';
import {
inputIntent,
@@ -28,41 +38,112 @@ import {
} from 'utils';
import { useAutofocus } from 'hooks';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { useSetPrimaryBranchToForm } from './utils';
import { useRefundCreditNoteContext } from './RefundCreditNoteFormProvider';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
/**
* Refund credit note form fields.
*/
function RefundCreditNoteFormFields() {
const { accounts } = useRefundCreditNoteContext();
function RefundCreditNoteFormFields({
// #withCurrentOrganization
organization: { base_currency },
}) {
const { accounts, branches } = useRefundCreditNoteContext();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={Classes.DIALOG_BODY}>
{/* ------------- Refund date ------------- */}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.refund_date'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
// inline={true}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<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}>
{/* ------------- Refund date ------------- */}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.refund_date'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
// inline={true}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/* ------------ Form account ------------ */}
<FastField name={'from_account_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.from_account'} />}
className={classNames(
'form-group--from_account_id',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'from_account_id'} />}
>
<AccountsSuggestField
selectedAccountId={value}
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('from_account_id', id)
}
inputProps={{
placeholder: intl.get('select_account'),
}}
filterByTypes={[
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.FIXED_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/* ------------- Amount ------------- */}
<FastField name={'amount'}>
{({
@@ -76,7 +157,6 @@ function RefundCreditNoteFormFields() {
className={classNames('form-group--amount', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
// inline={true}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
@@ -93,6 +173,19 @@ function RefundCreditNoteFormFields() {
</FormGroup>
)}
</FastField>
<If condition={!isEqual(base_currency, values.currency_code)}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={base_currency}
toCurrency={values.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
{/* ------------ Reference No. ------------ */}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
@@ -101,7 +194,6 @@ function RefundCreditNoteFormFields() {
className={classNames('form-group--reference', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference" />}
// inline={true}
>
<InputGroup
intent={inputIntent({ error, touched })}
@@ -112,46 +204,12 @@ function RefundCreditNoteFormFields() {
)}
</FastField>
{/* ------------ Form account ------------ */}
<FastField name={'from_account_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.from_account'} />}
className={classNames(
'form-group--from_account_id',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'from_account_id'} />}
// inline={true}
>
<AccountsSuggestField
selectedAccountId={value}
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('from_account_id', id)
}
inputProps={{
placeholder: intl.get('select_account'),
}}
filterByTypes={[
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.FIXED_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
{/* --------- Statement --------- */}
<FastField name={'description'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.description'} />}
className={'form-group--description'}
// inline={true}
>
<TextArea growVertically={true} {...field} />
</FormGroup>
@@ -161,4 +219,10 @@ function RefundCreditNoteFormFields() {
);
}
export default RefundCreditNoteFormFields;
export default compose(withCurrentOrganization())(RefundCreditNoteFormFields);
export const BranchRowDivider = styled.div`
height: 1px;
background: #ebf1f6;
margin-bottom: 13px;
`;

View File

@@ -1,10 +1,12 @@
import React from 'react';
import { DialogContent } from 'components';
import { pick } from 'lodash';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
import {
useAccounts,
useCreditNote,
useBranches,
useCreateRefundCreditNote,
} from 'hooks/query';
@@ -13,7 +15,16 @@ const RefundCreditNoteContext = React.createContext();
/**
* Refund credit note form provider.
*/
function RefundCreditNoteFormProvider({ creditNoteId, dialogName, ...props }) {
function RefundCreditNoteFormProvider({
creditNoteId,
dialogName,
query,
...props
}) {
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Handle fetch accounts data.
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
@@ -24,6 +35,14 @@ function RefundCreditNoteFormProvider({ creditNoteId, dialogName, ...props }) {
enabled: !!creditNoteId,
},
);
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches(query, { enabled: isBranchFeatureCan });
// Create and edit credit note mutations.
const { mutateAsync: createRefundCreditNoteMutate } =
useCreateRefundCreditNote();
@@ -35,12 +54,17 @@ function RefundCreditNoteFormProvider({ creditNoteId, dialogName, ...props }) {
amount: creditNote.credits_remaining,
},
accounts,
branches,
dialogName,
isBranchesSuccess,
createRefundCreditNoteMutate,
};
return (
<DialogContent isLoading={isAccountsLoading || isCreditNoteLoading}>
<DialogContent
isLoading={isAccountsLoading || isCreditNoteLoading || isBranchesLoading}
>
<RefundCreditNoteContext.Provider value={provider} {...props} />
</DialogContent>
);

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { first } from 'lodash';
import { useRefundCreditNoteContext } from './RefundCreditNoteFormProvider';
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useRefundCreditNoteContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};

View File

@@ -3,14 +3,13 @@ import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import moment from 'moment';
import { omit, defaultTo } from 'lodash';
import { omit } from 'lodash';
import { AppToaster } from 'components';
import { useRefundVendorCreditContext } from './RefundVendorCreditFormProvider';
import { CreateVendorRefundCreditFormSchema } from './RefundVendorCreditForm.schema';
import RefundVendorCreditFormContent from './RefundVendorCreditFormContent';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, transactionNumber } from 'utils';
@@ -20,6 +19,7 @@ const defaultInitialValues = {
reference_no: '',
description: '',
amount: '',
exchange_rate: 1,
};
/**

View File

@@ -10,5 +10,6 @@ const Schema = Yup.object().shape({
.required()
.label(intl.get('deposit_account_')),
description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
exchange_rate: Yup.number(),
});
export const CreateVendorRefundCreditFormSchema = Schema;

View File

@@ -1,6 +1,7 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FastField, ErrorMessage } from 'formik';
import styled from 'styled-components';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import {
Classes,
FormGroup,
@@ -12,13 +13,21 @@ import {
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { DateInput } from '@blueprintjs/datetime';
import { isEqual } from 'lodash';
import {
Icon,
Col,
Row,
If,
FieldRequiredHint,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FormattedMessage as T,
ExchangeRateMutedField,
BranchSelect,
BranchSelectButton,
FeatureCan,
} from 'components';
import {
inputIntent,
@@ -28,42 +37,113 @@ import {
compose,
} from 'utils';
import { useAutofocus } from 'hooks';
import { Features } from 'common';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { useSetPrimaryBranchToForm } from './utils';
import { useRefundVendorCreditContext } from './RefundVendorCreditFormProvider';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
/**
* Refund Vendor credit form fields.
*/
function RefundVendorCreditFormFields() {
const { accounts } = useRefundVendorCreditContext();
function RefundVendorCreditFormFields({
// #withCurrentOrganization
organization: { base_currency },
}) {
const { accounts, branches } = useRefundVendorCreditContext();
const { values } = useFormikContext();
const amountFieldRef = useAutofocus();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={Classes.DIALOG_BODY}>
{/* ------------- Refund date ------------- */}
<FastField name={'refund_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_vendor_credit.dialog.refund_date'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="refund_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('refund_date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<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}>
{/* ------------- Refund date ------------- */}
<FastField name={'refund_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_vendor_credit.dialog.refund_date'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="refund_date" />}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('refund_date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
</Col>
<Col xs={5}>
{/* ------------ Form account ------------ */}
<FastField name={'deposit_account_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={
<T id={'refund_vendor_credit.dialog.deposit_to_account'} />
}
className={classNames(
'form-group--deposit_account_id',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'deposit_account_id'} />}
>
<AccountsSuggestField
selectedAccountId={value}
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('deposit_account_id', id)
}
inputProps={{
placeholder: intl.get('select_account'),
}}
filterByTypes={[
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.FIXED_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
</Col>
</Row>
{/* ------------- Amount ------------- */}
<FastField name={'amount'}>
{({
@@ -93,6 +173,19 @@ function RefundVendorCreditFormFields() {
</FormGroup>
)}
</FastField>
<If condition={!isEqual(base_currency, values.currency_code)}>
{/*------------ exchange rate -----------*/}
<ExchangeRateMutedField
name={'exchange_rate'}
fromCurrency={base_currency}
toCurrency={values.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.date}
exchangeRate={values.exchange_rate}
/>
</If>
{/* ------------ Reference No. ------------ */}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
@@ -111,38 +204,6 @@ function RefundVendorCreditFormFields() {
)}
</FastField>
{/* ------------ Form account ------------ */}
<FastField name={'deposit_account_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_vendor_credit.dialog.deposit_to_account'} />}
className={classNames(
'form-group--deposit_account_id',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'deposit_account_id'} />}
>
<AccountsSuggestField
selectedAccountId={value}
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('deposit_account_id', id)
}
inputProps={{
placeholder: intl.get('select_account'),
}}
filterByTypes={[
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.FIXED_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
{/* --------- Statement --------- */}
<FastField name={'description'}>
{({ form, field, meta: { error, touched } }) => (
@@ -158,4 +219,10 @@ function RefundVendorCreditFormFields() {
);
}
export default RefundVendorCreditFormFields;
export default compose(withCurrentOrganization())(RefundVendorCreditFormFields);
export const BranchRowDivider = styled.div`
height: 1px;
background: #ebf1f6;
margin-bottom: 13px;
`;

View File

@@ -1,10 +1,12 @@
import React from 'react';
import { DialogContent } from 'components';
import { pick } from 'lodash';
import { Features } from 'common';
import { useFeatureCan } from 'hooks/state';
import {
useAccounts,
useVendorCredit,
useBranches,
useCreateRefundVendorCredit,
} from 'hooks/query';
@@ -13,11 +15,23 @@ const RefundVendorCreditContext = React.createContext();
function RefundVendorCreditFormProvider({
vendorCreditId,
dialogName,
query,
...props
}) {
// Features guard.
const { featureCan } = useFeatureCan();
const isBranchFeatureCan = featureCan(Features.Branches);
// Handle fetch accounts data.
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
// Fetches the branches list.
const {
data: branches,
isLoading: isBranchesLoading,
isSuccess: isBranchesSuccess,
} = useBranches(query, { enabled: isBranchFeatureCan });
// Handle fetch vendor credit details.
const { data: vendorCredit, isLoading: isVendorCreditLoading } =
useVendorCredit(vendorCreditId, {
@@ -35,12 +49,18 @@ function RefundVendorCreditFormProvider({
amount: vendorCredit.credits_remaining,
},
accounts,
branches,
dialogName,
isBranchesSuccess,
createRefundVendorCreditMutate,
};
return (
<DialogContent isLoading={isAccountsLoading || isVendorCreditLoading}>
<DialogContent
isLoading={
isAccountsLoading || isVendorCreditLoading || isBranchesLoading
}
>
<RefundVendorCreditContext.Provider value={provider} {...props} />
</DialogContent>
);

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { first } from 'lodash';
import { useRefundVendorCreditContext } from './RefundVendorCreditFormProvider';
export const useSetPrimaryBranchToForm = () => {
const { setFieldValue } = useFormikContext();
const { branches, isBranchesSuccess } = useRefundVendorCreditContext();
React.useEffect(() => {
if (isBranchesSuccess) {
const primaryBranch = branches.find((b) => b.primary) || first(branches);
if (primaryBranch) {
setFieldValue('branch_id', primaryBranch.id);
}
}
}, [isBranchesSuccess, setFieldValue, branches]);
};

View File

@@ -0,0 +1,25 @@
import React from 'react';
import 'style/pages/VendorOpeningBalance/VendorOpeningBalance.scss';
import VendorOpeningBalanceForm from './VendorOpeningBalanceForm';
import { VendorOpeningBalanceFormProvider } from './VendorOpeningBalanceFormProvider';
/**
* Vendor Opening balance dialog content.
* @returns
*/
export default function VendorOpeningBalanceDialogContent({
// #ownProps
dialogName,
vendorId,
}) {
return (
<VendorOpeningBalanceFormProvider
vendorId={vendorId}
dialogName={dialogName}
>
<VendorOpeningBalanceForm />
</VendorOpeningBalanceFormProvider>
);
}

View File

@@ -0,0 +1,83 @@
import React from 'react';
import moment from 'moment';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import { defaultTo } from 'lodash';
import { AppToaster } from 'components';
import { CreateVendorOpeningBalanceFormSchema } from './VendorOpeningBalanceForm.schema';
import { useVendorOpeningBalanceContext } from './VendorOpeningBalanceFormProvider';
import VendorOpeningBalanceFormContent from './VendorOpeningBalanceFormContent';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
const defaultInitialValues = {
opening_balance: '0',
opening_balance_branch_id: '',
opening_balance_exchange_rate: 1,
opening_balance_at: moment(new Date()).format('YYYY-MM-DD'),
};
/**
* Vendor Opening balance form.
* @returns
*/
function VendorOpeningBalanceForm({
// #withDialogActions
closeDialog,
}) {
const { dialogName, vendor, editVendorOpeningBalanceMutate } =
useVendorOpeningBalanceContext();
// Initial form values
const initialValues = {
...defaultInitialValues,
...vendor,
opening_balance: defaultTo(vendor.opening_balance, ''),
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const formValues = {
...values,
};
// Handle request response success.
const onSuccess = (response) => {
AppToaster.show({
message: intl.get('vendor_opening_balance.success_message'),
intent: Intent.SUCCESS,
});
closeDialog(dialogName);
};
// Handle request response errors.
const onError = ({
response: {
data: { errors },
},
}) => {
if (errors) {
}
setSubmitting(false);
};
editVendorOpeningBalanceMutate([vendor.id, formValues])
.then(onSuccess)
.catch(onError);
};
return (
<Formik
validationSchema={CreateVendorOpeningBalanceFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={VendorOpeningBalanceFormContent}
/>
);
}
export default compose(withDialogActions)(VendorOpeningBalanceForm);

View File

@@ -0,0 +1,10 @@
import * as Yup from 'yup';
const Schema = Yup.object().shape({
opening_balance_branch_id: Yup.string(),
opening_balance: Yup.number().nullable(),
opening_balance_at: Yup.date(),
opening_balance_exchange_rate: Yup.number(),
});
export const CreateVendorOpeningBalanceFormSchema = Schema;

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { Form } from 'formik';
import VendorOpeningBalanceFormFields from './VendorOpeningBalanceFormFields';
import VendorOpeningBalanceFormFloatingActions from './VendorOpeningBalanceFormFloatingActions';
/**
* Vendor Opening balance form content.
* @returns
*/
function VendorOpeningBalanceFormContent() {
return (
<Form>
<VendorOpeningBalanceFormFields />
<VendorOpeningBalanceFormFloatingActions />
</Form>
);
}
export default VendorOpeningBalanceFormContent;

View File

@@ -0,0 +1,116 @@
import React from 'react';
import { Classes, Position, FormGroup, ControlGroup } from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { isEqual } from 'lodash';
import { FastField, useFormikContext } from 'formik';
import { momentFormatter, tansformDateValue, handleDateChange } from 'utils';
import { Features } from 'common';
import classNames from 'classnames';
import {
If,
Icon,
FormattedMessage as T,
ExchangeRateMutedField,
BranchSelect,
BranchSelectButton,
FeatureCan,
InputPrependText,
} from 'components';
import { FMoneyInputGroup, FFormGroup } from '../../../components/Forms';
import { useVendorOpeningBalanceContext } from './VendorOpeningBalanceFormProvider';
import { useSetPrimaryBranchToForm } from './utils';
import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
import { compose } from 'utils';
/**
* Vendor Opening balance form fields.
* @returns
*/
function VendorOpeningBalanceFormFields({
// #withCurrentOrganization
organization: { base_currency },
}) {
// Formik context.
const { values } = useFormikContext();
const { branches, vendor } = useVendorOpeningBalanceContext();
// Sets the primary branch to form.
useSetPrimaryBranchToForm();
return (
<div className={Classes.DIALOG_BODY}>
{/*------------ Opening balance -----------*/}
<FFormGroup
name={'opening_balance'}
label={<T id={'vendor_opening_balance.label.opening_balance'} />}
>
<ControlGroup>
<InputPrependText text={vendor.currency_code} />
<FMoneyInputGroup
name={'opening_balance'}
allowDecimals={true}
allowNegativeValue={true}
/>
</ControlGroup>
</FFormGroup>
{/*------------ Opening balance at -----------*/}
<FastField name={'opening_balance_at'}>
{({ form, field: { value } }) => (
<FormGroup
label={<T id={'vendor_opening_balance.label.opening_balance_at'} />}
className={Classes.FILL}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('opening_balance_at', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
<If condition={!isEqual(base_currency, vendor.currency_code)}>
{/*------------ Opening balance exchange rate -----------*/}
<ExchangeRateMutedField
name={'opening_balance_exchange_rate'}
fromCurrency={base_currency}
toCurrency={vendor.currency_code}
formGroupProps={{ label: '', inline: false }}
date={values.opening_balance_at}
exchangeRate={values.opening_balance_exchange_rate}
/>
</If>
{/*------------ Opening balance branch id -----------*/}
<FeatureCan feature={Features.Branches}>
<FFormGroup
label={<T id={'branch'} />}
name={'opening_balance_branch_id'}
className={classNames('form-group--select-list', Classes.FILL)}
>
<BranchSelect
name={'opening_balance_branch_id'}
branches={branches}
input={BranchSelectButton}
popoverProps={{ minimal: true }}
/>
</FFormGroup>
</FeatureCan>
</div>
);
}
export default compose(withCurrentOrganization())(
VendorOpeningBalanceFormFields,
);

Some files were not shown because too many files have changed in this diff Show More