mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 20:30:33 +00:00
Merge branch 'develop'
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MoneyFieldCell, DataTableEditable } from 'components';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DataTableEditable } from 'components';
|
||||
|
||||
import { compose, updateTableCell } from 'utils';
|
||||
import { useAllocateLandedCostEntriesTableColumns } from './utils';
|
||||
|
||||
/**
|
||||
* Allocate landed cost entries table.
|
||||
@@ -11,42 +14,7 @@ export default function AllocateLandedCostEntriesTable({
|
||||
entries,
|
||||
}) {
|
||||
// Allocate landed cost entries table columns.
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('item'),
|
||||
accessor: 'item.name',
|
||||
disableSortBy: true,
|
||||
width: '150',
|
||||
},
|
||||
{
|
||||
Header: intl.get('quantity'),
|
||||
accessor: 'quantity',
|
||||
disableSortBy: true,
|
||||
width: '100',
|
||||
},
|
||||
{
|
||||
Header: intl.get('rate'),
|
||||
accessor: 'rate',
|
||||
disableSortBy: true,
|
||||
width: '100',
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'amount',
|
||||
disableSortBy: true,
|
||||
width: '100',
|
||||
},
|
||||
{
|
||||
Header: intl.get('cost'),
|
||||
accessor: 'cost',
|
||||
width: '150',
|
||||
Cell: MoneyFieldCell,
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
const columns = useAllocateLandedCostEntriesTableColumns();
|
||||
|
||||
// Handle update data.
|
||||
const handleUpdateData = React.useCallback(
|
||||
@@ -60,7 +28,7 @@ export default function AllocateLandedCostEntriesTable({
|
||||
);
|
||||
|
||||
return (
|
||||
<DataTableEditable
|
||||
<AllocateLandeedCostEntriesEditableTable
|
||||
columns={columns}
|
||||
data={entries}
|
||||
payload={{
|
||||
@@ -70,3 +38,18 @@ export default function AllocateLandedCostEntriesTable({
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const AllocateLandeedCostEntriesEditableTable = styled(
|
||||
DataTableEditable,
|
||||
)`
|
||||
.table {
|
||||
.thead .tr .th {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.tbody .tr .td {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -12,12 +12,18 @@ import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogP
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Allocate landed cost floating actions.
|
||||
* @returns {React.JSX}
|
||||
*/
|
||||
function AllocateLandedCostFloatingActions({
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}) {
|
||||
// Formik context.
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
// Allocate landed cost dialog context.
|
||||
const { dialogName, costTransactionEntry, formattedUnallocatedCostAmount } =
|
||||
useAllocateLandedConstDialogContext();
|
||||
|
||||
@@ -27,7 +33,7 @@ function AllocateLandedCostFloatingActions({
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogFooter>
|
||||
<AllocateDialogFooter>
|
||||
<DialogFooterActions alignment={'left'}>
|
||||
{costTransactionEntry && (
|
||||
<UnallocatedAmount>
|
||||
@@ -43,19 +49,23 @@ function AllocateLandedCostFloatingActions({
|
||||
</Button>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
style={{ minWidth: '100px' }}
|
||||
style={{ minWidth: '95px' }}
|
||||
type="submit"
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{<T id={'save'} />}
|
||||
</Button>
|
||||
</DialogFooterActions>
|
||||
</DialogFooter>
|
||||
</AllocateDialogFooter>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDialogActions)(AllocateLandedCostFloatingActions);
|
||||
|
||||
const AllocateDialogFooter = styled(DialogFooter)`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const UnallocatedAmount = styled.div`
|
||||
color: #3f5278;
|
||||
align-self: center;
|
||||
|
||||
@@ -11,21 +11,7 @@ import { useAllocateLandedConstDialogContext } from './AllocateLandedCostDialogP
|
||||
import AllocateLandedCostFormContent from './AllocateLandedCostFormContent';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
import { compose, transformToForm } from 'utils';
|
||||
|
||||
const defaultInitialItem = {
|
||||
entry_id: '',
|
||||
cost: '',
|
||||
};
|
||||
|
||||
// Default form initial values.
|
||||
const defaultInitialValues = {
|
||||
transaction_type: 'Bill',
|
||||
transaction_id: '',
|
||||
transaction_entry_id: '',
|
||||
amount: '',
|
||||
allocation_method: 'quantity',
|
||||
items: [defaultInitialItem],
|
||||
};
|
||||
import { defaultInitialValues } from './utils';
|
||||
|
||||
/**
|
||||
* Allocate landed cost form.
|
||||
@@ -34,13 +20,8 @@ function AllocateLandedCostForm({
|
||||
// #withDialogActions
|
||||
closeDialog,
|
||||
}) {
|
||||
const {
|
||||
dialogName,
|
||||
bill,
|
||||
billId,
|
||||
createLandedCostMutate,
|
||||
unallocatedCostAmount,
|
||||
} = useAllocateLandedConstDialogContext();
|
||||
const { dialogName, bill, billId, createLandedCostMutate } =
|
||||
useAllocateLandedConstDialogContext();
|
||||
|
||||
// Initial form values.
|
||||
const initialValues = {
|
||||
@@ -51,7 +32,6 @@ function AllocateLandedCostForm({
|
||||
cost: '',
|
||||
})),
|
||||
};
|
||||
|
||||
// Handle form submit.
|
||||
const handleFormSubmit = (values, { setSubmitting }) => {
|
||||
setSubmitting(true);
|
||||
@@ -78,20 +58,33 @@ function AllocateLandedCostForm({
|
||||
setSubmitting(false);
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
|
||||
// Handle the request error.
|
||||
const onError = () => {
|
||||
const onError = (res) => {
|
||||
const { errors } = res.response.data;
|
||||
setSubmitting(false);
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong!',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
|
||||
if (
|
||||
errors.some(
|
||||
(e) => e.type === 'COST_AMOUNT_BIGGER_THAN_UNALLOCATED_AMOUNT',
|
||||
)
|
||||
) {
|
||||
AppToaster.show({
|
||||
message:
|
||||
'The total located cost is bigger than the transaction line.',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
} else {
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong!',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
};
|
||||
createLandedCostMutate([billId, form]).then(onSuccess).catch(onError);
|
||||
};
|
||||
|
||||
// Computed validation schema.
|
||||
const validationSchema = AllocateLandedCostFormSchema(unallocatedCostAmount);
|
||||
const validationSchema = AllocateLandedCostFormSchema();
|
||||
|
||||
return (
|
||||
<Formik
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import * as Yup from 'yup';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export const AllocateLandedCostFormSchema = (maxAmount) =>
|
||||
export const AllocateLandedCostFormSchema = () =>
|
||||
Yup.object().shape({
|
||||
transaction_type: Yup.string()
|
||||
.required()
|
||||
.label(intl.get('transaction_type')),
|
||||
transaction_id: Yup.string()
|
||||
.required()
|
||||
.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(),
|
||||
transaction_type: Yup.string().label(intl.get('transaction_type')),
|
||||
transaction_date: Yup.date().label(intl.get('transaction_date')),
|
||||
transaction_id: Yup.string().label(intl.get('transaction_number')),
|
||||
transaction_entry_id: Yup.string().label(intl.get('transaction_line')),
|
||||
amount: Yup.number().label(intl.get('amount')),
|
||||
allocation_method: Yup.string().trim(),
|
||||
items: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
entry_id: Yup.number().nullable(),
|
||||
|
||||
@@ -103,8 +103,10 @@ export default function AllocateLandedCostFormFields() {
|
||||
selectedItem={value}
|
||||
selectedItemProp={'id'}
|
||||
textProp={'name'}
|
||||
labelProp={'id'}
|
||||
defaultText={intl.get('Select transaction')}
|
||||
labelProp={'formatted_unallocated_cost_amount'}
|
||||
defaultText={intl.get(
|
||||
'landed_cost.dialog.label_select_transaction',
|
||||
)}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -131,16 +133,17 @@ export default function AllocateLandedCostFormFields() {
|
||||
>
|
||||
<ListSelect
|
||||
items={costTransactionEntries}
|
||||
onItemSelect={({ id, unallocated_cost_amount }) => {
|
||||
onItemSelect={(entry) => {
|
||||
const { id, unallocated_cost_amount: unallocatedAmount } =
|
||||
entry;
|
||||
const { items, allocation_method } = form.values;
|
||||
|
||||
form.setFieldValue('amount', unallocatedAmount);
|
||||
form.setFieldValue('transaction_entry_id', id);
|
||||
form.setFieldValue('amount', unallocated_cost_amount);
|
||||
|
||||
form.setFieldValue(
|
||||
'items',
|
||||
allocateCostToEntries(
|
||||
unallocated_cost_amount,
|
||||
unallocatedAmount,
|
||||
allocation_method,
|
||||
items,
|
||||
),
|
||||
@@ -150,7 +153,10 @@ export default function AllocateLandedCostFormFields() {
|
||||
selectedItem={value}
|
||||
selectedItemProp={'id'}
|
||||
textProp={'name'}
|
||||
defaultText={intl.get('Select transaction entry')}
|
||||
labelProp={'formatted_unallocated_cost_amount'}
|
||||
defaultText={intl.get(
|
||||
'landed_cost.dialog.label_select_transaction_entry',
|
||||
)}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import React from 'react';
|
||||
import { sumBy, round } from 'lodash';
|
||||
import * as R from 'ramda';
|
||||
import { defaultFastFieldShouldUpdate } from 'utils';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
/**
|
||||
* Retrieve the landed cost transaction by the given id.
|
||||
*/
|
||||
export function getCostTransactionById(id, transactions) {
|
||||
return transactions.find((trans) => trans.id === id);
|
||||
}
|
||||
import { defaultFastFieldShouldUpdate } from 'utils';
|
||||
import { MoneyFieldCell } from 'components';
|
||||
|
||||
export const defaultInitialItem = {
|
||||
entry_id: '',
|
||||
cost: '',
|
||||
};
|
||||
|
||||
// Default form initial values.
|
||||
export const defaultInitialValues = {
|
||||
transaction_type: 'Bill',
|
||||
transaction_id: '',
|
||||
transaction_entry_id: '',
|
||||
amount: '',
|
||||
allocation_method: 'quantity',
|
||||
items: [defaultInitialItem],
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve transaction entries of the given transaction id.
|
||||
@@ -17,10 +29,23 @@ export function getEntriesByTransactionId(transactions, id) {
|
||||
return transaction ? transaction.entries : [];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} transaction
|
||||
* @param {*} transactionEntryId
|
||||
* @returns
|
||||
*/
|
||||
export function getTransactionEntryById(transaction, transactionEntryId) {
|
||||
return transaction.entries.find((entry) => entry.id === transactionEntryId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} total
|
||||
* @param {*} allocateType
|
||||
* @param {*} entries
|
||||
* @returns
|
||||
*/
|
||||
export function allocateCostToEntries(total, allocateType, entries) {
|
||||
return R.compose(
|
||||
R.when(
|
||||
@@ -43,12 +68,12 @@ export function allocateCostToEntries(total, allocateType, entries) {
|
||||
export function allocateCostByValue(total, entries) {
|
||||
const totalAmount = sumBy(entries, 'amount');
|
||||
|
||||
const _entries = entries.map((entry) => ({
|
||||
const entriesMapped = entries.map((entry) => ({
|
||||
...entry,
|
||||
percentageOfValue: entry.amount / totalAmount,
|
||||
}));
|
||||
|
||||
return _entries.map((entry) => ({
|
||||
return entriesMapped.map((entry) => ({
|
||||
...entry,
|
||||
cost: round(entry.percentageOfValue * total, 2),
|
||||
}));
|
||||
@@ -74,6 +99,13 @@ export function allocateCostByQuantity(total, entries) {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the landed cost transaction by the given id.
|
||||
*/
|
||||
export function getCostTransactionById(id, transactions) {
|
||||
return transactions.find((trans) => trans.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detarmines the transactions selet field when should update.
|
||||
*/
|
||||
@@ -84,7 +116,55 @@ export function transactionsSelectShouldUpdate(newProps, oldProps) {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} entries
|
||||
* @returns
|
||||
*/
|
||||
export function resetAllocatedCostEntries(entries) {
|
||||
return entries.map((entry) => ({ ...entry, cost: 0 }));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves allocate landed cost entries table columns.
|
||||
*/
|
||||
export const useAllocateLandedCostEntriesTableColumns = () => {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: intl.get('item'),
|
||||
accessor: 'item.name',
|
||||
disableSortBy: true,
|
||||
width: '150',
|
||||
},
|
||||
{
|
||||
Header: intl.get('quantity'),
|
||||
accessor: 'quantity',
|
||||
disableSortBy: true,
|
||||
width: '100',
|
||||
},
|
||||
{
|
||||
Header: intl.get('rate'),
|
||||
accessor: 'rate',
|
||||
disableSortBy: true,
|
||||
width: '100',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
Header: intl.get('amount'),
|
||||
accessor: 'amount',
|
||||
disableSortBy: true,
|
||||
align: 'right',
|
||||
width: '100',
|
||||
},
|
||||
{
|
||||
Header: intl.get('cost'),
|
||||
accessor: 'cost',
|
||||
width: '150',
|
||||
Cell: MoneyFieldCell,
|
||||
disableSortBy: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user