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 (
+
+ );
+}
+
+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 (
+
+ );
+}
+
+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 (
+
+ );
+}
+
+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 (
+
+ );
+}
+
+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,