diff --git a/locales/extracted/en.json b/locales/extracted/en.json index 86881329..3a8209de 100644 --- a/locales/extracted/en.json +++ b/locales/extracted/en.json @@ -43,6 +43,7 @@ "common.noOptions": "No options found.", "common.portfolio": "Portfolio", "common.previousPage": "Previous page", + "common.records": "records", "common.remove": "Remove", "common.requiredFields": "(*) required fields.", "common.resources": "Resources", @@ -158,7 +159,7 @@ "ledgers.account.sheet.edit.title": "Edit {accountName}", "ledgers.account.sheet.tabs.details": "Account Details", "ledgers.accounts.createFirst": "Create first account", - "ledgers.accounts.subtitle": "{count} {count, plural, =0 {accounts found} one {acount found} other {accounts found}}", + "ledgers.accounts.subtitle": "{count} {count, plural, =0 {accounts found} one {account found} other {accounts found}}", "ledgers.accounts.title": "Accounts", "ledgers.assets.createButton": "Create your first Asset", "ledgers.assets.emptyResource": "You haven't added any Asset within this Ledger yet.", @@ -203,9 +204,10 @@ "ledgers.sheetCreate.description": "Fill in the data of the Ledger you wish to create.", "ledgers.sheetCreate.title": "New Ledger", "ledgers.subtitle": "Visualize and edit the Ledgers of your Organization.", + "ledgers.tab.accounts": "Accounts", "ledgers.tab.assets": "Assets", "ledgers.tab.overview": "Overview", - "ledgers.tab.portfolios-and-accounts": "Portfolios and Accounts", + "ledgers.tab.portfolios": "Portfolios", "ledgers.tab.products": "Products", "ledgers.title": "Ledgers", "ledgers.toast.accountCreated": "{accountName} account successfully created", @@ -250,6 +252,7 @@ "products.delete.description": "You are about to permanently delete this product. This action cannot be undone. Do you wish to continue?", "settings.tab.organizations": "Organizations", "settings.tab.otherSettings": "Other Settings", + "settings.tab.portfolios": "Portfolios", "settings.tab.products": "Products", "settings.tabs.organizations": "Organizations", "settings.tabs.others": "Others Settings", diff --git a/locales/extracted/pt.json b/locales/extracted/pt.json index f1f380f1..07b01f0d 100644 --- a/locales/extracted/pt.json +++ b/locales/extracted/pt.json @@ -168,7 +168,6 @@ "ledgers.portfolio.sheet.edit.description": "Visualize e edite os campos do portfólio.", "ledgers.portfolio.sheet.edit.title": "Editar {portfolioName}", "ledgers.portfolio.sheet.title": "Novo Portfólio", - "ledgers.tab.portfolios-and-accounts": "Portfólios e Contas", "portfolio.create": "Criar primeiro portfólio", "entity.portfolio.description": "Insira o identificador único da entidade associado a este portfólio", "entity.portfolio.entityId": "ID da Entidade", @@ -280,5 +279,9 @@ "ledgers.portfolio.sheet.tabs.details": "Detalhes do Portfólio", "ledgers.products.sheet.tabs.details": "Detalhes do Produto", "notFound.backToHome": "Voltar para Home", - "ledgers.sheet.tabs.details": "Detalhes do Ledger" + "ledgers.sheet.tabs.details": "Detalhes do Ledger", + "common.records": "registros", + "ledgers.tab.accounts": "Contas", + "ledgers.tab.portfolios": "Portfólios", + "settings.tab.portfolios": "Portfólios" } diff --git a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-portfolios-tab-content.tsx b/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-portfolios-tab-content.tsx deleted file mode 100644 index 8917e8c2..00000000 --- a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-portfolios-tab-content.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { AccountsContent } from './accounts-content' -import { PortfoliosContent } from './portfolios-content' - -export const AccountsPortfoliosTabContent = () => { - return ( -
- - -
- ) -} diff --git a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/portfolios-content.tsx b/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/portfolios-content.tsx deleted file mode 100644 index 96fc9cb3..00000000 --- a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/portfolios-content.tsx +++ /dev/null @@ -1,334 +0,0 @@ -import { Button } from '@/components/ui/button' -import { MoreVertical, Plus, ChevronDown, ChevronUp } from 'lucide-react' -import { PortfolioSheet } from './portfolios-sheet' -import { useParams } from 'next/navigation' -import { EntityBox } from '@/components/entity-box' -import { useCreateUpdateSheet } from '@/components/sheet/use-create-update-sheet' -import { PortfolioResponseDto } from '@/core/application/dto/portfolios-dto' -import { useDeletePortfolio, useListPortfolios } from '@/client/portfolios' -import { useOrganization } from '@/context/organization-provider/organization-provider-client' -import { useIntl } from 'react-intl' -import { isNil } from 'lodash' -import { - getCoreRowModel, - getFilteredRowModel, - useReactTable -} from '@tanstack/react-table' -import React, { useState } from 'react' -import { useConfirmDialog } from '@/components/confirmation-dialog/use-confirm-dialog' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuSeparator, - DropdownMenuTrigger -} from '@/components/ui/dropdown-menu' -import { - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableHeader, - TableRow -} from '@/components/ui/table' -import { truncateString } from '@/helpers' -import ConfirmationDialog from '@/components/confirmation-dialog' -import { Skeleton } from '@/components/ui/skeleton' -import { useAllPortfoliosAccounts } from '@/client/accounts' -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger -} from '@/components/ui/tooltip' -import useCustomToast from '@/hooks/use-custom-toast' -import { Arrow } from '@radix-ui/react-tooltip' - -export const PortfoliosContent = () => { - const intl = useIntl() - const { id: ledgerId } = useParams<{ id: string }>() - const { currentOrganization } = useOrganization() - const [columnFilters, setColumnFilters] = React.useState([]) - const [isTableExpanded, setIsTableExpanded] = useState(false) - const { showInfo } = useCustomToast() - - const { - data: portfoliosData, - refetch, - isLoading - } = useAllPortfoliosAccounts({ - organizationId: currentOrganization.id!, - ledgerId: ledgerId - }) - - const { mutate: deletePortfolio, isPending: deletePending } = - useDeletePortfolio({ - organizationId: currentOrganization.id!, - ledgerId, - onSuccess: () => { - handleDialogClose() - refetch() - } - }) - - const { handleDialogOpen, dialogProps, handleDialogClose } = useConfirmDialog( - { - onConfirm: (id: string) => deletePortfolio({ id }) - } - ) - - const { handleCreate, handleEdit, sheetProps } = - useCreateUpdateSheet() - - const table = useReactTable({ - data: portfoliosData?.items!, - columns: [ - { - accessorKey: 'name' - } - ], - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - onColumnFiltersChange: setColumnFilters, - state: { - columnFilters - } - }) - - if (isLoading) { - return - } - const handleCopyToClipboard = (value: string, message: string) => { - navigator.clipboard.writeText(value) - showInfo(message) - } - - return ( - <> - - - - - - - - - {!isNil(portfoliosData?.items) && - portfoliosData?.items.length > 0 && ( - - )} - - - - {!isNil(portfoliosData?.items) && - portfoliosData?.items.length > 0 && - isTableExpanded && ( - - - - - - {intl.formatMessage({ - id: 'common.id', - defaultMessage: 'ID' - })} - - - {intl.formatMessage({ - id: 'common.name', - defaultMessage: 'Name' - })} - - - {intl.formatMessage({ - id: 'common.metadata', - defaultMessage: 'Metadata' - })} - - - {intl.formatMessage({ - id: 'common.accounts', - defaultMessage: 'Accounts' - })} - - - {intl.formatMessage({ - id: 'common.actions', - defaultMessage: 'Actions' - })} - - - - - {table.getRowModel().rows.map((portfolio) => { - const displayId = - portfolio.original.id && portfolio.original.id.length > 8 - ? `${truncateString(portfolio.original.id, 8)}` - : portfolio.original.id - - return ( - - - - - - handleCopyToClipboard( - portfolio.original.id, - intl.formatMessage({ - id: 'ledgers.toast.copyId', - defaultMessage: - 'The id has been copied to your clipboard.' - }) - ) - } - > -

- {displayId} -

-
- -

- {portfolio.original.id} -

-

- {intl.formatMessage({ - id: 'ledgers.columnsTable.tooltipCopyText', - defaultMessage: 'Click to copy' - })} -

- -
-
-
-
- {portfolio.original.name} - - {intl.formatMessage( - { - id: 'common.table.accounts', - defaultMessage: - '{number, plural, =0 {No accounts} one {# account} other {# accounts}}' - }, - { - number: portfolio.original.accounts?.length || 0 - } - )} - - - {intl.formatMessage( - { - id: 'common.table.metadata', - defaultMessage: - '{number, plural, =0 {-} one {# record} other {# records}}' - }, - { - number: Object.entries( - portfolio.original.metadata || [] - ).length - } - )} - - - - - - - - - - handleEdit({ - ...portfolio.original, - entityId: portfolio.original.id, - status: { - ...portfolio.original.status, - description: - portfolio.original.status.description ?? - '' - } - } as PortfolioResponseDto) - } - > - {intl.formatMessage({ - id: `common.edit`, - defaultMessage: 'Edit' - })} - - - { - handleDialogOpen(portfolio?.original?.id!) - }} - > - {intl.formatMessage({ - id: `common.delete`, - defaultMessage: 'Delete' - })} - - - - -
- ) - })} -
-
-
- )} - - ) -} diff --git a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-data-table.tsx b/src/app/(routes)/ledgers/[id]/accounts/accounts-data-table.tsx similarity index 67% rename from src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-data-table.tsx rename to src/app/(routes)/ledgers/[id]/accounts/accounts-data-table.tsx index 6e44835c..65171473 100644 --- a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-data-table.tsx +++ b/src/app/(routes)/ledgers/[id]/accounts/accounts-data-table.tsx @@ -41,7 +41,6 @@ type AccountsTableProps = { } onDelete: (id: string, account: AccountType) => void refetch: () => void - isTableExpanded: boolean handleEdit: (account: AccountType) => void } @@ -118,11 +117,13 @@ const AccountRow: React.FC = ({ ) )} - {account.original.portfolioName} + + {account.original.portfolio?.name ?? } + - @@ -155,7 +156,6 @@ export const AccountsDataTable: React.FC = ({ accounts, table, onDelete, - isTableExpanded, handleEdit }) => { const intl = useIntl() @@ -167,66 +167,64 @@ export const AccountsDataTable: React.FC = ({ } return ( - <> - {!isNil(accounts?.items) && - accounts?.items.length > 0 && - isTableExpanded && ( - - - - - - {intl.formatMessage({ - id: 'common.id', - defaultMessage: 'ID' - })} - - - {intl.formatMessage({ - id: 'entity.account.name', - defaultMessage: 'Account Name' - })} - - - {intl.formatMessage({ - id: 'entity.account.currency', - defaultMessage: 'Assets' - })} - - - {intl.formatMessage({ - id: 'common.metadata', - defaultMessage: 'Metadata' - })} - - - {intl.formatMessage({ - id: 'common.portfolio', - defaultMessage: 'Portfolio' - })} - - - {intl.formatMessage({ - id: 'common.actions', - defaultMessage: 'Actions' - })} - - - - - {table.getRowModel().rows.map((account) => ( - - ))} - -
-
- )} - + + {!isNil(accounts?.items) && accounts?.items.length > 0 && ( + + + + + + {intl.formatMessage({ + id: 'common.id', + defaultMessage: 'ID' + })} + + + {intl.formatMessage({ + id: 'entity.account.name', + defaultMessage: 'Account Name' + })} + + + {intl.formatMessage({ + id: 'entity.account.currency', + defaultMessage: 'Assets' + })} + + + {intl.formatMessage({ + id: 'common.metadata', + defaultMessage: 'Metadata' + })} + + + {intl.formatMessage({ + id: 'common.portfolio', + defaultMessage: 'Portfolio' + })} + + + {intl.formatMessage({ + id: 'common.actions', + defaultMessage: 'Actions' + })} + + + + + {table.getRowModel().rows.map((account) => ( + + ))} + +
+
+ )} +
) } diff --git a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-sheet.tsx b/src/app/(routes)/ledgers/[id]/accounts/accounts-sheet.tsx similarity index 77% rename from src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-sheet.tsx rename to src/app/(routes)/ledgers/[id]/accounts/accounts-sheet.tsx index 9e7160c1..8cfa1cb7 100644 --- a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-sheet.tsx +++ b/src/app/(routes)/ledgers/[id]/accounts/accounts-sheet.tsx @@ -20,7 +20,7 @@ import { MetadataField } from '@/components/form/metadata-field' import { useListProducts } from '@/client/products' import { useCreateAccount, useUpdateAccount } from '@/client/accounts' import { useListPortfolios } from '@/client/portfolios' -import { isNil } from 'lodash' +import { isNil, omitBy } from 'lodash' import { useListAssets } from '@/client/assets' import useCustomToast from '@/hooks/use-custom-toast' import { accountSchema } from '@/schema/account' @@ -34,12 +34,13 @@ export type AccountSheetProps = DialogProps & { ledgerId: string mode: 'create' | 'edit' data?: AccountType | null - onSucess?: () => void + onSuccess?: () => void } -const defaultValues = { +const initialValues = { name: '', entityId: '', + portfolioId: '', productId: '', assetCode: '', alias: '', @@ -52,7 +53,7 @@ type FormData = z.infer export const AccountSheet = ({ mode, data, - onSucess, + onSuccess, onOpenChange, ...others }: AccountSheetProps) => { @@ -104,21 +105,14 @@ export const AccountSheet = ({ const form = useForm>({ resolver: zodResolver(accountSchema), - defaultValues: Object.assign( - {}, - defaultValues, - mode === 'create' ? { entityId: '' } : {} - ) + defaultValues: initialValues }) - const selectedPortfolioId = form.watch('portfolioId') - const { mutate: createAccount, isPending: createPending } = useCreateAccount({ organizationId: currentOrganization.id!, ledgerId, - portfolioId: selectedPortfolioId, onSuccess: (data) => { - onSucess?.() + onSuccess?.() onOpenChange?.(false) showSuccess( intl.formatMessage( @@ -144,9 +138,8 @@ export const AccountSheet = ({ organizationId: currentOrganization.id!, ledgerId, accountId: data?.id!, - portfolioId: data?.portfolioId!, onSuccess: (data) => { - onSucess?.() + onSuccess?.() onOpenChange?.(false) showSuccess( intl.formatMessage( @@ -171,19 +164,21 @@ export const AccountSheet = ({ const { showSuccess, showError } = useCustomToast() const handleSubmit = (data: FormData) => { + const cleanedData = omitBy(data, (value) => value === '' || isNil(value)) + if (mode === 'create') { createAccount({ - ...data, - portfolioId: data.portfolioId + ...cleanedData, + portfolioId: cleanedData.portfolioId }) } else if (mode === 'edit') { - const { portfolioId, assetCode, ...updateData } = data + const { type, portfolioId, assetCode, ...updateData } = cleanedData updateAccount({ ...updateData }) } - form.reset(defaultValues) + form.reset(initialValues) } React.useEffect(() => { @@ -198,7 +193,7 @@ export const AccountSheet = ({ }, [data]) return ( - <> + e.preventDefault()}> {mode === 'create' && ( @@ -290,78 +285,76 @@ export const AccountSheet = ({ })} /> - - - {intl.formatMessage({ - id: 'account.sheet.type.deposit', - defaultMessage: 'Deposit' - })} - - - - {intl.formatMessage({ - id: 'account.sheet.type.savings', - defaultMessage: 'Savings' - })} - - - - {intl.formatMessage({ - id: 'account.sheet.type.loans', - defaultMessage: 'Loans' - })} - - - - {intl.formatMessage({ - id: 'account.sheet.type.marketplace', - defaultMessage: 'Marketplace' - })} - - - - {intl.formatMessage({ - id: 'account.sheet.type.creditCard', - defaultMessage: 'CreditCard' - })} - - - - {intl.formatMessage({ - id: 'account.sheet.type.external', - defaultMessage: 'External' - })} - - - {mode === 'create' && ( - - )} - {mode === 'create' && ( - <> + + + + {intl.formatMessage({ + id: 'account.sheet.type.deposit', + defaultMessage: 'Deposit' + })} + + + + {intl.formatMessage({ + id: 'account.sheet.type.savings', + defaultMessage: 'Savings' + })} + + + + {intl.formatMessage({ + id: 'account.sheet.type.loans', + defaultMessage: 'Loans' + })} + + + + {intl.formatMessage({ + id: 'account.sheet.type.marketplace', + defaultMessage: 'Marketplace' + })} + + + + {intl.formatMessage({ + id: 'account.sheet.type.creditCard', + defaultMessage: 'CreditCard' + })} + + + + {intl.formatMessage({ + id: 'account.sheet.type.external', + defaultMessage: 'External' + })} + + + + ))} - + )} @@ -458,6 +450,6 @@ export const AccountSheet = ({ - + ) } diff --git a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-content.tsx b/src/app/(routes)/ledgers/[id]/accounts/accounts-tab-content.tsx similarity index 64% rename from src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-content.tsx rename to src/app/(routes)/ledgers/[id]/accounts/accounts-tab-content.tsx index f7cc2008..e74e63ca 100644 --- a/src/app/(routes)/ledgers/[id]/accounts-and-portfolios/accounts-content.tsx +++ b/src/app/(routes)/ledgers/[id]/accounts/accounts-tab-content.tsx @@ -1,13 +1,11 @@ -import React, { useState, useMemo, useCallback } from 'react' +import React, { useMemo } from 'react' import { Button } from '@/components/ui/button' -import { ChevronDown, ChevronUp, Plus } from 'lucide-react' +import { Plus } from 'lucide-react' import { useParams } from 'next/navigation' import { EntityBox } from '@/components/entity-box' import { useCreateUpdateSheet } from '@/components/sheet/use-create-update-sheet' -import { useListPortfolios } from '@/client/portfolios' import { useOrganization } from '@/context/organization-provider/organization-provider-client' import { useIntl } from 'react-intl' -import { isNil } from 'lodash' import { getCoreRowModel, getFilteredRowModel, @@ -15,47 +13,41 @@ import { } from '@tanstack/react-table' import { useConfirmDialog } from '@/components/confirmation-dialog/use-confirm-dialog' import ConfirmationDialog from '@/components/confirmation-dialog' -import { useAllPortfoliosAccounts, useDeleteAccount } from '@/client/accounts' +import { useAccountsWithPortfolios, useDeleteAccount } from '@/client/accounts' import { Skeleton } from '@/components/ui/skeleton' import useCustomToast from '@/hooks/use-custom-toast' import { AccountType } from '@/types/accounts-type' import { AccountSheet } from './accounts-sheet' import { AccountsDataTable } from './accounts-data-table' -export const AccountsContent = () => { +export const AccountsTabContent = () => { const intl = useIntl() const { id: ledgerId } = useParams<{ id: string }>() const { currentOrganization } = useOrganization() const [columnFilters, setColumnFilters] = React.useState([]) - const [isTableExpanded, setIsTableExpanded] = useState(false) - - const { data, refetch, isLoading } = useListPortfolios({ - organizationId: currentOrganization.id!, - ledgerId: ledgerId - }) const { data: accountsData, refetch: refetchAccounts, isLoading: isAccountsLoading - } = useAllPortfoliosAccounts({ + } = useAccountsWithPortfolios({ organizationId: currentOrganization.id!, ledgerId: ledgerId }) - const accountsList = useMemo( - () => ({ - items: - accountsData?.items.flatMap((portfolio) => - portfolio.accounts.map((account) => ({ - ...account, - portfolioName: portfolio.name, - portfolioId: portfolio.id - })) - ) || [] - }), - [accountsData] - ) + const accountsList: AccountType[] = useMemo(() => { + return ( + accountsData?.items.map((account: any) => ({ + ...account, + assetCode: account.assetCode, + parentAccountId: account.parentAccountId, + productId: account.productId, + metadata: account.metadata, + portfolioId: account.portfolio?.id, + portfolioName: account.portfolio?.name + })) || [] + ) + }, [accountsData]) const { showSuccess, showError } = useCustomToast() @@ -73,7 +65,6 @@ export const AccountsContent = () => { organizationId: currentOrganization.id!, ledgerId, accountId: selectedAccount?.id || '', - portfolioId: selectedAccount?.portfolioId || '', onSuccess: () => { handleDialogClose() refetchAccounts() @@ -83,7 +74,7 @@ export const AccountsContent = () => { id: 'ledgers.toast.accountDeleted', defaultMessage: '{accountName} account successfully deleted' }, - { accountName: (selectedAccount as AccountType)?.name! } + { accountName: selectedAccount?.name! } ) ) }, @@ -104,18 +95,58 @@ export const AccountsContent = () => { } = useCreateUpdateSheet() const handleEdit = (account: AccountType) => { - handleEditOriginal(account as unknown as AccountType) + handleEditOriginal(account) } const table = useReactTable({ - data: accountsList?.items!, + data: accountsList, columns: [ - { accessorKey: 'id' }, - { accessorKey: 'name' }, - { accessorKey: 'assets' }, - { accessorKey: 'metadata' }, - { accessorKey: 'portfolio' }, - { accessorKey: 'actions' } + { accessorKey: 'id', header: 'ID' }, + { + accessorKey: 'name', + header: intl.formatMessage({ + id: 'entity.account.name', + defaultMessage: 'Account Name' + }) + }, + { + accessorKey: 'assetCode', + header: intl.formatMessage({ + id: 'entity.account.currency', + defaultMessage: 'Assets' + }), + cell: (info) => info.getValue() || '-' + }, + { + accessorKey: 'metadata', + header: intl.formatMessage({ + id: 'common.metadata', + defaultMessage: 'Metadata' + }), + cell: (info) => { + const metadata = info.getValue() || {} + const count = Object.keys(metadata).length + return count > 0 + ? `${count} ${intl.formatMessage({ id: 'common.records', defaultMessage: 'records' })}` + : '-' + } + }, + { + accessorKey: 'portfolio.name', + header: intl.formatMessage({ + id: 'common.portfolio', + defaultMessage: 'Portfolio' + }), + cell: (info) => info.getValue() || '-' + }, + { + accessorKey: 'actions', + header: intl.formatMessage({ + id: 'common.actions', + defaultMessage: 'Actions' + }), + cell: () => null + } ], getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), @@ -125,12 +156,12 @@ export const AccountsContent = () => { } }) - if (isLoading) { + if (isAccountsLoading) { return } return ( - <> + { @@ -157,15 +188,15 @@ export const AccountsContent = () => { defaultMessage: 'Accounts' })} subtitle={ - accountsList?.items?.length !== undefined + accountsList.length !== undefined ? intl.formatMessage( { id: 'ledgers.accounts.subtitle', defaultMessage: - '{count} {count, plural, =0 {accounts found} one {acount found} other {accounts found}}' + '{count} {count, plural, =0 {accounts found} one {account found} other {accounts found}}' }, { - count: accountsList.items.length + count: accountsList.length } ) : undefined @@ -175,9 +206,9 @@ export const AccountsContent = () => { - {!isNil(accountsList?.items) && accountsList?.items?.length > 0 && ( - - )} {accountsList && ( )} - + ) } diff --git a/src/app/(routes)/ledgers/[id]/ledger-details-view.tsx b/src/app/(routes)/ledgers/[id]/ledger-details-view.tsx index 07a0b486..222cdfc1 100644 --- a/src/app/(routes)/ledgers/[id]/ledger-details-view.tsx +++ b/src/app/(routes)/ledgers/[id]/ledger-details-view.tsx @@ -9,7 +9,6 @@ import { cn } from '@/lib/utils' import { useFormState } from '@/context/form-details-context' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' import { useIntl } from 'react-intl' -import { AccountsPortfoliosTabContent } from './accounts-and-portfolios/accounts-portfolios-tab-content' import { ProductsTabContent } from './products/products-tab-content' import { useTabs } from '@/hooks/use-tabs' import { getBreadcrumbPaths } from '@/components/breadcrumb/get-breadcrumb-paths' @@ -19,12 +18,15 @@ import { useUpdateLedger } from '@/client/ledgers' import { LedgerDetailsSkeleton } from './ledger-details-skeleton' import { OverviewTabContent } from './overview/overview-tab-content' import { AssetsTabContent } from './assets/assets-tab-content' +import { PortfoliosTabContent } from './portfolios/portfolios-tab-content' +import { AccountsTabContent } from './accounts/accounts-tab-content' const TAB_VALUES = { OVERVIEW: 'overview', ASSETS: 'assets', - PORTFOLIOS_AND_ACCOUNTS: 'portfolios-and-accounts', - PRODUCTS: 'products' + PRODUCTS: 'products', + PORTFOLIOS: 'portfolios', + ACCOUNTS: 'accounts' } const DEFAULT_TAB_VALUE = TAB_VALUES.OVERVIEW @@ -94,17 +96,24 @@ const LedgerDetailsView = ({ data }: LedgerDetailsViewProps) => { }, { name: intl.formatMessage({ - id: `ledgers.tab.portfolios-and-accounts`, - defaultMessage: 'Portfolios and Accounts' + id: `settings.tab.products`, + defaultMessage: 'Products' }), - active: () => activeTab === TAB_VALUES.PORTFOLIOS_AND_ACCOUNTS + active: () => activeTab === TAB_VALUES.PRODUCTS }, { name: intl.formatMessage({ - id: `settings.tab.products`, - defaultMessage: 'Products' + id: `settings.tab.portfolios`, + defaultMessage: 'Portfolios' }), - active: () => activeTab === TAB_VALUES.PRODUCTS + active: () => activeTab === TAB_VALUES.PORTFOLIOS + }, + { + name: intl.formatMessage({ + id: `ledgers.tab.accounts`, + defaultMessage: 'Accounts' + }), + active: () => activeTab === TAB_VALUES.ACCOUNTS } ]) @@ -160,17 +169,24 @@ const LedgerDetailsView = ({ data }: LedgerDetailsViewProps) => { })} - + + {intl.formatMessage({ + id: 'ledgers.tab.products', + defaultMessage: 'Products' + })} + + + {intl.formatMessage({ - id: 'ledgers.tab.portfolios-and-accounts', - defaultMessage: 'Portfolios and Accounts' + id: 'ledgers.tab.portfolios', + defaultMessage: 'Portfolios' })} - + {intl.formatMessage({ - id: 'ledgers.tab.products', - defaultMessage: 'Products' + id: 'ledgers.tab.accounts', + defaultMessage: 'Accounts' })} @@ -183,13 +199,17 @@ const LedgerDetailsView = ({ data }: LedgerDetailsViewProps) => { - - - - + + + + + + + + { + const intl = useIntl() + const { id: ledgerId } = useParams<{ id: string }>() + const { currentOrganization } = useOrganization() + const [columnFilters, setColumnFilters] = React.useState([]) + const { showInfo } = useCustomToast() + + const { + data: portfoliosData, + refetch, + isLoading + } = usePortfoliosWithAccounts({ + organizationId: currentOrganization.id!, + ledgerId: ledgerId + }) + + const { mutate: deletePortfolio, isPending: deletePending } = + useDeletePortfolio({ + organizationId: currentOrganization.id!, + ledgerId, + onSuccess: () => { + handleDialogClose() + refetch() + } + }) + + const { handleDialogOpen, dialogProps, handleDialogClose } = useConfirmDialog( + { + onConfirm: (id: string) => deletePortfolio({ id }) + } + ) + + const { handleCreate, handleEdit, sheetProps } = + useCreateUpdateSheet() + + const table = useReactTable({ + data: portfoliosData?.items!, + columns: [ + { + accessorKey: 'name' + } + ], + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnFiltersChange: setColumnFilters, + state: { + columnFilters + } + }) + + if (isLoading) { + return + } + const handleCopyToClipboard = (value: string, message: string) => { + navigator.clipboard.writeText(value) + showInfo(message) + } + + return ( + <> + + + + + + + + + + + + {!isNil(portfoliosData?.items) && portfoliosData?.items.length > 0 && ( + + + + + + {intl.formatMessage({ + id: 'common.id', + defaultMessage: 'ID' + })} + + + {intl.formatMessage({ + id: 'common.name', + defaultMessage: 'Name' + })} + + + {intl.formatMessage({ + id: 'common.metadata', + defaultMessage: 'Metadata' + })} + + + {intl.formatMessage({ + id: 'common.accounts', + defaultMessage: 'Accounts' + })} + + + {intl.formatMessage({ + id: 'common.actions', + defaultMessage: 'Actions' + })} + + + + + {table.getRowModel().rows.map((portfolio) => { + const metadataCount = Object.entries( + portfolio.original.metadata || [] + ).length + const displayId = + portfolio.original.id && portfolio.original.id.length > 8 + ? `${truncateString(portfolio.original.id, 8)}` + : portfolio.original.id + + return ( + + + + + + handleCopyToClipboard( + portfolio.original.id, + intl.formatMessage({ + id: 'ledgers.toast.copyId', + defaultMessage: + 'The id has been copied to your clipboard.' + }) + ) + } + > +

+ {displayId} +

+
+ +

+ {portfolio.original.id} +

+

+ {intl.formatMessage({ + id: 'ledgers.columnsTable.tooltipCopyText', + defaultMessage: 'Click to copy' + })} +

+ +
+
+
+
+ {portfolio.original.name} + + {metadataCount === 0 ? ( + + ) : ( + intl.formatMessage( + { + id: 'common.table.metadata', + defaultMessage: + '{number, plural, =0 {-} one {# record} other {# records}}' + }, + { + number: metadataCount + } + ) + )} + + + {intl.formatMessage( + { + id: 'common.table.accounts', + defaultMessage: + '{number, plural, =0 {No accounts} one {# account} other {# accounts}}' + }, + { + number: portfolio.original.accounts?.length || 0 + } + )} + + + + + + + + + + handleEdit({ + ...portfolio.original, + entityId: portfolio.original.id, + status: { + ...portfolio.original.status, + description: + portfolio.original.status.description ?? '' + } + } as PortfolioResponseDto) + } + > + {intl.formatMessage({ + id: `common.edit`, + defaultMessage: 'Edit' + })} + + + { + handleDialogOpen(portfolio?.original?.id!) + }} + > + {intl.formatMessage({ + id: `common.delete`, + defaultMessage: 'Delete' + })} + + + + +
+ ) + })} +
+
+
+ )} + + ) +} diff --git a/src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts-portfolios/route.ts b/src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts-portfolios/route.ts new file mode 100644 index 00000000..679b62fe --- /dev/null +++ b/src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts-portfolios/route.ts @@ -0,0 +1,38 @@ +import { container } from '@/core/infrastructure/container-registry/container-registry' +import { apiErrorHandler } from '@/app/api/utils/api-error-handler' +import { NextResponse } from 'next/server' +import { + FetchAccountsWithPortfolios, + FetchAccountsWithPortfoliosUseCase +} from '@/core/application/use-cases/accounts-with-portfolios/fetch-accounts-with-portfolios-use-case' + +const fetchAccountsWithPortfoliosUseCase = + container.get(FetchAccountsWithPortfoliosUseCase) + +export async function GET( + request: Request, + { params }: { params: { id: string; ledgerId: string; portfolioId: string } } +) { + try { + const { searchParams } = new URL(request.url) + const limit = Number(searchParams.get('limit')) || 100 + const page = Number(searchParams.get('page')) || 1 + const organizationId = params.id + + const accountsWithPortfolios = + await fetchAccountsWithPortfoliosUseCase.execute( + organizationId, + params.ledgerId, + limit, + page + ) + + return NextResponse.json(accountsWithPortfolios) + } catch (error: any) { + console.error('Error fetching accounts with portfolios', error) + + const { message, status } = await apiErrorHandler(error) + + return NextResponse.json({ message }, { status }) + } +} diff --git a/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/[portfolioId]/accounts/[accountId]/route.ts b/src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts/[accountId]/route.ts similarity index 85% rename from src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/[portfolioId]/accounts/[accountId]/route.ts rename to src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts/[accountId]/route.ts index 78d179c6..2ebfc20c 100644 --- a/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/[portfolioId]/accounts/[accountId]/route.ts +++ b/src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts/[accountId]/route.ts @@ -32,18 +32,16 @@ export async function GET( params: { id: string ledgerId: string - portfolioId: string accountId: string } } ) { try { - const { id: organizationId, ledgerId, portfolioId, accountId } = params + const { id: organizationId, ledgerId, accountId } = params const account = await getAccountByIdUseCase.execute( organizationId, ledgerId, - portfolioId, accountId ) @@ -64,19 +62,17 @@ export async function PATCH( params: { id: string ledgerId: string - portfolioId: string accountId: string } } ) { try { const body = await request.json() - const { id: organizationId, ledgerId, portfolioId, accountId } = params + const { id: organizationId, ledgerId, accountId } = params const accountUpdated = await updateAccountUseCase.execute( organizationId, ledgerId, - portfolioId, accountId, body ) @@ -98,7 +94,6 @@ export async function DELETE( params: { id: string ledgerId: string - portfolioId: string accountId: string } } @@ -106,15 +101,9 @@ export async function DELETE( try { const organizationId = params.id! const ledgerId = params.ledgerId - const portfolioId = params.portfolioId const accountId = params.accountId - await deleteAccountUseCase.execute( - organizationId, - ledgerId, - portfolioId, - accountId - ) + await deleteAccountUseCase.execute(organizationId, ledgerId, accountId) return NextResponse.json({}, { status: 200 }) } catch (error: any) { diff --git a/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/[portfolioId]/accounts/route.ts b/src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts/route.ts similarity index 83% rename from src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/[portfolioId]/accounts/route.ts rename to src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts/route.ts index e9f38cfc..d040fdf0 100644 --- a/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/[portfolioId]/accounts/route.ts +++ b/src/app/api/organizations/[id]/ledgers/[ledgerId]/accounts/route.ts @@ -11,7 +11,6 @@ import { import { NextResponse } from 'next/server' -// Update use case references const createAccountUseCase: CreateAccount = container.get(CreateAccountUseCase) @@ -26,23 +25,25 @@ export async function GET( params: { id: string ledgerId: string - organizationId: string - portfolioId: string } } ) { try { const { searchParams } = new URL(request.url) + const { id: organizationId, ledgerId } = params const limit = Number(searchParams.get('limit')) || 10 const page = Number(searchParams.get('page')) || 1 - const organizationId = params.id - const ledgerId = params.ledgerId - const portfolioId = params.portfolioId + + if (!organizationId || !ledgerId) { + return NextResponse.json( + { message: 'Missing required parameters' }, + { status: 400 } + ) + } const accounts = await fetchAllAccountsUseCase.execute( organizationId, ledgerId, - portfolioId, page, limit ) @@ -59,18 +60,17 @@ export async function GET( export async function POST( request: Request, - { params }: { params: { id: string; ledgerId: string; portfolioId: string } } + { params }: { params: { id: string; ledgerId: string } } ) { try { const body = await request.json() + console.log(body) const organizationId = params.id const ledgerId = params.ledgerId - const portfolioId = params.portfolioId const account = await createAccountUseCase.execute( organizationId, ledgerId, - portfolioId, body ) diff --git a/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios-accounts/route.ts b/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios-accounts/route.ts new file mode 100644 index 00000000..693e957e --- /dev/null +++ b/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios-accounts/route.ts @@ -0,0 +1,39 @@ +import { container } from '@/core/infrastructure/container-registry/container-registry' +import { apiErrorHandler } from '@/app/api/utils/api-error-handler' +import { NextResponse } from 'next/server' +import { + FetchPortfoliosWithAccounts, + FetchPortfoliosWithAccountsUseCase +} from '@/core/application/use-cases/portfolios-with-accounts/fetch-portfolios-with-account-use-case' + +const fetchPortfoliosWithAccountsUseCase = + container.get(FetchPortfoliosWithAccountsUseCase) + +export async function GET( + request: Request, + { params }: { params: { id: string; ledgerId: string } } +) { + try { + const { searchParams } = new URL(request.url) + const limit = Number(searchParams.get('limit')) || 100 + const page = Number(searchParams.get('page')) || 1 + const organizationId = params.id + const ledgerId = params.ledgerId + + const portfoliosWithAccounts = + await fetchPortfoliosWithAccountsUseCase.execute( + organizationId, + ledgerId, + limit, + page + ) + + return NextResponse.json(portfoliosWithAccounts) + } catch (error: any) { + console.error('Error fetching portfolios with accounts', error) + + const { message, status } = await apiErrorHandler(error) + + return NextResponse.json({ message }, { status }) + } +} diff --git a/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/portfolios-accounts/route.ts b/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/portfolios-accounts/route.ts deleted file mode 100644 index ed751f27..00000000 --- a/src/app/api/organizations/[id]/ledgers/[ledgerId]/portfolios/portfolios-accounts/route.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { container } from '@/core/infrastructure/container-registry/container-registry' -import { apiErrorHandler } from '@/app/api/utils/api-error-handler' -import { - FetchAllPortfoliosAccounts, - FetchAllPortfoliosAccountsUseCase -} from '@/core/application/use-cases/portfolios-accounts/fetch-portfolios-accounts-use-case' -import { NextResponse } from 'next/server' - -const fetchAllPortfoliosAccountsUseCases = - container.get(FetchAllPortfoliosAccountsUseCase) - -export async function GET( - request: Request, - { params }: { params: { id: string; ledgerId: string; portfolioId: string } } -) { - try { - const { searchParams } = new URL(request.url) - const limit = Number(searchParams.get('limit')) || 10 - const page = Number(searchParams.get('page')) || 1 - const organizationId = params.id - - const ledgers = await fetchAllPortfoliosAccountsUseCases.execute( - organizationId, - params.ledgerId, - limit, - page - ) - - return NextResponse.json(ledgers) - } catch (error: any) { - console.error('Error fetching all ledgers', error) - - const { message, status } = await apiErrorHandler(error) - - return NextResponse.json({ message }, { status }) - } -} diff --git a/src/client/accounts.ts b/src/client/accounts.ts index 627d80c4..c46abee4 100644 --- a/src/client/accounts.ts +++ b/src/client/accounts.ts @@ -1,5 +1,5 @@ +import { AccountResponseDto } from '@/core/application/dto/account-dto' import { PaginationDto } from '@/core/application/dto/pagination-dto' -import { PortfolioViewResponseDTO } from '@/core/application/dto/portfolio-view-dto' import { AccountEntity } from '@/core/domain/entities/account-entity' import { deleteFetcher, @@ -10,45 +10,42 @@ import { import { useMutation, UseMutationOptions, - useQuery, - UseQueryOptions + useQuery } from '@tanstack/react-query' type UseListAccountsProps = { organizationId: string ledgerId: string - portfolioId: string } export const useListAccounts = ({ organizationId, ledgerId, - portfolioId, ...options }: UseListAccountsProps) => { return useQuery>({ - queryKey: [organizationId, ledgerId, portfolioId, 'accounts'], + queryKey: [organizationId, ledgerId, 'accounts'], queryFn: getFetcher( - `/api/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts` + `/api/organizations/${organizationId}/ledgers/${ledgerId}/accounts` ), ...options }) } -type UseAllPortfoliosAccountsProps = { +type UseAccountsWithPortfoliosProps = { organizationId: string ledgerId: string } -export const useAllPortfoliosAccounts = ({ +export const useAccountsWithPortfolios = ({ organizationId, ledgerId, ...options -}: UseAllPortfoliosAccountsProps) => { - return useQuery>({ - queryKey: [organizationId, ledgerId, 'portfolios-accounts'], +}: UseAccountsWithPortfoliosProps) => { + return useQuery>({ + queryKey: [organizationId, ledgerId, 'accounts-with-portfolios'], queryFn: getFetcher( - `/api/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/portfolios-accounts` + `/api/organizations/${organizationId}/ledgers/${ledgerId}/accounts-portfolios` ), ...options }) @@ -57,21 +54,19 @@ export const useAllPortfoliosAccounts = ({ type UseDeleteAccountProps = UseMutationOptions & { organizationId: string ledgerId: string - portfolioId: string accountId: string } export const useDeleteAccount = ({ organizationId, ledgerId, - portfolioId, accountId, ...options }: UseDeleteAccountProps) => { return useMutation({ - mutationKey: [organizationId, ledgerId, portfolioId, accountId], + mutationKey: [organizationId, ledgerId, accountId], mutationFn: deleteFetcher( - `/api/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts` + `/api/organizations/${organizationId}/ledgers/${ledgerId}/accounts` ), ...options }) @@ -80,19 +75,17 @@ export const useDeleteAccount = ({ type UseCreateAccountProps = UseMutationOptions & { organizationId: string ledgerId: string - portfolioId: string } export const useCreateAccount = ({ organizationId, ledgerId, - portfolioId, ...options }: UseCreateAccountProps) => { return useMutation({ - mutationKey: [organizationId, ledgerId, portfolioId], + mutationKey: [organizationId, ledgerId], mutationFn: postFetcher( - `/api/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts` + `/api/organizations/${organizationId}/ledgers/${ledgerId}/accounts` ), ...options }) @@ -101,21 +94,19 @@ export const useCreateAccount = ({ type UseUpdateAccountProps = UseMutationOptions & { organizationId: string ledgerId: string - portfolioId: string accountId: string } export const useUpdateAccount = ({ organizationId, ledgerId, - portfolioId, accountId, ...options }: UseUpdateAccountProps) => { return useMutation({ - mutationKey: [organizationId, ledgerId, portfolioId, accountId], + mutationKey: [organizationId, ledgerId, accountId], mutationFn: patchFetcher( - `/api/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts/${accountId}` + `/api/organizations/${organizationId}/ledgers/${ledgerId}/accounts/${accountId}` ), ...options }) diff --git a/src/client/portfolios.ts b/src/client/portfolios.ts index 17dd047e..ad3457de 100644 --- a/src/client/portfolios.ts +++ b/src/client/portfolios.ts @@ -1,8 +1,6 @@ import { PaginationDto } from '@/core/application/dto/pagination-dto' -import { - PortfolioResponseDto, - UpdatePortfolioDto -} from '@/core/application/dto/portfolios-dto' +import { PortfolioViewResponseDTO } from '@/core/application/dto/portfolio-view-dto' +import { PortfolioResponseDto } from '@/core/application/dto/portfolios-dto' import { PortfolioEntity } from '@/core/domain/entities/portfolios-entity' import { getFetcher, @@ -21,6 +19,25 @@ type UseCreatePortfolioProps = UseMutationOptions & { ledgerId: string } +type UsePortfoliosWithAccountsProps = { + organizationId: string + ledgerId: string +} + +export const usePortfoliosWithAccounts = ({ + organizationId, + ledgerId, + ...options +}: UsePortfoliosWithAccountsProps) => { + return useQuery>({ + queryKey: [organizationId, ledgerId, 'portfolios-with-accounts'], + queryFn: getFetcher( + `/api/organizations/${organizationId}/ledgers/${ledgerId}/portfolios-accounts` + ), + ...options + }) +} + export const useCreatePortfolio = ({ organizationId, ledgerId, diff --git a/src/core/application/dto/account-dto.ts b/src/core/application/dto/account-dto.ts index 4c91f3c2..6f5667e1 100644 --- a/src/core/application/dto/account-dto.ts +++ b/src/core/application/dto/account-dto.ts @@ -7,6 +7,7 @@ export interface CreateAccountDto { type: string entityId?: string | null parentAccountId?: string | null + portfolioId?: string | null productId?: string status: { code: string @@ -25,7 +26,7 @@ export interface AccountResponseDto { type: string entityId: string parentAccountId: string - portfolioId: string + portfolioId?: string | null productId: string status: StatusDto metadata: Record @@ -42,6 +43,7 @@ export interface UpdateAccountDto { entityId?: string parentAccountId?: string productId?: string + portfolioId?: string status?: StatusDto metadata?: Record } diff --git a/src/core/application/dto/portfolios-dto.ts b/src/core/application/dto/portfolios-dto.ts index 5b3c4f9c..e333250a 100644 --- a/src/core/application/dto/portfolios-dto.ts +++ b/src/core/application/dto/portfolios-dto.ts @@ -1,3 +1,4 @@ +import { AccountResponseDto } from './account-dto' import { StatusDto } from './status.dto' export interface CreatePortfolioDto { @@ -20,6 +21,7 @@ export interface PortfolioResponseDto { createdAt: Date updatedAt: Date deletedAt: Date | null + accounts?: AccountResponseDto[] } export interface UpdatePortfolioDto { diff --git a/src/core/application/mappers/account-mapper.ts b/src/core/application/mappers/account-mapper.ts index 3465d2c2..10cc51ba 100644 --- a/src/core/application/mappers/account-mapper.ts +++ b/src/core/application/mappers/account-mapper.ts @@ -28,7 +28,7 @@ export class AccountMapper { assetCode: account.assetCode, parentAccountId: account.parentAccountId!, productId: account.productId!, - portfolioId: account.portfolioId! + portfolioId: account.portfolioId } } @@ -44,6 +44,7 @@ export class AccountMapper { status: dto.status!, parentAccountId: dto.parentAccountId, productId: dto.productId, + portfolioId: dto.portfolioId, metadata: dto.metadata ?? {} } } diff --git a/src/core/application/mappers/portfolio-mapper.ts b/src/core/application/mappers/portfolio-mapper.ts index 540677b4..4b70c29d 100644 --- a/src/core/application/mappers/portfolio-mapper.ts +++ b/src/core/application/mappers/portfolio-mapper.ts @@ -2,6 +2,8 @@ import { PortfolioEntity } from '@/core/domain/entities/portfolios-entity' import { CreatePortfolioDto, PortfolioResponseDto } from '../dto/portfolios-dto' import { PaginationEntity } from '@/core/domain/entities/pagination-entity' import { PaginationMapper } from './pagination-mapper' +import { AccountMapper } from './account-mapper' +import { AccountResponseDto } from '../dto/account-dto' export class PortfolioMapper { public static toDomain(dto: CreatePortfolioDto): PortfolioEntity { @@ -40,4 +42,14 @@ export class PortfolioMapper { ): PaginationEntity { return PaginationMapper.toResponseDto(result, PortfolioMapper.toResponseDto) } + + public static toDtoWithAccounts( + portfolio: PortfolioEntity, + accounts: AccountResponseDto[] + ): PortfolioResponseDto { + return { + ...PortfolioMapper.toResponseDto(portfolio), + accounts: accounts.map(AccountMapper.toDto) + } + } } diff --git a/src/core/application/use-cases/portfolios-accounts/fetch-portfolios-accounts-use-case.ts b/src/core/application/use-cases/accounts-with-portfolios/fetch-accounts-with-portfolios-use-case.ts similarity index 50% rename from src/core/application/use-cases/portfolios-accounts/fetch-portfolios-accounts-use-case.ts rename to src/core/application/use-cases/accounts-with-portfolios/fetch-accounts-with-portfolios-use-case.ts index b15e7d87..92ba4bd2 100644 --- a/src/core/application/use-cases/portfolios-accounts/fetch-portfolios-accounts-use-case.ts +++ b/src/core/application/use-cases/accounts-with-portfolios/fetch-accounts-with-portfolios-use-case.ts @@ -1,14 +1,14 @@ import { FetchAllAccountsRepository } from '@/core/domain/repositories/accounts/fetch-all-accounts-repository' +import { FetchAllPortfoliosRepository } from '@/core/domain/repositories/portfolios/fetch-all-portfolio-repository' import { PaginationDto } from '../../dto/pagination-dto' import { PaginationEntity } from '@/core/domain/entities/pagination-entity' -import { FetchAllPortfoliosRepository } from '@/core/domain/repositories/portfolios/fetch-all-portfolio-repository' import { PortfolioEntity } from '@/core/domain/entities/portfolios-entity' import { AccountEntity } from '@/core/domain/entities/account-entity' import { PortfolioViewResponseDTO } from '../../dto/portfolio-view-dto' import { AccountMapper } from '../../mappers/account-mapper' import { inject, injectable } from 'inversify' -export interface FetchAllPortfoliosAccounts { +export interface FetchAccountsWithPortfolios { execute: ( organizationId: string, ledgerId: string, @@ -18,8 +18,8 @@ export interface FetchAllPortfoliosAccounts { } @injectable() -export class FetchAllPortfoliosAccountsUseCase - implements FetchAllPortfoliosAccounts +export class FetchAccountsWithPortfoliosUseCase + implements FetchAccountsWithPortfolios { constructor( @inject(FetchAllPortfoliosRepository) @@ -34,6 +34,14 @@ export class FetchAllPortfoliosAccountsUseCase limit: number, page: number ): Promise> { + const accountsResult: PaginationEntity = + await this.fetchAllAccountsRepository.fetchAll( + organizationId, + ledgerId, + limit, + page + ) + const portfoliosResult: PaginationEntity = await this.fetchAllPortfoliosRepository.fetchAll( organizationId, @@ -42,48 +50,35 @@ export class FetchAllPortfoliosAccountsUseCase page ) - let portfoliosAccountResponseDTO: PaginationDto = - { - items: [], - limit: portfoliosResult.limit, - page: portfoliosResult.page + const portfolioMap = new Map() + portfoliosResult.items.forEach((portfolio) => { + if (portfolio.id) { + portfolioMap.set(portfolio.id, portfolio) } + }) - const portfolioItems = portfoliosResult.items || [] + const accountsWithPortfolio: any[] = accountsResult.items.map((account) => { + const portfolio = account.portfolioId + ? portfolioMap.get(account.portfolioId) + : null - portfoliosAccountResponseDTO.items = await Promise.all( - portfolioItems.map(async (portfolio) => { - const accountsResult: PaginationEntity = - await this.fetchAllAccountsRepository.fetchAll( - organizationId, - ledgerId, - portfolio.id!, - limit, - page - ) - - const portfolioAccounts: PortfolioViewResponseDTO = { - id: portfolio.id!, - ledgerId: portfolio.ledgerId!, - organizationId: portfolio.organizationId!, - name: portfolio.name!, - status: { - code: portfolio.status!.code!, - description: portfolio.status!.description! - }, - metadata: portfolio.metadata!, - createdAt: portfolio.createdAt!, - updatedAt: portfolio.updatedAt!, - deletedAt: portfolio.deletedAt!, - accounts: accountsResult.items - ? accountsResult.items.map(AccountMapper.toDto) - : [] - } + return { + ...AccountMapper.toDto(account), + portfolio: portfolio + ? { + id: portfolio.id!, + name: portfolio.name! + } + : null + } + }) - return portfolioAccounts - }) - ) + const responseDTO: PaginationDto = { + items: accountsWithPortfolio, + limit: accountsResult.limit, + page: accountsResult.page + } - return portfoliosAccountResponseDTO + return responseDTO } } diff --git a/src/core/application/use-cases/accounts/create-account-use-case.ts b/src/core/application/use-cases/accounts/create-account-use-case.ts index c1578f00..a6d14ea0 100644 --- a/src/core/application/use-cases/accounts/create-account-use-case.ts +++ b/src/core/application/use-cases/accounts/create-account-use-case.ts @@ -8,7 +8,6 @@ export interface CreateAccount { execute: ( organizationId: string, ledgerId: string, - portfolioId: string, account: CreateAccountDto ) => Promise } @@ -23,7 +22,6 @@ export class CreateAccountUseCase implements CreateAccount { async execute( organizationId: string, ledgerId: string, - portfolioId: string, account: CreateAccountDto ): Promise { account.status = { @@ -34,7 +32,6 @@ export class CreateAccountUseCase implements CreateAccount { const accountCreated = await this.createAccountRepository.create( organizationId, ledgerId, - portfolioId, accountEntity ) diff --git a/src/core/application/use-cases/accounts/delete-account-use-case.ts b/src/core/application/use-cases/accounts/delete-account-use-case.ts index a5bbd7a4..145517a0 100644 --- a/src/core/application/use-cases/accounts/delete-account-use-case.ts +++ b/src/core/application/use-cases/accounts/delete-account-use-case.ts @@ -5,7 +5,6 @@ export interface DeleteAccount { execute: ( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string ) => Promise } @@ -20,13 +19,11 @@ export class DeleteAccountUseCase implements DeleteAccount { async execute( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string ): Promise { await this.deleteAccountRepository.delete( organizationId, ledgerId, - portfolioId, accountId ) } diff --git a/src/core/application/use-cases/accounts/fetch-account-by-id-use-case.ts b/src/core/application/use-cases/accounts/fetch-account-by-id-use-case.ts index 73f14ea1..d9266ac6 100644 --- a/src/core/application/use-cases/accounts/fetch-account-by-id-use-case.ts +++ b/src/core/application/use-cases/accounts/fetch-account-by-id-use-case.ts @@ -7,7 +7,6 @@ export interface FetchAccountById { execute: ( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string ) => Promise } @@ -22,13 +21,11 @@ export class FetchAccountByIdUseCase implements FetchAccountById { async execute( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string ): Promise { const account = await this.fetchAccountByIdRepository.fetchById( organizationId, ledgerId, - portfolioId, accountId ) diff --git a/src/core/application/use-cases/accounts/fetch-all-account-use-case.ts b/src/core/application/use-cases/accounts/fetch-all-account-use-case.ts index 6c9dce29..185cc9b9 100644 --- a/src/core/application/use-cases/accounts/fetch-all-account-use-case.ts +++ b/src/core/application/use-cases/accounts/fetch-all-account-use-case.ts @@ -10,7 +10,6 @@ export interface FetchAllAccounts { execute: ( organizationId: string, ledgerId: string, - portfolioId: string, limit: number, page: number ) => Promise> @@ -26,7 +25,6 @@ export class FetchAllAccountsUseCase implements FetchAllAccounts { async execute( organizationId: string, ledgerId: string, - portfolioId: string, limit: number, page: number ): Promise> { @@ -34,7 +32,6 @@ export class FetchAllAccountsUseCase implements FetchAllAccounts { await this.fetchAllAccountsRepository.fetchAll( organizationId, ledgerId, - portfolioId, page, limit ) diff --git a/src/core/application/use-cases/accounts/update-account-use-case.ts b/src/core/application/use-cases/accounts/update-account-use-case.ts index fb7163de..b0ce6ea2 100644 --- a/src/core/application/use-cases/accounts/update-account-use-case.ts +++ b/src/core/application/use-cases/accounts/update-account-use-case.ts @@ -8,7 +8,6 @@ export interface UpdateAccount { execute: ( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string, account: Partial ) => Promise @@ -24,7 +23,6 @@ export class UpdateAccountUseCase implements UpdateAccount { async execute( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string, account: Partial ): Promise { @@ -40,7 +38,6 @@ export class UpdateAccountUseCase implements UpdateAccount { await this.updateAccountRepository.update( organizationId, ledgerId, - portfolioId, accountId, accountEntity ) diff --git a/src/core/application/use-cases/portfolios-with-accounts/fetch-portfolios-with-account-use-case.tsx b/src/core/application/use-cases/portfolios-with-accounts/fetch-portfolios-with-account-use-case.tsx new file mode 100644 index 00000000..90ded390 --- /dev/null +++ b/src/core/application/use-cases/portfolios-with-accounts/fetch-portfolios-with-account-use-case.tsx @@ -0,0 +1,69 @@ +import { FetchAllAccountsRepository } from '@/core/domain/repositories/accounts/fetch-all-accounts-repository' +import { FetchAllPortfoliosRepository } from '@/core/domain/repositories/portfolios/fetch-all-portfolio-repository' +import { PaginationDto } from '../../dto/pagination-dto' +import { AccountMapper } from '../../mappers/account-mapper' +import { inject, injectable } from 'inversify' +import { groupBy } from 'lodash' +import { PortfolioMapper } from '../../mappers/portfolio-mapper' + +export interface FetchPortfoliosWithAccounts { + execute: ( + organizationId: string, + ledgerId: string, + limit: number, + page: number + ) => Promise> +} + +@injectable() +export class FetchPortfoliosWithAccountsUseCase + implements FetchPortfoliosWithAccounts +{ + constructor( + @inject(FetchAllPortfoliosRepository) + private readonly fetchAllPortfoliosRepository: FetchAllPortfoliosRepository, + @inject(FetchAllAccountsRepository) + private readonly fetchAllAccountsRepository: FetchAllAccountsRepository + ) {} + + async execute( + organizationId: string, + ledgerId: string, + limit: number, + page: number + ): Promise> { + const portfoliosResult = await this.fetchAllPortfoliosRepository.fetchAll( + organizationId, + ledgerId, + limit, + page + ) + + const allAccountsResult = await this.fetchAllAccountsRepository.fetchAll( + organizationId, + ledgerId, + limit, + page + ) + + const accountsGrouped = groupBy( + allAccountsResult.items, + (account) => account.portfolioId || 'no_portfolio' + ) + + const portfoliosWithAccounts = portfoliosResult.items.map((portfolio) => + PortfolioMapper.toDtoWithAccounts( + portfolio, + (accountsGrouped[portfolio.id!] || []).map(AccountMapper.toDto) + ) + ) + + const responseDTO: PaginationDto = { + items: portfoliosWithAccounts, + limit: portfoliosResult.limit, + page: portfoliosResult.page + } + + return responseDTO + } +} diff --git a/src/core/domain/repositories/accounts/create-accounts-repository.ts b/src/core/domain/repositories/accounts/create-accounts-repository.ts index 10be10cb..090fa776 100644 --- a/src/core/domain/repositories/accounts/create-accounts-repository.ts +++ b/src/core/domain/repositories/accounts/create-accounts-repository.ts @@ -4,7 +4,6 @@ export abstract class CreateAccountsRepository { abstract create: ( organizationId: string, ledgerId: string, - portfolioId: string, account: AccountEntity ) => Promise } diff --git a/src/core/domain/repositories/accounts/delete-accounts-repository.ts b/src/core/domain/repositories/accounts/delete-accounts-repository.ts index d9cd82af..18dd6233 100644 --- a/src/core/domain/repositories/accounts/delete-accounts-repository.ts +++ b/src/core/domain/repositories/accounts/delete-accounts-repository.ts @@ -2,7 +2,6 @@ export abstract class DeleteAccountsRepository { abstract delete: ( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string ) => Promise } diff --git a/src/core/domain/repositories/accounts/fetch-account-by-id-repository.ts b/src/core/domain/repositories/accounts/fetch-account-by-id-repository.ts index 0faf0a7a..0db734b9 100644 --- a/src/core/domain/repositories/accounts/fetch-account-by-id-repository.ts +++ b/src/core/domain/repositories/accounts/fetch-account-by-id-repository.ts @@ -4,7 +4,6 @@ export abstract class FetchAccountByIdRepository { abstract fetchById: ( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string ) => Promise } diff --git a/src/core/domain/repositories/accounts/fetch-all-accounts-repository.ts b/src/core/domain/repositories/accounts/fetch-all-accounts-repository.ts index d2d35b41..1fa71934 100644 --- a/src/core/domain/repositories/accounts/fetch-all-accounts-repository.ts +++ b/src/core/domain/repositories/accounts/fetch-all-accounts-repository.ts @@ -5,7 +5,6 @@ export abstract class FetchAllAccountsRepository { abstract fetchAll: ( organizationId: string, ledgerId: string, - portfolioId: string, limit: number, page: number ) => Promise> diff --git a/src/core/domain/repositories/accounts/update-accounts-repository.ts b/src/core/domain/repositories/accounts/update-accounts-repository.ts index 1dd03d56..c185bb8f 100644 --- a/src/core/domain/repositories/accounts/update-accounts-repository.ts +++ b/src/core/domain/repositories/accounts/update-accounts-repository.ts @@ -4,7 +4,6 @@ export abstract class UpdateAccountsRepository { abstract update: ( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string, account: Partial ) => Promise diff --git a/src/core/infrastructure/container-registry/use-cases/account-module.ts b/src/core/infrastructure/container-registry/use-cases/account-module.ts index c1591ff7..40334e59 100644 --- a/src/core/infrastructure/container-registry/use-cases/account-module.ts +++ b/src/core/infrastructure/container-registry/use-cases/account-module.ts @@ -20,6 +20,10 @@ import { DeleteAccount, DeleteAccountUseCase } from '@/core/application/use-cases/accounts/delete-account-use-case' +import { + FetchAccountsWithPortfolios, + FetchAccountsWithPortfoliosUseCase +} from '@/core/application/use-cases/accounts-with-portfolios/fetch-accounts-with-portfolios-use-case' export const AccountUseCaseModule = new ContainerModule( (container: Container) => { @@ -28,5 +32,8 @@ export const AccountUseCaseModule = new ContainerModule( container.bind(FetchAccountByIdUseCase).toSelf() container.bind(UpdateAccountUseCase).toSelf() container.bind(DeleteAccountUseCase).toSelf() + container + .bind(FetchAccountsWithPortfoliosUseCase) + .toSelf() } ) diff --git a/src/core/infrastructure/container-registry/use-cases/portfolios-module.ts b/src/core/infrastructure/container-registry/use-cases/portfolios-module.ts index 709ef095..2c64a1bb 100644 --- a/src/core/infrastructure/container-registry/use-cases/portfolios-module.ts +++ b/src/core/infrastructure/container-registry/use-cases/portfolios-module.ts @@ -21,9 +21,9 @@ import { FetchPortfolioByIdUseCase } from '@/core/application/use-cases/portfolios/fetch-portfolio-by-id-use-case' import { - FetchAllPortfoliosAccounts, - FetchAllPortfoliosAccountsUseCase -} from '@/core/application/use-cases/portfolios-accounts/fetch-portfolios-accounts-use-case' + FetchPortfoliosWithAccounts, + FetchPortfoliosWithAccountsUseCase +} from '@/core/application/use-cases/portfolios-with-accounts/fetch-portfolios-with-account-use-case' export const PortfolioUseCaseModule = new ContainerModule( (container: Container) => { @@ -32,9 +32,8 @@ export const PortfolioUseCaseModule = new ContainerModule( container.bind(UpdatePortfolioUseCase).toSelf() container.bind(DeletePortfolioUseCase).toSelf() container.bind(FetchPortfolioByIdUseCase).toSelf() - container - .bind(FetchAllPortfoliosAccountsUseCase) + .bind(FetchPortfoliosWithAccountsUseCase) .toSelf() } ) diff --git a/src/core/infrastructure/midaz/accounts/midaz-create-accounts-repository.ts b/src/core/infrastructure/midaz/accounts/midaz-create-accounts-repository.ts index fd6f1f6a..ea68575e 100644 --- a/src/core/infrastructure/midaz/accounts/midaz-create-accounts-repository.ts +++ b/src/core/infrastructure/midaz/accounts/midaz-create-accounts-repository.ts @@ -9,10 +9,9 @@ export class MidazCreateAccountRepository implements CreateAccountsRepository { async create( organizationId: string, ledgerId: string, - portfolioId: string, account: AccountEntity ): Promise { - const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts` + const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/accounts` const response = await httpMidazAuthFetch({ url, diff --git a/src/core/infrastructure/midaz/accounts/midaz-delete-accounts-repository.ts b/src/core/infrastructure/midaz/accounts/midaz-delete-accounts-repository.ts index 20cc5026..698da6c6 100644 --- a/src/core/infrastructure/midaz/accounts/midaz-delete-accounts-repository.ts +++ b/src/core/infrastructure/midaz/accounts/midaz-delete-accounts-repository.ts @@ -9,10 +9,9 @@ export class MidazDeleteAccountsRepository implements DeleteAccountsRepository { async delete( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string ): Promise { - const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts/${accountId}` + const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/accounts/${accountId}` await httpMidazAuthFetch({ url, method: HTTP_METHODS.DELETE diff --git a/src/core/infrastructure/midaz/accounts/midaz-fetch-account-by-id-repository.ts b/src/core/infrastructure/midaz/accounts/midaz-fetch-account-by-id-repository.ts index 45a991c5..141fd7d0 100644 --- a/src/core/infrastructure/midaz/accounts/midaz-fetch-account-by-id-repository.ts +++ b/src/core/infrastructure/midaz/accounts/midaz-fetch-account-by-id-repository.ts @@ -12,10 +12,9 @@ export class MidazFetchAccountByIdRepository async fetchById( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string ): Promise { - const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts/${accountId}` + const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/accounts/${accountId}` const response = await httpMidazAuthFetch({ url, diff --git a/src/core/infrastructure/midaz/accounts/midaz-fetch-all-accounts-repository.ts b/src/core/infrastructure/midaz/accounts/midaz-fetch-all-accounts-repository.ts index 18c44c39..0b47207f 100644 --- a/src/core/infrastructure/midaz/accounts/midaz-fetch-all-accounts-repository.ts +++ b/src/core/infrastructure/midaz/accounts/midaz-fetch-all-accounts-repository.ts @@ -13,11 +13,10 @@ export class MidazFetchAllAccountsRepository async fetchAll( organizationId: string, ledgerId: string, - portfolioId: string, limit: number, page: number ): Promise> { - const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts?limit=${limit}&page=${page}` + const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/accounts?limit=${limit}&page=${page}` const response = await httpMidazAuthFetch>({ url, diff --git a/src/core/infrastructure/midaz/accounts/midaz-update-accounts-repository.ts b/src/core/infrastructure/midaz/accounts/midaz-update-accounts-repository.ts index df049e22..1e4412df 100644 --- a/src/core/infrastructure/midaz/accounts/midaz-update-accounts-repository.ts +++ b/src/core/infrastructure/midaz/accounts/midaz-update-accounts-repository.ts @@ -10,11 +10,10 @@ export class MidazUpdateAccountsRepository implements UpdateAccountsRepository { async update( organizationId: string, ledgerId: string, - portfolioId: string, accountId: string, account: Partial ): Promise { - const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/portfolios/${portfolioId}/accounts/${accountId}` + const url = `${this.baseUrl}/organizations/${organizationId}/ledgers/${ledgerId}/accounts/${accountId}` const response = await httpMidazAuthFetch({ url, diff --git a/src/schema/account.ts b/src/schema/account.ts index 6e5548f7..a2414745 100644 --- a/src/schema/account.ts +++ b/src/schema/account.ts @@ -3,15 +3,15 @@ import { metadata } from './metadata' const name = z.string().min(3).max(255) -const alias = z.string().min(1).max(255).optional() +const alias = z.string().min(1).max(255) -const entityId = z.string().min(1).max(255).optional() +const entityId = z.string().nullable().optional() const assetCode = z.string() -const portfolioId = z.string() +const portfolioId = z.string().nullable().optional() -const productId = z.string() +const productId = z.string().nullable().optional() const type = z.string() diff --git a/src/types/accounts-type.ts b/src/types/accounts-type.ts index 61062e65..3dd05966 100644 --- a/src/types/accounts-type.ts +++ b/src/types/accounts-type.ts @@ -1,21 +1,24 @@ +import { PortfolioType } from './portfolio-type' + export type AccountType = { id: string ledgerId: string assetCode: string organizationId: string name: string - alias: string - type: string - entityId: string + alias?: string + type?: string + entityId?: string parentAccountId: string - portfolioId: string + portfolioId?: string | null + portfolio: Pick portfolioName?: string productId: string status: { code: string description: string } - metadata: Record + metadata?: Record createdAt: Date updatedAt: Date deletedAt: Date | null diff --git a/src/types/portfolio-type.ts b/src/types/portfolio-type.ts new file mode 100644 index 00000000..4fd10b86 --- /dev/null +++ b/src/types/portfolio-type.ts @@ -0,0 +1,4 @@ +export type PortfolioType = { + id: string + name: string +}