From 1cb826163b03ab5fe7bad5cee1fa830495a73465 Mon Sep 17 00:00:00 2001 From: elforjani3 Date: Tue, 4 Aug 2020 17:51:17 +0200 Subject: [PATCH] WIP / Feature Fix _bills --- .../PaymentReceiveListFieldCell.js | 35 ++ .../src/components/PaymentReceiveListField.js | 38 +++ client/src/config/sidebarMenu.js | 16 +- client/src/containers/Purchases/BillForm.js | 299 ++++++++++++++++++ .../containers/Purchases/BillFormFooter.js | 41 +++ .../containers/Purchases/BillFormHeader.js | 185 +++++++++++ client/src/containers/Purchases/Bills.js | 62 ++++ .../containers/Purchases/withBillActions.js | 32 ++ .../PaymentReceive/PaymentReceiveForm.js | 260 +++++++++++++++ .../PaymentReceiveFormFooter.js | 46 +++ .../PaymentReceiveFormHeader.js | 204 ++++++++++++ .../PaymentReceiveItemsTable.js | 13 + .../Sales/PaymentReceive/PaymentReceives.js | 69 ++++ .../withPaymentReceivesActions.js | 34 ++ client/src/lang/en/index.js | 29 ++ client/src/routes/dashboard.js | 38 +++ client/src/store/Bills/bills.actions.js | 136 ++++++++ client/src/store/Bills/bills.reducer.js | 0 client/src/store/Bills/bills.selectors.js | 6 + client/src/store/Bills/bills.type.js | 12 + .../PaymentReceive/paymentReceive.actions.js | 136 ++++++++ .../PaymentReceive/paymentReceive.reducer.js | 0 .../PaymentReceive/paymentReceive.selector.js | 0 .../PaymentReceive/paymentReceive.type.js | 11 + client/src/store/types.js | 6 +- 25 files changed, 1703 insertions(+), 5 deletions(-) create mode 100644 client/src/components/DataTableCells/PaymentReceiveListFieldCell.js create mode 100644 client/src/components/PaymentReceiveListField.js create mode 100644 client/src/containers/Purchases/BillForm.js create mode 100644 client/src/containers/Purchases/BillFormFooter.js create mode 100644 client/src/containers/Purchases/BillFormHeader.js create mode 100644 client/src/containers/Purchases/Bills.js create mode 100644 client/src/containers/Purchases/withBillActions.js create mode 100644 client/src/containers/Sales/PaymentReceive/PaymentReceiveForm.js create mode 100644 client/src/containers/Sales/PaymentReceive/PaymentReceiveFormFooter.js create mode 100644 client/src/containers/Sales/PaymentReceive/PaymentReceiveFormHeader.js create mode 100644 client/src/containers/Sales/PaymentReceive/PaymentReceiveItemsTable.js create mode 100644 client/src/containers/Sales/PaymentReceive/PaymentReceives.js create mode 100644 client/src/containers/Sales/PaymentReceive/withPaymentReceivesActions.js create mode 100644 client/src/store/Bills/bills.actions.js create mode 100644 client/src/store/Bills/bills.reducer.js create mode 100644 client/src/store/Bills/bills.selectors.js create mode 100644 client/src/store/Bills/bills.type.js create mode 100644 client/src/store/PaymentReceive/paymentReceive.actions.js create mode 100644 client/src/store/PaymentReceive/paymentReceive.reducer.js create mode 100644 client/src/store/PaymentReceive/paymentReceive.selector.js create mode 100644 client/src/store/PaymentReceive/paymentReceive.type.js diff --git a/client/src/components/DataTableCells/PaymentReceiveListFieldCell.js b/client/src/components/DataTableCells/PaymentReceiveListFieldCell.js new file mode 100644 index 000000000..0b37e05f7 --- /dev/null +++ b/client/src/components/DataTableCells/PaymentReceiveListFieldCell.js @@ -0,0 +1,35 @@ +import React, { useCallback } from 'react'; +import PaymentReceiveListField from 'components/PaymentReceiveListField'; +import classNames from 'classnames'; +import { FormGroup, Classes, Intent } from '@blueprintjs/core'; + +function PaymentReceiveListFieldCell({ + column: { id }, + row: { index }, + cell: { value: initialValue }, + payload: { invoices, updateData, errors }, +}) { + const handleInvoicesSelected = useCallback( + (_item) => { + updateData(index, id, _item.id); + }, + [updateData, index, id], + ); + + const error = errors?.[index]?.[id]; + + return ( + + + + ); +} + +export default PaymentReceiveListFieldCell; diff --git a/client/src/components/PaymentReceiveListField.js b/client/src/components/PaymentReceiveListField.js new file mode 100644 index 000000000..4c9f828ba --- /dev/null +++ b/client/src/components/PaymentReceiveListField.js @@ -0,0 +1,38 @@ +import React, { useCallback } from 'react'; +import { MenuItem } from '@blueprintjs/core'; +import ListSelect from 'components/ListSelect'; +import { FormattedMessage as T } from 'react-intl'; + +function PaymentReceiveListField({ + invoices, + selectedInvoiceId, + onInvoiceSelected, + defaultSelectText = , +}) { + const onInvoiceSelect = useCallback((_invoice) => { + onInvoiceSelected && onInvoiceSelected(_invoice); + }); + + const handleInvoiceRenderer = useCallback( + (item, { handleClick }) => ( + + ), + [], + ); + + return ( + } + itemRenderer={handleInvoiceRenderer} + popoverProps={{ minimal: true }} + onItemSelect={onInvoiceSelect} + selectedItem={`${selectedInvoiceId}`} + selectedItemProp={'id'} + labelProp={'name'} + defaultText={defaultSelectText} + /> + ); +} + +export default PaymentReceiveListField; diff --git a/client/src/config/sidebarMenu.js b/client/src/config/sidebarMenu.js index 7f6e7bf23..9e72634cf 100644 --- a/client/src/config/sidebarMenu.js +++ b/client/src/config/sidebarMenu.js @@ -46,6 +46,13 @@ export default [ text: , href: '/invoices/new', }, + { + text: , + href: '/payment-receive/new', + }, + { + divider: true, + }, { text: , href: '/receipts/new', @@ -56,10 +63,11 @@ export default [ text: , children: [ { - icon: 'cut', - text: 'cut', - label: '⌘C', - disabled: false, + text: , + href: '/bill/new', + }, + { + text: , }, ], }, diff --git a/client/src/containers/Purchases/BillForm.js b/client/src/containers/Purchases/BillForm.js new file mode 100644 index 000000000..fce4b0ae4 --- /dev/null +++ b/client/src/containers/Purchases/BillForm.js @@ -0,0 +1,299 @@ +import React, { + useMemo, + useState, + useCallback, + useEffect, + useRef, +} from 'react'; +import * as Yup from 'yup'; +import { useFormik } from 'formik'; +import moment from 'moment'; +import { Intent, FormGroup, TextArea } from '@blueprintjs/core'; + +import { FormattedMessage as T, useIntl } from 'react-intl'; +import { pick, omit } from 'lodash'; + +import BillFormHeader from './BillFormHeader'; +import EstimatesItemsTable from 'containers/Sales/Estimate/EntriesItemsTable'; +import BillFormFooter from './BillFormFooter'; + +import withDashboardActions from 'containers/Dashboard/withDashboardActions'; +import withMediaActions from 'containers/Media/withMediaActions'; +import withBillActions from './withBillActions'; + +import { AppToaster } from 'components'; +import Dragzone from 'components/Dragzone'; +import useMedia from 'hooks/useMedia'; + +import { compose, repeatValue } from 'utils'; + +const MIN_LINES_NUMBER = 4; + +function BillForm({ + //#WithMedia + requestSubmitMedia, + requestDeleteMedia, + + //#withBillActions + requestSubmitBill, + + //#withDashboard + changePageTitle, + changePageSubtitle, + + //#withBillDetail + bill, + + //#Own Props + onFormSubmit, + onCancelForm, +}) { + const { formatMessage } = useIntl(); + const [payload, setPayload] = useState({}); + + const { + setFiles, + saveMedia, + deletedFiles, + setDeletedFiles, + deleteMedia, + } = useMedia({ + saveCallback: requestSubmitMedia, + deleteCallback: requestDeleteMedia, + }); + + const handleDropFiles = useCallback((_files) => { + setFiles(_files.filter((file) => file.uploaded === false)); + }, []); + + const savedMediaIds = useRef([]); + const clearSavedMediaIds = () => { + savedMediaIds.current = []; + }; + + useEffect(() => { + if (bill && bill.id) { + changePageTitle(formatMessage({ id: 'edit_bill' })); + } else { + changePageTitle(formatMessage({ id: 'new_bill' })); + } + }); + + const validationSchema = Yup.object().shape({ + vendor_id: Yup.number() + .required() + .label(formatMessage({ id: 'vendor_name_' })), + bill_date: Yup.date() + .required() + .label(formatMessage({ id: 'bill_date_' })), + due_date: Yup.date() + .required() + .label(formatMessage({ id: 'due_date_' })), + bill_number: Yup.number() + .required() + .label(formatMessage({ id: 'bill_number_' })), + reference_no: Yup.string().min(1).max(255), + status: Yup.string().required(), + note: Yup.string() + .trim() + .min(1) + .max(1024) + .label(formatMessage({ id: 'note' })), + + entries: Yup.array().of( + Yup.object().shape({ + quantity: Yup.number().nullable(), + rate: Yup.number().nullable(), + item_id: Yup.number() + .nullable() + .when(['quantity', 'rate'], { + is: (quantity, rate) => quantity || rate, + then: Yup.number().required(), + }), + total: Yup.number().nullable(), + discount: Yup.number().nullable(), + description: Yup.string().nullable(), + }), + ), + }); + + const saveBillSubmit = useCallback( + (payload) => { + onFormSubmit && onFormSubmit(payload); + }, + [onFormSubmit], + ); + + const defaultBill = useMemo(() => ({ + index: 0, + item_id: null, + rate: null, + discount: null, + quantity: null, + description: '', + status: '', + })); + + const defaultInitialValues = useMemo( + () => ({ + accept: '', + vendor_name: '', + bill_number: '', + bill_date: moment(new Date()).format('YYYY-MM-DD'), + due_date: moment(new Date()).format('YYYY-MM-DD'), + reference_no: '', + note: '', + entries: [...repeatValue(defaultBill, MIN_LINES_NUMBER)], + }), + [defaultBill], + ); + + const orderingIndex = (_invoice) => { + return _invoice.map((item, index) => ({ + ...item, + index: index + 1, + })); + }; + + const initialValues = useMemo( + () => ({ + ...defaultInitialValues, + entries: orderingIndex(defaultInitialValues.entries), + }), + [defaultInitialValues], + ); + + const initialAttachmentFiles = useMemo(() => { + return bill && bill.media + ? bill.media.map((attach) => ({ + preview: attach.attachment_file, + uploaded: true, + metadata: { ...attach }, + })) + : []; + }, [bill]); + + const formik = useFormik({ + enableReinitialize: true, + validationSchema, + initialValues: { + ...initialValues, + }, + onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => { + setSubmitting(true); + const entries = values.entries.map((item) => omit(item, ['total'])); + + const form = { + ...values, + entries, + }; + const saveBill = (mediaIds) => + new Promise((resolve, reject) => { + const requestForm = { ...form, media_ids: mediaIds }; + + requestSubmitBill(requestForm) + .then((response) => { + AppToaster.show({ + message: formatMessage( + { id: 'the_bill_has_been_successfully_created' }, + { number: values.bill_number }, + ), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + resetForm(); + saveBillSubmit({ action: 'new', ...payload }); + clearSavedMediaIds(); + }) + .catch((errors) => { + setSubmitting(false); + }); + }); + + Promise.all([saveMedia(), deleteMedia()]) + .then(([savedMediaResponses]) => { + const mediaIds = savedMediaResponses.map((res) => res.data.media.id); + savedMediaIds.current = mediaIds; + return savedMediaResponses; + }) + .then(() => { + return saveBill(savedMediaIds.current); + }); + }, + }); + + const handleSubmitClick = useCallback( + (payload) => { + setPayload(payload); + formik.submitForm(); + }, + [setPayload, formik], + ); + + const handleCancelClick = useCallback( + (payload) => { + onCancelForm && onCancelForm(payload); + }, + [onCancelForm], + ); + + console.log(formik.errors, 'Bill'); + const handleDeleteFile = useCallback( + (_deletedFiles) => { + _deletedFiles.forEach((deletedFile) => { + if (deletedFile.uploaded && deletedFile.metadata.id) { + setDeletedFiles([...deletedFiles, deletedFile.metadata.id]); + } + }); + }, + [setDeletedFiles, deletedFiles], + ); + + const onClickCleanAllLines = () => { + formik.setFieldValue( + 'entries', + orderingIndex([...repeatValue(defaultBill, MIN_LINES_NUMBER)]), + ); + }; + + const onClickAddNewRow = () => { + formik.setFieldValue( + 'entries', + orderingIndex([...formik.values.entries, defaultBill]), + ); + }; + + return ( +
+
+ + + } className={'form-group--'}> +