feat(Sales): add sales.

This commit is contained in:
elforjani13
2022-03-20 16:16:22 +02:00
parent 89b28903fa
commit 39a68f5c25
23 changed files with 559 additions and 130 deletions

View File

@@ -13,8 +13,9 @@ function VendorDrawerLinkComponent({
openDrawer, openDrawer,
}) { }) {
// Handle view customer drawer. // Handle view customer drawer.
const handleVendorDrawer = () => { const handleVendorDrawer = (event) => {
openDrawer('vendor-details-drawer', { vendorId }); openDrawer('vendor-details-drawer', { vendorId });
event.preventDefault();
}; };
return <ButtonLink onClick={handleVendorDrawer}>{children}</ButtonLink>; return <ButtonLink onClick={handleVendorDrawer}>{children}</ButtonLink>;

View File

@@ -8,29 +8,37 @@ import {
TotalLineBorderStyle, TotalLineBorderStyle,
TotalLineTextStyle, TotalLineTextStyle,
} from 'components'; } from 'components';
import { useBillTotals } from './utils';
export function BillFormFooterRight() { export function BillFormFooterRight() {
const {
formattedSubtotal,
formattedTotal,
formattedDueTotal,
formattedPaymentTotal,
} = useBillTotals();
return ( return (
<BillTotalLines labelColWidth={'180px'} amountColWidth={'180px'}> <BillTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine <TotalLine
title={<T id={'bill.details.subtotal'} />} title={<T id={'bill.details.subtotal'} />}
value={'$5000.00'} value={formattedSubtotal}
borderStyle={TotalLineBorderStyle.None} borderStyle={TotalLineBorderStyle.None}
/> />
<TotalLine <TotalLine
title={<T id={'bill.details.total'} />} title={<T id={'bill.details.total'} />}
value={'$5000.00'} value={formattedTotal}
borderStyle={TotalLineBorderStyle.SingleDark} borderStyle={TotalLineBorderStyle.SingleDark}
textStyle={TotalLineTextStyle.Bold} textStyle={TotalLineTextStyle.Bold}
/> />
<TotalLine <TotalLine
title={<T id={'bill.details.payment_amount'} />} title={<T id={'bill.details.payment_amount'} />}
value={'$0.00'} value={formattedPaymentTotal}
borderStyle={TotalLineBorderStyle.None} borderStyle={TotalLineBorderStyle.None}
/> />
<TotalLine <TotalLine
title={<T id={'bill.details.due_amount'} />} title={<T id={'bill.details.due_amount'} />}
value={'$5000.00'} value={formattedDueTotal}
textStyle={TotalLineTextStyle.Bold} textStyle={TotalLineTextStyle.Bold}
/> />
</BillTotalLines> </BillTotalLines>

View File

@@ -16,13 +16,13 @@ import {
VendorSelectField, VendorSelectField,
FieldRequiredHint, FieldRequiredHint,
Icon, Icon,
ExchangeRateInputGroup, CustomerDrawerLink,
If, VendorDrawerLink,
} from 'components'; } from 'components';
import { vendorsFieldShouldUpdate } from './utils'; import { vendorsFieldShouldUpdate } from './utils';
import { useBillFormContext } from './BillFormProvider'; import { useBillFormContext } from './BillFormProvider';
import BillFormCurrencyTag from './BillFormCurrencyTag'; import { BillExchangeRateInputField } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import { import {
momentFormatter, momentFormatter,
@@ -37,13 +37,7 @@ import {
*/ */
function BillFormHeader() { function BillFormHeader() {
// Bill form context. // Bill form context.
const { const { vendors } = useBillFormContext();
vendors,
isForeignVendor,
baseCurrency,
selectVendor,
setSelectVendor,
} = useBillFormContext();
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}> <div className={classNames(CLASSES.PAGE_FORM_HEADER_FIELDS)}>
@@ -57,7 +51,11 @@ function BillFormHeader() {
<FormGroup <FormGroup
label={<T id={'vendor_name'} />} label={<T id={'vendor_name'} />}
inline={true} inline={true}
className={classNames(CLASSES.FILL, 'form-group--vendor')} className={classNames(
'form-group--customer-name',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'vendor_id'} />} helperText={<ErrorMessage name={'vendor_id'} />}
@@ -70,26 +68,25 @@ function BillFormHeader() {
onContactSelected={(contact) => { onContactSelected={(contact) => {
form.setFieldValue('vendor_id', contact.id); form.setFieldValue('vendor_id', contact.id);
form.setFieldValue('currency_code', contact?.currency_code); form.setFieldValue('currency_code', contact?.currency_code);
setSelectVendor(contact);
}} }}
popoverFill={true} popoverFill={true}
allowCreate={true} allowCreate={true}
/> />
<BillFormCurrencyTag />
</ControlVendorGroup> </ControlVendorGroup>
{value && (
<VendorButtonLink vendorId={value}>
View Vendor Details
</VendorButtonLink>
)}
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<If condition={isForeignVendor}> <BillExchangeRateInputField
<ExchangeRateInputGroup
fromCurrency={baseCurrency}
toCurrency={selectVendor?.currency_code}
name={'exchange_rate'} name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }} formGroupProps={{ label: ' ', inline: true }}
/> />
</If>
{/* ------- Bill date ------- */} {/* ------- Bill date ------- */}
<FastField name={'bill_date'}> <FastField name={'bill_date'}>
@@ -184,3 +181,8 @@ const ControlVendorGroup = styled(ControlGroup)`
align-items: center; align-items: center;
transform: none; transform: none;
`; `;
const VendorButtonLink = styled(VendorDrawerLink)`
font-size: 11px;
margin-top: 6px;
`;

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { ExchangeRateInputGroup } from 'components';
import { useCurrentOrganization } from 'hooks/state';
import { useBillIsForeignCustomer } from './utils';
/**
* bill exchange rate input field.
* @returns {JSX.Element}
*/
export function BillExchangeRateInputField({ ...props }) {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
const isForeignCustomer = useBillIsForeignCustomer();
// Can't continue if the customer is not foreign.
if (!isForeignCustomer) {
return null;
}
return (
<ExchangeRateInputGroup
fromCurrency={values.currency_code}
toCurrency={currentOrganization.base_currency}
{...props}
/>
);
}

View File

@@ -11,12 +11,14 @@ import {
transformToForm, transformToForm,
repeatValue, repeatValue,
orderingLinesIndexes, orderingLinesIndexes,
formattedAmount,
} from 'utils'; } from 'utils';
import { import {
updateItemsEntriesTotal, updateItemsEntriesTotal,
ensureEntriesHaveEmptyLine, ensureEntriesHaveEmptyLine,
} from 'containers/Entries/utils'; } from 'containers/Entries/utils';
import { isLandedCostDisabled } from '../../../Entries/utils'; import { useCurrentOrganization } from 'hooks/state';
import { isLandedCostDisabled, getEntriesTotal } from '../../../Entries/utils';
import { useBillFormContext } from './BillFormProvider'; import { useBillFormContext } from './BillFormProvider';
export const MIN_LINES_NUMBER = 1; export const MIN_LINES_NUMBER = 1;
@@ -216,3 +218,69 @@ export const useSetPrimaryWarehouseToForm = () => {
} }
}, [isWarehousesSuccess, setFieldValue, warehouses]); }, [isWarehousesSuccess, setFieldValue, warehouses]);
}; };
/**
* Retreives the bill totals.
*/
export const useBillTotals = () => {
const {
values: { entries, currency_code: currencyCode },
} = useFormikContext();
// Retrieves the bili entries total.
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
// Retrieves the formatted total money.
const formattedTotal = React.useMemo(
() => formattedAmount(total, currencyCode),
[total, currencyCode],
);
// Retrieves the formatted subtotal.
const formattedSubtotal = React.useMemo(
() => formattedAmount(total, currencyCode, { money: false }),
[total, currencyCode],
);
// Retrieves the payment total.
const paymentTotal = React.useMemo(() => 0, []);
// Retireves the formatted payment total.
const formattedPaymentTotal = React.useMemo(
() => formattedAmount(paymentTotal, currencyCode),
[paymentTotal, currencyCode],
);
// Retrieves the formatted due total.
const dueTotal = React.useMemo(
() => total - paymentTotal,
[total, paymentTotal],
);
// Retrieves the formatted due total.
const formattedDueTotal = React.useMemo(
() => formattedAmount(dueTotal, currencyCode),
[dueTotal, currencyCode],
);
return {
total,
paymentTotal,
dueTotal,
formattedTotal,
formattedSubtotal,
formattedPaymentTotal,
formattedDueTotal,
};
};
/**
* Detarmines whether the bill has foreign customer.
* @returns {boolean}
*/
export const useBillIsForeignCustomer = () => {
const { values } = useFormikContext();
const currentOrganization = useCurrentOrganization();
const isForeignCustomer = React.useMemo(
() => values.currency_code !== currentOrganization.base_currency,
[values.currency_code, currentOrganization.base_currency],
);
return isForeignCustomer;
};

View File

@@ -7,18 +7,21 @@ import {
TotalLineBorderStyle, TotalLineBorderStyle,
TotalLineTextStyle, TotalLineTextStyle,
} from 'components'; } from 'components';
import { useCreditNoteTotals } from './utils';
export function CreditNoteFormFooterRight() { export function CreditNoteFormFooterRight() {
const { formattedSubtotal, formattedTotal } = useCreditNoteTotals();
return ( return (
<CreditNoteTotalLines labelColWidth={'180px'} amountColWidth={'180px'}> <CreditNoteTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine <TotalLine
title={<T id={'credit_note.drawer.label_subtotal'} />} title={<T id={'credit_note.drawer.label_subtotal'} />}
value={'$5000.00'} value={formattedSubtotal}
borderStyle={TotalLineBorderStyle.None} borderStyle={TotalLineBorderStyle.None}
/> />
<TotalLine <TotalLine
title={<T id={'credit_note.drawer.label_total'} />} title={<T id={'credit_note.drawer.label_total'} />}
value={'$5000.00'} value={formattedTotal}
// borderStyle={TotalLineBorderStyle.SingleDark} // borderStyle={TotalLineBorderStyle.SingleDark}
textStyle={TotalLineTextStyle.Bold} textStyle={TotalLineTextStyle.Bold}
/> />

View File

@@ -15,9 +15,8 @@ import {
FieldRequiredHint, FieldRequiredHint,
InputPrependButton, InputPrependButton,
Icon, Icon,
If,
FormattedMessage as T, FormattedMessage as T,
ExchangeRateInputGroup, CustomerDrawerLink,
} from 'components'; } from 'components';
import { import {
customerNameFieldShouldUpdate, customerNameFieldShouldUpdate,
@@ -27,7 +26,7 @@ import {
import { useCreditNoteFormContext } from './CreditNoteFormProvider'; import { useCreditNoteFormContext } from './CreditNoteFormProvider';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import CreditNotetFormCurrencyTag from './CreditNotetFormCurrencyTag'; import { CreditNoteExchangeRateInputField } from './components';
import { import {
momentFormatter, momentFormatter,
compose, compose,
@@ -49,13 +48,7 @@ function CreditNoteFormHeaderFields({
creditNextNumber, creditNextNumber,
}) { }) {
// Credit note form context. // Credit note form context.
const { const { customers } = useCreditNoteFormContext();
customers,
isForeignCustomer,
baseCurrency,
selectCustomer,
setSelectCustomer,
} = useCreditNoteFormContext();
// Handle credit number changing. // Handle credit number changing.
const handleCreditNumberChange = () => { const handleCreditNumberChange = () => {
@@ -108,25 +101,25 @@ function CreditNoteFormHeaderFields({
onContactSelected={(customer) => { onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id); form.setFieldValue('customer_id', customer.id);
form.setFieldValue('currency_code', customer?.currency_code); form.setFieldValue('currency_code', customer?.currency_code);
setSelectCustomer(customer);
}} }}
popoverFill={true} popoverFill={true}
allowCreate={true} allowCreate={true}
/> />
</ControlCustomerGroup> </ControlCustomerGroup>
{value && (
<CustomerButtonLink customerId={value}>
View Customer Details
</CustomerButtonLink>
)}
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<If condition={isForeignCustomer}> <CreditNoteExchangeRateInputField
<ExchangeRateInputGroup
fromCurrency={baseCurrency}
toCurrency={selectCustomer?.currency_code}
name={'exchange_rate'} name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }} formGroupProps={{ label: ' ', inline: true }}
/> />
</If>
{/* ----------- Credit note date ----------- */} {/* ----------- Credit note date ----------- */}
<FastField name={'credit_note_date'}> <FastField name={'credit_note_date'}>
@@ -222,3 +215,7 @@ const ControlCustomerGroup = styled(ControlGroup)`
align-items: center; align-items: center;
transform: none; transform: none;
`; `;
const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px;
margin-top: 6px;
`;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { ExchangeRateInputGroup } from 'components';
import { useCurrentOrganization } from 'hooks/state';
import { useCreditNoteIsForeignCustomer } from './utils';
/**
* credit exchange rate input field.
* @returns {JSX.Element}
*/
export function CreditNoteExchangeRateInputField({ ...props }) {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
const isForeignCustomer = useCreditNoteIsForeignCustomer();
// Can't continue if the customer is not foreign.
if (!isForeignCustomer) {
return null;
}
return (
<ExchangeRateInputGroup
fromCurrency={values.currency_code}
toCurrency={currentOrganization.base_currency}
{...props}
/>
);
}

View File

@@ -8,6 +8,7 @@ import {
transformToForm, transformToForm,
repeatValue, repeatValue,
transactionNumber, transactionNumber,
formattedAmount,
orderingLinesIndexes, orderingLinesIndexes,
} from 'utils'; } from 'utils';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
@@ -17,6 +18,8 @@ import {
updateItemsEntriesTotal, updateItemsEntriesTotal,
ensureEntriesHaveEmptyLine, ensureEntriesHaveEmptyLine,
} from 'containers/Entries/utils'; } from 'containers/Entries/utils';
import { useCurrentOrganization } from 'hooks/state';
import { getEntriesTotal } from 'containers/Entries/utils';
export const MIN_LINES_NUMBER = 1; export const MIN_LINES_NUMBER = 1;
@@ -168,3 +171,47 @@ export const useSetPrimaryWarehouseToForm = () => {
} }
}, [isWarehousesSuccess, setFieldValue, warehouses]); }, [isWarehousesSuccess, setFieldValue, warehouses]);
}; };
/**
* Retreives the credit note totals.
*/
export const useCreditNoteTotals = () => {
const {
values: { entries, currency_code: currencyCode },
} = useFormikContext();
// Retrieves the invoice entries total.
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
// Retrieves the formatted total money.
const formattedTotal = React.useMemo(
() => formattedAmount(total, currencyCode),
[total, currencyCode],
);
// Retrieves the formatted subtotal.
const formattedSubtotal = React.useMemo(
() => formattedAmount(total, currencyCode, { money: false }),
[total, currencyCode],
);
return {
total,
formattedTotal,
formattedSubtotal,
};
};
/**
* Detarmines whether the receipt has foreign customer.
* @returns {boolean}
*/
export const useCreditNoteIsForeignCustomer = () => {
const { values } = useFormikContext();
const currentOrganization = useCurrentOrganization();
const isForeignCustomer = React.useMemo(
() => values.currency_code !== currentOrganization.base_currency,
[values.currency_code, currentOrganization.base_currency],
);
return isForeignCustomer;
};

View File

@@ -7,18 +7,22 @@ import {
TotalLineBorderStyle, TotalLineBorderStyle,
TotalLineTextStyle, TotalLineTextStyle,
} from 'components'; } from 'components';
import { useEstimateTotals } from './utils';
export function EstimateFormFooterRight() { export function EstimateFormFooterRight() {
const { formattedSubtotal, formattedTotal } = useEstimateTotals();
return ( return (
<EstimateTotalLines labelColWidth={'180px'} amountColWidth={'180px'}> <EstimateTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine <TotalLine
title={<T id={'estimate.details.subtotal'} />} title={<T id={'estimate.details.subtotal'} />}
value={'$5000.00'} value={formattedSubtotal}
borderStyle={TotalLineBorderStyle.None} borderStyle={TotalLineBorderStyle.None}
/> />
<TotalLine <TotalLine
title={<T id={'estimate.details.total'} />} title={<T id={'estimate.details.total'} />}
value={'$5000.00'} value={formattedTotal}
// borderStyle={TotalLineBorderStyle.SingleDark} // borderStyle={TotalLineBorderStyle.SingleDark}
textStyle={TotalLineTextStyle.Bold} textStyle={TotalLineTextStyle.Bold}
/> />

View File

@@ -23,16 +23,15 @@ import { CLASSES } from 'common/classes';
import { import {
CustomerSelectField, CustomerSelectField,
FieldRequiredHint, FieldRequiredHint,
If,
Icon, Icon,
InputPrependButton, InputPrependButton,
ExchangeRateInputGroup, CustomerDrawerLink,
} from 'components'; } from 'components';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import EstimateFormCurrencyTag from './EstimateFormCurrencyTag';
import { useObserveEstimateNoSettings } from './utils'; import { useObserveEstimateNoSettings } from './utils';
import { EstimateExchangeRateInputField } from './components';
import { useEstimateFormContext } from './EstimateFormProvider'; import { useEstimateFormContext } from './EstimateFormProvider';
/** /**
@@ -47,13 +46,7 @@ function EstimateFormHeader({
estimateNumberPrefix, estimateNumberPrefix,
estimateNextNumber, estimateNextNumber,
}) { }) {
const { const { customers } = useEstimateFormContext();
customers,
isForeignCustomer,
baseCurrency,
selectCustomer,
setSelectCustomer,
} = useEstimateFormContext();
const handleEstimateNumberBtnClick = () => { const handleEstimateNumberBtnClick = () => {
openDialog('estimate-number-form', {}); openDialog('estimate-number-form', {});
@@ -100,27 +93,26 @@ function EstimateFormHeader({
onContactSelected={(customer) => { onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id); form.setFieldValue('customer_id', customer.id);
form.setFieldValue('currency_code', customer?.currency_code); form.setFieldValue('currency_code', customer?.currency_code);
setSelectCustomer(customer);
}} }}
popoverFill={true} popoverFill={true}
intent={inputIntent({ error, touched })} intent={inputIntent({ error, touched })}
allowCreate={true} allowCreate={true}
/> />
</ControlCustomerGroup> </ControlCustomerGroup>
{value && (
<CustomerButtonLink customerId={value}>
View Customer Details
</CustomerButtonLink>
)}
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<If condition={isForeignCustomer}> <EstimateExchangeRateInputField
<ExchangeRateInputGroup
fromCurrency={baseCurrency}
toCurrency={selectCustomer?.currency_code}
name={'exchange_rate'} name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }} formGroupProps={{ label: ' ', inline: true }}
/> />
</If>
{/* ----------- Estimate date ----------- */} {/* ----------- Estimate date ----------- */}
<FastField name={'estimate_date'}> <FastField name={'estimate_date'}>
@@ -246,3 +238,8 @@ const ControlCustomerGroup = styled(ControlGroup)`
align-items: center; align-items: center;
transform: none; transform: none;
`; `;
const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px;
margin-top: 6px;
`;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { ExchangeRateInputGroup } from 'components';
import { useCurrentOrganization } from 'hooks/state';
import { useEstimateIsForeignCustomer } from './utils';
/**
* Estimate exchange rate input field.
* @returns {JSX.Element}
*/
export function EstimateExchangeRateInputField({ ...props }) {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
const isForeignCustomer = useEstimateIsForeignCustomer();
// Can't continue if the customer is not foreign.
if (!isForeignCustomer) {
return null;
}
return (
<ExchangeRateInputGroup
fromCurrency={values.currency_code}
toCurrency={currentOrganization.base_currency}
{...props}
/>
);
}

View File

@@ -9,12 +9,15 @@ import {
transactionNumber, transactionNumber,
repeatValue, repeatValue,
transformToForm, transformToForm,
formattedAmount,
} from 'utils'; } from 'utils';
import { useEstimateFormContext } from './EstimateFormProvider'; import { useEstimateFormContext } from './EstimateFormProvider';
import { import {
updateItemsEntriesTotal, updateItemsEntriesTotal,
ensureEntriesHaveEmptyLine, ensureEntriesHaveEmptyLine,
} from 'containers/Entries/utils'; } from 'containers/Entries/utils';
import { useCurrentOrganization } from 'hooks/state';
import { getEntriesTotal } from 'containers/Entries/utils';
export const MIN_LINES_NUMBER = 1; export const MIN_LINES_NUMBER = 1;
@@ -187,3 +190,47 @@ export const useSetPrimaryBranchToForm = () => {
} }
}, [isBranchesSuccess, setFieldValue, branches]); }, [isBranchesSuccess, setFieldValue, branches]);
}; };
/**
* Retreives the estimate totals.
*/
export const useEstimateTotals = () => {
const {
values: { entries, currency_code: currencyCode },
} = useFormikContext();
// Retrieves the invoice entries total.
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
// Retrieves the formatted total money.
const formattedTotal = React.useMemo(
() => formattedAmount(total, currencyCode),
[total, currencyCode],
);
// Retrieves the formatted subtotal.
const formattedSubtotal = React.useMemo(
() => formattedAmount(total, currencyCode, { money: false }),
[total, currencyCode],
);
return {
total,
formattedTotal,
formattedSubtotal,
};
};
/**
* Detarmines whether the estimate has foreign customer.
* @returns {boolean}
*/
export const useEstimateIsForeignCustomer = () => {
const { values } = useFormikContext();
const currentOrganization = useCurrentOrganization();
const isForeignCustomer = React.useMemo(
() => values.currency_code !== currentOrganization.base_currency,
[values.currency_code, currentOrganization.base_currency],
);
return isForeignCustomer;
};

View File

@@ -104,7 +104,6 @@ function InvoiceFormHeaderFields({
popoverFill={true} popoverFill={true}
allowCreate={true} allowCreate={true}
/> />
{/* <InvoiceCurrencyTag /> */}
</ControlCustomerGroup> </ControlCustomerGroup>
{value && ( {value && (
<CustomerButtonLink customerId={value}> <CustomerButtonLink customerId={value}>

View File

@@ -6,7 +6,7 @@ export function PaymentReceiveFormFootetLeft() {
return ( return (
<React.Fragment> <React.Fragment>
{/* --------- Statement--------- */} {/* --------- Statement--------- */}
<StatementFormGroup <TermsConditsFormGroup
name={'statement'} name={'statement'}
label={<T id={'statement'} />} label={<T id={'statement'} />}
hintText={'Will be displayed on the Payment'} hintText={'Will be displayed on the Payment'}
@@ -15,15 +15,13 @@ export function PaymentReceiveFormFootetLeft() {
name={'statement'} name={'statement'}
placeholder={'Thanks for your business and have a great day!'} placeholder={'Thanks for your business and have a great day!'}
/> />
</StatementFormGroup> </TermsConditsFormGroup>
</React.Fragment> </React.Fragment>
); );
} }
const StatementFormGroup = styled(FFormGroup)` const TermsConditsFormGroup = styled(FFormGroup)`
&.bp3-form-group { &.bp3-form-group {
margin-bottom: 40px;
.bp3-label { .bp3-label {
font-size: 12px; font-size: 12px;
margin-bottom: 12px; margin-bottom: 12px;
@@ -33,3 +31,4 @@ const StatementFormGroup = styled(FFormGroup)`
} }
} }
`; `;

View File

@@ -7,18 +7,21 @@ import {
TotalLineBorderStyle, TotalLineBorderStyle,
TotalLineTextStyle, TotalLineTextStyle,
} from 'components'; } from 'components';
import { usePaymentReceiveTotals } from './utils';
export function PaymentReceiveFormFootetRight() { export function PaymentReceiveFormFootetRight() {
const { formattedSubtotal, formattedTotal } = usePaymentReceiveTotals();
return ( return (
<PaymentReceiveTotalLines labelColWidth={'180px'} amountColWidth={'180px'}> <PaymentReceiveTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine <TotalLine
title={<T id={'estimate.details.subtotal'} />} title={<T id={'estimate.details.subtotal'} />}
value={'$5000.00'} value={formattedSubtotal}
borderStyle={TotalLineBorderStyle.None} borderStyle={TotalLineBorderStyle.None}
/> />
<TotalLine <TotalLine
title={<T id={'estimate.details.total'} />} title={<T id={'estimate.details.total'} />}
value={'$5000.00'} value={formattedTotal}
// borderStyle={TotalLineBorderStyle.SingleDark} // borderStyle={TotalLineBorderStyle.SingleDark}
textStyle={TotalLineTextStyle.Bold} textStyle={TotalLineTextStyle.Bold}
/> />

View File

@@ -30,13 +30,13 @@ import {
InputPrependButton, InputPrependButton,
MoneyInputGroup, MoneyInputGroup,
InputPrependText, InputPrependText,
ExchangeRateInputGroup, CustomerDrawerLink,
Hint, Hint,
Money, Money,
} from 'components'; } from 'components';
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider'; import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
import { ACCOUNT_TYPE } from 'common/accountTypes'; import { ACCOUNT_TYPE } from 'common/accountTypes';
import PaymentReceiveFormCurrencyTag from './PaymentReceiveFormCurrencyTag'; import { PaymentReceiveExchangeRateInputField } from './components';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
@@ -49,6 +49,7 @@ import {
customersFieldShouldUpdate, customersFieldShouldUpdate,
accountsFieldShouldUpdate, accountsFieldShouldUpdate,
} from './utils'; } from './utils';
import { toSafeInteger } from 'lodash'; import { toSafeInteger } from 'lodash';
/** /**
@@ -67,15 +68,7 @@ function PaymentReceiveHeaderFields({
paymentReceiveNextNumber, paymentReceiveNextNumber,
}) { }) {
// Payment receive form context. // Payment receive form context.
const { const { customers, accounts, isNewMode } = usePaymentReceiveFormContext();
customers,
accounts,
isNewMode,
isForeignCustomer,
baseCurrency,
selectCustomer,
setSelectCustomer,
} = usePaymentReceiveFormContext();
// Formik form context. // Formik form context.
const { const {
@@ -154,7 +147,6 @@ function PaymentReceiveHeaderFields({
form.setFieldValue('customer_id', customer.id); form.setFieldValue('customer_id', customer.id);
form.setFieldValue('full_amount', ''); form.setFieldValue('full_amount', '');
form.setFieldValue('currency_code', customer?.currency_code); form.setFieldValue('currency_code', customer?.currency_code);
setSelectCustomer(customer);
}} }}
popoverFill={true} popoverFill={true}
disabled={!isNewMode} disabled={!isNewMode}
@@ -164,20 +156,20 @@ function PaymentReceiveHeaderFields({
allowCreate={true} allowCreate={true}
/> />
</ControlCustomerGroup> </ControlCustomerGroup>
{value && (
<CustomerButtonLink customerId={value}>
View Customer Details
</CustomerButtonLink>
)}
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<If condition={isForeignCustomer}> <PaymentReceiveExchangeRateInputField
<ExchangeRateInputGroup
fromCurrency={baseCurrency}
toCurrency={selectCustomer?.currency_code}
name={'exchange_rate'} name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }} formGroupProps={{ label: ' ', inline: true }}
/> />
</If>
{/* ------------- Payment date ------------- */} {/* ------------- Payment date ------------- */}
<FastField name={'payment_date'}> <FastField name={'payment_date'}>
{({ form, field: { value }, meta: { error, touched } }) => ( {({ form, field: { value }, meta: { error, touched } }) => (
@@ -359,3 +351,8 @@ const ControlCustomerGroup = styled(ControlGroup)`
align-items: center; align-items: center;
transform: none; transform: none;
`; `;
const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px;
margin-top: 6px;
`;

View File

@@ -2,9 +2,12 @@ import React from 'react';
import moment from 'moment'; import moment from 'moment';
import intl from 'react-intl-universal'; import intl from 'react-intl-universal';
import { Money } from 'components'; import { Money, ExchangeRateInputGroup } from 'components';
import { MoneyFieldCell } from 'components/DataTableCells'; import { MoneyFieldCell } from 'components/DataTableCells';
import { safeSumBy, formattedAmount } from 'utils'; import { safeSumBy, formattedAmount } from 'utils';
import { useFormikContext } from 'formik';
import { useCurrentOrganization } from 'hooks/state';
import { useEstimateIsForeignCustomer } from './utils';
/** /**
* Invoice date cell. * Invoice date cell.
@@ -59,15 +62,13 @@ function MoneyTableCell({ row: { original }, value }) {
} }
function DateFooterCell() { function DateFooterCell() {
return intl.get('total') return intl.get('total');
} }
/** /**
* Retrieve payment receive form entries columns. * Retrieve payment receive form entries columns.
*/ */
export const usePaymentReceiveEntriesColumns = () => { export const usePaymentReceiveEntriesColumns = () => {
return React.useMemo( return React.useMemo(
() => [ () => [
{ {
@@ -127,3 +128,26 @@ export const usePaymentReceiveEntriesColumns = () => {
[], [],
); );
}; };
/**
* payment receive exchange rate input field.
* @returns {JSX.Element}
*/
export function PaymentReceiveExchangeRateInputField({ ...props }) {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
const isForeignCustomer = useEstimateIsForeignCustomer();
// Can't continue if the customer is not foreign.
if (!isForeignCustomer) {
return null;
}
return (
<ExchangeRateInputGroup
fromCurrency={values.currency_code}
toCurrency={currentOrganization.base_currency}
{...props}
/>
);
}

View File

@@ -12,7 +12,10 @@ import {
transformToForm, transformToForm,
safeSumBy, safeSumBy,
orderingLinesIndexes, orderingLinesIndexes,
formattedAmount,
} from 'utils'; } from 'utils';
import { useCurrentOrganization } from 'hooks/state';
import { getEntriesTotal } from 'containers/Entries/utils';
// Default payment receive entry. // Default payment receive entry.
export const defaultPaymentReceiveEntry = { export const defaultPaymentReceiveEntry = {
@@ -213,3 +216,47 @@ export const transformErrors = (errors, { setFieldError }) => {
}); });
} }
}; };
/**
* Retreives the payment receive totals.
*/
export const usePaymentReceiveTotals = () => {
const {
values: { entries, currency_code: currencyCode },
} = useFormikContext();
// Retrieves the invoice entries total.
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
// Retrieves the formatted total money.
const formattedTotal = React.useMemo(
() => formattedAmount(total, currencyCode),
[total, currencyCode],
);
// Retrieves the formatted subtotal.
const formattedSubtotal = React.useMemo(
() => formattedAmount(total, currencyCode, { money: false }),
[total, currencyCode],
);
return {
total,
formattedTotal,
formattedSubtotal,
};
};
/**
* Detarmines whether the payment has foreign customer.
* @returns {boolean}
*/
export const useEstimateIsForeignCustomer = () => {
const { values } = useFormikContext();
const currentOrganization = useCurrentOrganization();
const isForeignCustomer = React.useMemo(
() => values.currency_code !== currentOrganization.base_currency,
[values.currency_code, currentOrganization.base_currency],
);
return isForeignCustomer;
};

View File

@@ -8,29 +8,37 @@ import {
TotalLineBorderStyle, TotalLineBorderStyle,
TotalLineTextStyle, TotalLineTextStyle,
} from 'components'; } from 'components';
import { useReceiptTotals } from './utils';
export function ReceiptFormFooterRight() { export function ReceiptFormFooterRight() {
const {
formattedSubtotal,
formattedTotal,
formattedDueTotal,
formattedPaymentTotal,
} = useReceiptTotals();
return ( return (
<ReceiptTotalLines labelColWidth={'180px'} amountColWidth={'180px'}> <ReceiptTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
<TotalLine <TotalLine
title={<T id={'receipt.details.subtotal'} />} title={<T id={'receipt.details.subtotal'} />}
value={'$5000.00'} value={formattedSubtotal}
borderStyle={TotalLineBorderStyle.None} borderStyle={TotalLineBorderStyle.None}
/> />
<TotalLine <TotalLine
title={<T id={'receipt.details.total'} />} title={<T id={'receipt.details.total'} />}
value={'$5000.00'} value={formattedTotal}
borderStyle={TotalLineBorderStyle.SingleDark} borderStyle={TotalLineBorderStyle.SingleDark}
textStyle={TotalLineTextStyle.Bold} textStyle={TotalLineTextStyle.Bold}
/> />
<TotalLine <TotalLine
title={<T id={'receipt.details.payment_amount'} />} title={<T id={'receipt.details.payment_amount'} />}
value={'$0.00'} value={formattedPaymentTotal}
borderStyle={TotalLineBorderStyle.None} borderStyle={TotalLineBorderStyle.None}
/> />
<TotalLine <TotalLine
title={<T id={'receipt.details.due_amount'} />} title={<T id={'receipt.details.due_amount'} />}
value={'$5000.00'} value={formattedDueTotal}
textStyle={TotalLineTextStyle.Bold} textStyle={TotalLineTextStyle.Bold}
/> />
</ReceiptTotalLines> </ReceiptTotalLines>

View File

@@ -19,7 +19,7 @@ import {
Icon, Icon,
If, If,
InputPrependButton, InputPrependButton,
ExchangeRateInputGroup, CustomerDrawerLink,
} from 'components'; } from 'components';
import withSettings from 'containers/Settings/withSettings'; import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions'; import withDialogActions from 'containers/Dialog/withDialogActions';
@@ -31,13 +31,13 @@ import {
handleDateChange, handleDateChange,
inputIntent, inputIntent,
} from 'utils'; } from 'utils';
import ReceiptFormCurrencyTag from './ReceiptFormCurrencyTag';
import { useReceiptFormContext } from './ReceiptFormProvider'; import { useReceiptFormContext } from './ReceiptFormProvider';
import { import {
accountsFieldShouldUpdate, accountsFieldShouldUpdate,
customersFieldShouldUpdate, customersFieldShouldUpdate,
useObserveReceiptNoSettings, useObserveReceiptNoSettings,
} from './utils'; } from './utils';
import { ReceiptExchangeRateInputField } from './components';
/** /**
* Receipt form header fields. * Receipt form header fields.
@@ -46,22 +46,12 @@ function ReceiptFormHeader({
//#withDialogActions //#withDialogActions
openDialog, openDialog,
// #ownProps
onReceiptNumberChanged,
// #withSettings // #withSettings
receiptAutoIncrement, receiptAutoIncrement,
receiptNextNumber, receiptNextNumber,
receiptNumberPrefix, receiptNumberPrefix,
}) { }) {
const { const { accounts, customers } = useReceiptFormContext();
accounts,
customers,
isForeignCustomer,
baseCurrency,
selectCustomer,
setSelectCustomer,
} = useReceiptFormContext();
const handleReceiptNumberChange = useCallback(() => { const handleReceiptNumberChange = useCallback(() => {
openDialog('receipt-number-form', {}); openDialog('receipt-number-form', {});
@@ -108,26 +98,25 @@ function ReceiptFormHeader({
onContactSelected={(customer) => { onContactSelected={(customer) => {
form.setFieldValue('customer_id', customer.id); form.setFieldValue('customer_id', customer.id);
form.setFieldValue('currency_code', customer?.currency_code); form.setFieldValue('currency_code', customer?.currency_code);
setSelectCustomer(customer);
}} }}
popoverFill={true} popoverFill={true}
allowCreate={true} allowCreate={true}
/> />
</ControlCustomerGroup> </ControlCustomerGroup>
{value && (
<CustomerButtonLink customerId={value}>
View Customer Details
</CustomerButtonLink>
)}
</FormGroup> </FormGroup>
)} )}
</FastField> </FastField>
{/* ----------- Exchange rate ----------- */} {/* ----------- Exchange rate ----------- */}
<If condition={isForeignCustomer}> <ReceiptExchangeRateInputField
<ExchangeRateInputGroup
fromCurrency={baseCurrency}
toCurrency={selectCustomer?.currency_code}
name={'exchange_rate'} name={'exchange_rate'}
formGroupProps={{ label: ' ', inline: true }} formGroupProps={{ label: ' ', inline: true }}
/> />
</If>
{/* ----------- Deposit account ----------- */} {/* ----------- Deposit account ----------- */}
<FastField <FastField
@@ -261,3 +250,8 @@ const ControlCustomerGroup = styled(ControlGroup)`
align-items: center; align-items: center;
transform: none; transform: none;
`; `;
const CustomerButtonLink = styled(CustomerDrawerLink)`
font-size: 11px;
margin-top: 6px;
`;

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { ExchangeRateInputGroup } from 'components';
import { useCurrentOrganization } from 'hooks/state';
import { useReceiptIsForeignCustomer } from './utils';
/**
* Receipt exchange rate input field.
* @returns {JSX.Element}
*/
export function ReceiptExchangeRateInputField({ ...props }) {
const currentOrganization = useCurrentOrganization();
const { values } = useFormikContext();
const isForeignCustomer = useReceiptIsForeignCustomer();
// Can't continue if the customer is not foreign.
if (!isForeignCustomer) {
return null;
}
return (
<ExchangeRateInputGroup
fromCurrency={values.currency_code}
toCurrency={currentOrganization.base_currency}
{...props}
/>
);
}

View File

@@ -9,12 +9,15 @@ import {
transactionNumber, transactionNumber,
repeatValue, repeatValue,
transformToForm, transformToForm,
formattedAmount,
} from 'utils'; } from 'utils';
import { useReceiptFormContext } from './ReceiptFormProvider'; import { useReceiptFormContext } from './ReceiptFormProvider';
import { import {
updateItemsEntriesTotal, updateItemsEntriesTotal,
ensureEntriesHaveEmptyLine, ensureEntriesHaveEmptyLine,
} from 'containers/Entries/utils'; } from 'containers/Entries/utils';
import { useCurrentOrganization } from 'hooks/state';
import { getEntriesTotal } from 'containers/Entries/utils';
export const MIN_LINES_NUMBER = 1; export const MIN_LINES_NUMBER = 1;
@@ -178,3 +181,69 @@ export const useSetPrimaryBranchToForm = () => {
} }
}, [isBranchesSuccess, setFieldValue, branches]); }, [isBranchesSuccess, setFieldValue, branches]);
}; };
/**
* Retreives the Receipt totals.
*/
export const useReceiptTotals = () => {
const {
values: { entries, currency_code: currencyCode },
} = useFormikContext();
// Retrieves the invoice entries total.
const total = React.useMemo(() => getEntriesTotal(entries), [entries]);
// Retrieves the formatted total money.
const formattedTotal = React.useMemo(
() => formattedAmount(total, currencyCode),
[total, currencyCode],
);
// Retrieves the formatted subtotal.
const formattedSubtotal = React.useMemo(
() => formattedAmount(total, currencyCode, { money: false }),
[total, currencyCode],
);
// Retrieves the payment total.
const paymentTotal = React.useMemo(() => 0, []);
// Retireves the formatted payment total.
const formattedPaymentTotal = React.useMemo(
() => formattedAmount(paymentTotal, currencyCode),
[paymentTotal, currencyCode],
);
// Retrieves the formatted due total.
const dueTotal = React.useMemo(
() => total - paymentTotal,
[total, paymentTotal],
);
// Retrieves the formatted due total.
const formattedDueTotal = React.useMemo(
() => formattedAmount(dueTotal, currencyCode),
[dueTotal, currencyCode],
);
return {
total,
paymentTotal,
dueTotal,
formattedTotal,
formattedSubtotal,
formattedPaymentTotal,
formattedDueTotal,
};
};
/**
* Detarmines whether the receipt has foreign customer.
* @returns {boolean}
*/
export const useReceiptIsForeignCustomer = () => {
const { values } = useFormikContext();
const currentOrganization = useCurrentOrganization();
const isForeignCustomer = React.useMemo(
() => values.currency_code !== currentOrganization.base_currency,
[values.currency_code, currentOrganization.base_currency],
);
return isForeignCustomer;
};