mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
fix: subtitle issue with sales transaction number.
This commit is contained in:
@@ -27,7 +27,12 @@ import Dragzone from 'components/Dragzone';
|
|||||||
import withMediaActions from 'containers/Media/withMediaActions';
|
import withMediaActions from 'containers/Media/withMediaActions';
|
||||||
|
|
||||||
import useMedia from 'hooks/useMedia';
|
import useMedia from 'hooks/useMedia';
|
||||||
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
|
import {
|
||||||
|
compose,
|
||||||
|
repeatValue,
|
||||||
|
orderingLinesIndexes,
|
||||||
|
defaultToTransform,
|
||||||
|
} from 'utils';
|
||||||
import withManualJournalsActions from './withManualJournalsActions';
|
import withManualJournalsActions from './withManualJournalsActions';
|
||||||
import withManualJournals from './withManualJournals';
|
import withManualJournals from './withManualJournals';
|
||||||
|
|
||||||
@@ -97,13 +102,18 @@ function MakeJournalEntriesForm({
|
|||||||
: journalNextNumber;
|
: journalNextNumber;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const transactionNumber = manualJournal
|
||||||
|
? manualJournal.journal_number
|
||||||
|
: journalNumber;
|
||||||
|
|
||||||
if (manualJournal && manualJournal.id) {
|
if (manualJournal && manualJournal.id) {
|
||||||
changePageTitle(formatMessage({ id: 'edit_journal' }));
|
changePageTitle(formatMessage({ id: 'edit_journal' }));
|
||||||
changePageSubtitle(`No. ${manualJournal.journal_number}`);
|
|
||||||
} else {
|
} else {
|
||||||
changePageSubtitle(`No. ${journalNumber}`);
|
|
||||||
changePageTitle(formatMessage({ id: 'new_journal' }));
|
changePageTitle(formatMessage({ id: 'new_journal' }));
|
||||||
}
|
}
|
||||||
|
changePageSubtitle(
|
||||||
|
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
|
||||||
|
);
|
||||||
}, [
|
}, [
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
@@ -383,7 +393,9 @@ function MakeJournalEntriesForm({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (journalNumberChanged) {
|
if (journalNumberChanged) {
|
||||||
setFieldValue('journal_number', journalNumber);
|
setFieldValue('journal_number', journalNumber);
|
||||||
changePageSubtitle(`No. ${journalNumber}`);
|
changePageSubtitle(
|
||||||
|
defaultToTransform(journalNumber, `No. ${journalNumber}`, ''),
|
||||||
|
);
|
||||||
setJournalNumberChanged(false);
|
setJournalNumberChanged(false);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -440,7 +452,9 @@ function MakeJournalEntriesForm({
|
|||||||
// Handle journal number field change.
|
// Handle journal number field change.
|
||||||
const handleJournalNumberChanged = useCallback(
|
const handleJournalNumberChanged = useCallback(
|
||||||
(journalNumber) => {
|
(journalNumber) => {
|
||||||
changePageSubtitle(`No. ${journalNumber}`);
|
changePageSubtitle(
|
||||||
|
defaultToTransform(journalNumber, `No. ${journalNumber}`, '')
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[changePageSubtitle],
|
[changePageSubtitle],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ function AccountFormDialogContent({
|
|||||||
onAccountSelected={onChangeSubaccount}
|
onAccountSelected={onChangeSubaccount}
|
||||||
defaultSelectText={<T id={'select_parent_account'} />}
|
defaultSelectText={<T id={'select_parent_account'} />}
|
||||||
selectedAccountId={values.parent_account_id}
|
selectedAccountId={values.parent_account_id}
|
||||||
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</If>
|
</If>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function PaymentMadeActionsBar({
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const handleClickNewPaymentMade = useCallback(() => {
|
const handleClickNewPaymentMade = useCallback(() => {
|
||||||
history.push('/payment-made/new');
|
history.push('/payment-mades/new');
|
||||||
}, [history]);
|
}, [history]);
|
||||||
|
|
||||||
// const filterDropdown = FilterDropdown({
|
// const filterDropdown = FilterDropdown({
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const Schema = Yup.object().shape({
|
|||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'payment_account_' })),
|
.label(formatMessage({ id: 'payment_account_' })),
|
||||||
payment_number: Yup.string()
|
payment_number: Yup.string()
|
||||||
|
.nullable()
|
||||||
.max(DATATYPES_LENGTH.STRING)
|
.max(DATATYPES_LENGTH.STRING)
|
||||||
.label(formatMessage({ id: 'payment_no_' })),
|
.label(formatMessage({ id: 'payment_no_' })),
|
||||||
reference: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
|
reference: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ function PaymentMadeFormHeader({
|
|||||||
defaultSelectText={ <T id={'select_vender_account'} /> }
|
defaultSelectText={ <T id={'select_vender_account'} /> }
|
||||||
onContactSelected={onChangeSelect('vendor_id')}
|
onContactSelected={onChangeSelect('vendor_id')}
|
||||||
disabled={!isNewMode}
|
disabled={!isNewMode}
|
||||||
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,12 @@ import Dragzone from 'components/Dragzone';
|
|||||||
import useMedia from 'hooks/useMedia';
|
import useMedia from 'hooks/useMedia';
|
||||||
import { ERROR } from 'common/errors';
|
import { ERROR } from 'common/errors';
|
||||||
|
|
||||||
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
|
import {
|
||||||
|
compose,
|
||||||
|
repeatValue,
|
||||||
|
defaultToTransform,
|
||||||
|
orderingLinesIndexes,
|
||||||
|
} from 'utils';
|
||||||
|
|
||||||
const MIN_LINES_NUMBER = 4;
|
const MIN_LINES_NUMBER = 4;
|
||||||
|
|
||||||
@@ -98,16 +103,20 @@ const EstimateForm = ({
|
|||||||
: estimateNextNumber;
|
: estimateNextNumber;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (estimate && estimate.id) {
|
const transNumber = !isNewMode ? estimate.estimate_number : estimateNumber;
|
||||||
|
|
||||||
|
if (isNewMode) {
|
||||||
changePageTitle(formatMessage({ id: 'edit_estimate' }));
|
changePageTitle(formatMessage({ id: 'edit_estimate' }));
|
||||||
changePageSubtitle(`No. ${estimate.estimate_number}`);
|
|
||||||
} else {
|
} else {
|
||||||
changePageSubtitle(`No. ${estimateNumber}`);
|
|
||||||
changePageTitle(formatMessage({ id: 'new_estimate' }));
|
changePageTitle(formatMessage({ id: 'new_estimate' }));
|
||||||
}
|
}
|
||||||
|
changePageSubtitle(
|
||||||
|
defaultToTransform(estimateNumber, `No. ${transNumber}`, ''),
|
||||||
|
);
|
||||||
}, [
|
}, [
|
||||||
estimate,
|
estimate,
|
||||||
estimateNumber,
|
estimateNumber,
|
||||||
|
isNewMode,
|
||||||
formatMessage,
|
formatMessage,
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
@@ -211,18 +220,26 @@ const EstimateForm = ({
|
|||||||
|
|
||||||
const handleEstimateNumberChange = useCallback(
|
const handleEstimateNumberChange = useCallback(
|
||||||
(estimateNumber) => {
|
(estimateNumber) => {
|
||||||
changePageSubtitle(`No. ${estimateNumber}`);
|
changePageSubtitle(
|
||||||
|
defaultToTransform(estimateNumber, `No. ${estimateNumber}`, ''),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[changePageSubtitle],
|
[changePageSubtitle],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmitClick = useCallback((event) => {
|
const handleSubmitClick = useCallback(
|
||||||
|
(event) => {
|
||||||
setSubmitPayload({ redirect: true });
|
setSubmitPayload({ redirect: true });
|
||||||
}, [setSubmitPayload]);
|
},
|
||||||
|
[setSubmitPayload],
|
||||||
|
);
|
||||||
|
|
||||||
const handleCancelClick = useCallback((event) => {
|
const handleCancelClick = useCallback(
|
||||||
|
(event) => {
|
||||||
history.goBack();
|
history.goBack();
|
||||||
}, [history]);
|
},
|
||||||
|
[history],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_ESTIMATE)}>
|
<div className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_ESTIMATE)}>
|
||||||
@@ -239,7 +256,7 @@ const EstimateForm = ({
|
|||||||
onEstimateNumberChanged={handleEstimateNumberChange}
|
onEstimateNumberChanged={handleEstimateNumberChange}
|
||||||
/>
|
/>
|
||||||
<EstimateNumberWatcher estimateNumber={estimateNumber} />
|
<EstimateNumberWatcher estimateNumber={estimateNumber} />
|
||||||
<EditableItemsEntriesTable />
|
<EditableItemsEntriesTable filterSellableItems={true} />
|
||||||
<EstimateFormFooter />
|
<EstimateFormFooter />
|
||||||
<EstimateFloatingActions
|
<EstimateFloatingActions
|
||||||
isSubmiting={isSubmitting}
|
isSubmiting={isSubmitting}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ function EstimateFormHeader({
|
|||||||
onContactSelected={(customer) => {
|
onContactSelected={(customer) => {
|
||||||
form.setFieldValue('customer_id', customer.id);
|
form.setFieldValue('customer_id', customer.id);
|
||||||
}}
|
}}
|
||||||
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -30,7 +30,12 @@ import { AppToaster } from 'components';
|
|||||||
import useMedia from 'hooks/useMedia';
|
import useMedia from 'hooks/useMedia';
|
||||||
import { ERROR } from 'common/errors';
|
import { ERROR } from 'common/errors';
|
||||||
|
|
||||||
import { compose, repeatValue, saveInvoke, orderingLinesIndexes } from 'utils';
|
import {
|
||||||
|
compose,
|
||||||
|
repeatValue,
|
||||||
|
defaultToTransform,
|
||||||
|
orderingLinesIndexes,
|
||||||
|
} from 'utils';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
const MIN_LINES_NUMBER = 4;
|
const MIN_LINES_NUMBER = 4;
|
||||||
@@ -94,13 +99,16 @@ function InvoiceForm({
|
|||||||
: invoiceNextNumber;
|
: invoiceNextNumber;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const transactionNumber = invoice ? invoice.invoice_no : invoiceNumber;
|
||||||
|
|
||||||
if (invoice && invoice.id) {
|
if (invoice && invoice.id) {
|
||||||
changePageTitle(formatMessage({ id: 'edit_invoice' }));
|
changePageTitle(formatMessage({ id: 'edit_invoice' }));
|
||||||
changePageSubtitle(`No. ${invoice.invoice_no}`);
|
|
||||||
} else {
|
} else {
|
||||||
changePageSubtitle(`No. ${invoiceNumber}`);
|
|
||||||
changePageTitle(formatMessage({ id: 'new_invoice' }));
|
changePageTitle(formatMessage({ id: 'new_invoice' }));
|
||||||
}
|
}
|
||||||
|
changePageSubtitle(
|
||||||
|
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
|
||||||
|
);
|
||||||
}, [
|
}, [
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
@@ -216,7 +224,9 @@ function InvoiceForm({
|
|||||||
|
|
||||||
const handleInvoiceNumberChanged = useCallback(
|
const handleInvoiceNumberChanged = useCallback(
|
||||||
(invoiceNumber) => {
|
(invoiceNumber) => {
|
||||||
changePageSubtitle(`No. ${invoiceNumber}`);
|
changePageSubtitle(
|
||||||
|
defaultToTransform(invoiceNumber, `No. ${invoiceNumber}`, ''),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[changePageSubtitle],
|
[changePageSubtitle],
|
||||||
);
|
);
|
||||||
@@ -236,7 +246,10 @@ function InvoiceForm({
|
|||||||
onInvoiceNumberChanged={handleInvoiceNumberChanged}
|
onInvoiceNumberChanged={handleInvoiceNumberChanged}
|
||||||
/>
|
/>
|
||||||
<InvoiceNumberChangeWatcher invoiceNumber={invoiceNumber} />
|
<InvoiceNumberChangeWatcher invoiceNumber={invoiceNumber} />
|
||||||
<EditableItemsEntriesTable defaultEntry={defaultInvoice} />
|
<EditableItemsEntriesTable
|
||||||
|
defaultEntry={defaultInvoice}
|
||||||
|
filterSellableItems={true}
|
||||||
|
/>
|
||||||
<InvoiceFormFooter />
|
<InvoiceFormFooter />
|
||||||
<InvoiceFloatingActions
|
<InvoiceFloatingActions
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ function InvoiceFormHeader({
|
|||||||
onContactSelected={(customer) => {
|
onContactSelected={(customer) => {
|
||||||
form.setFieldValue('customer_id', customer.id);
|
form.setFieldValue('customer_id', customer.id);
|
||||||
}}
|
}}
|
||||||
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function PaymentReceiveActionsBar({
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const handleClickNewPaymentReceive = useCallback(() => {
|
const handleClickNewPaymentReceive = useCallback(() => {
|
||||||
history.push('/payment-receive/new');
|
history.push('/payment-receives/new');
|
||||||
}, [history]);
|
}, [history]);
|
||||||
|
|
||||||
// const filterDropdown = FilterDropdown({
|
// const filterDropdown = FilterDropdown({
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
import React, {
|
import React, { useMemo, useCallback, useEffect, useState } from 'react';
|
||||||
useMemo,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
useRef,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import * as Yup from 'yup';
|
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { pick, sumBy, omit } from 'lodash';
|
import { pick, sumBy, omit } from 'lodash';
|
||||||
import { Intent, Alert } from '@blueprintjs/core';
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import PaymentReceiveHeader from './PaymentReceiveFormHeader';
|
import PaymentReceiveHeader from './PaymentReceiveFormHeader';
|
||||||
@@ -32,7 +25,7 @@ import {
|
|||||||
} from './PaymentReceiveForm.schema';
|
} from './PaymentReceiveForm.schema';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose, defaultToTransform } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Payment Receive form.
|
* Payment Receive form.
|
||||||
@@ -62,35 +55,45 @@ function PaymentReceiveForm({
|
|||||||
changePageTitle,
|
changePageTitle,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
}) {
|
}) {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
const [amountChangeAlert, setAmountChangeAlert] = useState(false);
|
const [amountChangeAlert, setAmountChangeAlert] = useState(false);
|
||||||
const [clearLinesAlert, setClearLinesAlert] = useState(false);
|
const [clearLinesAlert, setClearLinesAlert] = useState(false);
|
||||||
const [fullAmount, setFullAmount] = useState(null);
|
const [fullAmount, setFullAmount] = useState(null);
|
||||||
const [clearFormAlert, setClearFormAlert] = useState(false);
|
const [clearFormAlert, setClearFormAlert] = useState(false);
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const isNewMode = !paymentReceiveId;
|
|
||||||
const [localPaymentEntries, setLocalPaymentEntries] = useState(
|
const [localPaymentEntries, setLocalPaymentEntries] = useState(
|
||||||
paymentReceiveEntries,
|
paymentReceiveEntries,
|
||||||
);
|
);
|
||||||
|
const isNewMode = !paymentReceiveId;
|
||||||
|
|
||||||
const paymentReceiveNumber = paymentReceiveNumberPrefix
|
const paymentReceiveNumber = paymentReceiveNumberPrefix
|
||||||
? `${paymentReceiveNumberPrefix}-${paymentReceiveNextNumber}`
|
? `${paymentReceiveNumberPrefix}-${paymentReceiveNextNumber}`
|
||||||
: paymentReceiveNextNumber;
|
: paymentReceiveNextNumber;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const transactionNumber = !isNewMode
|
||||||
|
? paymentReceive.payment_receive_no
|
||||||
|
: paymentReceiveNumber;
|
||||||
|
|
||||||
if (paymentReceive && paymentReceiveId) {
|
if (paymentReceive && paymentReceiveId) {
|
||||||
changePageTitle(formatMessage({ id: 'edit_payment_receive' }));
|
changePageTitle(formatMessage({ id: 'edit_payment_receive' }));
|
||||||
changePageSubtitle(`No. ${paymentReceive.payment_receive_no}`);
|
changePageSubtitle(`No. ${paymentReceive.payment_receive_no}`);
|
||||||
} else {
|
} else {
|
||||||
changePageSubtitle(`No. ${paymentReceiveNumber}`);
|
|
||||||
changePageTitle(formatMessage({ id: 'payment_receive' }));
|
changePageTitle(formatMessage({ id: 'payment_receive' }));
|
||||||
}
|
}
|
||||||
|
changePageSubtitle(
|
||||||
|
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
|
||||||
|
);
|
||||||
}, [
|
}, [
|
||||||
|
isNewMode,
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
formatMessage,
|
formatMessage,
|
||||||
|
paymentReceiveNumber,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -167,8 +170,8 @@ function PaymentReceiveForm({
|
|||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
id: 'you_cannot_make_payment_with_zero_total_amount',
|
id: 'you_cannot_make_payment_with_zero_total_amount',
|
||||||
intent: Intent.WARNING,
|
|
||||||
}),
|
}),
|
||||||
|
intent: Intent.DANGER,
|
||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
return;
|
return;
|
||||||
@@ -180,13 +183,14 @@ function PaymentReceiveForm({
|
|||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: formatMessage({
|
message: formatMessage({
|
||||||
id: paymentReceiveId
|
id: paymentReceiveId
|
||||||
? 'the_payment_has_been_received_successfully_edited'
|
? 'the_payment_receive_transaction_has_been_edited'
|
||||||
: 'the_payment_has_been_received_successfully_created',
|
: 'the_payment_receive_transaction_has_been_created',
|
||||||
}),
|
}),
|
||||||
intent: Intent.SUCCESS,
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
resetForm();
|
resetForm();
|
||||||
|
history.push('/payment-receives');
|
||||||
};
|
};
|
||||||
// Handle request response errors.
|
// Handle request response errors.
|
||||||
const onError = (errors) => {
|
const onError = (errors) => {
|
||||||
@@ -321,7 +325,13 @@ function PaymentReceiveForm({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (paymentReceiveNumberChanged) {
|
if (paymentReceiveNumberChanged) {
|
||||||
setFieldValue('payment_receive_no', paymentReceiveNumber);
|
setFieldValue('payment_receive_no', paymentReceiveNumber);
|
||||||
changePageSubtitle(`No. ${paymentReceiveNumber}`);
|
changePageSubtitle(
|
||||||
|
defaultToTransform(
|
||||||
|
paymentReceiveNumber,
|
||||||
|
`No. ${paymentReceiveNumber}`,
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
);
|
||||||
setPaymentReceiveNumberChanged(false);
|
setPaymentReceiveNumberChanged(false);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -329,11 +339,14 @@ function PaymentReceiveForm({
|
|||||||
paymentReceiveNumberChanged,
|
paymentReceiveNumberChanged,
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
|
setPaymentReceiveNumberChanged,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handlePaymentReceiveNumberChanged = useCallback(
|
const handlePaymentReceiveNumberChanged = useCallback(
|
||||||
(payment_receive_no) => {
|
(payment_receive_no) => {
|
||||||
changePageSubtitle(`No.${payment_receive_no}`);
|
changePageSubtitle(
|
||||||
|
defaultToTransform(payment_receive_no, `No.${payment_receive_no}`, ''),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[changePageSubtitle],
|
[changePageSubtitle],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const Schema = Yup.object().shape({
|
|||||||
.label(formatMessage({ id: 'deposit_account_' })),
|
.label(formatMessage({ id: 'deposit_account_' })),
|
||||||
full_amount: Yup.number().nullable(),
|
full_amount: Yup.number().nullable(),
|
||||||
payment_receive_no: Yup.string()
|
payment_receive_no: Yup.string()
|
||||||
|
.nullable()
|
||||||
.max(DATATYPES_LENGTH.STRING)
|
.max(DATATYPES_LENGTH.STRING)
|
||||||
.label(formatMessage({ id: 'payment_receive_no_' })),
|
.label(formatMessage({ id: 'payment_receive_no_' })),
|
||||||
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
|
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ function PaymentReceiveFormHeader({
|
|||||||
selectedContactId={values.customer_id}
|
selectedContactId={values.customer_id}
|
||||||
defaultSelectText={<T id={'select_customer_account'} />}
|
defaultSelectText={<T id={'select_customer_account'} />}
|
||||||
onContactSelected={onChangeSelect('customer_id')}
|
onContactSelected={onChangeSelect('customer_id')}
|
||||||
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default function PaymentReceivesEmptyStatus() {
|
|||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
large={true}
|
large={true}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
history.push('/payment-receive/new');
|
history.push('/payment-receives/new');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
New payment receive
|
New payment receive
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ import { AppToaster } from 'components';
|
|||||||
import Dragzone from 'components/Dragzone';
|
import Dragzone from 'components/Dragzone';
|
||||||
import useMedia from 'hooks/useMedia';
|
import useMedia from 'hooks/useMedia';
|
||||||
|
|
||||||
import { compose, repeatValue, orderingLinesIndexes } from 'utils';
|
import {
|
||||||
|
compose,
|
||||||
|
repeatValue,
|
||||||
|
orderingLinesIndexes,
|
||||||
|
defaultToTransform,
|
||||||
|
} from 'utils';
|
||||||
|
|
||||||
const MIN_LINES_NUMBER = 4;
|
const MIN_LINES_NUMBER = 4;
|
||||||
|
|
||||||
@@ -97,14 +102,20 @@ function ReceiptForm({
|
|||||||
: receiptNextNumber;
|
: receiptNextNumber;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const transactionNumber = !isNewMode
|
||||||
|
? receipt.receipt_number
|
||||||
|
: receiptNumber;
|
||||||
|
|
||||||
if (receipt && receipt.id) {
|
if (receipt && receipt.id) {
|
||||||
changePageTitle(formatMessage({ id: 'edit_receipt' }));
|
changePageTitle(formatMessage({ id: 'edit_receipt' }));
|
||||||
changePageSubtitle(`No. ${receipt.receipt_number}`);
|
|
||||||
} else {
|
} else {
|
||||||
changePageSubtitle(`No. ${receiptNumber}`);
|
|
||||||
changePageTitle(formatMessage({ id: 'new_receipt' }));
|
changePageTitle(formatMessage({ id: 'new_receipt' }));
|
||||||
}
|
}
|
||||||
|
changePageSubtitle(
|
||||||
|
defaultToTransform(transactionNumber, `No. ${transactionNumber}`, ''),
|
||||||
|
);
|
||||||
}, [
|
}, [
|
||||||
|
isNewMode,
|
||||||
changePageTitle,
|
changePageTitle,
|
||||||
changePageSubtitle,
|
changePageSubtitle,
|
||||||
receipt,
|
receipt,
|
||||||
@@ -213,7 +224,9 @@ function ReceiptForm({
|
|||||||
|
|
||||||
const handleReceiptNumberChanged = useCallback(
|
const handleReceiptNumberChanged = useCallback(
|
||||||
(receiptNumber) => {
|
(receiptNumber) => {
|
||||||
changePageSubtitle(`No. ${receiptNumber}`);
|
changePageSubtitle(
|
||||||
|
defaultToTransform(receiptNumber, `No. ${receiptNumber}`, ''),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[changePageSubtitle],
|
[changePageSubtitle],
|
||||||
);
|
);
|
||||||
@@ -244,7 +257,7 @@ function ReceiptForm({
|
|||||||
onReceiptNumberChanged={handleReceiptNumberChanged}
|
onReceiptNumberChanged={handleReceiptNumberChanged}
|
||||||
/>
|
/>
|
||||||
<ReceiptNumberWatcher receiptNumber={receiptNumber} />
|
<ReceiptNumberWatcher receiptNumber={receiptNumber} />
|
||||||
<EditableItemsEntriesTable />
|
<EditableItemsEntriesTable filterSellableItems={true} />
|
||||||
<ReceiptFormFooter />
|
<ReceiptFormFooter />
|
||||||
<ReceiptFormFloatingActions
|
<ReceiptFormFloatingActions
|
||||||
receiptId={receiptId}
|
receiptId={receiptId}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ function ReceiptFormHeader({
|
|||||||
onContactSelected={(contact) => {
|
onContactSelected={(contact) => {
|
||||||
form.setFieldValue('customer_id', contact.id);
|
form.setFieldValue('customer_id', contact.id);
|
||||||
}}
|
}}
|
||||||
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
@@ -103,6 +104,7 @@ function ReceiptFormHeader({
|
|||||||
defaultSelectText={<T id={'select_deposit_account'} />}
|
defaultSelectText={<T id={'select_deposit_account'} />}
|
||||||
selectedAccountId={value}
|
selectedAccountId={value}
|
||||||
filterByTypes={['current_asset']}
|
filterByTypes={['current_asset']}
|
||||||
|
popoverFill={true}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ export default {
|
|||||||
count: 'Count',
|
count: 'Count',
|
||||||
item_type: 'Item Type',
|
item_type: 'Item Type',
|
||||||
item_name: 'Item Name',
|
item_name: 'Item Name',
|
||||||
sku: 'SKU',
|
|
||||||
category: 'Category',
|
category: 'Category',
|
||||||
account: 'Account',
|
account: 'Account',
|
||||||
sales_information: 'Sales Information',
|
sales_information: 'Sales Information',
|
||||||
@@ -689,12 +688,12 @@ export default {
|
|||||||
payment_receive_no_: 'Payment receive no',
|
payment_receive_no_: 'Payment receive no',
|
||||||
receive_amount: 'Receive Amount',
|
receive_amount: 'Receive Amount',
|
||||||
receive_amount_: 'Receive amount',
|
receive_amount_: 'Receive amount',
|
||||||
the_payment_has_been_received_successfully_created:
|
the_payment_receive_transaction_has_been_created:
|
||||||
'The payment has been received successfully created.',
|
'The payment receive transaction has been created successfully.',
|
||||||
the_payment_receive_has_been_successfully_deleted:
|
the_payment_receive_has_been_successfully_deleted:
|
||||||
'The payment receive has been successfully deleted.',
|
'The payment receive has been successfully deleted.',
|
||||||
the_payment_has_been_received_successfully_edited:
|
the_payment_receive_transaction_has_been_edited:
|
||||||
'The payment has been received successfully edited.',
|
'The payment receive transaction has been edited successfully.',
|
||||||
once_delete_this_payment_receive_you_will_able_to_restore_it: `Once you delete this payment receive, you won\'t be able to restore it later. Are you sure you want to delete this payment receive?`,
|
once_delete_this_payment_receive_you_will_able_to_restore_it: `Once you delete this payment receive, you won\'t be able to restore it later. Are you sure you want to delete this payment receive?`,
|
||||||
select_invoice: 'Select Invoice',
|
select_invoice: 'Select Invoice',
|
||||||
payment_mades: 'Payment Mades',
|
payment_mades: 'Payment Mades',
|
||||||
@@ -841,4 +840,6 @@ export default {
|
|||||||
once_delete_these_vendors_you_will_not_able_restore_them:
|
once_delete_these_vendors_you_will_not_able_restore_them:
|
||||||
"Once you delete these vendors, you won't be able to retrieve them later. Are you sure you want to delete them?",
|
"Once you delete these vendors, you won't be able to retrieve them later. Are you sure you want to delete them?",
|
||||||
vendor_has_bills: 'Vendor has bills',
|
vendor_has_bills: 'Vendor has bills',
|
||||||
|
the_item_has_been_edited_successfully: 'The item has been edited successfully.',
|
||||||
|
you_cannot_make_payment_with_zero_total_amount: 'You cannot record payment transaction with zero total amount',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -89,13 +89,17 @@ $sidebar-submenu-item-bg-color: #01287d;
|
|||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
color: rgba(255, 255, 255, 0.25);
|
color: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
&-label{
|
&-labeler{
|
||||||
display: block;
|
display: block;
|
||||||
color: $sidebar-menu-label-color;
|
color: $sidebar-menu-label-color;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
padding: 6px 16px;
|
padding: 6px 16px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover .bp3-button.menu-item__add-btn{
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}-submenu {
|
.#{$ns}-submenu {
|
||||||
@@ -188,4 +192,42 @@ $sidebar-submenu-item-bg-color: #01287d;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bp3-button.menu-item__add-btn{
|
||||||
|
width: auto;
|
||||||
|
|
||||||
|
padding: 2px;
|
||||||
|
margin-right: 0px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: none;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
|
&:not([class*="bp3-intent-"]):not(.bp3-minimal):not(:disabled){
|
||||||
|
|
||||||
|
.bp3-icon{
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
&,
|
||||||
|
&:hover{
|
||||||
|
min-height: auto;
|
||||||
|
min-width: auto;
|
||||||
|
outline: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
background-color: rgba(255, 255, 255, 0.12);
|
||||||
|
|
||||||
|
.bp3-icon{
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.bp3-icon{
|
||||||
|
margin: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { check, param, query, ValidationChain } from 'express-validator';
|
import { check, param, query, body, ValidationChain } from 'express-validator';
|
||||||
import BaseController from "api/controllers/BaseController";
|
import BaseController from "api/controllers/BaseController";
|
||||||
|
|
||||||
export default class ContactsController extends BaseController {
|
export default class ContactsController extends BaseController {
|
||||||
@@ -48,7 +48,8 @@ export default class ContactsController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
get contactNewDTOSchema(): ValidationChain[] {
|
get contactNewDTOSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('balance').optional().isNumeric().toInt(),
|
check('opening_balance').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
|
body('opening_balance_at').if(body('opening_balance').exists()).exists(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -90,9 +90,6 @@ export default class CustomersController extends ContactsController {
|
|||||||
*/
|
*/
|
||||||
get createCustomerDTOSchema() {
|
get createCustomerDTOSchema() {
|
||||||
return [
|
return [
|
||||||
check('opening_balance').optional({ nullable: true }).isNumeric().toInt(),
|
|
||||||
check('opening_balance_at').optional({ nullable: true }).isISO8601(),
|
|
||||||
|
|
||||||
check('currency_code').optional().trim().escape(),
|
check('currency_code').optional().trim().escape(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Request, Response, Router, NextFunction } from 'express';
|
import { Request, Response, Router, NextFunction } from 'express';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import { check, query, ValidationChain } from 'express-validator';
|
import { body, query, ValidationChain, check } from 'express-validator';
|
||||||
|
|
||||||
import ContactsController from 'api/controllers/Contacts/Contacts';
|
import ContactsController from 'api/controllers/Contacts/Contacts';
|
||||||
import VendorsService from 'services/Contacts/VendorsService';
|
import VendorsService from 'services/Contacts/VendorsService';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
@@ -72,7 +73,7 @@ export default class VendorsController extends ContactsController {
|
|||||||
*/
|
*/
|
||||||
get vendorDTOSchema(): ValidationChain[] {
|
get vendorDTOSchema(): ValidationChain[] {
|
||||||
return [
|
return [
|
||||||
check('opening_balance').optional().isNumeric().toInt(),
|
check('currency_code').optional().trim().escape(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +106,11 @@ export default class VendorsController extends ContactsController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const vendor = await this.vendorsService.newVendor(tenantId, contactDTO);
|
const vendor = await this.vendorsService.newVendor(tenantId, contactDTO);
|
||||||
return res.status(200).send({ id: vendor.id });
|
|
||||||
|
return res.status(200).send({
|
||||||
|
id: vendor.id,
|
||||||
|
message: 'The vendor has been created successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -124,7 +129,11 @@ export default class VendorsController extends ContactsController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.vendorsService.editVendor(tenantId, contactId, contactDTO);
|
await this.vendorsService.editVendor(tenantId, contactId, contactDTO);
|
||||||
return res.status(200).send({ id: contactId });
|
|
||||||
|
return res.status(200).send({
|
||||||
|
id: contactId,
|
||||||
|
message: 'The vendor has been edited successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -142,7 +151,11 @@ export default class VendorsController extends ContactsController {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.vendorsService.deleteVendor(tenantId, contactId)
|
await this.vendorsService.deleteVendor(tenantId, contactId)
|
||||||
return res.status(200).send({ id: contactId });
|
|
||||||
|
return res.status(200).send({
|
||||||
|
id: contactId,
|
||||||
|
message: 'The vendor has been deleted successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -198,7 +211,11 @@ export default class VendorsController extends ContactsController {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { vendors, pagination, filterMeta } = await this.vendorsService.getVendorsList(tenantId, vendorsFilter);
|
const {
|
||||||
|
vendors,
|
||||||
|
pagination,
|
||||||
|
filterMeta,
|
||||||
|
} = await this.vendorsService.getVendorsList(tenantId, vendorsFilter);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
vendors,
|
vendors,
|
||||||
@@ -219,21 +236,27 @@ export default class VendorsController extends ContactsController {
|
|||||||
*/
|
*/
|
||||||
handlerServiceErrors(error, req: Request, res: Response, next: NextFunction) {
|
handlerServiceErrors(error, req: Request, res: Response, next: NextFunction) {
|
||||||
if (error instanceof ServiceError) {
|
if (error instanceof ServiceError) {
|
||||||
|
if (error.errorType === 'contact_not_found') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'VENDOR.NOT.FOUND', code: 100 }],
|
||||||
|
});
|
||||||
|
}
|
||||||
if (error.errorType === 'contacts_not_found') {
|
if (error.errorType === 'contacts_not_found') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
errors: [{ type: 'VENDORS.NOT.FOUND', code: 100 }],
|
errors: [{ type: 'VENDORS.NOT.FOUND', code: 200 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (error.errorType === 'some_vendors_have_bills') {
|
if (error.errorType === 'some_vendors_have_bills') {
|
||||||
return res.boom.badRequest(null, {
|
return res.boom.badRequest(null, {
|
||||||
errors: [{ type: 'SOME.VENDORS.HAVE.BILLS', code: 200 }],
|
errors: [{ type: 'SOME.VENDORS.HAVE.BILLS', code: 300 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (error.errorType === 'vendor_has_bills') {
|
if (error.errorType === 'vendor_has_bills') {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
errors: [{ type: 'VENDOR.HAS.BILLS', code: 200 }],
|
errors: [{ type: 'VENDOR.HAS.BILLS', code: 400 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
check('payment_date').exists(),
|
check('payment_date').exists(),
|
||||||
check('reference_no').optional(),
|
check('reference_no').optional(),
|
||||||
check('deposit_account_id').exists().isNumeric().toInt(),
|
check('deposit_account_id').exists().isNumeric().toInt(),
|
||||||
check('payment_receive_no').exists().trim().escape(),
|
check('payment_receive_no').optional({ nullable: true }).trim().escape(),
|
||||||
check('statement').optional().trim().escape(),
|
check('statement').optional().trim().escape(),
|
||||||
|
|
||||||
check('entries').isArray({ min: 1 }),
|
check('entries').isArray({ min: 1 }),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { omit, difference } from 'lodash';
|
import { omit, difference, defaultTo } from 'lodash';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -51,8 +51,7 @@ export default class CustomersService {
|
|||||||
return {
|
return {
|
||||||
...omit(customerDTO, ['customerType']),
|
...omit(customerDTO, ['customerType']),
|
||||||
contactType: customerDTO.customerType,
|
contactType: customerDTO.customerType,
|
||||||
active: (typeof customerDTO.active === 'undefined') ?
|
active: defaultTo(customerDTO.active, true),
|
||||||
true : customerDTO.active,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { difference, rest } from 'lodash';
|
import { difference, defaultTo } from 'lodash';
|
||||||
import {
|
import {
|
||||||
EventDispatcher,
|
EventDispatcher,
|
||||||
EventDispatcherInterface,
|
EventDispatcherInterface,
|
||||||
@@ -45,8 +45,7 @@ export default class VendorsService {
|
|||||||
private vendorToContactDTO(vendorDTO: IVendorNewDTO|IVendorEditDTO) {
|
private vendorToContactDTO(vendorDTO: IVendorNewDTO|IVendorEditDTO) {
|
||||||
return {
|
return {
|
||||||
...vendorDTO,
|
...vendorDTO,
|
||||||
active: (typeof vendorDTO.active === 'undefined') ?
|
active: defaultTo(vendorDTO.active, true),
|
||||||
true : vendorDTO.active,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +61,7 @@ export default class VendorsService {
|
|||||||
const contactDTO = this.vendorToContactDTO(vendorDTO);
|
const contactDTO = this.vendorToContactDTO(vendorDTO);
|
||||||
const vendor = await this.contactService.newContact(tenantId, contactDTO, 'vendor');
|
const vendor = await this.contactService.newContact(tenantId, contactDTO, 'vendor');
|
||||||
|
|
||||||
|
// Triggers `onVendorCreated` event.
|
||||||
await this.eventDispatcher.dispatch(events.vendors.onCreated, {
|
await this.eventDispatcher.dispatch(events.vendors.onCreated, {
|
||||||
tenantId, vendorId: vendor.id, vendor,
|
tenantId, vendorId: vendor.id, vendor,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user