feat: convert estimate to invoice.

This commit is contained in:
elforjani3
2021-03-02 11:45:20 +02:00
parent 4d751f772e
commit aa8ba546aa
11 changed files with 111 additions and 40 deletions

View File

@@ -11,7 +11,7 @@ import 'style/pages/ExchangeRate/ExchangeRateDialog.scss';
/**
* Exchange rate form content.
*/
function ExchangeRateFormDialogContent({
export default function ExchangeRateFormDialogContent({
// #ownProp
action,
exchangeRateId,
@@ -27,5 +27,3 @@ function ExchangeRateFormDialogContent({
</ExchangeRateFormProvider>
);
}
export default compose(withExchangeRateDetail)(ExchangeRateFormDialogContent);

View File

@@ -66,12 +66,17 @@ function EstimatesDataTable({
const handleRejectEstimate = ({ id }) => {
openAlert('estimate-reject', { estimateId: id });
};
// Handle drawer estimate.
const handleDrawerEstimate = () => {
openDrawer('estimate-drawer', {});
};
// Handle convent to invoice.
const handleConvertToInvoice = ({ id }) => {
history.push(`/invoices/new?from_estimate_id=${id}`, { action: id });
};
// Handles fetch data.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
@@ -114,6 +119,7 @@ function EstimatesDataTable({
onDeliver: handleDeliverEstimate,
onDelete: handleDeleteEstimate,
onDrawer: handleDrawerEstimate,
onConvert: handleConvertToInvoice,
}}
/>
);

View File

@@ -49,7 +49,7 @@ export const statusAccessor = (row) => (
*/
export function ActionsMenu({
row: { original },
payload: { onEdit, onDeliver, onReject, onApprove, onDelete, onDrawer },
payload: { onEdit, onDeliver, onReject, onApprove, onDelete, onDrawer ,onConvert },
}) {
const { formatMessage } = useIntl();
@@ -65,6 +65,11 @@ export function ActionsMenu({
text={formatMessage({ id: 'edit_estimate' })}
onClick={safeCallback(onEdit, original)}
/>
<MenuItem
icon={<Icon icon="convert_to" />}
text={formatMessage({ id: 'convert_to_invoice' })}
onClick={safeCallback(onConvert, original)}
/>
<If condition={!original.is_delivered}>
<MenuItem
icon={<Icon icon={'check'} iconSize={18} />}

View File

@@ -22,10 +22,9 @@ import withMediaActions from 'containers/Media/withMediaActions';
import withSettings from 'containers/Settings/withSettings';
import { AppToaster } from 'components';
import { ERROR } from 'common/errors';
import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { transformToEditForm, defaultInvoice } from './utils';
import { transformToEditForm, defaultInvoice, transformErrors } from './utils';
/**
* Invoice form.
@@ -42,6 +41,8 @@ function InvoiceForm({
const {
isNewMode,
invoice,
estimateId,
newInvoice,
createInvoiceMutate,
editInvoiceMutate,
submitPayload,
@@ -62,20 +63,12 @@ function InvoiceForm({
...defaultInvoice,
invoice_no: invoiceNumber,
entries: orderingLinesIndexes(defaultInvoice.entries),
...newInvoice,
}),
}),
[invoice, invoiceNumber],
[invoice, newInvoice,invoiceNumber],
);
// Handle form errors.
const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERROR.SALE_INVOICE_NUMBER_IS_EXISTS)) {
setErrors({
invoice_no: formatMessage({ id: 'sale_invoice_number_is_exists' }),
});
}
};
// Handles form submit.
const handleSubmit = (values, { setSubmitting, setErrors, resetForm }) => {
setSubmitting(true);
@@ -97,6 +90,7 @@ function InvoiceForm({
const form = {
...values,
delivered: submitPayload.deliver,
from_estimate_id: estimateId,
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
};
// Handle the request success.
@@ -129,7 +123,7 @@ function InvoiceForm({
},
}) => {
if (errors) {
handleErrors(errors, { setErrors });
transformErrors(errors, { setErrors });
}
setSubmitting(false);
};

View File

@@ -18,6 +18,7 @@ const Schema = Yup.object().shape({
.label(formatMessage({ id: 'invoice_no_' })),
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING),
delivered: Yup.boolean(),
from_estimate_id: Yup.string(),
invoice_message: Yup.string()
.trim()
.min(1)
@@ -31,7 +32,8 @@ const Schema = Yup.object().shape({
entries: Yup.array().of(
Yup.object().shape({
quantity: Yup.number()
.nullable().max(DATATYPES_LENGTH.INT_10)
.nullable()
.max(DATATYPES_LENGTH.INT_10)
.when(['rate'], {
is: (rate) => rate,
then: Yup.number().required(),

View File

@@ -1,5 +1,8 @@
import React, { createContext, useState } from 'react';
import { isEmpty, pick } from 'lodash';
import { useLocation } from 'react-router-dom';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import { transformToEditForm } from './utils';
import {
useInvoice,
useItems,
@@ -7,6 +10,7 @@ import {
useCreateInvoice,
useEditInvoice,
useSettingsInvoices,
useEstimate,
} from 'hooks/query';
const InvoiceFormContext = createContext();
@@ -15,12 +19,24 @@ const InvoiceFormContext = createContext();
* Accounts chart data provider.
*/
function InvoiceFormProvider({ invoiceId, ...props }) {
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(
invoiceId,
{
enabled: !!invoiceId,
},
);
const { state } = useLocation();
const estimateId = state?.action;
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
enabled: !!invoiceId,
});
const {
data: estimate,
isFetching: isEstimateFetching,
} = useEstimate(estimateId, { enabled: !!estimateId });
const newInvoice = !isEmpty(estimate)
? transformToEditForm({
...pick(estimate, ['customer_id', 'customer', 'entries']),
})
: [];
// Handle fetching the items table based on the given query.
const {
@@ -52,6 +68,8 @@ function InvoiceFormProvider({ invoiceId, ...props }) {
invoice,
items,
customers,
newInvoice,
estimateId,
submitPayload,
isInvoiceLoading,
@@ -71,6 +89,7 @@ function InvoiceFormProvider({ invoiceId, ...props }) {
isInvoiceLoading ||
isItemsLoading ||
isCustomersLoading ||
isEstimateFetching ||
isSettingsLoading
}
name={'invoice-form'}

View File

@@ -1,6 +1,11 @@
import moment from 'moment';
import { compose, transformToForm, repeatValue } from 'utils';
import { updateItemsEntriesTotal } from 'containers/Entries/utils';
import { ERROR } from 'common/errors';
import { Intent } from '@blueprintjs/core';
import { formatMessage } from 'services/intl';
import { AppToaster } from 'components';
export const MIN_LINES_NUMBER = 4;
@@ -47,3 +52,24 @@ export function transformToEditForm(invoice) {
entries,
};
}
export const transformErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERROR.SALE_INVOICE_NUMBER_IS_EXISTS)) {
setErrors({
invoice_no: formatMessage({ id: 'sale_invoice_number_is_exists' }),
});
}
if (
errors.some(
({ type }) =>
type === ERROR.SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE,
)
) {
AppToaster.show({
message: formatMessage({
id: 'sale_estimate_is_already_converted_to_invoice',
}),
intent: Intent.DANGER,
});
}
};