mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40: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
|
||||
SALE_INVOICE_NUMBER_IS_EXISTS: 'SALE.INVOICE.NUMBER.IS.EXISTS',
|
||||
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
|
||||
SALE_RECEIPT_NUMBER_NOT_UNIQUE: 'SALE_RECEIPT_NUMBER_NOT_UNIQUE',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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} />}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -984,8 +984,10 @@ export default {
|
||||
financial_accounting: ' Financial Accounting',
|
||||
products_services_inventory: 'Products,Services & Inventory',
|
||||
payable_a_p: 'Payable A/P',
|
||||
keyboard_shortcuts:'Keyboard Shortcuts',
|
||||
shortcut_keys:'Shortcut Keys',
|
||||
oK_:'Ok'
|
||||
|
||||
keyboard_shortcuts: 'Keyboard Shortcuts',
|
||||
shortcut_keys: 'Shortcut Keys',
|
||||
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,
|
||||
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`,
|
||||
component: lazy(() =>
|
||||
|
||||
@@ -366,30 +366,28 @@ export default {
|
||||
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',
|
||||
],
|
||||
viewBox: '0 0 20 20'
|
||||
viewBox: '0 0 20 20',
|
||||
},
|
||||
'swap-vert': {
|
||||
path: [
|
||||
'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: [
|
||||
'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',
|
||||
},
|
||||
"close-black": {
|
||||
'close-black': {
|
||||
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',
|
||||
],
|
||||
viewBox: '0 0 20 20',
|
||||
},
|
||||
"send": {
|
||||
path: [
|
||||
'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z'
|
||||
],
|
||||
send: {
|
||||
path: ['M2.01 21L23 12 2.01 3 2 10l15 2-15 2z'],
|
||||
viewBox: '0 0 24 24',
|
||||
},
|
||||
'arrow-top-right': {
|
||||
@@ -397,7 +395,15 @@ export default {
|
||||
viewBox: '0 0 24 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',
|
||||
}
|
||||
},
|
||||
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