From ae617b2e1d14b18249a82b231f13b95edac0a60a Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 29 Jul 2024 22:49:07 +0300 Subject: [PATCH 01/12] Fixed Quick Payment Dialogs PaymentReceives and BillsPayments Controllers expect 'amount' parameter, but webapp sends 'payment_amount' --- .../QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx | 7 ++++--- .../QuickPaymentReceiveForm.tsx | 5 +++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx index 49b51a32b..fe042ff90 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx @@ -3,7 +3,7 @@ import React from 'react'; import intl from 'react-intl-universal'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { pick } from 'lodash'; +import { pick, omit } from 'lodash'; import { AppToaster } from '@/components'; import { CreateQuickPaymentMadeFormSchema } from './QuickPaymentMade.schema'; @@ -21,7 +21,7 @@ function QuickPaymentMadeForm({ // #withDialogActions closeDialog, }) { - + const { bill, dialogName, @@ -44,8 +44,9 @@ function QuickPaymentMadeForm({ })); const form = { - ...values, + ...omit(values, ['payment_amount']), vendor_id: values?.vendor?.id, + amount: values?.payment_amount, entries, }; diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx index 082707f26..c31195715 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx @@ -28,7 +28,7 @@ function QuickPaymentReceiveForm({ paymentReceiveNextNumber, preferredDepositAccount }) { - + const { dialogName, invoice, @@ -61,11 +61,12 @@ function QuickPaymentReceiveForm({ })); const form = { - ...omit(values, ['payment_receive_no']), + ...omit(values, ['payment_receive_no', 'payment_amount']), ...(!paymentReceiveAutoIncrement && { payment_receive_no: values.payment_receive_no, }), customer_id: values.customer.id, + amount: values.payment_amount, entries, }; From 783102449f55787e9d754f22585915e8999aaca4 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 29 Jul 2024 23:15:42 +0200 Subject: [PATCH 02/12] fix: create quick payment received and payment made transactions --- .../QuickPaymentMadeForm.tsx | 20 ++++++--------- .../QuickPaymentMadeFormFields.tsx | 6 ++--- .../QuickPaymentMadeFormDialog/utils.tsx | 1 + .../QuickPaymentReceiveForm.tsx | 25 ++++++++----------- .../QuickPaymentReceiveFormFields.tsx | 6 ++--- .../QuickPaymentReceiveFormDialog/utils.tsx | 1 + 6 files changed, 26 insertions(+), 33 deletions(-) diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx index fe042ff90..8cb6d3294 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx @@ -3,8 +3,7 @@ import React from 'react'; import intl from 'react-intl-universal'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { pick, omit } from 'lodash'; - +import { omit } from 'lodash'; import { AppToaster } from '@/components'; import { CreateQuickPaymentMadeFormSchema } from './QuickPaymentMade.schema'; import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; @@ -21,28 +20,25 @@ function QuickPaymentMadeForm({ // #withDialogActions closeDialog, }) { - const { bill, dialogName, createPaymentMadeMutate, } = useQuickPaymentMadeContext(); - // Initial form values + // Initial form values. const initialValues = { ...defaultPaymentMade, ...bill, }; - // Handles the form submit. const handleFormSubmit = (values, { setSubmitting, setFieldError }) => { - const entries = [values] - .filter((entry) => entry.id && entry.payment_amount) - .map((entry) => ({ - bill_id: entry.id, - ...pick(entry, ['payment_amount']), - })); - + const entries = [ + { + payment_amount: values.amount, + bill_id: values.id, + }, + ]; const form = { ...omit(values, ['payment_amount']), vendor_id: values?.vendor?.id, diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.tsx index d5e875519..bcb0f473d 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormFields.tsx @@ -124,7 +124,7 @@ function QuickPaymentMadeFormFields({ {/*------------ Amount Received -----------*/} - + {({ form: { values, setFieldValue }, field: { value }, @@ -135,7 +135,7 @@ function QuickPaymentMadeFormFields({ labelInfo={} className={classNames('form-group--payment_amount', CLASSES.FILL)} intent={inputIntent({ error, touched })} - helperText={} + helperText={} > @@ -144,7 +144,7 @@ function QuickPaymentMadeFormFields({ value={value} minimal={true} onChange={(amount) => { - setFieldValue('payment_amount', amount); + setFieldValue('amount', amount); }} intent={inputIntent({ error, touched })} inputRef={(ref) => (paymentMadeFieldRef.current = ref)} diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.tsx index b90e21da8..4c3e105e5 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.tsx @@ -16,6 +16,7 @@ export const defaultPaymentMade = { payment_date: moment(new Date()).format('YYYY-MM-DD'), reference: '', payment_number: '', + amount: '', // statement: '', exchange_rate: 1, branch_id: '', diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx index c31195715..664ab64bb 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx @@ -3,7 +3,7 @@ import React from 'react'; import intl from 'react-intl-universal'; import { Formik } from 'formik'; import { Intent } from '@blueprintjs/core'; -import { pick, defaultTo, omit } from 'lodash'; +import { defaultTo, omit } from 'lodash'; import { AppToaster } from '@/components'; import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider'; @@ -26,14 +26,10 @@ function QuickPaymentReceiveForm({ paymentReceiveAutoIncrement, paymentReceiveNumberPrefix, paymentReceiveNextNumber, - preferredDepositAccount + preferredDepositAccount, }) { - - const { - dialogName, - invoice, - createPaymentReceiveMutate, - } = useQuickPaymentReceiveContext(); + const { dialogName, invoice, createPaymentReceiveMutate } = + useQuickPaymentReceiveContext(); // Payment receive number. const nextPaymentNumber = transactionNumber( @@ -53,13 +49,12 @@ function QuickPaymentReceiveForm({ // Handles the form submit. const handleFormSubmit = (values, { setSubmitting, setFieldError }) => { - const entries = [values] - .filter((entry) => entry.id && entry.payment_amount) - .map((entry) => ({ - invoice_id: entry.id, - ...pick(entry, ['payment_amount']), - })); - + const entries = [ + { + invoice_id: values.id, + payment_amount: values.amount, + }, + ]; const form = { ...omit(values, ['payment_receive_no', 'payment_amount']), ...(!paymentReceiveAutoIncrement && { diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.tsx index b5ebcd358..0825b1cf9 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormFields.tsx @@ -128,7 +128,7 @@ function QuickPaymentReceiveFormFields({ {/*------------ Amount Received -----------*/} - + {({ form: { values, setFieldValue }, field: { value }, @@ -139,7 +139,7 @@ function QuickPaymentReceiveFormFields({ labelInfo={} className={classNames('form-group--payment_amount', CLASSES.FILL)} intent={inputIntent({ error, touched })} - helperText={} + helperText={} > @@ -148,7 +148,7 @@ function QuickPaymentReceiveFormFields({ value={value} minimal={true} onChange={(amount) => { - setFieldValue('payment_amount', amount); + setFieldValue('amount', amount); }} intent={inputIntent({ error, touched })} inputRef={(ref) => (paymentReceiveFieldRef.current = ref)} diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.tsx index 673ad9f49..5b74b8268 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.tsx @@ -15,6 +15,7 @@ export const defaultInitialValues = { payment_receive_no: '', payment_date: moment(new Date()).format('YYYY-MM-DD'), reference_no: '', + amount: '', // statement: '', exchange_rate: 1, branch_id: '', From 4cd0405078992229bde3bbb309be15e7f39c328e Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Tue, 30 Jul 2024 11:02:49 +0200 Subject: [PATCH 03/12] fix: quick payment received and payment made form initial values --- .../QuickPaymentMadeForm.tsx | 17 ++++++----------- .../QuickPaymentMadeFormProvider.tsx | 14 +++++++------- .../QuickPaymentMadeFormDialog/utils.tsx | 14 +++++++++++--- .../QuickPaymentReceiveForm.tsx | 14 ++++++++------ .../QuickPaymentReceiveFormProvider.tsx | 13 +++++++------ .../QuickPaymentReceiveFormDialog/utils.tsx | 16 +++++++++++++--- 6 files changed, 52 insertions(+), 36 deletions(-) diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx index 8cb6d3294..503544f04 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeForm.tsx @@ -10,7 +10,7 @@ import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; import QuickPaymentMadeFormContent from './QuickPaymentMadeFormContent'; import withDialogActions from '@/containers/Dialog/withDialogActions'; -import { defaultPaymentMade, transformErrors } from './utils'; +import { defaultPaymentMade, transformBillToForm, transformErrors } from './utils'; import { compose } from '@/utils'; /** @@ -20,29 +20,24 @@ function QuickPaymentMadeForm({ // #withDialogActions closeDialog, }) { - const { - bill, - dialogName, - createPaymentMadeMutate, - } = useQuickPaymentMadeContext(); + const { bill, dialogName, createPaymentMadeMutate } = + useQuickPaymentMadeContext(); // Initial form values. const initialValues = { ...defaultPaymentMade, - ...bill, + ...transformBillToForm(bill), }; // Handles the form submit. const handleFormSubmit = (values, { setSubmitting, setFieldError }) => { const entries = [ { payment_amount: values.amount, - bill_id: values.id, + bill_id: values.bill_id, }, ]; const form = { - ...omit(values, ['payment_amount']), - vendor_id: values?.vendor?.id, - amount: values?.payment_amount, + ...omit(values, ['bill_id']), entries, }; diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormProvider.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormProvider.tsx index cc444ab6b..b79a31e89 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormProvider.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/QuickPaymentMadeFormProvider.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React from 'react'; +import React, { useMemo } from 'react'; import { DialogContent } from '@/components'; import { useBill, @@ -11,7 +11,6 @@ import { Features } from '@/constants'; import { useFeatureCan } from '@/hooks/state'; import { pick } from 'lodash'; - const QuickPaymentMadeContext = React.createContext(); /** @@ -40,13 +39,14 @@ function QuickPaymentMadeFormProvider({ query, billId, dialogName, ...props }) { isSuccess: isBranchesSuccess, } = useBranches(query, { enabled: isBranchFeatureCan }); + const paymentBill = useMemo( + () => pick(bill, ['id', 'due_amount', 'vendor_id', 'currency_code']), + [bill], + ); + // State provider. const provider = { - bill: { - ...pick(bill, ['id', 'due_amount', 'vendor', 'currency_code']), - vendor_id: bill?.vendor?.display_name, - payment_amount: bill?.due_amount, - }, + bill: paymentBill, accounts, branches, dialogName, diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.tsx index 4c3e105e5..03470ff50 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentMadeFormDialog/utils.tsx @@ -2,15 +2,16 @@ import React from 'react'; import moment from 'moment'; import intl from 'react-intl-universal'; -import { first } from 'lodash'; +import { first, pick } from 'lodash'; +import { useFormikContext } from 'formik'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from '@/components'; -import { useFormikContext } from 'formik'; import { useQuickPaymentMadeContext } from './QuickPaymentMadeFormProvider'; import { PAYMENT_MADE_ERRORS } from '@/containers/Purchases/PaymentMades/constants'; // Default initial values of payment made. export const defaultPaymentMade = { + bill_id: '', vendor_id: '', payment_account_id: '', payment_date: moment(new Date()).format('YYYY-MM-DD'), @@ -20,7 +21,6 @@ export const defaultPaymentMade = { // statement: '', exchange_rate: 1, branch_id: '', - entries: [{ bill_id: '', payment_amount: '' }], }; export const transformErrors = (errors, { setFieldError }) => { @@ -59,3 +59,11 @@ export const useSetPrimaryBranchToForm = () => { } }, [isBranchesSuccess, setFieldValue, branches]); }; + +export const transformBillToForm = (bill) => { + return { + ...pick(bill, ['vendor_id', 'currency_code']), + amount: bill.due_amount, + bill_id: bill.id, + }; +} diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx index 664ab64bb..b2326c525 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveForm.tsx @@ -12,7 +12,11 @@ import QuickPaymentReceiveFormContent from './QuickPaymentReceiveFormContent'; import withSettings from '@/containers/Settings/withSettings'; import withDialogActions from '@/containers/Dialog/withDialogActions'; -import { defaultInitialValues, transformErrors } from './utils'; +import { + defaultInitialValues, + transformErrors, + transformInvoiceToForm, +} from './utils'; import { compose, transactionNumber } from '@/utils'; /** @@ -44,24 +48,22 @@ function QuickPaymentReceiveForm({ payment_receive_no: nextPaymentNumber, }), deposit_account_id: defaultTo(preferredDepositAccount, ''), - ...invoice, + ...transformInvoiceToForm(invoice), }; // Handles the form submit. const handleFormSubmit = (values, { setSubmitting, setFieldError }) => { const entries = [ { - invoice_id: values.id, + invoice_id: values.invoice_id, payment_amount: values.amount, }, ]; const form = { - ...omit(values, ['payment_receive_no', 'payment_amount']), + ...omit(values, ['payment_receive_no', 'invoice_id']), ...(!paymentReceiveAutoIncrement && { payment_receive_no: values.payment_receive_no, }), - customer_id: values.customer.id, - amount: values.payment_amount, entries, }; diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormProvider.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormProvider.tsx index 0c17e7ee1..64438f4ed 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormProvider.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/QuickPaymentReceiveFormProvider.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React, { useContext, createContext } from 'react'; +import React, { useContext, createContext, useMemo } from 'react'; import { pick } from 'lodash'; import { DialogContent } from '@/components'; import { Features } from '@/constants'; @@ -47,15 +47,16 @@ function QuickPaymentReceiveFormProvider({ isSuccess: isBranchesSuccess, } = useBranches(query, { enabled: isBranchFeatureCan }); + const invoicePayment = useMemo( + () => pick(invoice, ['id', 'due_amount', 'customer_id', 'currency_code']), + [invoice], + ); + // State provider. const provider = { accounts, branches, - invoice: { - ...pick(invoice, ['id', 'due_amount', 'customer', 'currency_code']), - customer_id: invoice?.customer?.display_name, - payment_amount: invoice.due_amount, - }, + invoice: invoicePayment, isAccountsLoading, isSettingsLoading, isBranchesSuccess, diff --git a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.tsx b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.tsx index 5b74b8268..c6e7a8bb9 100644 --- a/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.tsx +++ b/packages/webapp/src/containers/Dialogs/QuickPaymentReceiveFormDialog/utils.tsx @@ -2,7 +2,7 @@ import React from 'react'; import moment from 'moment'; import intl from 'react-intl-universal'; -import { first } from 'lodash'; +import { first, pick } from 'lodash'; import { Intent } from '@blueprintjs/core'; import { AppToaster } from '@/components'; @@ -10,6 +10,7 @@ import { useFormikContext } from 'formik'; import { useQuickPaymentReceiveContext } from './QuickPaymentReceiveFormProvider'; export const defaultInitialValues = { + invoice_id: '', customer_id: '', deposit_account_id: '', payment_receive_no: '', @@ -19,7 +20,6 @@ export const defaultInitialValues = { // statement: '', exchange_rate: 1, branch_id: '', - entries: [{ invoice_id: '', payment_amount: '' }], }; export const transformErrors = (errors, { setFieldError }) => { @@ -45,7 +45,9 @@ export const transformErrors = (errors, { setFieldError }) => { } if (getError('PAYMENT_ACCOUNT_CURRENCY_INVALID')) { AppToaster.show({ - message: intl.get('payment_Receive.error.payment_account_currency_invalid'), + message: intl.get( + 'payment_Receive.error.payment_account_currency_invalid', + ), intent: Intent.DANGER, }); } @@ -65,3 +67,11 @@ export const useSetPrimaryBranchToForm = () => { } }, [isBranchesSuccess, setFieldValue, branches]); }; + +export const transformInvoiceToForm = (invoice) => { + return { + ...pick(invoice, ['customer_id', 'currency_code']), + amount: invoice.due_amount, + invoice_id: invoice.id, + }; +}; From 832cdacebf5f2b61aebbb014a215f6746a17146b Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 30 Jul 2024 23:48:15 +0300 Subject: [PATCH 04/12] Download attachments with original filenames --- .../Attachments/AttachmentsController.ts | 2 ++ .../Attachments/AttachmentsApplication.ts | 5 +++-- .../Attachments/GetAttachmentPresignedUrl.ts | 22 +++++++++++++++---- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/packages/server/src/api/controllers/Attachments/AttachmentsController.ts b/packages/server/src/api/controllers/Attachments/AttachmentsController.ts index 657aa0240..ad75ef550 100644 --- a/packages/server/src/api/controllers/Attachments/AttachmentsController.ts +++ b/packages/server/src/api/controllers/Attachments/AttachmentsController.ts @@ -250,10 +250,12 @@ export class AttachmentsController extends BaseController { res: Response, next: NextFunction ): Promise { + const { tenantId } = req; const { id: documentKey } = req.params; try { const presignedUrl = await this.attachmentsApplication.getPresignedUrl( + tenantId, documentKey ); return res.status(200).send({ presignedUrl }); diff --git a/packages/server/src/services/Attachments/AttachmentsApplication.ts b/packages/server/src/services/Attachments/AttachmentsApplication.ts index 2cf7c08f1..6e75c9783 100644 --- a/packages/server/src/services/Attachments/AttachmentsApplication.ts +++ b/packages/server/src/services/Attachments/AttachmentsApplication.ts @@ -96,10 +96,11 @@ export class AttachmentsApplication { /** * Retrieves the presigned url of the given attachment key. + * @param {number} tenantId * @param {string} key * @returns {Promise} */ - public getPresignedUrl(key: string): Promise { - return this.getPresignedUrlService.getPresignedUrl(key); + public getPresignedUrl(tenantId: number, key: string): Promise { + return this.getPresignedUrlService.getPresignedUrl(tenantId, key); } } diff --git a/packages/server/src/services/Attachments/GetAttachmentPresignedUrl.ts b/packages/server/src/services/Attachments/GetAttachmentPresignedUrl.ts index 62ca3f91c..b76b5aa4e 100644 --- a/packages/server/src/services/Attachments/GetAttachmentPresignedUrl.ts +++ b/packages/server/src/services/Attachments/GetAttachmentPresignedUrl.ts @@ -1,20 +1,34 @@ -import { Service } from 'typedi'; +import { Inject, Service } from 'typedi'; import { GetObjectCommand } from '@aws-sdk/client-s3'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { s3 } from '@/lib/S3/S3'; import config from '@/config'; +import HasTenancyService from '../Tenancy/TenancyService'; @Service() export class getAttachmentPresignedUrl { + @Inject() + private tenancy: HasTenancyService; + /** - * Retrieves the presigned url of the given attachment key. + * Retrieves the presigned url of the given attachment key with the original filename. + * @param {number} tenantId * @param {string} key - * @returns {Promise} + * @returns {string} */ - async getPresignedUrl(key: string) { + async getPresignedUrl(tenantId: number, key: string) { + const { Document } = this.tenancy.models(tenantId); + const foundDocument = await Document.query().findOne({ key }); + + let ResponseContentDisposition = 'attachment'; + if (foundDocument && foundDocument.originName) { + ResponseContentDisposition += `; filename="${foundDocument.originName}"`; + } + const command = new GetObjectCommand({ Bucket: config.s3.bucket, Key: key, + ResponseContentDisposition, }); const signedUrl = await getSignedUrl(s3, command, { expiresIn: 300 }); From a1ddc81dac16ca6209956546a2b8356fd3faa453 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 30 Jul 2024 23:54:46 +0300 Subject: [PATCH 05/12] Fixed double slash in attachments route --- packages/webapp/src/hooks/query/attachments.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/webapp/src/hooks/query/attachments.ts b/packages/webapp/src/hooks/query/attachments.ts index 4255e3d0a..4b92bbbcb 100644 --- a/packages/webapp/src/hooks/query/attachments.ts +++ b/packages/webapp/src/hooks/query/attachments.ts @@ -9,7 +9,7 @@ export function useUploadAttachments(props) { const apiRequest = useApiRequest(); return useMutation( - (values) => apiRequest.post('/attachments', values), + (values) => apiRequest.post('attachments', values), props, ); } @@ -21,7 +21,7 @@ export function useDeleteAttachment(props) { const apiRequest = useApiRequest(); return useMutation( - (key: string) => apiRequest.delete(`/attachments/${key}`), + (key: string) => apiRequest.delete(`attachments/${key}`), props, ); } @@ -35,7 +35,7 @@ export function useGetPresignedUrlAttachment(props) { return useMutation( (key: string) => apiRequest - .get(`/attachments/${key}/presigned-url`) + .get(`attachments/${key}/presigned-url`) .then((res) => res.data), props, ); From 219e6fb466afe2e89c8b3599660d9dff5460feb4 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 1 Aug 2024 19:51:25 +0200 Subject: [PATCH 06/12] fix: onboarding page layout on small screens --- .../SetupSubscription.module.scss | 1 + .../SetupSubscription/SubscriptionPlans.tsx | 2 +- packages/webapp/src/style/App.scss | 2 +- .../src/style/pages/Setup/SetupPage.scss | 63 +++++++------------ 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/packages/webapp/src/containers/Setup/SetupSubscription/SetupSubscription.module.scss b/packages/webapp/src/containers/Setup/SetupSubscription/SetupSubscription.module.scss index 7a3087aa0..f31317e0f 100644 --- a/packages/webapp/src/containers/Setup/SetupSubscription/SetupSubscription.module.scss +++ b/packages/webapp/src/containers/Setup/SetupSubscription/SetupSubscription.module.scss @@ -2,6 +2,7 @@ .root{ margin: 0 auto; padding: 0 40px; + padding-right: 20px; } .periodSwitch { diff --git a/packages/webapp/src/containers/Setup/SetupSubscription/SubscriptionPlans.tsx b/packages/webapp/src/containers/Setup/SetupSubscription/SubscriptionPlans.tsx index 7fa489f0e..6fde58c75 100644 --- a/packages/webapp/src/containers/Setup/SetupSubscription/SubscriptionPlans.tsx +++ b/packages/webapp/src/containers/Setup/SetupSubscription/SubscriptionPlans.tsx @@ -6,7 +6,7 @@ export function SubscriptionPlans() { const subscriptionPlans = useSubscriptionPlans(); return ( - + {subscriptionPlans.map((plan, index) => ( div { font-size: 13px; - margin-right: 15px; - display: inline; a { color: #fff; From 5e12a4cea4a927e672aa7b5076e2d67adfeba36c Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 4 Aug 2024 00:36:19 +0200 Subject: [PATCH 07/12] feat: pause/resume bank account feeds syncing --- .../Banking/BankAccountsController.ts | 47 ++++++++++++++++++- .../BankAccounts/BankAccountsApplication.tsx | 35 +++++++++++++- .../BankAccounts/PauseBankAccountFeeds.tsx | 9 ++++ .../BankAccounts/ResumeBankAccountFeeds.tsx | 11 +++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx create mode 100644 packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx diff --git a/packages/server/src/api/controllers/Banking/BankAccountsController.ts b/packages/server/src/api/controllers/Banking/BankAccountsController.ts index f337c0b38..2a2661216 100644 --- a/packages/server/src/api/controllers/Banking/BankAccountsController.ts +++ b/packages/server/src/api/controllers/Banking/BankAccountsController.ts @@ -1,7 +1,6 @@ import { Inject, Service } from 'typedi'; import { NextFunction, Request, Response, Router } from 'express'; import BaseController from '@/api/controllers/BaseController'; -import { CashflowApplication } from '@/services/Cashflow/CashflowApplication'; import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary'; import { BankAccountsApplication } from '@/services/Banking/BankAccounts/BankAccountsApplication'; @@ -25,6 +24,14 @@ export class BankAccountsController extends BaseController { this.disconnectBankAccount.bind(this) ); router.post('/:bankAccountId/update', this.refreshBankAccount.bind(this)); + router.post( + '/:bankAccountId/pause_feeds', + this.pauseBankAccountFeeds.bind(this) + ); + router.post( + '/:bankAccountId/resume_feeds', + this.resumeBankAccountFeeds.bind(this) + ); return router; } @@ -109,4 +116,42 @@ export class BankAccountsController extends BaseController { next(error); } } + + async resumeBankAccountFeeds( + req: Request<{ bankAccountId: number }>, + res: Response, + next: NextFunction + ) { + const { bankAccountId } = req.params; + const { tenantId } = req; + + try { + await this.bankAccountsApp.resumeBankAccount(tenantId, bankAccountId); + + return res.status(200).send({ + message: '', + }); + } catch (error) { + next(error); + } + } + + async pauseBankAccountFeeds( + req: Request<{ bankAccountId: number }>, + res: Response, + next: NextFunction + ) { + const { bankAccountId } = req.params; + const { tenantId } = req; + + try { + await this.bankAccountsApp.pauseBankAccount(tenantId, bankAccountId); + + return res.status(200).send({ + message: '', + }); + } catch (error) { + next(error); + } + } } diff --git a/packages/server/src/services/Banking/BankAccounts/BankAccountsApplication.tsx b/packages/server/src/services/Banking/BankAccounts/BankAccountsApplication.tsx index 51c12106e..faef21ac5 100644 --- a/packages/server/src/services/Banking/BankAccounts/BankAccountsApplication.tsx +++ b/packages/server/src/services/Banking/BankAccounts/BankAccountsApplication.tsx @@ -1,6 +1,7 @@ import { Inject, Service } from 'typedi'; import { DisconnectBankAccount } from './DisconnectBankAccount'; import { RefreshBankAccountService } from './RefreshBankAccount'; +import { ResumeBankAccountFeeds } from './PauseBankAccountFeeds'; @Service() export class BankAccountsApplication { @@ -10,6 +11,12 @@ export class BankAccountsApplication { @Inject() private refreshBankAccountService: RefreshBankAccountService; + @Inject() + private resumeBankAccountFeedsService: ResumeBankAccountFeeds; + + @Inject() + private pauseBankAccountFeedsService: ResumeBankAccountFeeds; + /** * Disconnects the given bank account. * @param {number} tenantId @@ -27,7 +34,7 @@ export class BankAccountsApplication { * Refresh the bank transactions of the given bank account. * @param {number} tenantId * @param {number} bankAccountId - * @returns {Promise} + * @returns {Promise} */ async refreshBankAccount(tenantId: number, bankAccountId: number) { return this.refreshBankAccountService.refreshBankAccount( @@ -35,4 +42,30 @@ export class BankAccountsApplication { bankAccountId ); } + + /** + * Pauses the feeds sync of the given bank account. + * @param {number} tenantId + * @param {number} bankAccountId + * @returns {Promise} + */ + async pauseBankAccount(tenantId: number, bankAccountId: number) { + return this.pauseBankAccountFeedsService.resumeBankAccountFeeds( + tenantId, + bankAccountId + ); + } + + /** + * Resumes the feeds sync of the given bank account. + * @param {number} tenantId + * @param {number} bankAccountId + * @returns {Promise} + */ + async resumeBankAccount(tenantId: number, bankAccountId: number) { + return this.resumeBankAccountFeedsService.resumeBankAccountFeeds( + tenantId, + bankAccountId + ); + } } diff --git a/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx b/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx new file mode 100644 index 000000000..34cf2664b --- /dev/null +++ b/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx @@ -0,0 +1,9 @@ +import { Service } from 'typedi'; + +@Service() +export class ResumeBankAccountFeeds { + public resumeBankAccountFeeds(tenantId: number, bankAccountId: number) { + + + } +} diff --git a/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx b/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx new file mode 100644 index 000000000..7851e0e16 --- /dev/null +++ b/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx @@ -0,0 +1,11 @@ +import { Service } from "typedi"; + +@Service() +export class ResumeBankAccountFeeds { + /** + * + * @param {number} tenantId + * @param {number} bankAccountId + */ + public resumeBankAccountFeeds(tenantId: number, bankAccountId: number) {} +} From 208800b411e07fc24085006aaeac8b16e5bdf277 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 4 Aug 2024 11:22:21 +0200 Subject: [PATCH 08/12] feat: wip pause/resume bank feeds syncing --- .../Banking/BankAccountsController.ts | 29 ++++++- ...e_paused_at_column_to_plaid_items_table.js | 11 +++ packages/server/src/models/PlaidItem.ts | 17 ++++ .../BankAccounts/BankAccountsApplication.tsx | 15 ++-- .../BankAccounts/PauseBankAccountFeeds.tsx | 30 ++++++- .../BankAccounts/ResumeBankAccountFeeds.tsx | 27 +++++- .../containers/AlertsContainer/registered.tsx | 4 +- .../AccountTransactionsActionsBar.tsx | 24 ++++++ .../alerts/PauseFeedsBankAccount.tsx | 67 +++++++++++++++ .../alerts/ResumeFeedsBankAccount.tsx | 67 +++++++++++++++ .../AccountTransactions/alerts/index.ts | 24 ++++++ .../webapp/src/hooks/query/bank-accounts.ts | 85 +++++++++++++++++++ 12 files changed, 384 insertions(+), 16 deletions(-) create mode 100644 packages/server/src/database/migrations/20240804084709_create_paused_at_column_to_plaid_items_table.js create mode 100644 packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx create mode 100644 packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx create mode 100644 packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/index.ts create mode 100644 packages/webapp/src/hooks/query/bank-accounts.ts diff --git a/packages/server/src/api/controllers/Banking/BankAccountsController.ts b/packages/server/src/api/controllers/Banking/BankAccountsController.ts index 2a2661216..942c6b890 100644 --- a/packages/server/src/api/controllers/Banking/BankAccountsController.ts +++ b/packages/server/src/api/controllers/Banking/BankAccountsController.ts @@ -3,6 +3,7 @@ import { NextFunction, Request, Response, Router } from 'express'; import BaseController from '@/api/controllers/BaseController'; import { GetBankAccountSummary } from '@/services/Banking/BankAccounts/GetBankAccountSummary'; import { BankAccountsApplication } from '@/services/Banking/BankAccounts/BankAccountsApplication'; +import { param } from 'express-validator'; @Service() export class BankAccountsController extends BaseController { @@ -26,10 +27,18 @@ export class BankAccountsController extends BaseController { router.post('/:bankAccountId/update', this.refreshBankAccount.bind(this)); router.post( '/:bankAccountId/pause_feeds', + [ + param('bankAccountId').exists().isNumeric().toInt(), + ], + this.validationResult, this.pauseBankAccountFeeds.bind(this) ); router.post( '/:bankAccountId/resume_feeds', + [ + param('bankAccountId').exists().isNumeric().toInt(), + ], + this.validationResult, this.resumeBankAccountFeeds.bind(this) ); @@ -117,6 +126,13 @@ export class BankAccountsController extends BaseController { } } + /** + * + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {Promise} + */ async resumeBankAccountFeeds( req: Request<{ bankAccountId: number }>, res: Response, @@ -129,13 +145,21 @@ export class BankAccountsController extends BaseController { await this.bankAccountsApp.resumeBankAccount(tenantId, bankAccountId); return res.status(200).send({ - message: '', + message: 'The bank account feeds syncing has been resumed.', + id: bankAccountId, }); } catch (error) { next(error); } } + /** + * + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + * @returns {Promise} + */ async pauseBankAccountFeeds( req: Request<{ bankAccountId: number }>, res: Response, @@ -148,7 +172,8 @@ export class BankAccountsController extends BaseController { await this.bankAccountsApp.pauseBankAccount(tenantId, bankAccountId); return res.status(200).send({ - message: '', + message: 'The bank account feeds syncing has been paused.', + id: bankAccountId, }); } catch (error) { next(error); diff --git a/packages/server/src/database/migrations/20240804084709_create_paused_at_column_to_plaid_items_table.js b/packages/server/src/database/migrations/20240804084709_create_paused_at_column_to_plaid_items_table.js new file mode 100644 index 000000000..2947eb30b --- /dev/null +++ b/packages/server/src/database/migrations/20240804084709_create_paused_at_column_to_plaid_items_table.js @@ -0,0 +1,11 @@ +exports.up = function (knex) { + return knex.schema.table('plaid_items', (table) => { + table.datetime('paused_at'); + }); +}; + +exports.down = function (knex) { + return knex.schema.table('plaid_items', (table) => { + table.dropColumn('paused_at'); + }); +}; diff --git a/packages/server/src/models/PlaidItem.ts b/packages/server/src/models/PlaidItem.ts index 6dc515394..aca189038 100644 --- a/packages/server/src/models/PlaidItem.ts +++ b/packages/server/src/models/PlaidItem.ts @@ -1,6 +1,8 @@ import TenantModel from 'models/TenantModel'; export default class PlaidItem extends TenantModel { + pausedAt: Date; + /** * Table name. */ @@ -21,4 +23,19 @@ export default class PlaidItem extends TenantModel { static get relationMappings() { return {}; } + + /** + * Virtual attributes. + */ + static get virtualAttributes() { + return ['isPaused']; + } + + /** + * Detarmines whether the Plaid item feeds syncing is paused. + * @return {boolean} + */ + get isPaused() { + return !!this.pausedAt; + } } diff --git a/packages/server/src/services/Banking/BankAccounts/BankAccountsApplication.tsx b/packages/server/src/services/Banking/BankAccounts/BankAccountsApplication.tsx index faef21ac5..b2fc48775 100644 --- a/packages/server/src/services/Banking/BankAccounts/BankAccountsApplication.tsx +++ b/packages/server/src/services/Banking/BankAccounts/BankAccountsApplication.tsx @@ -1,7 +1,8 @@ import { Inject, Service } from 'typedi'; import { DisconnectBankAccount } from './DisconnectBankAccount'; import { RefreshBankAccountService } from './RefreshBankAccount'; -import { ResumeBankAccountFeeds } from './PauseBankAccountFeeds'; +import { PauseBankAccountFeeds } from './PauseBankAccountFeeds'; +import { ResumeBankAccountFeeds } from './ResumeBankAccountFeeds'; @Service() export class BankAccountsApplication { @@ -15,7 +16,7 @@ export class BankAccountsApplication { private resumeBankAccountFeedsService: ResumeBankAccountFeeds; @Inject() - private pauseBankAccountFeedsService: ResumeBankAccountFeeds; + private pauseBankAccountFeedsService: PauseBankAccountFeeds; /** * Disconnects the given bank account. @@ -45,12 +46,12 @@ export class BankAccountsApplication { /** * Pauses the feeds sync of the given bank account. - * @param {number} tenantId - * @param {number} bankAccountId + * @param {number} tenantId + * @param {number} bankAccountId * @returns {Promise} */ async pauseBankAccount(tenantId: number, bankAccountId: number) { - return this.pauseBankAccountFeedsService.resumeBankAccountFeeds( + return this.pauseBankAccountFeedsService.pauseBankAccountFeeds( tenantId, bankAccountId ); @@ -58,8 +59,8 @@ export class BankAccountsApplication { /** * Resumes the feeds sync of the given bank account. - * @param {number} tenantId - * @param {number} bankAccountId + * @param {number} tenantId + * @param {number} bankAccountId * @returns {Promise} */ async resumeBankAccount(tenantId: number, bankAccountId: number) { diff --git a/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx b/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx index 34cf2664b..3108bebf5 100644 --- a/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx +++ b/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx @@ -1,9 +1,33 @@ -import { Service } from 'typedi'; +import { Inject, Service } from 'typedi'; +import { Knex } from 'knex'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import UnitOfWork from '@/services/UnitOfWork'; @Service() -export class ResumeBankAccountFeeds { - public resumeBankAccountFeeds(tenantId: number, bankAccountId: number) { +export class PauseBankAccountFeeds { + @Inject() + private tenancy: HasTenancyService; + @Inject() + private uow: UnitOfWork; + /** + * Pauses the bankfeed syncing of the given bank account. + * @param {number} tenantId + * @param {number} bankAccountId + * @returns {Promise} + */ + public async pauseBankAccountFeeds(tenantId: number, bankAccountId: number) { + const { Account, PlaidItem } = this.tenancy.models(tenantId); + + const oldAccount = await Account.query() + .findById(bankAccountId) + .withGraphFetched('plaidItem'); + + return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { + await PlaidItem.query().findById(oldAccount.plaidItem.id).patch({ + pausedAt: null, + }); + }); } } diff --git a/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx b/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx index 7851e0e16..680568470 100644 --- a/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx +++ b/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx @@ -1,11 +1,32 @@ -import { Service } from "typedi"; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import UnitOfWork from '@/services/UnitOfWork'; +import { Inject, Service } from 'typedi'; @Service() export class ResumeBankAccountFeeds { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private uow: UnitOfWork; + /** - * + * Resumes the bank feeds syncing of the bank account. * @param {number} tenantId * @param {number} bankAccountId + * @returns {Promise} */ - public resumeBankAccountFeeds(tenantId: number, bankAccountId: number) {} + public async resumeBankAccountFeeds(tenantId: number, bankAccountId: number) { + const { Account, PlaidItem } = this.tenancy.models(tenantId); + + const oldAccount = await Account.query() + .findById(bankAccountId) + .withGraphFetched('plaidItem'); + + return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { + await PlaidItem.query().findById(oldAccount.plaidItem.id).patch({ + pausedAt: new Date(), + }); + }); + } } diff --git a/packages/webapp/src/containers/AlertsContainer/registered.tsx b/packages/webapp/src/containers/AlertsContainer/registered.tsx index 40724c66c..f585c398f 100644 --- a/packages/webapp/src/containers/AlertsContainer/registered.tsx +++ b/packages/webapp/src/containers/AlertsContainer/registered.tsx @@ -28,6 +28,7 @@ import TaxRatesAlerts from '@/containers/TaxRates/alerts'; import { CashflowAlerts } from '../CashFlow/CashflowAlerts'; import { BankRulesAlerts } from '../Banking/Rules/RulesList/BankRulesAlerts'; import { SubscriptionAlerts } from '../Subscriptions/alerts/alerts'; +import { BankAccountAlerts } from '@/containers/CashFlow/AccountTransactions/alerts'; export default [ ...AccountsAlerts, @@ -58,5 +59,6 @@ export default [ ...TaxRatesAlerts, ...CashflowAlerts, ...BankRulesAlerts, - ...SubscriptionAlerts + ...SubscriptionAlerts, + ...BankAccountAlerts, ]; diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx index d6ff1b075..f01427c43 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx @@ -46,6 +46,7 @@ import { useUnexcludeUncategorizedTransactions, } from '@/hooks/query/bank-rules'; import { withBanking } from '../withBanking'; +import withAlertActions from '@/containers/Alert/withAlertActions'; function AccountTransactionsActionsBar({ // #withDialogActions @@ -60,6 +61,9 @@ function AccountTransactionsActionsBar({ // #withBanking uncategorizedTransationsIdsSelected, excludedTransactionsIdsSelected, + + // #withAlerts + openAlert, }) { const history = useHistory(); const { accountId, currentAccount } = useAccountTransactionsContext(); @@ -191,6 +195,16 @@ function AccountTransactionsActionsBar({ }); }; + // Handle resume bank feeds syncing. + const handleResumeFeedsSyncing = () => { + openAlert('resume-feeds-syncing-bank-accounnt'); + }; + + // Handles pause bank feeds syncing. + const handlePauseFeedsSyncing = () => { + openAlert('pause-feeds-syncing-bank-accounnt'); + }; + return ( @@ -284,6 +298,15 @@ function AccountTransactionsActionsBar({ }} content={ + + + @@ -311,6 +334,7 @@ function AccountTransactionsActionsBar({ export default compose( withDialogActions, + withAlertActions, withSettingsActions, withSettings(({ cashflowTransactionsSettings }) => ({ cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize, diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx new file mode 100644 index 000000000..5c4dbaf60 --- /dev/null +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx @@ -0,0 +1,67 @@ +// @ts-nocheck +import React from 'react'; +import intl from 'react-intl-universal'; +import { Intent, Alert } from '@blueprintjs/core'; + +import { AppToaster, FormattedMessage as T } from '@/components'; +import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect'; +import withAlertActions from '@/containers/Alert/withAlertActions'; + +import { usePauseFeedsBankAccount } from '@/hooks/query/bank-accounts'; +import { compose } from '@/utils'; + +/** + * Item activate alert. + */ +function PauseFeedsBankAccountAlert({ + name, + + // #withAlertStoreConnect + isOpen, + payload: { bankAccountId }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: pauseBankAccountFeeds, isLoading } = + usePauseFeedsBankAccount(); + + // Handle activate item alert cancel. + const handleCancelActivateItem = () => { + closeAlert(name); + }; + + // Handle confirm item activated. + const handleConfirmItemActivate = () => { + pauseBankAccountFeeds(bankAccountId) + .then(() => { + AppToaster.show({ + message: 'The bank feeds of the bank account has been paused.', + intent: Intent.SUCCESS, + }); + }) + .catch((error) => {}) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={isOpen} + onCancel={handleCancelActivateItem} + loading={isLoading} + onConfirm={handleConfirmItemActivate} + > +

Are you sure.

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(PauseFeedsBankAccountAlert); diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx new file mode 100644 index 000000000..23a867349 --- /dev/null +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx @@ -0,0 +1,67 @@ +// @ts-nocheck +import React from 'react'; +import intl from 'react-intl-universal'; +import { Intent, Alert } from '@blueprintjs/core'; + +import { AppToaster, FormattedMessage as T } from '@/components'; +import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect'; +import withAlertActions from '@/containers/Alert/withAlertActions'; + +import { useResumeFeedsBankAccount } from '@/hooks/query/bank-accounts'; +import { compose } from '@/utils'; + +/** + * + */ +function ResumeFeedsBankAccountAlert({ + name, + + // #withAlertStoreConnect + isOpen, + payload: { bankAccountId }, + + // #withAlertActions + closeAlert, +}) { + const { mutateAsync: resumeFeedsBankAccount, isLoading } = + useResumeFeedsBankAccount(); + + // Handle activate item alert cancel. + const handleCancelActivateItem = () => { + closeAlert(name); + }; + + // Handle confirm item activated. + const handleConfirmItemActivate = () => { + resumeFeedsBankAccount(bankAccountId) + .then(() => { + AppToaster.show({ + message: 'The bank feeds of the bank account has been resumed.', + intent: Intent.SUCCESS, + }); + }) + .catch((error) => {}) + .finally(() => { + closeAlert(name); + }); + }; + + return ( + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={isOpen} + onCancel={handleCancelActivateItem} + loading={isLoading} + onConfirm={handleConfirmItemActivate} + > +

Are you sure.

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(ResumeFeedsBankAccountAlert); diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/index.ts b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/index.ts new file mode 100644 index 000000000..c8b6e0fcb --- /dev/null +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/index.ts @@ -0,0 +1,24 @@ +// @ts-nocheck +import React from 'react'; + +const ResumeFeedsBankAccountAlert = React.lazy( + () => import('./ResumeFeedsBankAccount'), +); + +const PauseFeedsBankAccountAlert = React.lazy( + () => import('./PauseFeedsBankAccount'), +); + +/** + * Bank account alerts. + */ +export const BankAccountAlerts = [ + { + name: 'resume-feeds-syncing-bank-accounnt', + component: ResumeFeedsBankAccountAlert, + }, + { + name: 'pause-feeds-syncing-bank-accounnt', + component: PauseFeedsBankAccountAlert, + }, +]; diff --git a/packages/webapp/src/hooks/query/bank-accounts.ts b/packages/webapp/src/hooks/query/bank-accounts.ts new file mode 100644 index 000000000..a55863dda --- /dev/null +++ b/packages/webapp/src/hooks/query/bank-accounts.ts @@ -0,0 +1,85 @@ +import { + UseMutationOptions, + UseMutationResult, + useQueryClient, + useMutation, +} from 'react-query'; +import useApiRequest from '../useRequest'; + +type PuaseFeedsBankAccountValues = { bankAccountId: number }; + +interface PuaseFeedsBankAccountResponse {} + +/** + * Resumes the feeds syncing of the bank account. + * @param {UseMutationResult} options + * @returns {UseMutationResult} + */ +export function usePauseFeedsBankAccount( + options?: UseMutationOptions< + PuaseFeedsBankAccountResponse, + Error, + PuaseFeedsBankAccountValues + >, +): UseMutationResult< + PuaseFeedsBankAccountResponse, + Error, + PuaseFeedsBankAccountValues +> { + const queryClient = useQueryClient(); + const apiRequest = useApiRequest(); + + return useMutation< + PuaseFeedsBankAccountResponse, + Error, + PuaseFeedsBankAccountValues + >( + (values) => + apiRequest.post( + `/banking/bank_accounts/${values.bankAccountId}/pause_feeds`, + ), + { + onSuccess: (res, id) => {}, + ...options, + }, + ); +} + +type ResumeFeedsBankAccountValues = { bankAccountId: number }; + +interface ResumeFeedsBankAccountResponse {} + +/** + * Resumes the feeds syncing of the bank account. + * @param {UseMutationResult} options + * @returns {UseMutationResult} + */ +export function useResumeFeedsBankAccount( + options?: UseMutationOptions< + ResumeFeedsBankAccountResponse, + Error, + ResumeFeedsBankAccountValues + >, +): UseMutationResult< + ResumeFeedsBankAccountResponse, + Error, + ResumeFeedsBankAccountValues +> { + const queryClient = useQueryClient(); + const apiRequest = useApiRequest(); + + return useMutation< + ResumeFeedsBankAccountResponse, + Error, + ResumeFeedsBankAccountValues + >( + (values) => + apiRequest.post( + `/banking/bank_accounts/${values.bankAccountId}/resume_feeds`, + ), + { + onSuccess: (res, id) => {}, + ...options, + }, + ); +} From b84675325f94bb226be02fa2981900d19f7f10ff Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 4 Aug 2024 16:05:35 +0200 Subject: [PATCH 09/12] feat: alert messages of pause.resume bank feeds --- .../BankAccounts/PauseBankAccountFeeds.tsx | 15 +++++++-- .../BankAccounts/ResumeBankAccountFeeds.tsx | 15 +++++++-- .../services/Banking/BankAccounts/types.ts | 2 ++ .../AccountTransactionsActionsBar.tsx | 32 ++++++++++++------- .../alerts/PauseFeedsBankAccount.tsx | 11 ++++--- .../alerts/ResumeFeedsBankAccount.tsx | 11 ++++--- 6 files changed, 62 insertions(+), 24 deletions(-) diff --git a/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx b/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx index 3108bebf5..4a4f24a6c 100644 --- a/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx +++ b/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx @@ -2,6 +2,8 @@ import { Inject, Service } from 'typedi'; import { Knex } from 'knex'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import UnitOfWork from '@/services/UnitOfWork'; +import { ServiceError } from '@/exceptions'; +import { ERRORS } from './types'; @Service() export class PauseBankAccountFeeds { @@ -22,10 +24,19 @@ export class PauseBankAccountFeeds { const oldAccount = await Account.query() .findById(bankAccountId) - .withGraphFetched('plaidItem'); + .withGraphFetched('plaidItem') + .throwIfNotFound(); + // Can't continue if the bank account is not connected. + if (!oldAccount.plaidItem) { + throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED); + } + // Cannot continue if the bank account feeds is already paused. + if (oldAccount.plaidItem.isPaused) { + throw new ServiceError(ERRORS.BANK_ACCOUNT_FEEDS_ALREADY_PAUSED); + } return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - await PlaidItem.query().findById(oldAccount.plaidItem.id).patch({ + await PlaidItem.query(trx).findById(oldAccount.plaidItem.id).patch({ pausedAt: null, }); }); diff --git a/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx b/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx index 680568470..0808782c6 100644 --- a/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx +++ b/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx @@ -1,6 +1,9 @@ +import { Inject, Service } from 'typedi'; +import { Knex } from 'knex'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import UnitOfWork from '@/services/UnitOfWork'; -import { Inject, Service } from 'typedi'; +import { ServiceError } from '@/exceptions'; +import { ERRORS } from './types'; @Service() export class ResumeBankAccountFeeds { @@ -23,8 +26,16 @@ export class ResumeBankAccountFeeds { .findById(bankAccountId) .withGraphFetched('plaidItem'); + // Can't continue if the bank account is not connected. + if (!oldAccount.plaidItem) { + throw new ServiceError(ERRORS.BANK_ACCOUNT_NOT_CONNECTED); + } + // Cannot continue if the bank account feeds is already paused. + if (!oldAccount.plaidItem.isPaused) { + throw new ServiceError(ERRORS.BANK_ACCOUNT_FEEDS_ALREADY_RESUMED); + } return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { - await PlaidItem.query().findById(oldAccount.plaidItem.id).patch({ + await PlaidItem.query(trx).findById(oldAccount.plaidItem.id).patch({ pausedAt: new Date(), }); }); diff --git a/packages/server/src/services/Banking/BankAccounts/types.ts b/packages/server/src/services/Banking/BankAccounts/types.ts index d3198cc5c..cd21e490c 100644 --- a/packages/server/src/services/Banking/BankAccounts/types.ts +++ b/packages/server/src/services/Banking/BankAccounts/types.ts @@ -14,4 +14,6 @@ export interface IBankAccountDisconnectedEventPayload { export const ERRORS = { BANK_ACCOUNT_NOT_CONNECTED: 'BANK_ACCOUNT_NOT_CONNECTED', + BANK_ACCOUNT_FEEDS_ALREADY_PAUSED: 'BANK_ACCOUNT_FEEDS_ALREADY_PAUSED', + BANK_ACCOUNT_FEEDS_ALREADY_RESUMED: 'BANK_ACCOUNT_FEEDS_ALREADY_RESUMED', }; diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx index f01427c43..bbbc8a187 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx @@ -197,12 +197,15 @@ function AccountTransactionsActionsBar({ // Handle resume bank feeds syncing. const handleResumeFeedsSyncing = () => { - openAlert('resume-feeds-syncing-bank-accounnt'); + openAlert('resume-feeds-syncing-bank-accounnt', { + bankAccountId: accountId, + }); }; - // Handles pause bank feeds syncing. const handlePauseFeedsSyncing = () => { - openAlert('pause-feeds-syncing-bank-accounnt'); + openAlert('pause-feeds-syncing-bank-accounnt', { + bankAccountId: accountId, + }); }; return ( @@ -298,14 +301,21 @@ function AccountTransactionsActionsBar({ }} content={ - - + + + + + + + + + diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx index 5c4dbaf60..6bf930712 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx @@ -1,6 +1,5 @@ // @ts-nocheck import React from 'react'; -import intl from 'react-intl-universal'; import { Intent, Alert } from '@blueprintjs/core'; import { AppToaster, FormattedMessage as T } from '@/components'; @@ -30,10 +29,9 @@ function PauseFeedsBankAccountAlert({ const handleCancelActivateItem = () => { closeAlert(name); }; - // Handle confirm item activated. const handleConfirmItemActivate = () => { - pauseBankAccountFeeds(bankAccountId) + pauseBankAccountFeeds({ bankAccountId }) .then(() => { AppToaster.show({ message: 'The bank feeds of the bank account has been paused.', @@ -49,14 +47,17 @@ function PauseFeedsBankAccountAlert({ return ( } - confirmButtonText={} + confirmButtonText={'Pause bank feeds'} intent={Intent.WARNING} isOpen={isOpen} onCancel={handleCancelActivateItem} loading={isLoading} onConfirm={handleConfirmItemActivate} > -

Are you sure.

+

+ Are you sure want to pause bank feeds syncing of this bank account, you + can always resume it again? +

); } diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx index 23a867349..32a10bace 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx @@ -33,7 +33,7 @@ function ResumeFeedsBankAccountAlert({ // Handle confirm item activated. const handleConfirmItemActivate = () => { - resumeFeedsBankAccount(bankAccountId) + resumeFeedsBankAccount({ bankAccountId }) .then(() => { AppToaster.show({ message: 'The bank feeds of the bank account has been resumed.', @@ -49,14 +49,17 @@ function ResumeFeedsBankAccountAlert({ return ( } - confirmButtonText={} - intent={Intent.WARNING} + confirmButtonText={'Resume bank feeds'} + intent={Intent.SUCCESS} isOpen={isOpen} onCancel={handleCancelActivateItem} loading={isLoading} onConfirm={handleConfirmItemActivate} > -

Are you sure.

+

+ Are you sure want to resume bank feeds syncing of this bank account, you + can always pause it again? +

); } From fc0240c692fd144739ece557ddd6658d248087ef Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 4 Aug 2024 19:44:36 +0200 Subject: [PATCH 10/12] feat: confimation dialog on disconnecting bank account --- .../src/components/DialogsContainer.tsx | 6 +- packages/webapp/src/constants/dialogs.ts | 3 +- .../AccountTransactionsActionsBar.tsx | 23 +--- .../alerts/ResumeFeedsBankAccount.tsx | 1 - .../DisconnectBankAccountDialog.tsx | 42 +++++++ .../DisconnectBankAccountDialogContent.tsx | 104 ++++++++++++++++++ .../webapp/src/hooks/query/bank-accounts.ts | 10 +- 7 files changed, 167 insertions(+), 22 deletions(-) create mode 100644 packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialog.tsx create mode 100644 packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialogContent.tsx diff --git a/packages/webapp/src/components/DialogsContainer.tsx b/packages/webapp/src/components/DialogsContainer.tsx index 5c753c213..284c3cfbc 100644 --- a/packages/webapp/src/components/DialogsContainer.tsx +++ b/packages/webapp/src/components/DialogsContainer.tsx @@ -52,6 +52,7 @@ import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/Rec import PaymentMailDialog from '@/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog'; import { ExportDialog } from '@/containers/Dialogs/ExportDialog'; import { RuleFormDialog } from '@/containers/Banking/Rules/RuleFormDialog/RuleFormDialog'; +import { DisconnectBankAccountDialog } from '@/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialog'; /** * Dialogs container. @@ -148,7 +149,10 @@ export default function DialogsContainer() { - + + ); } diff --git a/packages/webapp/src/constants/dialogs.ts b/packages/webapp/src/constants/dialogs.ts index 07ed83d67..b86755cfb 100644 --- a/packages/webapp/src/constants/dialogs.ts +++ b/packages/webapp/src/constants/dialogs.ts @@ -75,5 +75,6 @@ export enum DialogsName { GeneralLedgerPdfPreview = 'GeneralLedgerPdfPreview', SalesTaxLiabilitySummaryPdfPreview = 'SalesTaxLiabilitySummaryPdfPreview', Export = 'Export', - BankRuleForm = 'BankRuleForm' + BankRuleForm = 'BankRuleForm', + DisconnectBankAccountConfirmation = 'DisconnectBankAccountConfirmation', } diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx index bbbc8a187..7a5ddd8fc 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx @@ -40,13 +40,13 @@ import withSettingsActions from '@/containers/Settings/withSettingsActions'; import { compose } from '@/utils'; import { - useDisconnectBankAccount, useUpdateBankAccount, useExcludeUncategorizedTransactions, useUnexcludeUncategorizedTransactions, } from '@/hooks/query/bank-rules'; import { withBanking } from '../withBanking'; import withAlertActions from '@/containers/Alert/withAlertActions'; +import { DialogsName } from '@/constants/dialogs'; function AccountTransactionsActionsBar({ // #withDialogActions @@ -71,7 +71,6 @@ function AccountTransactionsActionsBar({ // Refresh cashflow infinity transactions hook. const { refresh } = useRefreshCashflowTransactionsInfinity(); - const { mutateAsync: disconnectBankAccount } = useDisconnectBankAccount(); const { mutateAsync: updateBankAccount } = useUpdateBankAccount(); // Retrieves the money in/out buttons options. @@ -112,19 +111,9 @@ function AccountTransactionsActionsBar({ // Handles the bank account disconnect click. const handleDisconnectClick = () => { - disconnectBankAccount({ bankAccountId: accountId }) - .then(() => { - AppToaster.show({ - message: 'The bank account has been disconnected.', - intent: Intent.SUCCESS, - }); - }) - .catch((error) => { - AppToaster.show({ - message: 'Something went wrong.', - intent: Intent.DANGER, - }); - }); + openDialog(DialogsName.DisconnectBankAccountConfirmation, { + bankAccountId: accountId, + }); }; // handles the bank update button click. const handleBankUpdateClick = () => { @@ -301,7 +290,7 @@ function AccountTransactionsActionsBar({ }} content={ - + - + import('./DisconnectBankAccountDialogContent'), +); + +/** + * Payment mail dialog.X + */ +function DisconnectBankAccountDialogRoot({ + dialogName, + payload: { bankAccountId }, + isOpen, +}) { + return ( + + + + + + ); +} + +export const DisconnectBankAccountDialog = compose(withDialogRedux())( + DisconnectBankAccountDialogRoot, +); + +DisconnectBankAccountDialog.displayName = 'DisconnectBankAccountDialog'; diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialogContent.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialogContent.tsx new file mode 100644 index 000000000..0da3dd486 --- /dev/null +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialogContent.tsx @@ -0,0 +1,104 @@ +// @ts-nocheck +import * as Yup from 'yup'; +import { Button, Intent, Classes } from '@blueprintjs/core'; +import * as R from 'ramda'; +import { Form, Formik, FormikHelpers } from 'formik'; +import { AppToaster, FFormGroup, FInputGroup } from '@/components'; +import { useDisconnectBankAccount } from '@/hooks/query/bank-rules'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; + +interface DisconnectFormValues { + label: string; +} + +const initialValues = { + label: '', +}; + +const Schema = Yup.object().shape({ + label: Yup.string().required().label('Confirmation'), +}); + +interface DisconnectBankAccountDialogContentProps { + bankAccountId: number; +} + +function DisconnectBankAccountDialogContent({ + bankAccountId, + + // #withDialogActions + closeDialog, +}: DisconnectBankAccountDialogContentProps) { + const { mutateAsync: disconnectBankAccount } = useDisconnectBankAccount(); + + const handleSubmit = ( + values: DisconnectFormValues, + { setErrors, setSubmitting }: FormikHelpers, + ) => { + debugger; + setSubmitting(true); + + if (values.label !== 'DISCONNECT ACCOUNT') { + setErrors({ + label: 'The entered value is incorrect.', + }); + setSubmitting(false); + return; + } + disconnectBankAccount({ bankAccountId }) + .then(() => { + setSubmitting(false); + AppToaster.show({ + message: 'The bank account has been disconnected.', + intent: Intent.SUCCESS, + }); + closeDialog(DialogsName.DisconnectBankAccountConfirmation); + }) + .catch((error) => { + setSubmitting(false); + AppToaster.show({ + message: 'Something went wrong.', + intent: Intent.DANGER, + }); + }); + }; + + const handleCancelBtnClick = () => { + closeDialog(DialogsName.DisconnectBankAccountConfirmation); + }; + + return ( + +
+
+ + + +
+ +
+
+ + + +
+
+
+
+ ); +} + +export default R.compose(withDialogActions)(DisconnectBankAccountDialogContent); diff --git a/packages/webapp/src/hooks/query/bank-accounts.ts b/packages/webapp/src/hooks/query/bank-accounts.ts index a55863dda..e5ba6795d 100644 --- a/packages/webapp/src/hooks/query/bank-accounts.ts +++ b/packages/webapp/src/hooks/query/bank-accounts.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { UseMutationOptions, UseMutationResult, @@ -5,6 +6,7 @@ import { useMutation, } from 'react-query'; import useApiRequest from '../useRequest'; +import t from './types'; type PuaseFeedsBankAccountValues = { bankAccountId: number }; @@ -39,7 +41,9 @@ export function usePauseFeedsBankAccount( `/banking/bank_accounts/${values.bankAccountId}/pause_feeds`, ), { - onSuccess: (res, id) => {}, + onSuccess: (res, values) => { + queryClient.invalidateQueries([t.ACCOUNT, values.bankAccountId]); + }, ...options, }, ); @@ -78,7 +82,9 @@ export function useResumeFeedsBankAccount( `/banking/bank_accounts/${values.bankAccountId}/resume_feeds`, ), { - onSuccess: (res, id) => {}, + onSuccess: (res, values) => { + queryClient.invalidateQueries([t.ACCOUNT, values.bankAccountId]); + }, ...options, }, ); From f9cf6d325acd6455734ca0a656143ccd2c048513 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 4 Aug 2024 21:14:05 +0200 Subject: [PATCH 11/12] feat: pause bank account feeds --- .../Banking/BankAccountsController.ts | 4 ++-- .../src/services/Accounts/AccountTransform.ts | 18 ++++++++++++++ .../src/services/Accounts/GetAccount.ts | 5 +++- .../BankAccounts/PauseBankAccountFeeds.tsx | 2 +- .../BankAccounts/ResumeBankAccountFeeds.tsx | 2 +- .../services/Banking/Plaid/PlaidWebhooks.ts | 19 ++++++++++++++- .../AccountTransactionsActionsBar.tsx | 24 +++++++++++++------ .../src/style/pages/Dashboard/Dashboard.scss | 3 +++ 8 files changed, 64 insertions(+), 13 deletions(-) diff --git a/packages/server/src/api/controllers/Banking/BankAccountsController.ts b/packages/server/src/api/controllers/Banking/BankAccountsController.ts index 942c6b890..424c28857 100644 --- a/packages/server/src/api/controllers/Banking/BankAccountsController.ts +++ b/packages/server/src/api/controllers/Banking/BankAccountsController.ts @@ -127,7 +127,7 @@ export class BankAccountsController extends BaseController { } /** - * + * Resumes the bank account feeds sync. * @param {Request} req * @param {Response} res * @param {NextFunction} next @@ -154,7 +154,7 @@ export class BankAccountsController extends BaseController { } /** - * + * Pauses the bank account feeds sync. * @param {Request} req * @param {Response} res * @param {NextFunction} next diff --git a/packages/server/src/services/Accounts/AccountTransform.ts b/packages/server/src/services/Accounts/AccountTransform.ts index 28f3b74a5..1fde95fe4 100644 --- a/packages/server/src/services/Accounts/AccountTransform.ts +++ b/packages/server/src/services/Accounts/AccountTransform.ts @@ -18,9 +18,18 @@ export class AccountTransformer extends Transformer { 'flattenName', 'bankBalanceFormatted', 'lastFeedsUpdatedAtFormatted', + 'isFeedsPaused', ]; }; + /** + * Exclude attributes. + * @returns {string[]} + */ + public excludeAttributes = (): string[] => { + return ['plaidItem']; + }; + /** * Retrieves the flatten name with all dependants accounts names. * @param {IAccount} account - @@ -66,6 +75,15 @@ export class AccountTransformer extends Transformer { return this.formatDate(account.lastFeedsUpdatedAt); }; + /** + * Detarmines whether the bank account connection is paused. + * @param account + * @returns {boolean} + */ + protected isFeedsPaused = (account: any): boolean => { + return account.plaidItem?.isPaused || false; + }; + /** * Transformes the accounts collection to flat or nested array. * @param {IAccount[]} diff --git a/packages/server/src/services/Accounts/GetAccount.ts b/packages/server/src/services/Accounts/GetAccount.ts index 7da213328..c16c69459 100644 --- a/packages/server/src/services/Accounts/GetAccount.ts +++ b/packages/server/src/services/Accounts/GetAccount.ts @@ -25,7 +25,10 @@ export class GetAccount { const { accountRepository } = this.tenancy.repositories(tenantId); // Find the given account or throw not found error. - const account = await Account.query().findById(accountId).throwIfNotFound(); + const account = await Account.query() + .findById(accountId) + .withGraphFetched('plaidItem') + .throwIfNotFound(); const accountsGraph = await accountRepository.getDependencyGraph(); diff --git a/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx b/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx index 4a4f24a6c..3b16ecd76 100644 --- a/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx +++ b/packages/server/src/services/Banking/BankAccounts/PauseBankAccountFeeds.tsx @@ -37,7 +37,7 @@ export class PauseBankAccountFeeds { } return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { await PlaidItem.query(trx).findById(oldAccount.plaidItem.id).patch({ - pausedAt: null, + pausedAt: new Date(), }); }); } diff --git a/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx b/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx index 0808782c6..ab630a145 100644 --- a/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx +++ b/packages/server/src/services/Banking/BankAccounts/ResumeBankAccountFeeds.tsx @@ -36,7 +36,7 @@ export class ResumeBankAccountFeeds { } return this.uow.withTransaction(tenantId, async (trx: Knex.Transaction) => { await PlaidItem.query(trx).findById(oldAccount.plaidItem.id).patch({ - pausedAt: new Date(), + pausedAt: null, }); }); } diff --git a/packages/server/src/services/Banking/Plaid/PlaidWebhooks.ts b/packages/server/src/services/Banking/Plaid/PlaidWebhooks.ts index 5c3afb1ec..24a8c5d8d 100644 --- a/packages/server/src/services/Banking/Plaid/PlaidWebhooks.ts +++ b/packages/server/src/services/Banking/Plaid/PlaidWebhooks.ts @@ -1,11 +1,15 @@ import { Inject, Service } from 'typedi'; import { PlaidUpdateTransactions } from './PlaidUpdateTransactions'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; @Service() export class PlaidWebooks { @Inject() private updateTransactionsService: PlaidUpdateTransactions; + @Inject() + private tenancy: HasTenancyService; + /** * Listens to Plaid webhooks * @param {number} tenantId - Tenant Id. @@ -61,7 +65,7 @@ export class PlaidWebooks { plaidItemId: string ): void { console.log( - `WEBHOOK: TRANSACTIONS: ${webhookCode}: Plaid_item_id ${plaidItemId}: ${additionalInfo}` + `PLAID WEBHOOK: TRANSACTIONS: ${webhookCode}: Plaid_item_id ${plaidItemId}: ${additionalInfo}` ); } @@ -78,8 +82,21 @@ export class PlaidWebooks { plaidItemId: string, webhookCode: string ): Promise { + const { PlaidItem } = this.tenancy.models(tenantId); + const plaidItem = await PlaidItem.query() + .findById(plaidItemId) + .throwIfNotFound(); + switch (webhookCode) { case 'SYNC_UPDATES_AVAILABLE': { + if (plaidItem.isPaused) { + this.serverLogAndEmitSocket( + 'Plaid item syncing is paused.', + webhookCode, + plaidItemId + ); + return; + } // Fired when new transactions data becomes available. const { addedCount, modifiedCount, removedCount } = await this.updateTransactionsService.updateTransactions( diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx index 7a5ddd8fc..994b2c6e0 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/AccountTransactionsActionsBar.tsx @@ -78,6 +78,7 @@ function AccountTransactionsActionsBar({ const addMoneyOutOptions = useMemo(() => getAddMoneyOutOptions(), []); const isFeedsActive = !!currentAccount.is_feeds_active; + const isFeedsPaused = currentAccount.is_feeds_paused; const isSyncingOwner = currentAccount.is_syncing_owner; // Handle table row size change. @@ -244,7 +245,9 @@ function AccountTransactionsActionsBar({ } - intent={isFeedsActive ? Intent.SUCCESS : Intent.DANGER} + intent={ + isFeedsActive + ? isFeedsPaused + ? Intent.WARNING + : Intent.SUCCESS + : Intent.DANGER + } />
@@ -291,6 +300,11 @@ function AccountTransactionsActionsBar({ content={ + + + + + - + - - - - diff --git a/packages/webapp/src/style/pages/Dashboard/Dashboard.scss b/packages/webapp/src/style/pages/Dashboard/Dashboard.scss index da1a3249f..a6ea3968f 100644 --- a/packages/webapp/src/style/pages/Dashboard/Dashboard.scss +++ b/packages/webapp/src/style/pages/Dashboard/Dashboard.scss @@ -229,6 +229,9 @@ $dashboard-views-bar-height: 44px; } } + &.#{$ns}-minimal.#{$ns}-intent-warning{ + color: #cc7e25; + } &.button--blue-highlight { background-color: #ebfaff; From 8608144ec1731e88e4839758974f0a1a05be934b Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 4 Aug 2024 21:47:16 +0200 Subject: [PATCH 12/12] chore: components description --- .../AccountTransactions/alerts/PauseFeedsBankAccount.tsx | 2 +- .../AccountTransactions/alerts/ResumeFeedsBankAccount.tsx | 2 +- .../DisconnectBankAccountDialog/DisconnectBankAccountDialog.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx index 6bf930712..d86f875ec 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/PauseFeedsBankAccount.tsx @@ -10,7 +10,7 @@ import { usePauseFeedsBankAccount } from '@/hooks/query/bank-accounts'; import { compose } from '@/utils'; /** - * Item activate alert. + * Pause feeds of the bank account alert. */ function PauseFeedsBankAccountAlert({ name, diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx index 305660327..7d5211a84 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/alerts/ResumeFeedsBankAccount.tsx @@ -10,7 +10,7 @@ import { useResumeFeedsBankAccount } from '@/hooks/query/bank-accounts'; import { compose } from '@/utils'; /** - * + * Resume bank account feeds alert. */ function ResumeFeedsBankAccountAlert({ name, diff --git a/packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialog.tsx b/packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialog.tsx index 83c0cfb73..5f07fe70f 100644 --- a/packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialog.tsx +++ b/packages/webapp/src/containers/CashFlow/AccountTransactions/dialogs/DisconnectBankAccountDialog/DisconnectBankAccountDialog.tsx @@ -9,7 +9,7 @@ const DisconnectBankAccountDialogContent = React.lazy( ); /** - * Payment mail dialog.X + * Disconnect bank account confirmation dialog. */ function DisconnectBankAccountDialogRoot({ dialogName,