This commit is contained in:
a.bouhuolia
2020-12-16 15:38:21 +02:00
20 changed files with 330 additions and 87 deletions

View File

@@ -25,37 +25,37 @@ export default function BillFloatingActions({
onCancelClick, onCancelClick,
onClearClick, onClearClick,
billId, billId,
billPublished, isOpen,
}) { }) {
const { resetForm, submitForm } = useFormikContext(); const { resetForm, submitForm } = useFormikContext();
const handleSubmitPublishBtnClick = (event) => { const handleSubmitOpenBtnClick = (event) => {
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: true, redirect: true,
publish: true, status: true,
}); });
}; };
const handleSubmitPublishAndNewBtnClick = (event) => { const handleSubmitOpenAndNewBtnClick = (event) => {
submitForm(); submitForm();
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: false, redirect: false,
publish: true, status: true,
resetForm: true, resetForm: true,
}); });
}; };
const handleSubmitPublishContinueEditingBtnClick = (event) => { const handleSubmitOpenContinueEditingBtnClick = (event) => {
submitForm(); submitForm();
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: false, redirect: false,
publish: true, status: true,
}); });
}; };
const handleSubmitDraftBtnClick = (event) => { const handleSubmitDraftBtnClick = (event) => {
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: true, redirect: true,
publish: false, status: false,
}); });
}; };
@@ -63,7 +63,7 @@ export default function BillFloatingActions({
submitForm(); submitForm();
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: false, redirect: false,
publish: false, status: false,
resetForm: true, resetForm: true,
}); });
}; };
@@ -72,7 +72,7 @@ export default function BillFloatingActions({
submitForm(); submitForm();
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: false, redirect: false,
publish: false, status: false,
}); });
}; };
@@ -87,26 +87,26 @@ export default function BillFloatingActions({
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}> <div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
{/* ----------- Save And Publish ----------- */} {/* ----------- Save And Open ----------- */}
<If condition={!billId || !billPublished}> <If condition={!billId || !isOpen}>
<ButtonGroup> <ButtonGroup>
<Button <Button
disabled={isSubmitting} disabled={isSubmitting}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
type="submit" type="submit"
onClick={handleSubmitPublishBtnClick} onClick={handleSubmitOpenBtnClick}
text={<T id={'save_publish'} />} text={<T id={'save_open'} />}
/> />
<Popover <Popover
content={ content={
<Menu> <Menu>
<MenuItem <MenuItem
text={<T id={'publish_and_new'} />} text={<T id={'open_and_new'} />}
onClick={handleSubmitPublishAndNewBtnClick} onClick={handleSubmitOpenAndNewBtnClick}
/> />
<MenuItem <MenuItem
text={<T id={'publish_continue_editing'} />} text={<T id={'open_continue_editing'} />}
onClick={handleSubmitPublishContinueEditingBtnClick} onClick={handleSubmitOpenContinueEditingBtnClick}
/> />
</Menu> </Menu>
} }
@@ -155,13 +155,13 @@ export default function BillFloatingActions({
</ButtonGroup> </ButtonGroup>
</If> </If>
{/* ----------- Save and New ----------- */} {/* ----------- Save and New ----------- */}
<If condition={billId && billPublished}> <If condition={billId && isOpen}>
<ButtonGroup> <ButtonGroup>
<Button <Button
disabled={isSubmitting} disabled={isSubmitting}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
type="submit" type="submit"
onClick={handleSubmitPublishBtnClick} onClick={handleSubmitOpenBtnClick}
text={<T id={'save'} />} text={<T id={'save'} />}
/> />
<Popover <Popover
@@ -169,7 +169,7 @@ export default function BillFloatingActions({
<Menu> <Menu>
<MenuItem <MenuItem
text={<T id={'save_and_new'} />} text={<T id={'save_and_new'} />}
onClick={handleSubmitPublishAndNewBtnClick} onClick={handleSubmitOpenAndNewBtnClick}
/> />
</Menu> </Menu>
} }

View File

@@ -43,6 +43,7 @@ const defaultInitialValues = {
due_date: moment(new Date()).format('YYYY-MM-DD'), due_date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '', reference_no: '',
note: '', note: '',
open:'',
entries: [...repeatValue(defaultBill, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultBill, MIN_LINES_NUMBER)],
}; };
@@ -141,6 +142,7 @@ function BillForm({
const form = { const form = {
...values, ...values,
open:submitPayload.status,
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })), entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
}; };
// Handle the request success. // Handle the request success.
@@ -217,7 +219,7 @@ function BillForm({
initialValues={initialValues} initialValues={initialValues}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
> >
{({ isSubmitting }) => ( {({ isSubmitting ,values }) => (
<Form> <Form>
<BillFormHeader onBillNumberChanged={handleBillNumberChanged} /> <BillFormHeader onBillNumberChanged={handleBillNumberChanged} />
<BillFormBody defaultBill={defaultBill} /> <BillFormBody defaultBill={defaultBill} />
@@ -228,7 +230,7 @@ function BillForm({
<BillFloatingActions <BillFloatingActions
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
billId={billId} billId={billId}
billPublished={true} isOpen={values.open}
onSubmitClick={handleSubmitClick} onSubmitClick={handleSubmitClick}
onCancelClick={handleCancelClick} onCancelClick={handleCancelClick}
/> />

View File

@@ -22,6 +22,7 @@ const BillFormSchema = Yup.object().shape({
.min(1) .min(1)
.max(DATATYPES_LENGTH.TEXT) .max(DATATYPES_LENGTH.TEXT)
.label(formatMessage({ id: 'note' })), .label(formatMessage({ id: 'note' })),
open: Yup.boolean().required(),
entries: Yup.array().of( entries: Yup.array().of(
Yup.object().shape({ Yup.object().shape({
quantity: Yup.number() quantity: Yup.number()

View File

@@ -35,11 +35,13 @@ function BillList({
//#withBillActions //#withBillActions
requestFetchBillsTable, requestFetchBillsTable,
requestDeleteBill, requestDeleteBill,
requestOpenBill,
addBillsTableQueries, addBillsTableQueries,
}) { }) {
const history = useHistory(); const history = useHistory();
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [deleteBill, setDeleteBill] = useState(false); const [deleteBill, setDeleteBill] = useState(false);
const [openBill, setOpenBill] = useState(false);
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
useEffect(() => { useEffect(() => {
@@ -86,12 +88,36 @@ function BillList({
}); });
}, [deleteBill, requestDeleteBill, formatMessage]); }, [deleteBill, requestDeleteBill, formatMessage]);
// Handle cancel/confirm bill open.
const handleOpenBill = useCallback((bill) => {
setOpenBill(bill);
}, []);
// Handle cancel open bill alert.
const handleCancelOpenBill = useCallback(() => {
setOpenBill(false);
}, []);
// Handle confirm bill open.
const handleConfirmBillOpen = useCallback(() => {
requestOpenBill(openBill.id)
.then(() => {
setOpenBill(false);
AppToaster.show({
message: formatMessage({
id: 'the_bill_has_been_successfully_opened',
}),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('bills-table');
})
.catch((error) => {});
}, [openBill, requestOpenBill, formatMessage]);
const handleEditBill = useCallback((bill) => { const handleEditBill = useCallback((bill) => {
history.push(`/bills/${bill.id}/edit`); history.push(`/bills/${bill.id}/edit`);
}); });
// Handle selected rows change. // Handle selected rows change.
const handleSelectedRowsChange = useCallback( const handleSelectedRowsChange = useCallback(
(_invoices) => { (_invoices) => {
@@ -127,6 +153,7 @@ function BillList({
<BillsDataTable <BillsDataTable
onDeleteBill={handleDeleteBill} onDeleteBill={handleDeleteBill}
onEditBill={handleEditBill} onEditBill={handleEditBill}
onOpenBill={handleOpenBill}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
/> />
</Route> </Route>
@@ -144,6 +171,18 @@ function BillList({
<T id={'once_delete_this_bill_you_will_able_to_restore_it'} /> <T id={'once_delete_this_bill_you_will_able_to_restore_it'} />
</p> </p>
</Alert> </Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'open'} />}
intent={Intent.WARNING}
isOpen={openBill}
onCancel={handleCancelOpenBill}
onConfirm={handleConfirmBillOpen}
>
<p>
<T id={'are_sure_to_open_this_bill'} />
</p>
</Alert>
</DashboardPageContent> </DashboardPageContent>
</DashboardInsider> </DashboardInsider>
); );

View File

@@ -7,6 +7,7 @@ import {
MenuItem, MenuItem,
MenuDivider, MenuDivider,
Position, Position,
Tag,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
@@ -20,7 +21,7 @@ import { compose, saveInvoke } from 'utils';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks'; import { useIsValuePassed } from 'hooks';
import { LoadingIndicator, Choose } from 'components'; import { LoadingIndicator, Choose, If } from 'components';
import DataTable from 'components/DataTable'; import DataTable from 'components/DataTable';
import BillsEmptyStatus from './BillsEmptyStatus'; import BillsEmptyStatus from './BillsEmptyStatus';
@@ -56,6 +57,7 @@ function BillsDataTable({
onFetchData, onFetchData,
onEditBill, onEditBill,
onDeleteBill, onDeleteBill,
onOpenBill,
onSelectedRowsChange, onSelectedRowsChange,
}) { }) {
const { custom_view_id: customViewId } = useParams(); const { custom_view_id: customViewId } = useParams();
@@ -121,6 +123,13 @@ function BillsDataTable({
text={formatMessage({ id: 'edit_bill' })} text={formatMessage({ id: 'edit_bill' })}
onClick={handleEditBill(bill)} onClick={handleEditBill(bill)}
/> />
<If condition={!bill.is_open}>
<MenuItem
text={formatMessage({ id: 'mark_as_opened' })}
onClick={() => onOpenBill(bill)}
/>
</If>
<MenuItem <MenuItem
text={formatMessage({ id: 'delete_bill' })} text={formatMessage({ id: 'delete_bill' })}
intent={Intent.DANGER} intent={Intent.DANGER}
@@ -186,7 +195,21 @@ function BillsDataTable({
{ {
id: 'status', id: 'status',
Header: formatMessage({ id: 'status' }), Header: formatMessage({ id: 'status' }),
accessor: 'status', accessor: (row) => (
<Choose>
<Choose.When condition={row.is_open}>
<Tag minimal={true}>
<T id={'opened'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true} intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
),
width: 140, width: 140,
className: 'status', className: 'status',
}, },

View File

@@ -6,6 +6,7 @@ import {
fetchBillsTable, fetchBillsTable,
fetchBill, fetchBill,
fetchDueBills, fetchDueBills,
openBill,
} from 'store/Bills/bills.actions'; } from 'store/Bills/bills.actions';
import t from 'store/types'; import t from 'store/types';
@@ -17,7 +18,7 @@ const mapDispatchToProps = (dispatch) => ({
requestFetchBillsTable: (query = {}) => requestFetchBillsTable: (query = {}) =>
dispatch(fetchBillsTable({ query: { ...query } })), dispatch(fetchBillsTable({ query: { ...query } })),
requestFetchDueBills: (vendorId) => dispatch(fetchDueBills({ vendorId })), requestFetchDueBills: (vendorId) => dispatch(fetchDueBills({ vendorId })),
requestOpenBill: (id) => dispatch(openBill({ id })),
changeBillView: (id) => changeBillView: (id) =>
dispatch({ dispatch({
type: t.BILLS_SET_CURRENT_VIEW, type: t.BILLS_SET_CURRENT_VIEW,

View File

@@ -232,11 +232,13 @@ function InvoiceForm({
); );
return ( return (
<div className={classNames( <div
CLASSES.PAGE_FORM, className={classNames(
CLASSES.PAGE_FORM_STRIP_STYLE, CLASSES.PAGE_FORM,
CLASSES.PAGE_FORM_INVOICE CLASSES.PAGE_FORM_STRIP_STYLE,
)}> CLASSES.PAGE_FORM_INVOICE,
)}
>
<Formik <Formik
validationSchema={ validationSchema={
isNewMode ? CreateInvoiceFormSchema : EditInvoiceFormSchema isNewMode ? CreateInvoiceFormSchema : EditInvoiceFormSchema
@@ -244,7 +246,7 @@ function InvoiceForm({
initialValues={initialValues} initialValues={initialValues}
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
{({ isSubmitting }) => ( {({ isSubmitting, values }) => (
<Form> <Form>
<InvoiceFormHeader <InvoiceFormHeader
onInvoiceNumberChanged={handleInvoiceNumberChanged} onInvoiceNumberChanged={handleInvoiceNumberChanged}

View File

@@ -38,11 +38,13 @@ function InvoiceList({
//#withInvoiceActions //#withInvoiceActions
requestFetchInvoiceTable, requestFetchInvoiceTable,
requestDeleteInvoice, requestDeleteInvoice,
requestDeliverInvoice,
addInvoiceTableQueries, addInvoiceTableQueries,
}) { }) {
const history = useHistory(); const history = useHistory();
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [deleteInvoice, setDeleteInvoice] = useState(false); const [deleteInvoice, setDeleteInvoice] = useState(false);
const [deliverInvoice, setDeliverInvoice] = useState(false);
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
useEffect(() => { useEffect(() => {
@@ -88,6 +90,34 @@ function InvoiceList({
}); });
}, [deleteInvoice, requestDeleteInvoice, formatMessage]); }, [deleteInvoice, requestDeleteInvoice, formatMessage]);
// Handle cancel/confirm invoice deliver.
const handleDeliverInvoice = useCallback((invoice) => {
setDeliverInvoice(invoice);
}, []);
// Handle cancel deliver invoice alert.
const handleCancelDeliverInvoice = useCallback(() => {
setDeliverInvoice(false);
}, []);
// Handle confirm invoiec deliver.
const handleConfirmInvoiceDeliver = useCallback(() => {
requestDeliverInvoice(deliverInvoice.id)
.then(() => {
setDeliverInvoice(false);
AppToaster.show({
message: formatMessage({
id: 'the_invoice_has_been_successfully_delivered',
}),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('invoices-table');
})
.catch((error) => {
// setDeliverInvoice(false);
});
}, [deliverInvoice, requestDeliverInvoice, formatMessage]);
const handleEditInvoice = useCallback((invoice) => { const handleEditInvoice = useCallback((invoice) => {
history.push(`/invoices/${invoice.id}/edit`); history.push(`/invoices/${invoice.id}/edit`);
}); });
@@ -127,6 +157,7 @@ function InvoiceList({
<InvoicesDataTable <InvoicesDataTable
onDeleteInvoice={handleDeleteInvoice} onDeleteInvoice={handleDeleteInvoice}
onEditInvoice={handleEditInvoice} onEditInvoice={handleEditInvoice}
onDeliverInvoice={handleDeliverInvoice}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
/> />
</Route> </Route>
@@ -145,6 +176,18 @@ function InvoiceList({
<T id={'once_delete_this_invoice_you_will_able_to_restore_it'} /> <T id={'once_delete_this_invoice_you_will_able_to_restore_it'} />
</p> </p>
</Alert> </Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'deliver'} />}
intent={Intent.WARNING}
isOpen={deliverInvoice}
onCancel={handleCancelDeliverInvoice}
onConfirm={handleConfirmInvoiceDeliver}
>
<p>
<T id={'are_sure_to_deliver_this_invoice'} />
</p>
</Alert>
</DashboardPageContent> </DashboardPageContent>
</DashboardInsider> </DashboardInsider>
); );

View File

@@ -50,6 +50,7 @@ function InvoicesDataTable({
// #OwnProps // #OwnProps
onEditInvoice, onEditInvoice,
onDeleteInvoice, onDeleteInvoice,
onDeliverInvoice,
onSelectedRowsChange, onSelectedRowsChange,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -82,6 +83,12 @@ function InvoicesDataTable({
text={formatMessage({ id: 'edit_invoice' })} text={formatMessage({ id: 'edit_invoice' })}
onClick={handleEditInvoice(invoice)} onClick={handleEditInvoice(invoice)}
/> />
<If condition={!invoice.is_delivered}>
<MenuItem
text={formatMessage({ id: 'mark_as_delivered' })}
onClick={() => onDeliverInvoice(invoice)}
/>
</If>
<MenuItem <MenuItem
text={formatMessage({ id: 'delete_invoice' })} text={formatMessage({ id: 'delete_invoice' })}
intent={Intent.DANGER} intent={Intent.DANGER}

View File

@@ -6,6 +6,7 @@ import {
fetchInvoice, fetchInvoice,
fetchInvoicesTable, fetchInvoicesTable,
fetchDueInvoices, fetchDueInvoices,
deliverInvoice,
} from 'store/Invoice/invoices.actions'; } from 'store/Invoice/invoices.actions';
import t from 'store/types'; import t from 'store/types';
@@ -18,6 +19,7 @@ const mapDipatchToProps = (dispatch) => ({
requestDeleteInvoice: (id) => dispatch(deleteInvoice({ id })), requestDeleteInvoice: (id) => dispatch(deleteInvoice({ id })),
requestFetchDueInvoices: (customerId) => requestFetchDueInvoices: (customerId) =>
dispatch(fetchDueInvoices({ customerId })), dispatch(fetchDueInvoices({ customerId })),
requestDeliverInvoice: (id) => dispatch(deliverInvoice({ id })),
changeInvoiceView: (id) => changeInvoiceView: (id) =>
dispatch({ dispatch({
type: t.INVOICES_SET_CURRENT_VIEW, type: t.INVOICES_SET_CURRENT_VIEW,

View File

@@ -58,6 +58,7 @@ const defaultInitialValues = {
reference_no: '', reference_no: '',
receipt_message: '', receipt_message: '',
statement: '', statement: '',
closed:'',
entries: [...repeatValue(defaultReceipt, MIN_LINES_NUMBER)], entries: [...repeatValue(defaultReceipt, MIN_LINES_NUMBER)],
}; };
@@ -182,6 +183,7 @@ function ReceiptForm({
} }
const form = { const form = {
...values, ...values,
closed: submitPayload.status,
entries: entries.map((entry) => ({ entries: entries.map((entry) => ({
// Exclude all properties that out of request entries schema. // Exclude all properties that out of request entries schema.
...pick(entry, Object.keys(defaultReceipt)), ...pick(entry, Object.keys(defaultReceipt)),
@@ -259,7 +261,7 @@ function ReceiptForm({
initialValues={initialValues} initialValues={initialValues}
onSubmit={handleFormSubmit} onSubmit={handleFormSubmit}
> >
{({ isSubmitting }) => ( {({ isSubmitting ,values }) => (
<Form> <Form>
<ReceiptFromHeader <ReceiptFromHeader
onReceiptNumberChanged={handleReceiptNumberChanged} onReceiptNumberChanged={handleReceiptNumberChanged}
@@ -270,7 +272,7 @@ function ReceiptForm({
<ReceiptFormFloatingActions <ReceiptFormFloatingActions
isSubmitting={isSubmitting} isSubmitting={isSubmitting}
receiptId={receiptId} receiptId={receiptId}
receiptPublished={true} isClosed={values.closed}
onSubmitClick={handleSubmitClick} onSubmitClick={handleSubmitClick}
onCancelClick={handleCancelClick} onCancelClick={handleCancelClick}
/> />

View File

@@ -28,6 +28,7 @@ const Schema = Yup.object().shape({
.min(1) .min(1)
.max(DATATYPES_LENGTH.TEXT) .max(DATATYPES_LENGTH.TEXT)
.label(formatMessage({ id: 'note' })), .label(formatMessage({ id: 'note' })),
closed: Yup.boolean().required(),
entries: Yup.array().of( entries: Yup.array().of(
Yup.object().shape({ Yup.object().shape({
quantity: Yup.number() quantity: Yup.number()

View File

@@ -22,41 +22,41 @@ import { If, Icon } from 'components';
export default function ReceiptFormFloatingActions({ export default function ReceiptFormFloatingActions({
isSubmitting, isSubmitting,
receiptId, receiptId,
receiptPublished, isClosed,
onSubmitClick, onSubmitClick,
onCancelClick, onCancelClick,
onClearClick, onClearClick,
}) { }) {
const { resetForm, submitForm } = useFormikContext(); const { resetForm, submitForm } = useFormikContext();
const handleSubmitPublishBtnClick = (event) => { const handleSubmitCloseBtnClick = (event) => {
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: true, redirect: true,
publish: true, status: true,
}); });
}; };
const handleSubmitPublishAndNewBtnClick = (event) => { const handleSubmitCloseAndNewBtnClick = (event) => {
submitForm(); submitForm();
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: false, redirect: false,
publish: true, status: true,
resetForm: true, resetForm: true,
}); });
}; };
const handleSubmitPublishContinueEditingBtnClick = (event) => { const handleSubmitCloseContinueEditingBtnClick = (event) => {
submitForm(); submitForm();
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: false, redirect: false,
publish: true, status: true,
}); });
}; };
const handleSubmitDraftBtnClick = (event) => { const handleSubmitDraftBtnClick = (event) => {
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: true, redirect: true,
publish: false, status: false,
}); });
}; };
@@ -64,7 +64,7 @@ export default function ReceiptFormFloatingActions({
submitForm(); submitForm();
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: false, redirect: false,
publish: false, status: false,
resetForm: true, resetForm: true,
}); });
}; };
@@ -73,7 +73,7 @@ export default function ReceiptFormFloatingActions({
submitForm(); submitForm();
saveInvoke(onSubmitClick, event, { saveInvoke(onSubmitClick, event, {
redirect: false, redirect: false,
publish: false, status: false,
}); });
}; };
@@ -88,26 +88,26 @@ export default function ReceiptFormFloatingActions({
return ( return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}> <div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
{/* ----------- Save And Publish ----------- */} {/* ----------- Save And Close ----------- */}
<If condition={!receiptId || !receiptPublished}> <If condition={!receiptId || !isClosed}>
<ButtonGroup> <ButtonGroup>
<Button <Button
disabled={isSubmitting} disabled={isSubmitting}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
type="submit" type="submit"
onClick={handleSubmitPublishBtnClick} onClick={handleSubmitCloseBtnClick}
text={<T id={'save_publish'} />} text={<T id={'save_close'} />}
/> />
<Popover <Popover
content={ content={
<Menu> <Menu>
<MenuItem <MenuItem
text={<T id={'publish_and_new'} />} text={<T id={'close_and_new'} />}
onClick={handleSubmitPublishAndNewBtnClick} onClick={handleSubmitCloseAndNewBtnClick}
/> />
<MenuItem <MenuItem
text={<T id={'publish_continue_editing'} />} text={<T id={'close_continue_editing'} />}
onClick={handleSubmitPublishContinueEditingBtnClick} onClick={handleSubmitCloseContinueEditingBtnClick}
/> />
</Menu> </Menu>
} }
@@ -156,13 +156,13 @@ export default function ReceiptFormFloatingActions({
</ButtonGroup> </ButtonGroup>
</If> </If>
{/* ----------- Save and New ----------- */} {/* ----------- Save and New ----------- */}
<If condition={receiptId && receiptPublished}> <If condition={receiptId && isClosed}>
<ButtonGroup> <ButtonGroup>
<Button <Button
disabled={isSubmitting} disabled={isSubmitting}
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
type="submit" type="submit"
onClick={handleSubmitPublishBtnClick} onClick={handleSubmitCloseBtnClick}
text={<T id={'save'} />} text={<T id={'save'} />}
/> />
<Popover <Popover
@@ -170,7 +170,7 @@ export default function ReceiptFormFloatingActions({
<Menu> <Menu>
<MenuItem <MenuItem
text={<T id={'save_and_new'} />} text={<T id={'save_and_new'} />}
onClick={handleSubmitPublishAndNewBtnClick} onClick={handleSubmitCloseAndNewBtnClick}
/> />
</Menu> </Menu>
} }

View File

@@ -34,11 +34,13 @@ function ReceiptList({
//#withReceiptActions //#withReceiptActions
requestFetchReceiptsTable, requestFetchReceiptsTable,
requestDeleteReceipt, requestDeleteReceipt,
requestCloseReceipt,
addReceiptsTableQueries, addReceiptsTableQueries,
}) { }) {
const history = useHistory(); const history = useHistory();
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const [deleteReceipt, setDeleteReceipt] = useState(false); const [deleteReceipt, setDeleteReceipt] = useState(false);
const [closeReceipt, setCloseReceipt] = useState(false);
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const fetchReceipts = useQuery(['receipts-table', receiptTableQuery], () => const fetchReceipts = useQuery(['receipts-table', receiptTableQuery], () =>
@@ -85,6 +87,34 @@ function ReceiptList({
}); });
}, [deleteReceipt, requestDeleteReceipt, formatMessage]); }, [deleteReceipt, requestDeleteReceipt, formatMessage]);
// Handle cancel/confirm receipt deliver.
const handleCloseReceipt = useCallback((receipt) => {
setCloseReceipt(receipt);
}, []);
// Handle cancel close receipt alert.
const handleCancelCloseReceipt = useCallback(() => {
setCloseReceipt(false);
}, []);
// Handle confirm receipt close.
const handleConfirmReceiptClose = useCallback(() => {
requestCloseReceipt(closeReceipt.id)
.then(() => {
setCloseReceipt(false);
AppToaster.show({
message: formatMessage({
id: 'the_receipt_has_been_successfully_closed',
}),
intent: Intent.SUCCESS,
});
queryCache.invalidateQueries('receipts-table');
})
.catch((error) => {
setCloseReceipt(false);
});
}, [closeReceipt, requestCloseReceipt, formatMessage]);
// Handle filter change to re-fetch data-table. // Handle filter change to re-fetch data-table.
// const handleFilterChanged = useCallback( // const handleFilterChanged = useCallback(
// (filterConditions) => { // (filterConditions) => {
@@ -136,6 +166,7 @@ function ReceiptList({
<ReceiptsDataTable <ReceiptsDataTable
onDeleteReceipt={handleDeleteReceipt} onDeleteReceipt={handleDeleteReceipt}
onEditReceipt={handleEditReceipt} onEditReceipt={handleEditReceipt}
onCloseReceipt={handleCloseReceipt}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
/> />
</Route> </Route>
@@ -154,6 +185,18 @@ function ReceiptList({
<T id={'once_delete_this_receipt_you_will_able_to_restore_it'} /> <T id={'once_delete_this_receipt_you_will_able_to_restore_it'} />
</p> </p>
</Alert> </Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'close'} />}
intent={Intent.WARNING}
isOpen={closeReceipt}
onCancel={handleCancelCloseReceipt}
onConfirm={handleConfirmReceiptClose}
>
<p>
<T id={'are_sure_to_close_this_receipt'} />
</p>
</Alert>
</DashboardPageContent> </DashboardPageContent>
</DashboardInsider> </DashboardInsider>
); );

View File

@@ -7,6 +7,7 @@ import {
MenuItem, MenuItem,
MenuDivider, MenuDivider,
Position, Position,
Tag,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { withRouter } from 'react-router'; import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
@@ -17,7 +18,14 @@ import { compose, saveInvoke } from 'utils';
import { useIsValuePassed } from 'hooks'; import { useIsValuePassed } from 'hooks';
import { CLASSES } from 'common/classes'; import { CLASSES } from 'common/classes';
import { Choose, LoadingIndicator, DataTable, Money, Icon } from 'components'; import {
Choose,
LoadingIndicator,
DataTable,
Money,
Icon,
If,
} from 'components';
import ReceiptsEmptyStatus from './ReceiptsEmptyStatus'; import ReceiptsEmptyStatus from './ReceiptsEmptyStatus';
@@ -42,6 +50,7 @@ function ReceiptsDataTable({
loading, loading,
onEditReceipt, onEditReceipt,
onDeleteReceipt, onDeleteReceipt,
onCloseReceipt,
onSelectedRowsChange, onSelectedRowsChange,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
@@ -62,7 +71,7 @@ function ReceiptsDataTable({
); );
const actionMenuList = useCallback( const actionMenuList = useCallback(
(estimate) => ( (receipt) => (
<Menu> <Menu>
<MenuItem <MenuItem
icon={<Icon icon="reader-18" />} icon={<Icon icon="reader-18" />}
@@ -72,12 +81,18 @@ function ReceiptsDataTable({
<MenuItem <MenuItem
icon={<Icon icon="pen-18" />} icon={<Icon icon="pen-18" />}
text={formatMessage({ id: 'edit_receipt' })} text={formatMessage({ id: 'edit_receipt' })}
onClick={handleEditReceipt(estimate)} onClick={handleEditReceipt(receipt)}
/> />
<If condition={!receipt.is_closed}>
<MenuItem
text={formatMessage({ id: 'mark_as_closed' })}
onClick={() => onCloseReceipt(receipt)}
/>
</If>
<MenuItem <MenuItem
text={formatMessage({ id: 'delete_receipt' })} text={formatMessage({ id: 'delete_receipt' })}
intent={Intent.DANGER} intent={Intent.DANGER}
onClick={handleDeleteReceipt(estimate)} onClick={handleDeleteReceipt(receipt)}
icon={<Icon icon="trash-16" iconSize={16} />} icon={<Icon icon="trash-16" iconSize={16} />}
/> />
</Menu> </Menu>
@@ -111,7 +126,8 @@ function ReceiptsDataTable({
{ {
id: 'receipt_number', id: 'receipt_number',
Header: formatMessage({ id: 'receipt_number' }), Header: formatMessage({ id: 'receipt_number' }),
accessor: (row) => (row.receipt_number ? `#${row.receipt_number}` : null), accessor: (row) =>
row.receipt_number ? `#${row.receipt_number}` : null,
width: 140, width: 140,
className: 'receipt_number', className: 'receipt_number',
}, },
@@ -137,6 +153,28 @@ function ReceiptsDataTable({
width: 140, width: 140,
className: 'amount', className: 'amount',
}, },
{
id: 'status',
Header: formatMessage({ id: 'status' }),
accessor: (row) => (
<Choose>
<Choose.When condition={row.is_closed}>
<Tag minimal={true}>
<T id={'closed'} />
</Tag>
</Choose.When>
<Choose.Otherwise>
<Tag minimal={true} intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
</Choose.Otherwise>
</Choose>
),
width: 140,
className: 'amount',
},
{ {
id: 'reference_no', id: 'reference_no',
Header: formatMessage({ id: 'reference_no' }), Header: formatMessage({ id: 'reference_no' }),

View File

@@ -5,16 +5,18 @@ import {
fetchReceipt, fetchReceipt,
fetchReceiptsTable, fetchReceiptsTable,
editReceipt, editReceipt,
closeReceipt,
} from 'store/receipt/receipt.actions'; } from 'store/receipt/receipt.actions';
import t from 'store/types'; import t from 'store/types';
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
requestSubmitReceipt: (form) => dispatch(submitReceipt({ form })), requestSubmitReceipt: (form) => dispatch(submitReceipt({ form })),
requestFetchReceipt: (id) => dispatch(fetchReceipt({ id })), requestFetchReceipt: (id) => dispatch(fetchReceipt({ id })),
requestEditReceipt: (id, form) => dispatch(editReceipt( id, form )), requestEditReceipt: (id, form) => dispatch(editReceipt(id, form)),
requestDeleteReceipt: (id) => dispatch(deleteReceipt({ id })), requestDeleteReceipt: (id) => dispatch(deleteReceipt({ id })),
requestFetchReceiptsTable: (query = {}) => requestFetchReceiptsTable: (query = {}) =>
dispatch(fetchReceiptsTable({ query: { ...query } })), dispatch(fetchReceiptsTable({ query: { ...query } })),
requestCloseReceipt: (id) => dispatch(closeReceipt({ id })),
// requestDeleteBulkReceipt: (ids) => dispatch(deleteBulkReceipt({ ids })), // requestDeleteBulkReceipt: (ids) => dispatch(deleteBulkReceipt({ ids })),
changeReceiptView: (id) => changeReceiptView: (id) =>
@@ -28,10 +30,11 @@ const mapDispatchToProps = (dispatch) => ({
type: t.RECEIPTS_TABLE_QUERIES_ADD, type: t.RECEIPTS_TABLE_QUERIES_ADD,
payload: { queries }, payload: { queries },
}), }),
setReceiptNumberChanged:(isChanged) => dispatch({ setReceiptNumberChanged: (isChanged) =>
dispatch({
type: t.RECEIPT_NUMBER_CHANGED, type: t.RECEIPT_NUMBER_CHANGED,
payload: { isChanged }, payload: { isChanged },
}), }),
}); });
export default connect(null, mapDispatchToProps); export default connect(null, mapDispatchToProps);

View File

@@ -866,7 +866,7 @@ export default {
receivable_accounts_should_assign_with_customers: receivable_accounts_should_assign_with_customers:
'receivable accounts should assign with customers', 'receivable accounts should assign with customers',
delivered: 'Delivered', delivered: 'Delivered',
save_and_deliver: 'Save and Deliver', save_and_deliver: 'Save & Deliver',
deliver_and_new: 'Deliver and new', deliver_and_new: 'Deliver and new',
deliver_continue_editing: 'Deliver (continue editing)', deliver_continue_editing: 'Deliver (continue editing)',
due_in: 'Due in', due_in: 'Due in',
@@ -878,4 +878,28 @@ export default {
paid: 'Paid', paid: 'Paid',
your_account_has_been_locked: your_account_has_been_locked:
'Your account has been locked due to repeated failed login attempts. Please wait a few minutes before trying again.', 'Your account has been locked due to repeated failed login attempts. Please wait a few minutes before trying again.',
the_invoice_has_been_successfully_delivered:
'The invoice has been successfully delivered.',
are_sure_to_deliver_this_invoice:
'Are you sure you want to deliver this invoice?',
mark_as_delivered: 'Mark as delivered',
deliver: 'Deliver',
mark_as_closed: 'Mark as closed',
mark_as_opened: 'Mark as opened',
save_close: 'Save & Close',
save_open: 'Save & Open',
close_and_new: 'Close and new',
close_continue_editing: 'Close (continue editing)',
the_receipt_has_been_successfully_closed:
'The receipt has been successfully closed.',
are_sure_to_close_this_receipt:
'Are you sure you want to close this receipt?',
closed: 'Closed',
open_and_new: 'Open and new',
open_continue_editing: 'Open (continue editing)',
the_bill_has_been_successfully_opened:
'The bill has been successfully opened.',
open: 'Open',
are_sure_to_open_this_bill: 'Are you sure you want to open this bill?',
opened: 'Opened',
}; };

View File

@@ -138,4 +138,8 @@ export const fetchDueBills = ({ vendorId }) => (dispatch) => new Promise((resolv
} }
resolve(response); resolve(response);
}).catch(error => { reject(error) }); }).catch(error => { reject(error) });
}); });
export const openBill = ({ id }) => {
return (dispatch) => ApiService.post(`purchases/bills/${id}/open`);
};

View File

@@ -121,31 +121,35 @@ export const fetchInvoice = ({ id }) => {
}); });
}; };
export const fetchDueInvoices = ({ customerId }) => (dispatch) => new Promise((resovle, reject) => { export const fetchDueInvoices = ({ customerId }) => (dispatch) =>
new Promise((resovle, reject) => {
ApiService.get(`sales/invoices/payable`, { ApiService.get(`sales/invoices/payable`, {
params: { customer_id: customerId }, params: { customer_id: customerId },
}) })
.then((response) => { .then((response) => {
dispatch({
type: t.INVOICES_ITEMS_SET,
payload: {
sales_invoices: response.data.sales_invoices,
},
});
if (customerId) {
dispatch({ dispatch({
type: t.INVOICES_RECEIVABLE_BY_CUSTOMER_ID, type: t.INVOICES_ITEMS_SET,
payload: { payload: {
customerId, sales_invoices: response.data.sales_invoices,
saleInvoices: response.data.sales_invoices,
}, },
}); });
} if (customerId) {
resovle(response); dispatch({
}) type: t.INVOICES_RECEIVABLE_BY_CUSTOMER_ID,
.catch((error) => { payload: {
const { response } = error; customerId,
const { data } = response; saleInvoices: response.data.sales_invoices,
reject(data?.errors); },
}); });
}
resovle(response);
})
.catch((error) => {
const { response } = error;
const { data } = response;
reject(data?.errors);
});
}); });
export const deliverInvoice = ({ id }) => {
return (dispatch) => ApiService.post(`sales/invoices/${id}/deliver`);
};

View File

@@ -116,3 +116,7 @@ export const fetchReceiptsTable = ({ query = {} }) => {
}); });
}); });
}; };
export const closeReceipt = ({ id }) => {
return (dispatch) => ApiService.post(`sales/receipts/${id}/close`);
};