diff --git a/packages/server/src/data/options.ts b/packages/server/src/data/options.ts index 023628ef8..ae35fa5da 100644 --- a/packages/server/src/data/options.ts +++ b/packages/server/src/data/options.ts @@ -59,6 +59,12 @@ export default { auto_increment: { type: 'boolean', }, + customer_notes: { + type: 'string', + }, + terms_conditions: { + type: 'string', + }, }, sales_receipts: { next_number: { @@ -73,6 +79,12 @@ export default { preferred_deposit_account: { type: 'number', }, + receipt_message: { + type: 'string', + }, + terms_conditions: { + type: 'string', + }, }, sales_invoices: { next_number: { @@ -84,6 +96,12 @@ export default { auto_increment: { type: 'boolean', }, + customer_notes: { + type: 'string', + }, + terms_conditions: { + type: 'string', + }, }, payment_receives: { next_number: { @@ -147,6 +165,12 @@ export default { auto_increment: { type: 'boolean', }, + customer_notes: { + type: 'string', + }, + terms_conditions: { + type: 'string', + }, }, vendor_credit: { next_number: { diff --git a/packages/webapp/src/constants/preferencesMenu.tsx b/packages/webapp/src/constants/preferencesMenu.tsx index f90c7ed04..0925532ff 100644 --- a/packages/webapp/src/constants/preferencesMenu.tsx +++ b/packages/webapp/src/constants/preferencesMenu.tsx @@ -12,6 +12,22 @@ export default [ text: , href: '/preferences/users', }, + { + text: , + href: '/preferences/estimates', + }, + { + text: , + href: '/preferences/invoices', + }, + { + text: , + href: '/preferences/receipts', + }, + { + text: , + href: '/preferences/credit-notes', + }, { text: , href: '/preferences/currencies', diff --git a/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotes.tsx b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotes.tsx new file mode 100644 index 000000000..4d2909f0e --- /dev/null +++ b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotes.tsx @@ -0,0 +1,14 @@ +// @ts-nocheck +import { PreferencesCreditNotesBoot } from './PreferencesCreditNotesFormBoot'; +import { PreferencesCreditNotesFormPage } from './PreferencesCreditNotesFormPage'; + +/** + * Credit notes preferences. + */ +export function PreferencesCreditNotes() { + return ( + + + + ); +} diff --git a/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesForm.schema.ts b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesForm.schema.ts new file mode 100644 index 000000000..0e3901591 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesForm.schema.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import * as Yup from 'yup'; + +const Schema = Yup.object().shape({ + termsConditions: Yup.string().optional(), + customerNotes: Yup.string().optional(), +}); + +export const PreferencesCreditNotesFormSchema = Schema; diff --git a/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesForm.tsx b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesForm.tsx new file mode 100644 index 000000000..649ffa557 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesForm.tsx @@ -0,0 +1,74 @@ +// @ts-nocheck +import styled from 'styled-components'; +import { Form } from 'formik'; +import { Button, Intent } from '@blueprintjs/core'; +import { useHistory } from 'react-router-dom'; + +import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components'; + +/** + * Preferences credit notes form. + */ +export function PreferencesCreditNotesForm({ isSubmitting }) { + const history = useHistory(); + + // Handle close click. + const handleCloseClick = () => { + history.go(-1); + }; + + return ( +
+ {/* ---------- Customer Notes ---------- */} + } + fastField={true} + > + + + + {/* ---------- Terms & Conditions ---------- */} + } + fastField={true} + > + + + + + + + +
+ ); +} + +const CardFooterActions = styled.div` + padding-top: 16px; + border-top: 1px solid #e0e7ea; + margin-top: 30px; + + .bp4-button { + min-width: 70px; + + + .bp4-button { + margin-left: 10px; + } + } +`; diff --git a/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesFormBoot.tsx b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesFormBoot.tsx new file mode 100644 index 000000000..0eb34f3d4 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesFormBoot.tsx @@ -0,0 +1,55 @@ +// @ts-nocheck +import React from 'react'; +import styled from 'styled-components'; +import classNames from 'classnames'; +import { CLASSES } from '@/constants/classes'; +import { useSettings } from '@/hooks/query'; +import PreferencesPageLoader from '../PreferencesPageLoader'; +import { Card } from '@/components'; + +const PreferencesCreditNotesFormContext = React.createContext(); + +function PreferencesCreditNotesBoot({ ...props }) { + // Fetches organization settings. + const { isLoading: isSettingsLoading } = useSettings(); + + // Provider state. + const provider = { + isSettingsLoading, + }; + // Detarmines whether if any query is loading. + const isLoading = isSettingsLoading; + + return ( +
+ + {isLoading ? ( + + ) : ( + + )} + +
+ ); +} + +const PreferencesCreditNotesCard = styled(Card)` + padding: 25px; + + .bp4-form-group { + max-width: 600px; + } +`; + +const usePreferencesCreditNotesFormContext = () => + React.useContext(PreferencesCreditNotesFormContext); + +export { PreferencesCreditNotesBoot, usePreferencesCreditNotesFormContext }; diff --git a/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesFormPage.tsx b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesFormPage.tsx new file mode 100644 index 000000000..4fc3956c8 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/CreditNotes/PreferencesCreditNotesFormPage.tsx @@ -0,0 +1,82 @@ +// @ts-nocheck +import { useEffect } from 'react'; +import intl from 'react-intl-universal'; +import { Formik } from 'formik'; +import * as R from 'ramda'; +import { Intent } from '@blueprintjs/core'; + +import { AppToaster } from '@/components'; +import { PreferencesCreditNotesFormSchema } from './PreferencesCreditNotesForm.schema'; +import { PreferencesCreditNotesForm } from './PreferencesCreditNotesForm'; +import withDashboardActions from '@/containers/Dashboard/withDashboardActions'; + +import { compose, transformToForm, transfromToSnakeCase } from '@/utils'; +import withSettings from '@/containers/Settings/withSettings'; +import { transferObjectOptionsToArray } from '../Accountant/utils'; +import { useSaveSettings } from '@/hooks/query'; + +const defaultValues = { + termsConditions: '', + customerNotes: '', +}; + +/** + * Preferences - Credit Notes. + */ +function PreferencesCreditNotesFormPageRoot({ + // #withDashboardActions + changePreferencesPageTitle, + + // #withSettings + creditNoteSettings, +}) { + // Save settings. + const { mutateAsync: saveSettingMutate } = useSaveSettings(); + + useEffect(() => { + changePreferencesPageTitle(intl.get('preferences.creditNotes')); + }, [changePreferencesPageTitle]); + + // Initial values. + const initialValues = { + ...defaultValues, + ...transformToForm(creditNoteSettings, defaultValues), + }; + // Handle the form submit. + const handleFormSubmit = (values, { setSubmitting }) => { + const options = R.compose( + transferObjectOptionsToArray, + transfromToSnakeCase, + )({ creditNote: { ...values } }); + + // Handle request success. + const onSuccess = () => { + AppToaster.show({ + message: intl.get('preferences.credit_notes.success_message'), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + }; + // Handle request error. + const onError = () => { + setSubmitting(false); + }; + saveSettingMutate({ options }).then(onSuccess).catch(onError); + }; + + return ( + + ); +} + +export const PreferencesCreditNotesFormPage = compose( + withDashboardActions, + withSettings(({ creditNoteSettings }) => ({ + creditNoteSettings: creditNoteSettings, + })), +)(PreferencesCreditNotesFormPageRoot); diff --git a/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimates.tsx b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimates.tsx new file mode 100644 index 000000000..d7a8b484d --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimates.tsx @@ -0,0 +1,14 @@ +// @ts-nocheck +import { PreferencesEstimatesBoot } from './PreferencesEstimatesFormBoot'; +import { PreferencesEstimatesFormPage } from './PreferencesEstimatesFormPage'; + +/** + * Estimates preferences. + */ +export function PreferencesEstimates() { + return ( + + + + ); +} diff --git a/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesForm.schema.ts b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesForm.schema.ts new file mode 100644 index 000000000..b6cf3eab6 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesForm.schema.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import * as Yup from 'yup'; + +const Schema = Yup.object().shape({ + termsConditions: Yup.string().optional(), + customerNotes: Yup.string().optional(), +}); + +export const PreferencesEstimatesFormSchema = Schema; diff --git a/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesForm.tsx b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesForm.tsx new file mode 100644 index 000000000..7e17acc10 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesForm.tsx @@ -0,0 +1,74 @@ +// @ts-nocheck +import styled from 'styled-components'; +import { Form } from 'formik'; +import { Button, Intent } from '@blueprintjs/core'; +import { useHistory } from 'react-router-dom'; + +import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components'; + +/** + * Preferences estimates form. + */ +export function PreferencesEstimatesForm({ isSubmitting }) { + const history = useHistory(); + + // Handle close click. + const handleCloseClick = () => { + history.go(-1); + }; + + return ( +
+ {/* ---------- Customer Notes ---------- */} + } + fastField={true} + > + + + + {/* ---------- Terms & Conditions ---------- */} + } + fastField={true} + > + + + + + + + +
+ ); +} + +const CardFooterActions = styled.div` + padding-top: 16px; + border-top: 1px solid #e0e7ea; + margin-top: 30px; + + .bp4-button { + min-width: 70px; + + + .bp4-button { + margin-left: 10px; + } + } +`; diff --git a/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesFormBoot.tsx b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesFormBoot.tsx new file mode 100644 index 000000000..d39d3c817 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesFormBoot.tsx @@ -0,0 +1,55 @@ +// @ts-nocheck +import React from 'react'; +import classNames from 'classnames'; +import { CLASSES } from '@/constants/classes'; +import { useSettings } from '@/hooks/query'; +import PreferencesPageLoader from '../PreferencesPageLoader'; +import styled from 'styled-components'; +import { Card } from '@/components'; + +const PreferencesEstimatesFormContext = React.createContext(); + +function PreferencesEstimatesBoot({ ...props }) { + // Fetches organization settings. + const { isLoading: isSettingsLoading } = useSettings(); + + // Provider state. + const provider = { + isSettingsLoading, + }; + // Detarmines whether if any query is loading. + const isLoading = isSettingsLoading; + + return ( +
+ + {isLoading ? ( + + ) : ( + + )} + +
+ ); +} + +const usePreferencesEstimatesFormContext = () => + React.useContext(PreferencesEstimatesFormContext); + +const PreferencesEstimatesCard = styled(Card)` + padding: 25px; + + .bp4-form-group { + max-width: 600px; + } +`; + +export { PreferencesEstimatesBoot, usePreferencesEstimatesFormContext }; diff --git a/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesFormPage.tsx b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesFormPage.tsx new file mode 100644 index 000000000..39d849ed3 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Estimates/PreferencesEstimatesFormPage.tsx @@ -0,0 +1,82 @@ +// @ts-nocheck +import React, { useEffect } from 'react'; +import intl from 'react-intl-universal'; +import { Formik } from 'formik'; +import { Intent } from '@blueprintjs/core'; +import * as R from 'ramda'; + +import { AppToaster } from '@/components'; +import { PreferencesEstimatesFormSchema } from './PreferencesEstimatesForm.schema'; +import { PreferencesEstimatesForm } from './PreferencesEstimatesForm'; +import withDashboardActions from '@/containers/Dashboard/withDashboardActions'; +import withSettings from '@/containers/Settings/withSettings'; + +import { transferObjectOptionsToArray } from '../Accountant/utils'; +import { compose, transformToForm, transfromToSnakeCase } from '@/utils'; +import { useSaveSettings } from '@/hooks/query'; + +const defaultValues = { + termsConditions: '', + customerNotes: '', +}; + +/** + * Preferences estimates form. + */ +function PreferencesEstimatesFormPageRoot({ + // #withDashboardActions + changePreferencesPageTitle, + + // #withSettings + estimatesSettings, +}) { + // Save Organization Settings. + const { mutateAsync: saveSettingMutate } = useSaveSettings(); + + useEffect(() => { + changePreferencesPageTitle(intl.get('preferences.estimates')); + }, [changePreferencesPageTitle]); + + // Initial values. + const initialValues = { + ...defaultValues, + ...transformToForm(estimatesSettings, defaultValues), + }; + // Handle the form submit. + const handleFormSubmit = (values, { setSubmitting }) => { + const options = R.compose( + transferObjectOptionsToArray, + transfromToSnakeCase, + )({ salesEstimates: { ...values } }); + + // Handle request success. + const onSuccess = (response) => { + AppToaster.show({ + message: intl.get('preferences.estimates.success_message'), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + }; + // Handle request error. + const onError = () => { + setSubmitting(false); + }; + saveSettingMutate({ options }).then(onSuccess).catch(onError); + }; + + return ( + + ); +} + +export const PreferencesEstimatesFormPage = compose( + withDashboardActions, + withSettings(({ estimatesSettings }) => ({ + estimatesSettings: estimatesSettings, + })), +)(PreferencesEstimatesFormPageRoot); diff --git a/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceForm.schema.ts b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceForm.schema.ts new file mode 100644 index 000000000..be7bead85 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceForm.schema.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import * as Yup from 'yup'; + +const Schema = Yup.object().shape({ + termsConditions: Yup.string().optional(), + customerNotes: Yup.string().optional(), +}); + +export const PreferencesInvoiceFormSchema = Schema; diff --git a/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceFormBoot.tsx b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceFormBoot.tsx new file mode 100644 index 000000000..2cd42db21 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceFormBoot.tsx @@ -0,0 +1,53 @@ +// @ts-nocheck +import React from 'react'; +import classNames from 'classnames'; +import styled from 'styled-components'; +import { CLASSES } from '@/constants/classes'; +import { useSettings } from '@/hooks/query'; +import PreferencesPageLoader from '../PreferencesPageLoader'; +import { Card } from '@/components'; + +const PreferencesInvoiceFormContext = React.createContext(); + +function PreferencesInvoicesBoot({ ...props }) { + // Fetches organization settings. + const { isLoading: isSettingsLoading } = useSettings(); + + // Provider state. + const provider = { + isSettingsLoading + }; + + // Detarmines whether if any query is loading. + const isLoading = isSettingsLoading; + + return ( +
+ + {isLoading ? ( + + ) : ( + + )} + +
+ ); +} + +const PreferencesInvoicesCard = styled(Card)` + padding: 25px; + + .bp4-form-group{ + max-width: 600px; + } +`; + +const usePreferencesInvoiceFormContext = () => + React.useContext(PreferencesInvoiceFormContext); + +export { PreferencesInvoicesBoot, usePreferencesInvoiceFormContext }; diff --git a/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceFormPage.tsx b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceFormPage.tsx new file mode 100644 index 000000000..097b8f996 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoiceFormPage.tsx @@ -0,0 +1,82 @@ +// @ts-nocheck +import React, { useEffect } from 'react'; +import intl from 'react-intl-universal'; +import { Formik } from 'formik'; +import { Intent } from '@blueprintjs/core'; +import * as R from 'ramda'; + +import { AppToaster } from '@/components'; +import { PreferencesInvoiceFormSchema } from './PreferencesInvoiceForm.schema'; +import { PreferencesInvoicesForm } from './PreferencesInvoicesForm'; +import withDashboardActions from '@/containers/Dashboard/withDashboardActions'; + +import { compose, transformToForm, transfromToSnakeCase } from '@/utils'; +import withSettings from '@/containers/Settings/withSettings'; +import { transferObjectOptionsToArray } from '../Accountant/utils'; +import { useSaveSettings } from '@/hooks/query'; + +const defaultValues = { + termsConditions: '', + customerNotes: '', +}; + +/** + * Preferences - Invoices. + */ +function PreferencesInvoiceFormPage({ + // #withDashboardActions + changePreferencesPageTitle, + + // #withSettings + invoiceSettings, +}) { + // Save settings. + const { mutateAsync: saveSettingMutate } = useSaveSettings(); + + useEffect(() => { + changePreferencesPageTitle(intl.get('preferences.invoices')); + }, [changePreferencesPageTitle]); + + // Initial values. + const initialValues = { + ...defaultValues, + ...transformToForm(invoiceSettings, defaultValues), + }; + // Handle the form submit. + const handleFormSubmit = (values, { setSubmitting }) => { + const options = R.compose( + transferObjectOptionsToArray, + transfromToSnakeCase, + )({ salesInvoices: { ...values } }); + + // Handle request success. + const onSuccess = () => { + AppToaster.show({ + message: intl.get('preferences.invoices.success_message'), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + }; + // Handle request error. + const onError = () => { + setSubmitting(false); + }; + saveSettingMutate({ options }).then(onSuccess).catch(onError); + }; + + return ( + + ); +} + +export default compose( + withDashboardActions, + withSettings(({ invoiceSettings }) => ({ + invoiceSettings: invoiceSettings, + })), +)(PreferencesInvoiceFormPage); diff --git a/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoices.tsx b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoices.tsx new file mode 100644 index 000000000..da349ea9a --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoices.tsx @@ -0,0 +1,14 @@ +// @ts-nocheck +import { PreferencesInvoicesBoot } from './PreferencesInvoiceFormBoot'; +import PreferencesInvoiceFormPage from './PreferencesInvoiceFormPage'; + +/** + * items preferences. + */ +export default function PreferencesInvoices() { + return ( + + + + ); +} diff --git a/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoicesForm.tsx b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoicesForm.tsx new file mode 100644 index 000000000..9237c58e8 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Invoices/PreferencesInvoicesForm.tsx @@ -0,0 +1,74 @@ +// @ts-nocheck +import styled from 'styled-components'; +import { Form } from 'formik'; +import { Button, Intent } from '@blueprintjs/core'; +import { useHistory } from 'react-router-dom'; + +import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components'; + +/** + * Invoices preferences form. + */ +export function PreferencesInvoicesForm({ isSubmitting }) { + const history = useHistory(); + + // Handle close click. + const handleCloseClick = () => { + history.go(-1); + }; + + return ( +
+ {/* ---------- Customer Notes ---------- */} + } + fastField={true} + > + + + + {/* ---------- Terms & Conditions ---------- */} + } + fastField={true} + > + + + + + + + +
+ ); +} + +const CardFooterActions = styled.div` + padding-top: 16px; + border-top: 1px solid #e0e7ea; + margin-top: 30px; + + .bp4-button { + min-width: 70px; + + + .bp4-button { + margin-left: 10px; + } + } +`; diff --git a/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceipts.tsx b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceipts.tsx new file mode 100644 index 000000000..83fc663a2 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceipts.tsx @@ -0,0 +1,14 @@ +// @ts-nocheck +import { PreferencesReceiptsBoot } from './PreferencesReceiptsFormBoot'; +import { PreferencesReceiptsFormPage } from './PreferencesReceiptsFormPage'; + +/** + * Preferences - Receipts. + */ +export function PreferencesReceipts() { + return ( + + + + ); +} diff --git a/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsForm.schema.ts b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsForm.schema.ts new file mode 100644 index 000000000..f28cc9407 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsForm.schema.ts @@ -0,0 +1,9 @@ +// @ts-nocheck +import * as Yup from 'yup'; + +const Schema = Yup.object().shape({ + termsConditions: Yup.string().optional(), + customerNotes: Yup.string().optional(), +}); + +export const PreferencesReceiptsFormSchema = Schema; diff --git a/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsForm.tsx b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsForm.tsx new file mode 100644 index 000000000..42836de51 --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsForm.tsx @@ -0,0 +1,74 @@ +// @ts-nocheck +import styled from 'styled-components'; +import { Form } from 'formik'; +import { Button, Intent } from '@blueprintjs/core'; +import { useHistory } from 'react-router-dom'; + +import { FormattedMessage as T, FFormGroup, FTextArea } from '@/components'; + +/** + * Preferences general form. + */ +export function PreferencesReceiptsForm({ isSubmitting }) { + const history = useHistory(); + + // Handle close click. + const handleCloseClick = () => { + history.go(-1); + }; + + return ( +
+ {/* ---------- Customer Notes ---------- */} + } + fastField={true} + > + + + + {/* ---------- Terms & Conditions ---------- */} + } + fastField={true} + > + + + + + + + +
+ ); +} + +const CardFooterActions = styled.div` + padding-top: 16px; + border-top: 1px solid #e0e7ea; + margin-top: 30px; + + .bp4-button { + min-width: 70px; + + + .bp4-button { + margin-left: 10px; + } + } +`; diff --git a/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsFormBoot.tsx b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsFormBoot.tsx new file mode 100644 index 000000000..539980a4e --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsFormBoot.tsx @@ -0,0 +1,56 @@ +// @ts-nocheck +import React from 'react'; +import classNames from 'classnames'; +import styled from 'styled-components'; +import { CLASSES } from '@/constants/classes'; +import { useSettings } from '@/hooks/query'; +import PreferencesPageLoader from '../PreferencesPageLoader'; +import { Card } from '@/components'; + +const PreferencesReceiptsFormContext = React.createContext(); + +function PreferencesReceiptsBoot({ ...props }) { + // Fetches organization settings. + const { isLoading: isSettingsLoading } = useSettings(); + + // Provider state. + const provider = { + isSettingsLoading, + }; + + // Detarmines whether if any query is loading. + const isLoading = isSettingsLoading; + + return ( +
+ + {isLoading ? ( + + ) : ( + + )} + +
+ ); +} + +const PreferencesReceiptsCard = styled(Card)` + padding: 25px; + + .bp4-form-group { + max-width: 600px; + } +`; + +const usePreferencesReceiptsFormContext = () => + React.useContext(PreferencesReceiptsFormContext); + +export { PreferencesReceiptsBoot, usePreferencesReceiptsFormContext }; diff --git a/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsFormPage.tsx b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsFormPage.tsx new file mode 100644 index 000000000..f5da0d06a --- /dev/null +++ b/packages/webapp/src/containers/Preferences/Receipts/PreferencesReceiptsFormPage.tsx @@ -0,0 +1,82 @@ +// @ts-nocheck +import React, { useEffect } from 'react'; +import intl from 'react-intl-universal'; +import { Formik } from 'formik'; +import { Intent } from '@blueprintjs/core'; +import * as R from 'ramda'; + +import { AppToaster } from '@/components'; +import { PreferencesReceiptsFormSchema } from './PreferencesReceiptsForm.schema'; +import { PreferencesReceiptsForm } from './PreferencesReceiptsForm'; +import withDashboardActions from '@/containers/Dashboard/withDashboardActions'; + +import { compose, transformToForm, transfromToSnakeCase } from '@/utils'; +import withSettings from '@/containers/Settings/withSettings'; +import { useSaveSettings } from '@/hooks/query'; +import { transferObjectOptionsToArray } from '../Accountant/utils'; + +const defaultValues = { + termsConditions: '', + receiptMessage: '', +}; + +/** + * Preferences - Receipts. + */ +function PreferencesReceiptsFormPageRoot({ + // #withDashboardActions + changePreferencesPageTitle, + + // #withSettings + receiptSettings, +}) { + // Save settings. + const { mutateAsync: saveSettingMutate } = useSaveSettings(); + + useEffect(() => { + changePreferencesPageTitle(intl.get('preferences.receipts')); + }, [changePreferencesPageTitle]); + + // Initial values. + const initialValues = { + ...defaultValues, + ...transformToForm(receiptSettings, defaultValues), + }; + // Handle the form submit. + const handleFormSubmit = (values, { setSubmitting }) => { + const options = R.compose( + transferObjectOptionsToArray, + transfromToSnakeCase, + )({ salesReceipts: { ...values } }); + + // Handle request success. + const onSuccess = () => { + AppToaster.show({ + message: intl.get('preferences.receipts.success_message'), + intent: Intent.SUCCESS, + }); + setSubmitting(false); + }; + // Handle request error. + const onError = () => { + setSubmitting(false); + }; + saveSettingMutate({ options }).then(onSuccess).catch(onError); + }; + + return ( + + ); +} + +export const PreferencesReceiptsFormPage = compose( + withDashboardActions, + withSettings(({ receiptSettings }) => ({ + receiptSettings: receiptSettings, + })), +)(PreferencesReceiptsFormPageRoot); diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.tsx index ec8467061..1d51ecbc6 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.tsx @@ -5,7 +5,7 @@ import classNames from 'classnames'; import { useHistory } from 'react-router-dom'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { isEmpty } from 'lodash'; +import { defaultTo, isEmpty } from 'lodash'; import { CLASSES } from '@/constants/classes'; import { CreateCreditNoteFormSchema, @@ -48,6 +48,8 @@ function CreditNoteForm({ creditAutoIncrement, creditNumberPrefix, creditNextNumber, + creditCustomerNotes, + creditTermsConditions, // #withCurrentOrganization organization: { base_currency }, @@ -68,22 +70,21 @@ function CreditNoteForm({ const creditNumber = transactionNumber(creditNumberPrefix, creditNextNumber); // Initial values. - const initialValues = React.useMemo( - () => ({ - ...(!isEmpty(creditNote) - ? { ...transformToEditForm(creditNote) } - : { - ...defaultCreditNote, - ...(creditAutoIncrement && { - credit_note_number: creditNumber, - }), - entries: orderingLinesIndexes(defaultCreditNote.entries), - currency_code: base_currency, - ...newCreditNote, + const initialValues = { + ...(!isEmpty(creditNote) + ? { ...transformToEditForm(creditNote) } + : { + ...defaultCreditNote, + ...(creditAutoIncrement && { + credit_note_number: creditNumber, }), - }), - [], - ); + entries: orderingLinesIndexes(defaultCreditNote.entries), + currency_code: base_currency, + terms_conditions: defaultTo(creditTermsConditions, ''), + note: defaultTo(creditCustomerNotes, ''), + ...newCreditNote, + }), + }; // Handles form submit. const handleFormSubmit = ( @@ -178,6 +179,8 @@ export default compose( creditAutoIncrement: creditNoteSettings?.autoIncrement, creditNextNumber: creditNoteSettings?.nextNumber, creditNumberPrefix: creditNoteSettings?.numberPrefix, + creditCustomerNotes: creditNoteSettings?.customerNotes, + creditTermsConditions: creditNoteSettings?.termsConditions, })), withCurrentOrganization(), )(CreditNoteForm); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx index a4db1cfff..f9cd24673 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimateForm/EstimateForm.tsx @@ -4,7 +4,7 @@ import intl from 'react-intl-universal'; import classNames from 'classnames'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { sumBy, isEmpty } from 'lodash'; +import { sumBy, isEmpty, defaultTo } from 'lodash'; import { useHistory } from 'react-router-dom'; import { CLASSES } from '@/constants/classes'; @@ -43,6 +43,8 @@ function EstimateForm({ estimateNextNumber, estimateNumberPrefix, estimateAutoIncrementMode, + estimateCustomerNotes, + estimateTermsConditions, // #withCurrentOrganization organization: { base_currency }, @@ -60,25 +62,23 @@ function EstimateForm({ estimateNumberPrefix, estimateNextNumber, ); - // Initial values in create and edit mode. - const initialValues = useMemo( - () => ({ - ...(!isEmpty(estimate) - ? { ...transformToEditForm(estimate) } - : { - ...defaultEstimate, - // If the auto-increment mode is enabled, take the next estimate - // number from the settings. - ...(estimateAutoIncrementMode && { - estimate_number: estimateNumber, - }), - entries: orderingLinesIndexes(defaultEstimate.entries), - currency_code: base_currency, + const initialValues = { + ...(!isEmpty(estimate) + ? { ...transformToEditForm(estimate) } + : { + ...defaultEstimate, + // If the auto-increment mode is enabled, take the next estimate + // number from the settings. + ...(estimateAutoIncrementMode && { + estimate_number: estimateNumber, }), - }), - [estimate, estimateNumber, estimateAutoIncrementMode, base_currency], - ); + entries: orderingLinesIndexes(defaultEstimate.entries), + currency_code: base_currency, + terms_conditions: defaultTo(estimateTermsConditions, ''), + note: defaultTo(estimateCustomerNotes, ''), + }), + }; // Handles form submit. const handleFormSubmit = ( @@ -181,6 +181,8 @@ export default compose( estimateNextNumber: estimatesSettings?.nextNumber, estimateNumberPrefix: estimatesSettings?.numberPrefix, estimateAutoIncrementMode: estimatesSettings?.autoIncrement, + estimateCustomerNotes: estimatesSettings?.customerNotes, + estimateTermsConditions: estimatesSettings?.termsConditions, })), withCurrentOrganization(), )(EstimateForm); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx index a8463619b..c3a447d11 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.tsx @@ -4,7 +4,7 @@ import intl from 'react-intl-universal'; import classNames from 'classnames'; import { Formik, Form } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { sumBy, isEmpty } from 'lodash'; +import { sumBy, isEmpty, defaultTo } from 'lodash'; import { useHistory } from 'react-router-dom'; import { CLASSES } from '@/constants/classes'; import { @@ -44,6 +44,8 @@ function InvoiceForm({ invoiceNextNumber, invoiceNumberPrefix, invoiceAutoIncrementMode, + invoiceCustomerNotes, + invoiceTermsConditions, // #withCurrentOrganization organization: { base_currency }, @@ -79,6 +81,8 @@ function InvoiceForm({ }), entries: orderingLinesIndexes(defaultInvoice.entries), currency_code: base_currency, + invoice_message: defaultTo(invoiceCustomerNotes, ''), + terms_conditions: defaultTo(invoiceTermsConditions, ''), ...newInvoice, }), }; @@ -192,6 +196,8 @@ export default compose( invoiceNextNumber: invoiceSettings?.nextNumber, invoiceNumberPrefix: invoiceSettings?.numberPrefix, invoiceAutoIncrementMode: invoiceSettings?.autoIncrement, + invoiceCustomerNotes: invoiceSettings?.customerNotes, + invoiceTermsConditions: invoiceSettings?.termsConditions, })), withCurrentOrganization(), )(InvoiceForm); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx index 75a8b9665..ca7dd26f6 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/ReceiptForm.tsx @@ -1,5 +1,4 @@ // @ts-nocheck -import React, { useMemo } from 'react'; import intl from 'react-intl-universal'; import classNames from 'classnames'; import { Formik, Form } from 'formik'; @@ -45,6 +44,8 @@ function ReceiptForm({ receiptNextNumber, receiptNumberPrefix, receiptAutoIncrement, + receiptTermsConditions, + receiptMessage, preferredDepositAccount, // #withCurrentOrganization @@ -67,23 +68,21 @@ function ReceiptForm({ receiptNextNumber, ); // Initial values in create and edit mode. - const initialValues = useMemo( - () => ({ - ...(!isEmpty(receipt) - ? { ...transformToEditForm(receipt) } - : { - ...defaultReceipt, - ...(receiptAutoIncrement && { - receipt_number: nextReceiptNumber, - }), - deposit_account_id: parseInt(preferredDepositAccount), - entries: orderingLinesIndexes(defaultReceipt.entries), - currency_code: base_currency, + const initialValues = { + ...(!isEmpty(receipt) + ? { ...transformToEditForm(receipt) } + : { + ...defaultReceipt, + ...(receiptAutoIncrement && { + receipt_number: nextReceiptNumber, }), - }), - [receipt, preferredDepositAccount, nextReceiptNumber, receiptAutoIncrement], - ); - + deposit_account_id: parseInt(preferredDepositAccount), + entries: orderingLinesIndexes(defaultReceipt.entries), + currency_code: base_currency, + receipt_message: receiptMessage, + terms_conditions: receiptTermsConditions, + }), + }; // Handle the form submit. const handleFormSubmit = ( values, @@ -184,6 +183,8 @@ export default compose( receiptNextNumber: receiptSettings?.nextNumber, receiptNumberPrefix: receiptSettings?.numberPrefix, receiptAutoIncrement: receiptSettings?.autoIncrement, + receiptMessage: receiptSettings?.receiptMessage, + receiptTermsConditions: receiptSettings?.termsConditions, preferredDepositAccount: receiptSettings?.preferredDepositAccount, })), withCurrentOrganization(), diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/utils.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/utils.tsx index e8c019e7a..d58cb6179 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/utils.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptForm/utils.tsx @@ -7,7 +7,6 @@ import { omit, first } from 'lodash'; import { useFormikContext } from 'formik'; import { defaultFastFieldShouldUpdate, - transactionNumber, repeatValue, transformToForm, formattedAmount, @@ -50,7 +49,7 @@ export const defaultReceipt = { receipt_date: moment(new Date()).format('YYYY-MM-DD'), reference_no: '', receipt_message: '', - statement: '', + terms_conditions: '', closed: '', branch_id: '', warehouse_id: '', diff --git a/packages/webapp/src/lang/en/index.json b/packages/webapp/src/lang/en/index.json index 4a78b4e76..5522f57d6 100644 --- a/packages/webapp/src/lang/en/index.json +++ b/packages/webapp/src/lang/en/index.json @@ -2070,7 +2070,7 @@ "project_task.dialog.edit_success_message": "The task has been edited successfully.", "project_task.action.edit_task": "Edit Task", "project_task.action.delete_task": "Delete Task", -"project_task.rate": "{rate} / hour", + "project_task.rate": "{rate} / hour", "project_task.fixed_price": "Fixed price", "project_task.non_chargable": "Non-chargeable", "project_task.estimate_hours": "• {estimate_hours}h 0m estimated", @@ -2290,5 +2290,27 @@ "sidebar.new_project": "New Project", "sidebar.new_time_entry": "New Time Entry", "sidebar.project_profitability_summary": "Project Profitability Summary", - "global_error.too_many_requests": "Too many requests" -} + "global_error.too_many_requests": "Too many requests", + + "pref.invoices.termsConditions.field": "Terms & Conditions", + "pref.invoices.customerNotes.field": "Customer Notes", + + "pref.creditNotes.termsConditions.field": "Terms & Conditions", + "pref.creditNotes.customerNotes.field": "Customer Notes", + + "pref.estimates.termsConditions.field": "Terms & Conditions", + "pref.estimates.customerNotes.field": "Customer Notes", + + "pref.receipts.termsConditions.field": "Terms & Conditions", + "pref.receipts.receiptMessage.field": "Receipt Message", + + "preferences.invoices": "Invoices", + "preferences.estimates": "Estimates", + "preferences.creditNotes": "Credit Notes", + "preferences.receipts": "Receipts", + + "preferences.estimates.success_message": "The preferences have been saved successfully.", + "preferences.credit_notes.success_message": "The preferences have been saved successfully.", + "preferences.receipts.success_message": "The preferences have been saved successfully.", + "preferences.invoices.success_message": "The preferences have been saved successfully." +} \ No newline at end of file diff --git a/packages/webapp/src/routes/preferences.tsx b/packages/webapp/src/routes/preferences.tsx index 775efcf82..8031230ed 100644 --- a/packages/webapp/src/routes/preferences.tsx +++ b/packages/webapp/src/routes/preferences.tsx @@ -9,6 +9,10 @@ import SMSIntegration from '../containers/Preferences/SMSIntegration'; import DefaultRoute from '../containers/Preferences/DefaultRoute'; import Warehouses from '../containers/Preferences/Warehouses'; import Branches from '../containers/Preferences/Branches'; +import Invoices from '../containers/Preferences/Invoices/PreferencesInvoices'; +import { PreferencesCreditNotes } from '../containers/Preferences/CreditNotes/PreferencesCreditNotes'; +import { PreferencesEstimates } from '@/containers/Preferences/Estimates/PreferencesEstimates'; +import{ PreferencesReceipts } from '@/containers/Preferences/Receipts/PreferencesReceipts' const BASE_URL = '/preferences'; @@ -23,6 +27,26 @@ export default [ component: Users, exact: true, }, + { + path: `${BASE_URL}/invoices`, + component: Invoices, + exact: true, + }, + { + path: `${BASE_URL}/credit-notes`, + component: PreferencesCreditNotes, + exact: true, + }, + { + path: `${BASE_URL}/estimates`, + component: PreferencesEstimates, + exact: true, + }, + { + path: `${BASE_URL}/receipts`, + component: PreferencesReceipts, + exact: true, + }, { path: `${BASE_URL}/roles`, component: Roles,