mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
Merge branch 'master' of https://github.com/abouolia/Ratteb
This commit is contained in:
@@ -5,6 +5,8 @@ export const ERROR = {
|
|||||||
// Sales Invoices
|
// Sales Invoices
|
||||||
SALE_INVOICE_NUMBER_IS_EXISTS: 'SALE.INVOICE.NUMBER.IS.EXISTS',
|
SALE_INVOICE_NUMBER_IS_EXISTS: 'SALE.INVOICE.NUMBER.IS.EXISTS',
|
||||||
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
|
SALE_INVOICE_NO_NOT_UNIQUE: 'SALE_INVOICE_NO_NOT_UNIQUE',
|
||||||
|
SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE:
|
||||||
|
'SALE_ESTIMATE_IS_ALREADY_CONVERTED_TO_INVOICE',
|
||||||
|
|
||||||
// Sales Receipts
|
// Sales Receipts
|
||||||
SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE',
|
SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import 'style/pages/ExchangeRate/ExchangeRateDialog.scss';
|
|||||||
/**
|
/**
|
||||||
* Exchange rate form content.
|
* Exchange rate form content.
|
||||||
*/
|
*/
|
||||||
function ExchangeRateFormDialogContent({
|
export default function ExchangeRateFormDialogContent({
|
||||||
// #ownProp
|
// #ownProp
|
||||||
action,
|
action,
|
||||||
exchangeRateId,
|
exchangeRateId,
|
||||||
@@ -27,5 +27,3 @@ function ExchangeRateFormDialogContent({
|
|||||||
</ExchangeRateFormProvider>
|
</ExchangeRateFormProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withExchangeRateDetail)(ExchangeRateFormDialogContent);
|
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ function EstimatesDataTable({
|
|||||||
openDrawer('estimate-drawer', {});
|
openDrawer('estimate-drawer', {});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle convent to invoice.
|
||||||
|
const handleConvertToInvoice = ({ id }) => {
|
||||||
|
history.push(`/invoices/new?from_estimate_id=${id}`, { action: id });
|
||||||
|
};
|
||||||
|
|
||||||
// Handles fetch data.
|
// Handles fetch data.
|
||||||
const handleFetchData = useCallback(
|
const handleFetchData = useCallback(
|
||||||
({ pageIndex, pageSize, sortBy }) => {
|
({ pageIndex, pageSize, sortBy }) => {
|
||||||
@@ -114,6 +119,7 @@ function EstimatesDataTable({
|
|||||||
onDeliver: handleDeliverEstimate,
|
onDeliver: handleDeliverEstimate,
|
||||||
onDelete: handleDeleteEstimate,
|
onDelete: handleDeleteEstimate,
|
||||||
onDrawer: handleDrawerEstimate,
|
onDrawer: handleDrawerEstimate,
|
||||||
|
onConvert: handleConvertToInvoice,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const statusAccessor = (row) => (
|
|||||||
*/
|
*/
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
row: { original },
|
row: { original },
|
||||||
payload: { onEdit, onDeliver, onReject, onApprove, onDelete, onDrawer },
|
payload: { onEdit, onDeliver, onReject, onApprove, onDelete, onDrawer ,onConvert },
|
||||||
}) {
|
}) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
@@ -65,6 +65,11 @@ export function ActionsMenu({
|
|||||||
text={formatMessage({ id: 'edit_estimate' })}
|
text={formatMessage({ id: 'edit_estimate' })}
|
||||||
onClick={safeCallback(onEdit, original)}
|
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}>
|
<If condition={!original.is_delivered}>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Icon icon={'check'} iconSize={18} />}
|
icon={<Icon icon={'check'} iconSize={18} />}
|
||||||
|
|||||||
@@ -22,10 +22,9 @@ import withMediaActions from 'containers/Media/withMediaActions';
|
|||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import { ERROR } from 'common/errors';
|
|
||||||
import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
|
import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
|
||||||
import { useInvoiceFormContext } from './InvoiceFormProvider';
|
import { useInvoiceFormContext } from './InvoiceFormProvider';
|
||||||
import { transformToEditForm, defaultInvoice } from './utils';
|
import { transformToEditForm, defaultInvoice, transformErrors } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoice form.
|
* Invoice form.
|
||||||
@@ -42,6 +41,8 @@ function InvoiceForm({
|
|||||||
const {
|
const {
|
||||||
isNewMode,
|
isNewMode,
|
||||||
invoice,
|
invoice,
|
||||||
|
estimateId,
|
||||||
|
newInvoice,
|
||||||
createInvoiceMutate,
|
createInvoiceMutate,
|
||||||
editInvoiceMutate,
|
editInvoiceMutate,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
@@ -62,20 +63,12 @@ function InvoiceForm({
|
|||||||
...defaultInvoice,
|
...defaultInvoice,
|
||||||
invoice_no: invoiceNumber,
|
invoice_no: invoiceNumber,
|
||||||
entries: orderingLinesIndexes(defaultInvoice.entries),
|
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.
|
// Handles form submit.
|
||||||
const handleSubmit = (values, { setSubmitting, setErrors, resetForm }) => {
|
const handleSubmit = (values, { setSubmitting, setErrors, resetForm }) => {
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
@@ -97,6 +90,7 @@ function InvoiceForm({
|
|||||||
const form = {
|
const form = {
|
||||||
...values,
|
...values,
|
||||||
delivered: submitPayload.deliver,
|
delivered: submitPayload.deliver,
|
||||||
|
from_estimate_id: estimateId,
|
||||||
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
|
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
|
||||||
};
|
};
|
||||||
// Handle the request success.
|
// Handle the request success.
|
||||||
@@ -129,7 +123,7 @@ function InvoiceForm({
|
|||||||
},
|
},
|
||||||
}) => {
|
}) => {
|
||||||
if (errors) {
|
if (errors) {
|
||||||
handleErrors(errors, { setErrors });
|
transformErrors(errors, { setErrors });
|
||||||
}
|
}
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const Schema = Yup.object().shape({
|
|||||||
.label(formatMessage({ id: 'invoice_no_' })),
|
.label(formatMessage({ id: 'invoice_no_' })),
|
||||||
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING),
|
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING),
|
||||||
delivered: Yup.boolean(),
|
delivered: Yup.boolean(),
|
||||||
|
from_estimate_id: Yup.string(),
|
||||||
invoice_message: Yup.string()
|
invoice_message: Yup.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1)
|
.min(1)
|
||||||
@@ -31,7 +32,8 @@ const Schema = Yup.object().shape({
|
|||||||
entries: Yup.array().of(
|
entries: Yup.array().of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
quantity: Yup.number()
|
quantity: Yup.number()
|
||||||
.nullable().max(DATATYPES_LENGTH.INT_10)
|
.nullable()
|
||||||
|
.max(DATATYPES_LENGTH.INT_10)
|
||||||
.when(['rate'], {
|
.when(['rate'], {
|
||||||
is: (rate) => rate,
|
is: (rate) => rate,
|
||||||
then: Yup.number().required(),
|
then: Yup.number().required(),
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import React, { createContext, useState } from 'react';
|
import React, { createContext, useState } from 'react';
|
||||||
|
import { isEmpty, pick } from 'lodash';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
import { transformToEditForm } from './utils';
|
||||||
import {
|
import {
|
||||||
useInvoice,
|
useInvoice,
|
||||||
useItems,
|
useItems,
|
||||||
@@ -7,6 +10,7 @@ import {
|
|||||||
useCreateInvoice,
|
useCreateInvoice,
|
||||||
useEditInvoice,
|
useEditInvoice,
|
||||||
useSettingsInvoices,
|
useSettingsInvoices,
|
||||||
|
useEstimate,
|
||||||
} from 'hooks/query';
|
} from 'hooks/query';
|
||||||
|
|
||||||
const InvoiceFormContext = createContext();
|
const InvoiceFormContext = createContext();
|
||||||
@@ -15,12 +19,24 @@ const InvoiceFormContext = createContext();
|
|||||||
* Accounts chart data provider.
|
* Accounts chart data provider.
|
||||||
*/
|
*/
|
||||||
function InvoiceFormProvider({ invoiceId, ...props }) {
|
function InvoiceFormProvider({ invoiceId, ...props }) {
|
||||||
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(
|
const { state } = useLocation();
|
||||||
invoiceId,
|
|
||||||
{
|
const estimateId = state?.action;
|
||||||
|
|
||||||
|
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
|
||||||
enabled: !!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.
|
// Handle fetching the items table based on the given query.
|
||||||
const {
|
const {
|
||||||
@@ -52,6 +68,8 @@ function InvoiceFormProvider({ invoiceId, ...props }) {
|
|||||||
invoice,
|
invoice,
|
||||||
items,
|
items,
|
||||||
customers,
|
customers,
|
||||||
|
newInvoice,
|
||||||
|
estimateId,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
|
|
||||||
isInvoiceLoading,
|
isInvoiceLoading,
|
||||||
@@ -71,6 +89,7 @@ function InvoiceFormProvider({ invoiceId, ...props }) {
|
|||||||
isInvoiceLoading ||
|
isInvoiceLoading ||
|
||||||
isItemsLoading ||
|
isItemsLoading ||
|
||||||
isCustomersLoading ||
|
isCustomersLoading ||
|
||||||
|
isEstimateFetching ||
|
||||||
isSettingsLoading
|
isSettingsLoading
|
||||||
}
|
}
|
||||||
name={'invoice-form'}
|
name={'invoice-form'}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { compose, transformToForm, repeatValue } from 'utils';
|
import { compose, transformToForm, repeatValue } from 'utils';
|
||||||
import { updateItemsEntriesTotal } from 'containers/Entries/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;
|
export const MIN_LINES_NUMBER = 4;
|
||||||
|
|
||||||
@@ -47,3 +52,24 @@ export function transformToEditForm(invoice) {
|
|||||||
entries,
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -984,8 +984,10 @@ export default {
|
|||||||
financial_accounting: ' Financial Accounting',
|
financial_accounting: ' Financial Accounting',
|
||||||
products_services_inventory: 'Products,Services & Inventory',
|
products_services_inventory: 'Products,Services & Inventory',
|
||||||
payable_a_p: 'Payable A/P',
|
payable_a_p: 'Payable A/P',
|
||||||
keyboard_shortcuts:'Keyboard Shortcuts',
|
keyboard_shortcuts: 'Keyboard Shortcuts',
|
||||||
shortcut_keys:'Shortcut Keys',
|
shortcut_keys: 'Shortcut Keys',
|
||||||
oK_:'Ok'
|
oK_: 'Ok',
|
||||||
|
convert_to_invoice: 'Convert to Invoice',
|
||||||
|
sale_estimate_is_already_converted_to_invoice:
|
||||||
|
'Sale estimate is already converted to invoice.',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -299,6 +299,17 @@ export default [
|
|||||||
backLink: true,
|
backLink: true,
|
||||||
sidebarShrink: true,
|
sidebarShrink: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: `/invoices/new?from_estimate_id=/:id`,
|
||||||
|
component: lazy(() =>
|
||||||
|
import('containers/Sales/Estimates/EstimateForm/EstimateFormPage'),
|
||||||
|
),
|
||||||
|
name: 'convert-to-invoice',
|
||||||
|
breadcrumb: 'New Estimate',
|
||||||
|
pageTitle: formatMessage({ id: 'new_estimate' }),
|
||||||
|
backLink: true,
|
||||||
|
sidebarShrink: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: `/estimates/new`,
|
path: `/estimates/new`,
|
||||||
component: lazy(() =>
|
component: lazy(() =>
|
||||||
|
|||||||
@@ -366,30 +366,28 @@ export default {
|
|||||||
path: [
|
path: [
|
||||||
'M10 20C4.48 20 0 15.52 0 10S4.48 0 10 0s10 4.48 10 10-4.48 10-10 10zm5-14c-.28 0-.53.11-.71.29L8 12.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29.28 0 .53-.11.71-.29l7-7A1.003 1.003 0 0015 6z',
|
'M10 20C4.48 20 0 15.52 0 10S4.48 0 10 0s10 4.48 10 10-4.48 10-10 10zm5-14c-.28 0-.53.11-.71.29L8 12.59l-2.29-2.3a1.003 1.003 0 00-1.42 1.42l3 3c.18.18.43.29.71.29.28 0 .53-.11.71-.29l7-7A1.003 1.003 0 0015 6z',
|
||||||
],
|
],
|
||||||
viewBox: '0 0 20 20'
|
viewBox: '0 0 20 20',
|
||||||
},
|
},
|
||||||
'swap-vert': {
|
'swap-vert': {
|
||||||
path: [
|
path: [
|
||||||
'M10.6,10.9V5.4H9v5.5H6.7L9.8,14l3.1-3.1ZM5.1,0,2,3.1H4.3V8.6H5.9V3.1H8.2Z',
|
'M10.6,10.9V5.4H9v5.5H6.7L9.8,14l3.1-3.1ZM5.1,0,2,3.1H4.3V8.6H5.9V3.1H8.2Z',
|
||||||
],
|
],
|
||||||
viewBox: '0 0 14 14'
|
viewBox: '0 0 14 14',
|
||||||
},
|
},
|
||||||
"check": {
|
check: {
|
||||||
path: [
|
path: [
|
||||||
'M17 4c-.28 0-.53.11-.71.29L7 13.59 3.71 10.3A.965.965 0 003 10a1.003 1.003 0 00-.71 1.71l4 4c.18.18.43.29.71.29s.53-.11.71-.29l10-10A1.003 1.003 0 0017 4z'
|
'M17 4c-.28 0-.53.11-.71.29L7 13.59 3.71 10.3A.965.965 0 003 10a1.003 1.003 0 00-.71 1.71l4 4c.18.18.43.29.71.29s.53-.11.71-.29l10-10A1.003 1.003 0 0017 4z',
|
||||||
],
|
],
|
||||||
viewBox: '0 0 24 24',
|
viewBox: '0 0 24 24',
|
||||||
},
|
},
|
||||||
"close-black": {
|
'close-black': {
|
||||||
path: [
|
path: [
|
||||||
'M11.41 10l4.29-4.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L10 8.59l-4.29-4.3a1.003 1.003 0 00-1.42 1.42L8.59 10 4.3 14.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4.29-4.3 4.29 4.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L11.41 10z',
|
'M11.41 10l4.29-4.29c.19-.18.3-.43.3-.71a1.003 1.003 0 00-1.71-.71L10 8.59l-4.29-4.3a1.003 1.003 0 00-1.42 1.42L8.59 10 4.3 14.29c-.19.18-.3.43-.3.71a1.003 1.003 0 001.71.71l4.29-4.3 4.29 4.29c.18.19.43.3.71.3a1.003 1.003 0 00.71-1.71L11.41 10z',
|
||||||
],
|
],
|
||||||
viewBox: '0 0 20 20',
|
viewBox: '0 0 20 20',
|
||||||
},
|
},
|
||||||
"send": {
|
send: {
|
||||||
path: [
|
path: ['M2.01 21L23 12 2.01 3 2 10l15 2-15 2z'],
|
||||||
'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z'
|
|
||||||
],
|
|
||||||
viewBox: '0 0 24 24',
|
viewBox: '0 0 24 24',
|
||||||
},
|
},
|
||||||
'arrow-top-right': {
|
'arrow-top-right': {
|
||||||
@@ -397,7 +395,15 @@ export default {
|
|||||||
viewBox: '0 0 24 24',
|
viewBox: '0 0 24 24',
|
||||||
},
|
},
|
||||||
'receipt-24': {
|
'receipt-24': {
|
||||||
path: ['M19.5 3.5L18 2l-1.5 1.5L15 2l-1.5 1.5L12 2l-1.5 1.5L9 2 7.5 3.5 6 2 4.5 3.5 3 2v20l1.5-1.5L6 22l1.5-1.5L9 22l1.5-1.5L12 22l1.5-1.5L15 22l1.5-1.5L18 22l1.5-1.5L21 22V2l-1.5 1.5zM19 19.09H5V4.91h14v14.18zM6 15h12v2H6zm0-4h12v2H6zm0-4h12v2H6z'],
|
path: [
|
||||||
|
'M19.5 3.5L18 2l-1.5 1.5L15 2l-1.5 1.5L12 2l-1.5 1.5L9 2 7.5 3.5 6 2 4.5 3.5 3 2v20l1.5-1.5L6 22l1.5-1.5L9 22l1.5-1.5L12 22l1.5-1.5L15 22l1.5-1.5L18 22l1.5-1.5L21 22V2l-1.5 1.5zM19 19.09H5V4.91h14v14.18zM6 15h12v2H6zm0-4h12v2H6zm0-4h12v2H6z',
|
||||||
|
],
|
||||||
viewBox: '0 0 24 24',
|
viewBox: '0 0 24 24',
|
||||||
}
|
},
|
||||||
|
convert_to: {
|
||||||
|
path: [
|
||||||
|
'M19 8l-4 4h3c0 3.31-2.69 6-6 6-1.01 0-1.97-.25-2.8-.7l-1.46 1.46C8.97 19.54 10.43 20 12 20c4.42 0 8-3.58 8-8h3l-4-4zM6 12c0-3.31 2.69-6 6-6 1.01 0 1.97.25 2.8.7l1.46-1.46C15.03 4.46 13.57 4 12 4c-4.42 0-8 3.58-8 8H1l4 4 4-4H6z',
|
||||||
|
],
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user