mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
fix: allocate landed cost on inventory items.
This commit is contained in:
15
src/components/Dialog/DialogFooter.js
Normal file
15
src/components/Dialog/DialogFooter.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
export function DialogFooter({ children }) {
|
||||||
|
return (
|
||||||
|
<DialogFooterRoot className={Classes.DIALOG_FOOTER}>
|
||||||
|
{children}
|
||||||
|
</DialogFooterRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DialogFooterRoot = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
@@ -14,13 +14,11 @@ export function DialogFooterActions({ alignment = 'right', children }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DialogFooterActionsRoot = styled.div`
|
const DialogFooterActionsRoot = styled.div`
|
||||||
margin-left: -10px;
|
${(props) =>
|
||||||
margin-right: -10px;
|
props.alignment === 'right' ? 'margin-left: auto;' : 'margin-right: auto;'};
|
||||||
justify-content: ${(props) =>
|
|
||||||
props.alignment === 'right' ? 'flex-end' : 'flex-start'};
|
|
||||||
|
|
||||||
.bp3-button {
|
.bp3-button {
|
||||||
margin-left: 10px;
|
margin-left: 5px;
|
||||||
margin-left: 10px;
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -3,4 +3,5 @@
|
|||||||
export * from './Dialog';
|
export * from './Dialog';
|
||||||
export * from './DialogFooterActions';
|
export * from './DialogFooterActions';
|
||||||
export * from './DialogSuspense';
|
export * from './DialogSuspense';
|
||||||
export * from './DialogContent';
|
export * from './DialogContent';
|
||||||
|
export * from './DialogFooter';
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { defaultTo, get } from 'lodash';
|
||||||
import { DialogContent } from 'components';
|
import { DialogContent } from 'components';
|
||||||
import { useBill, useCreateLandedCost } from 'hooks/query';
|
import {
|
||||||
|
useBill,
|
||||||
|
useCreateLandedCost,
|
||||||
|
useLandedCostTransaction,
|
||||||
|
} from 'hooks/query';
|
||||||
|
import {
|
||||||
|
getEntriesByTransactionId,
|
||||||
|
getCostTransactionById,
|
||||||
|
getTransactionEntryById,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
const AllocateLandedCostDialogContext = React.createContext();
|
const AllocateLandedCostDialogContext = React.createContext();
|
||||||
|
|
||||||
@@ -13,22 +23,79 @@ function AllocateLandedCostDialogProvider({
|
|||||||
dialogName,
|
dialogName,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
|
const [transactionsType, setTransactionsType] = React.useState(null);
|
||||||
|
const [transactionId, setTransactionId] = React.useState(null);
|
||||||
|
const [transactionEntryId, setTransactionEntryId] = React.useState(null);
|
||||||
|
|
||||||
// Handle fetch bill details.
|
// Handle fetch bill details.
|
||||||
const { isLoading: isBillLoading, data: bill } = useBill(billId, {
|
const { isLoading: isBillLoading, data: bill } = useBill(billId, {
|
||||||
enabled: !!billId,
|
enabled: !!billId,
|
||||||
});
|
});
|
||||||
|
// Retrieve the landed cost transactions based on the given transactions type.
|
||||||
|
const {
|
||||||
|
data: { transactions: landedCostTransactions },
|
||||||
|
} = useLandedCostTransaction(transactionsType, {
|
||||||
|
enabled: !!transactionsType,
|
||||||
|
});
|
||||||
|
// Landed cost selected transaction.
|
||||||
|
const costTransaction = React.useMemo(
|
||||||
|
() =>
|
||||||
|
transactionId
|
||||||
|
? getCostTransactionById(transactionId, landedCostTransactions)
|
||||||
|
: null,
|
||||||
|
[transactionId, landedCostTransactions],
|
||||||
|
);
|
||||||
|
// Retrieve the cost transaction entry.
|
||||||
|
const costTransactionEntry = React.useMemo(
|
||||||
|
() =>
|
||||||
|
costTransaction && transactionEntryId
|
||||||
|
? getTransactionEntryById(costTransaction, transactionEntryId)
|
||||||
|
: null,
|
||||||
|
[costTransaction, transactionEntryId],
|
||||||
|
);
|
||||||
|
// Retrieve entries of the given transaction id.
|
||||||
|
const costTransactionEntries = React.useMemo(
|
||||||
|
() =>
|
||||||
|
transactionId
|
||||||
|
? getEntriesByTransactionId(landedCostTransactions, transactionId)
|
||||||
|
: [],
|
||||||
|
[landedCostTransactions, transactionId],
|
||||||
|
);
|
||||||
// Create landed cost mutations.
|
// Create landed cost mutations.
|
||||||
const { mutateAsync: createLandedCostMutate } = useCreateLandedCost();
|
const { mutateAsync: createLandedCostMutate } = useCreateLandedCost();
|
||||||
|
|
||||||
// provider payload.
|
// Retrieve the unallocate cost amount of cost transaction.
|
||||||
|
const unallocatedCostAmount = defaultTo(
|
||||||
|
get(costTransactionEntry, 'unallocated_cost_amount'),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Retrieve the unallocate cost amount of cost transaction.
|
||||||
|
const formattedUnallocatedCostAmount = defaultTo(
|
||||||
|
get(costTransactionEntry, 'formatted_unallocated_cost_amount'),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Provider payload.
|
||||||
const provider = {
|
const provider = {
|
||||||
isBillLoading,
|
isBillLoading,
|
||||||
bill,
|
bill,
|
||||||
dialogName,
|
dialogName,
|
||||||
query,
|
query,
|
||||||
createLandedCostMutate,
|
createLandedCostMutate,
|
||||||
|
costTransaction,
|
||||||
|
costTransactionEntries,
|
||||||
|
transactionsType,
|
||||||
|
landedCostTransactions,
|
||||||
|
setTransactionsType,
|
||||||
|
setTransactionId,
|
||||||
|
setTransactionEntryId,
|
||||||
|
costTransactionEntry,
|
||||||
|
transactionEntryId,
|
||||||
|
transactionId,
|
||||||
billId,
|
billId,
|
||||||
|
unallocatedCostAmount,
|
||||||
|
formattedUnallocatedCostAmount,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
import { Intent, Button } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
|
||||||
|
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import {
|
||||||
|
DialogFooter,
|
||||||
|
DialogFooterActions,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogProvider';
|
import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogProvider';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
@@ -13,7 +18,8 @@ function AllocateLandedCostFloatingActions({
|
|||||||
}) {
|
}) {
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
const { dialogName } = useAllocateLandedConstDialogContext();
|
const { dialogName, costTransactionEntry, formattedUnallocatedCostAmount } =
|
||||||
|
useAllocateLandedConstDialogContext();
|
||||||
|
|
||||||
// Handle cancel button click.
|
// Handle cancel button click.
|
||||||
const handleCancelBtnClick = (event) => {
|
const handleCancelBtnClick = (event) => {
|
||||||
@@ -21,22 +27,41 @@ function AllocateLandedCostFloatingActions({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<DialogFooter>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<DialogFooterActions alignment={'left'}>
|
||||||
|
{costTransactionEntry && (
|
||||||
|
<UnallocatedAmount>
|
||||||
|
Unallocated cost Amount:{' '}
|
||||||
|
<strong>{formattedUnallocatedCostAmount}</strong>
|
||||||
|
</UnallocatedAmount>
|
||||||
|
)}
|
||||||
|
</DialogFooterActions>
|
||||||
|
|
||||||
|
<DialogFooterActions alignment={'right'}>
|
||||||
<Button onClick={handleCancelBtnClick} style={{ minWidth: '85px' }}>
|
<Button onClick={handleCancelBtnClick} style={{ minWidth: '85px' }}>
|
||||||
<T id={'cancel'} />
|
<T id={'cancel'} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
style={{ minWidth: '85px' }}
|
style={{ minWidth: '100px' }}
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
>
|
>
|
||||||
{<T id={'save'} />}
|
{<T id={'save'} />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</DialogFooterActions>
|
||||||
</div>
|
</DialogFooter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogActions)(AllocateLandedCostFloatingActions);
|
export default compose(withDialogActions)(AllocateLandedCostFloatingActions);
|
||||||
|
|
||||||
|
const UnallocatedAmount = styled.div`
|
||||||
|
color: #3f5278;
|
||||||
|
align-self: center;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #353535;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import React from 'react';
|
|||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import moment from 'moment';
|
|
||||||
import { sumBy } from 'lodash';
|
|
||||||
|
|
||||||
import 'style/pages/AllocateLandedCost/AllocateLandedCostForm.scss';
|
import 'style/pages/AllocateLandedCost/AllocateLandedCostForm.scss';
|
||||||
|
|
||||||
@@ -14,20 +12,19 @@ import AllocateLandedCostFormContent from './AllocateLandedCostFormContent';
|
|||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import { compose, transformToForm } from 'utils';
|
import { compose, transformToForm } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialItem = {
|
||||||
|
entry_id: '',
|
||||||
|
cost: '',
|
||||||
|
};
|
||||||
|
|
||||||
// Default form initial values.
|
// Default form initial values.
|
||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
transaction_type: 'Bill',
|
transaction_type: 'Bill',
|
||||||
transaction_date: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
transaction_id: '',
|
transaction_id: '',
|
||||||
transaction_entry_id: '',
|
transaction_entry_id: '',
|
||||||
amount: '',
|
amount: '',
|
||||||
allocation_method: 'quantity',
|
allocation_method: 'quantity',
|
||||||
items: [
|
items: [defaultInitialItem],
|
||||||
{
|
|
||||||
entry_id: '',
|
|
||||||
cost: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,8 +34,13 @@ function AllocateLandedCostForm({
|
|||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const { dialogName, bill, billId, createLandedCostMutate } =
|
const {
|
||||||
useAllocateLandedConstDialogContext();
|
dialogName,
|
||||||
|
bill,
|
||||||
|
billId,
|
||||||
|
createLandedCostMutate,
|
||||||
|
unallocatedCostAmount,
|
||||||
|
} = useAllocateLandedConstDialogContext();
|
||||||
|
|
||||||
// Initial form values.
|
// Initial form values.
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
@@ -49,11 +51,10 @@ function AllocateLandedCostForm({
|
|||||||
cost: '',
|
cost: '',
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
const amount = sumBy(initialValues.items, 'amount');
|
|
||||||
|
|
||||||
// Handle form submit.
|
// Handle form submit.
|
||||||
const handleFormSubmit = (values, { setSubmitting }) => {
|
const handleFormSubmit = (values, { setSubmitting }) => {
|
||||||
setSubmitting(false);
|
setSubmitting(true);
|
||||||
|
|
||||||
// Filters the entries has no cost.
|
// Filters the entries has no cost.
|
||||||
const entries = values.items
|
const entries = values.items
|
||||||
@@ -81,13 +82,16 @@ function AllocateLandedCostForm({
|
|||||||
// Handle the request error.
|
// Handle the request error.
|
||||||
const onError = () => {
|
const onError = () => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
AppToaster.show({ message: 'Something went wrong!', intent: Intent.DANGER });
|
AppToaster.show({
|
||||||
|
message: 'Something went wrong!',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
createLandedCostMutate([billId, form]).then(onSuccess).catch(onError);
|
createLandedCostMutate([billId, form]).then(onSuccess).catch(onError);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Computed validation schema.
|
// Computed validation schema.
|
||||||
const validationSchema = AllocateLandedCostFormSchema(amount);
|
const validationSchema = AllocateLandedCostFormSchema(unallocatedCostAmount);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
export const AllocateLandedCostFormSchema = (minAmount) =>
|
export const AllocateLandedCostFormSchema = (maxAmount) =>
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
transaction_type: Yup.string().label(intl.get('transaction_type')),
|
transaction_type: Yup.string()
|
||||||
transaction_date: Yup.date().label(intl.get('transaction_date')),
|
.required()
|
||||||
transaction_id: Yup.string().label(intl.get('transaction_number')),
|
.label(intl.get('transaction_type')),
|
||||||
transaction_entry_id: Yup.string().label(intl.get('transaction_line')),
|
transaction_id: Yup.string()
|
||||||
amount: Yup.number().max(minAmount).label(intl.get('amount')),
|
.required()
|
||||||
allocation_method: Yup.string().trim(),
|
.label(intl.get('transaction_number')),
|
||||||
|
transaction_entry_id: Yup.string()
|
||||||
|
.required()
|
||||||
|
.label(intl.get('transaction_line')),
|
||||||
|
amount: Yup.number().max(maxAmount).label(intl.get('amount')),
|
||||||
|
allocation_method: Yup.string().required().trim(),
|
||||||
items: Yup.array().of(
|
items: Yup.array().of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
entry_id: Yup.number().nullable(),
|
entry_id: Yup.number().nullable(),
|
||||||
|
|||||||
@@ -1,16 +1,37 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Form } from 'formik';
|
import { Form, useFormikContext } from 'formik';
|
||||||
|
import { FormObserver } from 'components';
|
||||||
import AllocateLandedCostFormFields from './AllocateLandedCostFormFields';
|
import AllocateLandedCostFormFields from './AllocateLandedCostFormFields';
|
||||||
|
import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogProvider';
|
||||||
import AllocateLandedCostFloatingActions from './AllocateLandedCostFloatingActions';
|
import AllocateLandedCostFloatingActions from './AllocateLandedCostFloatingActions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate landed cost form content.
|
* Allocate landed cost form content.
|
||||||
*/
|
*/
|
||||||
export default function AllocateLandedCostFormContent() {
|
export default function AllocateLandedCostFormContent() {
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
// Allocate landed cost dialog context.
|
||||||
|
const { setTransactionsType, setTransactionId, setTransactionEntryId } =
|
||||||
|
useAllocateLandedConstDialogContext();
|
||||||
|
|
||||||
|
// Handle the form change.
|
||||||
|
const handleFormChange = (values) => {
|
||||||
|
if (values.transaction_type) {
|
||||||
|
setTransactionsType(values.transaction_type);
|
||||||
|
}
|
||||||
|
if (values.transaction_id) {
|
||||||
|
setTransactionId(values.transaction_id);
|
||||||
|
}
|
||||||
|
if (values.transaction_entry_id) {
|
||||||
|
setTransactionEntryId(values.transaction_entry_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
<AllocateLandedCostFormFields />
|
<AllocateLandedCostFormFields />
|
||||||
<AllocateLandedCostFloatingActions />
|
<AllocateLandedCostFloatingActions />
|
||||||
|
<FormObserver values={values} onChange={handleFormChange} />
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FastField, Field, ErrorMessage, useFormikContext } from 'formik';
|
import { FastField, Field, ErrorMessage } from 'formik';
|
||||||
import {
|
import {
|
||||||
Classes,
|
Classes,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -14,31 +14,31 @@ import { inputIntent, handleStringChange } from 'utils';
|
|||||||
import { FieldRequiredHint, ListSelect } from 'components';
|
import { FieldRequiredHint, ListSelect } from 'components';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import allocateLandedCostType from 'common/allocateLandedCostType';
|
import allocateLandedCostType from 'common/allocateLandedCostType';
|
||||||
import { useLandedCostTransaction } from 'hooks/query';
|
|
||||||
|
|
||||||
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
|
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
|
||||||
import { getEntriesByTransactionId, allocateCostToEntries } from './utils';
|
import {
|
||||||
|
transactionsSelectShouldUpdate,
|
||||||
|
allocateCostToEntries,
|
||||||
|
resetAllocatedCostEntries,
|
||||||
|
} from './utils';
|
||||||
|
import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocate landed cost form fields.
|
* Allocate landed cost form fields.
|
||||||
*/
|
*/
|
||||||
export default function AllocateLandedCostFormFields() {
|
export default function AllocateLandedCostFormFields() {
|
||||||
const { values } = useFormikContext();
|
// Allocated landed cost dialog.
|
||||||
|
const { costTransactionEntries, landedCostTransactions } =
|
||||||
const {
|
useAllocateLandedConstDialogContext();
|
||||||
data: { transactions },
|
|
||||||
} = useLandedCostTransaction(values.transaction_type);
|
|
||||||
|
|
||||||
// Retrieve entries of the given transaction id.
|
|
||||||
const transactionEntries = React.useMemo(
|
|
||||||
() => getEntriesByTransactionId(transactions, values.transaction_id),
|
|
||||||
[transactions, values.transaction_id],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
{/*------------Transaction type -----------*/}
|
{/*------------Transaction type -----------*/}
|
||||||
<FastField name={'transaction_type'}>
|
<FastField
|
||||||
|
name={'transaction_type'}
|
||||||
|
transactions={allocateLandedCostType}
|
||||||
|
shouldUpdate={transactionsSelectShouldUpdate}
|
||||||
|
>
|
||||||
{({
|
{({
|
||||||
form: { values, setFieldValue },
|
form: { values, setFieldValue },
|
||||||
field: { value },
|
field: { value },
|
||||||
@@ -55,9 +55,14 @@ export default function AllocateLandedCostFormFields() {
|
|||||||
<ListSelect
|
<ListSelect
|
||||||
items={allocateLandedCostType}
|
items={allocateLandedCostType}
|
||||||
onItemSelect={(type) => {
|
onItemSelect={(type) => {
|
||||||
|
const { items } = values;
|
||||||
|
|
||||||
setFieldValue('transaction_type', type.value);
|
setFieldValue('transaction_type', type.value);
|
||||||
setFieldValue('transaction_id', '');
|
setFieldValue('transaction_id', '');
|
||||||
setFieldValue('transaction_entry_id', '');
|
setFieldValue('transaction_entry_id', '');
|
||||||
|
|
||||||
|
setFieldValue('amount', '');
|
||||||
|
setFieldValue('items', resetAllocatedCostEntries(items));
|
||||||
}}
|
}}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
selectedItem={value}
|
selectedItem={value}
|
||||||
@@ -70,7 +75,11 @@ export default function AllocateLandedCostFormFields() {
|
|||||||
</FastField>
|
</FastField>
|
||||||
|
|
||||||
{/*------------ Transaction -----------*/}
|
{/*------------ Transaction -----------*/}
|
||||||
<Field name={'transaction_id'}>
|
<Field
|
||||||
|
name={'transaction_id'}
|
||||||
|
transactions={landedCostTransactions}
|
||||||
|
shouldUpdate={transactionsSelectShouldUpdate}
|
||||||
|
>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'transaction_id'} />}
|
label={<T id={'transaction_id'} />}
|
||||||
@@ -81,10 +90,14 @@ export default function AllocateLandedCostFormFields() {
|
|||||||
inline={true}
|
inline={true}
|
||||||
>
|
>
|
||||||
<ListSelect
|
<ListSelect
|
||||||
items={transactions}
|
items={landedCostTransactions}
|
||||||
onItemSelect={({ id }) => {
|
onItemSelect={({ id }) => {
|
||||||
|
const { items } = form.values;
|
||||||
form.setFieldValue('transaction_id', id);
|
form.setFieldValue('transaction_id', id);
|
||||||
form.setFieldValue('transaction_entry_id', '');
|
form.setFieldValue('transaction_entry_id', '');
|
||||||
|
|
||||||
|
form.setFieldValue('amount', '');
|
||||||
|
form.setFieldValue('items', resetAllocatedCostEntries(items));
|
||||||
}}
|
}}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
selectedItem={value}
|
selectedItem={value}
|
||||||
@@ -99,8 +112,12 @@ export default function AllocateLandedCostFormFields() {
|
|||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
{/*------------ Transaction line -----------*/}
|
{/*------------ Transaction line -----------*/}
|
||||||
<If condition={transactionEntries.length > 0}>
|
<If condition={costTransactionEntries.length > 0}>
|
||||||
<Field name={'transaction_entry_id'}>
|
<Field
|
||||||
|
name={'transaction_entry_id'}
|
||||||
|
transactions={costTransactionEntries}
|
||||||
|
shouldUpdate={transactionsSelectShouldUpdate}
|
||||||
|
>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'transaction_line'} />}
|
label={<T id={'transaction_line'} />}
|
||||||
@@ -113,16 +130,20 @@ export default function AllocateLandedCostFormFields() {
|
|||||||
inline={true}
|
inline={true}
|
||||||
>
|
>
|
||||||
<ListSelect
|
<ListSelect
|
||||||
items={transactionEntries}
|
items={costTransactionEntries}
|
||||||
onItemSelect={({ id, amount }) => {
|
onItemSelect={({ id, unallocated_cost_amount }) => {
|
||||||
const { items, allocation_method } = form.values;
|
const { items, allocation_method } = form.values;
|
||||||
|
|
||||||
form.setFieldValue('amount', amount);
|
|
||||||
form.setFieldValue('transaction_entry_id', id);
|
form.setFieldValue('transaction_entry_id', id);
|
||||||
|
form.setFieldValue('amount', unallocated_cost_amount);
|
||||||
|
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
'items',
|
'items',
|
||||||
allocateCostToEntries(amount, allocation_method, items),
|
allocateCostToEntries(
|
||||||
|
unallocated_cost_amount,
|
||||||
|
allocation_method,
|
||||||
|
items,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
@@ -177,12 +198,12 @@ export default function AllocateLandedCostFormFields() {
|
|||||||
>
|
>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
onChange={handleStringChange((_value) => {
|
onChange={handleStringChange((_value) => {
|
||||||
const { amount, items, allocation_method } = form.values;
|
const { amount, items } = form.values;
|
||||||
|
|
||||||
form.setFieldValue('allocation_method', _value);
|
form.setFieldValue('allocation_method', _value);
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
'items',
|
'items',
|
||||||
allocateCostToEntries(amount, allocation_method, items),
|
allocateCostToEntries(amount, _value, items),
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
selectedValue={value}
|
selectedValue={value}
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { sumBy, round } from 'lodash';
|
import { sumBy, round } from 'lodash';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
|
import { defaultFastFieldShouldUpdate } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the landed cost transaction by the given id.
|
||||||
|
*/
|
||||||
|
export function getCostTransactionById(id, transactions) {
|
||||||
|
return transactions.find((trans) => trans.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve transaction entries of the given transaction id.
|
* Retrieve transaction entries of the given transaction id.
|
||||||
*/
|
*/
|
||||||
@@ -8,6 +17,10 @@ export function getEntriesByTransactionId(transactions, id) {
|
|||||||
return transaction ? transaction.entries : [];
|
return transaction ? transaction.entries : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getTransactionEntryById(transaction, transactionEntryId) {
|
||||||
|
return transaction.entries.find((entry) => entry.id === transactionEntryId);
|
||||||
|
}
|
||||||
|
|
||||||
export function allocateCostToEntries(total, allocateType, entries) {
|
export function allocateCostToEntries(total, allocateType, entries) {
|
||||||
return R.compose(
|
return R.compose(
|
||||||
R.when(
|
R.when(
|
||||||
@@ -60,3 +73,18 @@ export function allocateCostByQuantity(total, entries) {
|
|||||||
cost: round(entry.percentageOfQuantity * total, 2),
|
cost: round(entry.percentageOfQuantity * total, 2),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detarmines the transactions selet field when should update.
|
||||||
|
*/
|
||||||
|
export function transactionsSelectShouldUpdate(newProps, oldProps) {
|
||||||
|
return (
|
||||||
|
newProps.transactions !== oldProps.transactions ||
|
||||||
|
defaultFastFieldShouldUpdate(newProps, oldProps)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function resetAllocatedCostEntries(entries) {
|
||||||
|
return entries.map((entry) => ({ ...entry, cost: 0 }));
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
import { Intent, Button } from '@blueprintjs/core';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
import { DialogFooterActions, FormattedMessage as T } from 'components';
|
import {
|
||||||
|
DialogFooter,
|
||||||
|
DialogFooterActions,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
|
import { useSMSMessageDialogContext } from './SMSMessageDialogProvider';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
@@ -28,7 +31,7 @@ function SMSMessageFormFloatingActions({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<DialogFooter>
|
||||||
<DialogFooterActions alignment={'left'}>
|
<DialogFooterActions alignment={'left'}>
|
||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
@@ -42,7 +45,7 @@ function SMSMessageFormFloatingActions({
|
|||||||
<T id={'cancel'} />
|
<T id={'cancel'} />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooterActions>
|
</DialogFooterActions>
|
||||||
</div>
|
</DialogFooter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
import { Intent, Button } from '@blueprintjs/core';
|
||||||
|
|
||||||
import { DialogFooterActions, FormattedMessage as T } from 'components';
|
import {
|
||||||
|
DialogFooter,
|
||||||
|
DialogFooterActions,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from 'components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -17,7 +21,7 @@ export default function NotifyViaSMSFormFloatingActions({ onCancel }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<DialogFooter>
|
||||||
<DialogFooterActions alignment={'left'}>
|
<DialogFooterActions alignment={'left'}>
|
||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
@@ -35,6 +39,6 @@ export default function NotifyViaSMSFormFloatingActions({ onCancel }) {
|
|||||||
<T id={'cancel'} />
|
<T id={'cancel'} />
|
||||||
</Button>
|
</Button>
|
||||||
</DialogFooterActions>
|
</DialogFooterActions>
|
||||||
</div>
|
</DialogFooter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ const commonInvalidateQueries = (queryClient) => {
|
|||||||
|
|
||||||
// Invalidate financial reports.
|
// Invalidate financial reports.
|
||||||
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
|
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
|
||||||
|
|
||||||
|
// Invalidate landed cost.
|
||||||
|
queryClient.invalidateQueries(t.LANDED_COST);
|
||||||
|
queryClient.invalidateQueries(t.LANDED_COST_TRANSACTION);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ const commonInvalidateQueries = (queryClient) => {
|
|||||||
// Invalidate the cashflow transactions.
|
// Invalidate the cashflow transactions.
|
||||||
queryClient.invalidateQueries(t.CASH_FLOW_TRANSACTIONS);
|
queryClient.invalidateQueries(t.CASH_FLOW_TRANSACTIONS);
|
||||||
queryClient.invalidateQueries(t.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY);
|
queryClient.invalidateQueries(t.CASHFLOW_ACCOUNT_TRANSACTIONS_INFINITY);
|
||||||
|
|
||||||
|
// Invalidate landed cost.
|
||||||
|
queryClient.invalidateQueries(t.LANDED_COST);
|
||||||
|
queryClient.invalidateQueries(t.LANDED_COST_TRANSACTION);
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformExpenses = (response) => ({
|
const transformExpenses = (response) => ({
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export function useCreateLandedCost(props) {
|
|||||||
export function useDeleteLandedCost(props) {
|
export function useDeleteLandedCost(props) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const apiRequest = useApiRequest();
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
return useMutation(
|
return useMutation(
|
||||||
(landedCostId) =>
|
(landedCostId) =>
|
||||||
apiRequest.delete(`purchases/landed-cost/${landedCostId}`),
|
apiRequest.delete(`purchases/landed-cost/${landedCostId}`),
|
||||||
@@ -65,7 +66,6 @@ export function useLandedCostTransaction(query, props) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
select: (res) => res.data,
|
select: (res) => res.data,
|
||||||
|
|
||||||
defaultData: {
|
defaultData: {
|
||||||
transactions: [],
|
transactions: [],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user