Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Talouden viestintä aikuisen sivulta #6465

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

msavolainen-gofore
Copy link
Collaborator

No description provided.

@msavolainen-gofore msavolainen-gofore added the enhancement Uusi toiminnallisuus tai parannus label Mar 3, 2025
@msavolainen-gofore msavolainen-gofore force-pushed the finance-messaging branch 3 times, most recently from 3116d3b to 032ff94 Compare March 4, 2025 07:52
diff --git c/frontend/src/citizen-frontend/messages/MessageEditor.tsx i/frontend/src/citizen-frontend/messages/MessageEditor.tsx
index df46f81ccb..549b9c7944 100644
--- c/frontend/src/citizen-frontend/messages/MessageEditor.tsx
+++ i/frontend/src/citizen-frontend/messages/MessageEditor.tsx
@@ -87,11 +87,19 @@ export default React.memo(function MessageEditor({
   const i18n = useTranslation()
   const user = useUser()

+  // const childIds = useMemo(
+  //   () =>
+  //     Object.keys(
+  //       receiverOptions.childrenToMessageAccounts
+  //     ) as (keyof typeof receiverOptions.childrenToMessageAccounts)[],
+  //   [receiverOptions]
+  // )
+
   const childIds = useMemo(
     () =>
-      Object.keys(
-        receiverOptions.childrenToMessageAccounts
-      ) as (keyof typeof receiverOptions.childrenToMessageAccounts)[],
+      receiverOptions.childrenToMessageAccounts
+        .filter((c) => c.childId !== null)
+        .map((c) => c.childId!),
     [receiverOptions]
   )

@@ -143,9 +151,9 @@ export default React.memo(function MessageEditor({
     const newMessageAuthorized =
       (accountId: MessageAccountId) =>
       (childId: PersonId): boolean =>
-        receiverOptions.childrenToMessageAccounts[childId]?.newMessage.includes(
-          accountId
-        ) ?? false
+        receiverOptions.childrenToMessageAccounts
+          .find((value) => value.childId === childId)
+          ?.newMessage.includes(accountId) ?? false

     // Can send to groups which are recipients for at least one of the selected children,
     // as long as all the children and thus groups are in the same unit.
@@ -249,9 +257,11 @@ export default React.memo(function MessageEditor({
                                   (accountId) =>
                                     children.every(
                                       (childId) =>
-                                        receiverOptions.childrenToMessageAccounts[
-                                          childId
-                                        ]?.newMessage.includes(accountId) ??
+                                        receiverOptions.childrenToMessageAccounts
+                                          .find(
+                                            (value) => value.childId === childId
+                                          )
+                                          ?.newMessage.includes(accountId) ??
                                         false
                                     )
                                 )
diff --git c/frontend/src/citizen-frontend/messages/MessagesPage.tsx i/frontend/src/citizen-frontend/messages/MessagesPage.tsx
index 9d77795955..4c1e4bb439 100644
--- c/frontend/src/citizen-frontend/messages/MessagesPage.tsx
+++ i/frontend/src/citizen-frontend/messages/MessagesPage.tsx
@@ -134,7 +134,7 @@ export default React.memo(function MessagesPage() {
                     closeThread={() => selectThread(undefined)}
                     thread={selectedThread}
                     allowedAccounts={
-                      receivers.getOrElse(null)?.childrenToMessageAccounts ?? {}
+                      receivers.getOrElse(null)?.childrenToMessageAccounts ?? []
                     }
                     accountDetails={
                       receivers.getOrElse(null)?.messageAccounts ?? []
diff --git c/frontend/src/citizen-frontend/messages/ThreadView.tsx i/frontend/src/citizen-frontend/messages/ThreadView.tsx
index 3b51e1e7ac..dc9bc9fbc0 100644
--- c/frontend/src/citizen-frontend/messages/ThreadView.tsx
+++ i/frontend/src/citizen-frontend/messages/ThreadView.tsx
@@ -23,10 +23,7 @@ import {
   MessageAccount,
   MessageAccountWithPresence
 } from 'lib-common/generated/api-types/messaging'
-import {
-  ChildId,
-  MessageAccountId
-} from 'lib-common/generated/api-types/shared'
+import { MessageAccountId } from 'lib-common/generated/api-types/shared'
 import { formatFirstName } from 'lib-common/names'
 import { scrollRefIntoView } from 'lib-common/utils/scrolling'
 import { NotificationsContext } from 'lib-components/Notifications'
@@ -221,7 +218,7 @@ const SingleMessage = React.memo(
 interface Props {
   accountId: MessageAccountId
   thread: CitizenMessageThread.Regular
-  allowedAccounts: Partial<Record<ChildId, ChildMessageAccountAccess>>
+  allowedAccounts: ChildMessageAccountAccess[]
   accountDetails: MessageAccountWithPresence[]
   closeThread: () => void
   onThreadDeleted: () => void
@@ -310,8 +307,14 @@ export default React.memo(
       }

       const allowedReplyAccounts = new Set(
-        children.flatMap((c) => allowedAccounts[c.childId]?.reply ?? [])
+        children.flatMap(
+          (c) =>
+            allowedAccounts.find((a) => a.childId === c.childId)?.reply ?? []
+        )
       )
+      allowedAccounts
+        .filter((a) => a.childId === null)
+        .forEach((a) => a.reply.forEach((am) => allowedReplyAccounts.add(am)))
       return recipients.every((r) => allowedReplyAccounts.has(r.id))
     }, [allowedAccounts, applicationStatus, children, recipients])

diff --git c/frontend/src/employee-frontend/components/messages/MessageContext.tsx i/frontend/src/employee-frontend/components/messages/MessageContext.tsx
index 3c6feb909f..e1a9e08bf4 100644
--- c/frontend/src/employee-frontend/components/messages/MessageContext.tsx
+++ i/frontend/src/employee-frontend/components/messages/MessageContext.tsx
@@ -47,7 +47,8 @@ import {
   isGroupMessageAccount,
   isMunicipalMessageAccount,
   isPersonalMessageAccount,
-  isServiceWorkerMessageAccount
+  isServiceWorkerMessageAccount,
+  isFinanceMessageAccount
 } from 'lib-components/messages/types'
 import { SelectOption } from 'lib-components/molecules/Select'

@@ -74,7 +75,8 @@ import {
   isStandardView,
   municipalMessageBoxes,
   personalMessageBoxes,
-  serviceWorkerMessageBoxes
+  serviceWorkerMessageBoxes,
+  financeMessageBoxes
 } from './types-view'

 const getMessageCopiesResult = wrapResult(getMessageCopies)
@@ -95,6 +97,7 @@ export interface MessagesState {
   accounts: Result<AuthorizedMessageAccount[]>
   municipalAccount: AuthorizedMessageAccount | undefined
   serviceWorkerAccount: AuthorizedMessageAccount | undefined
+  financeAccount: AuthorizedMessageAccount | undefined
   personalAccount: AuthorizedMessageAccount | undefined
   groupAccounts: GroupMessageAccount[]
   unitOptions: SelectOption<DaycareId>[]
@@ -135,6 +138,7 @@ const defaultState: MessagesState = {
   accounts: Loading.of(),
   municipalAccount: undefined,
   serviceWorkerAccount: undefined,
+  financeAccount: undefined,
   personalAccount: undefined,
   groupAccounts: [],
   unitOptions: [],
@@ -298,6 +302,13 @@ export const MessageContextProvider = React.memo(
           .getOrElse(undefined),
       [accounts]
     )
+    const financeAccount = useMemo(
+      () =>
+        accounts
+          .map((accounts) => accounts.find(isFinanceMessageAccount))
+          .getOrElse(undefined),
+      [accounts]
+    )
     const personalAccount = useMemo(
       () =>
         accounts
@@ -380,7 +391,9 @@ export const MessageContextProvider = React.memo(
     }, [accountId, accounts, messageBox, unitId, folders])

     const accountAllowsNewMessage = useCallback(
-      () => selectedAccount?.account.type !== 'SERVICE_WORKER',
+      () =>
+        selectedAccount?.account.type !== 'SERVICE_WORKER' &&
+        selectedAccount?.account.type !== 'FINANCE',
       [selectedAccount]
     )

@@ -753,6 +766,13 @@ export const MessageContextProvider = React.memo(
           unitId: null,
           threadId: threadId
         })
+      } else if (financeAccount) {
+        setParams({
+          messageBox: messageBox ?? financeMessageBoxes[0],
+          accountId: financeAccount.account.id,
+          unitId: null,
+          threadId: threadId
+        })
       } else if (municipalAccount) {
         setParams({
           messageBox: messageBox ?? municipalMessageBoxes[0],
@@ -780,6 +800,7 @@ export const MessageContextProvider = React.memo(
       groupAccounts,
       municipalAccount,
       serviceWorkerAccount,
+      financeAccount,
       personalAccount,
       messageBox,
       threadId
@@ -790,6 +811,7 @@ export const MessageContextProvider = React.memo(
         accounts,
         municipalAccount,
         serviceWorkerAccount,
+        financeAccount,
         personalAccount,
         groupAccounts,
         unitOptions,
@@ -829,6 +851,7 @@ export const MessageContextProvider = React.memo(
         accounts,
         municipalAccount,
         serviceWorkerAccount,
+        financeAccount,
         personalAccount,
         groupAccounts,
         unitOptions,
diff --git c/frontend/src/employee-frontend/components/messages/MessageEditor.tsx i/frontend/src/employee-frontend/components/messages/MessageEditor.tsx
index 30119f85f1..7049aadf69 100644
--- c/frontend/src/employee-frontend/components/messages/MessageEditor.tsx
+++ i/frontend/src/employee-frontend/components/messages/MessageEditor.tsx
@@ -282,7 +282,8 @@ export default React.memo(function MessageEditor({
     [getSenderAccount, message]
   )
   const simpleMode = useMemo(
-    () => senderAccountType === 'SERVICE_WORKER',
+    () =>
+      senderAccountType === 'SERVICE_WORKER' || senderAccountType === 'FINANCE',
     [senderAccountType]
   )

diff --git c/frontend/src/employee-frontend/components/messages/Sidebar.tsx i/frontend/src/employee-frontend/components/messages/Sidebar.tsx
index a562ade30a..7f92183409 100644
--- c/frontend/src/employee-frontend/components/messages/Sidebar.tsx
+++ i/frontend/src/employee-frontend/components/messages/Sidebar.tsx
@@ -22,6 +22,7 @@ import GroupMessageAccountList from './GroupMessageAccountList'
 import MessageBox from './MessageBox'
 import { MessageContext } from './MessageContext'
 import {
+  financeMessageBoxes,
   municipalMessageBoxes,
   personalMessageBoxes,
   serviceWorkerMessageBoxes
@@ -98,6 +99,7 @@ function Accounts({ setReceivers }: AccountsProps) {
     selectedAccount,
     municipalAccount,
     serviceWorkerAccount,
+    financeAccount,
     folders,
     personalAccount,
     groupAccounts,
@@ -141,6 +143,7 @@ function Accounts({ setReceivers }: AccountsProps) {
     <>
       {!municipalAccount &&
         !serviceWorkerAccount &&
+        !financeAccount &&
         !personalAccount &&
         groupAccounts.length === 0 && (
           <NoAccounts>{i18n.messages.sidePanel.noAccountAccess}</NoAccounts>
@@ -204,6 +207,24 @@ function Accounts({ setReceivers }: AccountsProps) {
         </AccountSection>
       )}

+      {financeAccount && (
+        <AccountSection data-qa="finance-account">
+          <AccountHeader>
+            {i18n.messages.sidePanel.financeMessages}
+          </AccountHeader>
+          {financeMessageBoxes.map((view) => (
+            <MessageBox
+              key={view}
+              view={view}
+              account={financeAccount.account}
+              unitId={null}
+              activeView={selectedAccount}
+              selectAccount={selectAccount}
+            />
+          ))}
+        </AccountSection>
+      )}
+
       {personalAccount && (
         <AccountSection data-qa="personal-account">
           <AccountHeader>{i18n.messages.sidePanel.ownMessages}</AccountHeader>
diff --git c/frontend/src/employee-frontend/components/messages/queries.ts i/frontend/src/employee-frontend/components/messages/queries.ts
index a597b787e0..30bf76bc83 100644
--- c/frontend/src/employee-frontend/components/messages/queries.ts
+++ i/frontend/src/employee-frontend/components/messages/queries.ts
@@ -2,10 +2,13 @@
 //
 // SPDX-License-Identifier: LGPL-2.1-or-later

+import { PersonId } from 'lib-common/generated/api-types/shared'
 import { Queries } from 'lib-common/query'

 import {
+  archiveThread,
   createMessagePreflightCheck,
+  getFinanceMessagesWithPerson,
   replyToThread
 } from '../../generated/api-clients/messaging'

@@ -16,3 +19,9 @@ export const createMessagePreflightCheckQuery = q.query(
 )

 export const replyToThreadMutation = q.mutation(replyToThread)
+
+export const financeThreadsQuery = q.query(getFinanceMessagesWithPerson)
+
+export const deleteFinanceThreadMutation = q.parametricMutation<{
+  id: PersonId
+}>()(archiveThread, [({ id }) => financeThreadsQuery({ personId: id })])
diff --git c/frontend/src/employee-frontend/components/messages/types-view.ts i/frontend/src/employee-frontend/components/messages/types-view.ts
index ed2fae552c..a7407f35e5 100644
--- c/frontend/src/employee-frontend/components/messages/types-view.ts
+++ i/frontend/src/employee-frontend/components/messages/types-view.ts
@@ -42,6 +42,11 @@ export const serviceWorkerMessageBoxes: StandardView[] = [
   'drafts',
   'archive'
 ]
+export const financeMessageBoxes: StandardView[] = [
+  'sent',
+  'drafts',
+  'received'
+]
 export const personalMessageBoxes: StandardView[] = [
   'received',
   'sent',
diff --git c/frontend/src/employee-frontend/components/person-profile/PersonFinanceNotesAndMessages.tsx i/frontend/src/employee-frontend/components/person-profile/PersonFinanceNotesAndMessages.tsx
index 4a019d1515..babd43b36a 100644
--- c/frontend/src/employee-frontend/components/person-profile/PersonFinanceNotesAndMessages.tsx
+++ i/frontend/src/employee-frontend/components/person-profile/PersonFinanceNotesAndMessages.tsx
@@ -2,11 +2,24 @@
 //
 // SPDX-License-Identifier: LGPL-2.1-or-later

-import React, { useContext, useState } from 'react'
-import styled from 'styled-components'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React, { useCallback, useContext, useMemo, useState } from 'react'
+import { Link } from 'react-router'
+import styled, { useTheme } from 'styled-components'

-import { FinanceNoteId, PersonId } from 'lib-common/generated/api-types/shared'
-import { cancelMutation, useQueryResult } from 'lib-common/query'
+import { combine, wrapResult } from 'lib-common/api'
+import {
+  MessageThread,
+  PostMessageBody
+} from 'lib-common/generated/api-types/messaging'
+import {
+  FinanceNoteId,
+  MessageAccountId,
+  // MessageId,
+  MessageThreadId,
+  PersonId
+} from 'lib-common/generated/api-types/shared'
+import { cancelMutation, useMutationResult, useQueryResult } from 'lib-common/query'
 import AddButton from 'lib-components/atoms/buttons/AddButton'
 import { Button } from 'lib-components/atoms/buttons/Button'
 import { IconOnlyButton } from 'lib-components/atoms/buttons/IconOnlyButton'
@@ -23,15 +36,30 @@ import {
 import { MutateFormModal } from 'lib-components/molecules/modals/FormModal'
 import { H2, Label, Light } from 'lib-components/typography'
 import { Gap } from 'lib-components/white-space'
-import { faPen, faTrash } from 'lib-icons'
-import { faQuestion } from 'lib-icons'
+import { faEnvelope, faPen, faQuestion, faReply, faTrash } from 'lib-icons'
+import { faChevronDown, faChevronUp } from 'lib-icons'

+import { getAttachmentUrl, messageAttachment } from '../../api/attachments'
+import {
+  createMessage,
+  deleteDraftMessage,
+  initDraftMessage,
+  replyToThread,
+  updateDraftMessage
+} from '../../generated/api-clients/messaging'
 import { useTranslation } from '../../state/i18n'
 import { PersonContext } from '../../state/person'
 import { UIContext } from '../../state/ui'
+import { formatPersonName } from '../../utils'
 import { formatParagraphs } from '../../utils/html-utils'
 import { renderResult } from '../async-rendering'
 import { FlexRow } from '../common/styled/containers'
+import { MessageContext } from '../messages/MessageContext'
+import MessageEditor from '../messages/MessageEditor'
+import {
+  deleteFinanceThreadMutation,
+  financeThreadsQuery
+} from '../messages/queries'

 import {
   createFinanceNoteMutation,
@@ -40,6 +68,12 @@ import {
   updateFinanceNoteMutation
 } from './queries'

+const initDraftMessageResult = wrapResult(initDraftMessage)
+const updateDraftMessageResult = wrapResult(updateDraftMessage)
+const deleteDraftMessageResult = wrapResult(deleteDraftMessage)
+const createMessageResult = wrapResult(createMessage)
+const replyToFinanceThreadResult = wrapResult(replyToThread)
+
 interface Props {
   id: PersonId
   open: boolean
@@ -50,44 +84,190 @@ export default React.memo(function PersonFinanceNotesAndMessages({
   open: startOpen
 }: Props) {
   const { i18n } = useTranslation()
-  const { permittedActions } = useContext(PersonContext)
-  const { uiMode, toggleUiMode, clearUiMode } = useContext(UIContext)
+  const theme = useTheme()
+  const { person, permittedActions } = useContext(PersonContext)
+  const { uiMode, toggleUiMode, clearUiMode, setErrorMessage } =
+    useContext(UIContext)
+  const { selectedDraft, refreshMessages, financeAccount } =
+    useContext(MessageContext)
   const [open, setIsOpen] = useState(startOpen)
   const financeNotes = useQueryResult(financeNotesQuery({ personId: id }))
   const [text, setText] = useState<string>('')
-  const [confirmDelete, setConfirmDelete] = useState<FinanceNoteId>()
+  const [confirmDeleteNote, setConfirmDeleteNote] = useState<FinanceNoteId>()
+  const financeMessages = useQueryResult(financeThreadsQuery({ personId: id }))
+  const [sending, setSending] = useState(false)
+  const [thread, setThread] = useState<MessageThread>()
+  const [threadsOpen, setThreadsOpen] = useState<Record<string, boolean>>({})
+  const [confirmDeleteThread, setConfirmDeleteThread] =
+    useState<MessageThreadId>()
+
+  const personName = useMemo((): string | undefined => {
+    if (person.isSuccess) {
+      const p = person.getOrElse(null)
+      if (p !== null) {
+        return formatPersonName(p, i18n, true)
+      }
+    }
+    return undefined
+  }, [person, i18n])
+
+  const foo = useMutationResult()
+
+  const onSend = useCallback(
+    (accountId: MessageAccountId, messageBody: PostMessageBody) => {
+      const afterSend = (isSuccess: boolean, accountId: MessageAccountId) => {
+        if (isSuccess) {
+          refreshMessages(accountId)
+          clearUiMode()
+        } else {
+          setErrorMessage({
+            type: 'error',
+            title: i18n.common.error.unknown,
+            resolveLabel: i18n.common.ok
+          })
+        }
+      }
+
+      setSending(true)
+
+      if (thread) {
+        void replyToFinanceThreadResult({
+          accountId,
+          messageId: thread.messages[0].id,
+          body: {
+            content: messageBody.content,
+            recipientAccountIds: thread.messages[0].recipients.map((r) => r.id)
+          }
+        }).then((res) => afterSend(res.isSuccess, accountId))
+      } else {
+        void createMessageResult({
+          accountId,
+          body: {
+            ...messageBody,
+            sensitive: true
+          }
+        }).then((res) => afterSend(res.isSuccess, accountId))
+      }
+
+      setSending(false)
+    },
+    [
+      clearUiMode,
+      i18n.common.error.unknown,
+      i18n.common.ok,
+      refreshMessages,
+      setErrorMessage,
+      thread
+    ]
+  )

   return (
     <>
       {uiMode.startsWith('delete-finance-note') && (
         <MutateFormModal
           type="warning"
-          title={i18n.personProfile.financeNotesAndMessages.confirmDelete}
+          title={i18n.personProfile.financeNotesAndMessages.confirmDeleteNote}
           icon={faQuestion}
           resolveMutation={deleteFinanceNoteMutation}
           resolveAction={() =>
-            confirmDelete !== undefined
+            confirmDeleteNote !== undefined
               ? {
                   id,
-                  noteId: confirmDelete
+                  noteId: confirmDeleteNote
                 }
               : cancelMutation
           }
           resolveLabel={i18n.common.remove}
           resolveDisabled={false}
           rejectAction={() => {
-            setConfirmDelete(undefined)
+            setConfirmDeleteNote(undefined)
             clearUiMode()
           }}
           rejectLabel={i18n.common.cancel}
           onSuccess={() => {
-            setConfirmDelete(undefined)
+            setConfirmDeleteNote(undefined)
             clearUiMode()
           }}
           data-qa="delete-finance-note-modal"
         />
       )}

+      {uiMode.startsWith('delete-finance-thread') && (
+        <MutateFormModal
+          type="warning"
+          title={i18n.personProfile.financeNotesAndMessages.confirmDeleteThread}
+          icon={faQuestion}
+          resolveMutation={deleteFinanceThreadMutation}
+          resolveAction={() =>
+            confirmDeleteThread !== undefined && financeAccount !== undefined
+              ? {
+                  id,
+                  accountId: financeAccount.account.id,
+                  threadId: confirmDeleteThread
+                }
+              : cancelMutation
+          }
+          resolveLabel={i18n.common.remove}
+          resolveDisabled={false}
+          rejectAction={() => {
+            setConfirmDeleteThread(undefined)
+            clearUiMode()
+          }}
+          rejectLabel={i18n.common.cancel}
+          onSuccess={() => {
+            setConfirmDeleteThread(undefined)
+            clearUiMode()
+          }}
+          data-qa="delete-finance-message-modal"
+        />
+      )}
+
+      {uiMode === 'finance-message-editor' &&
+        financeAccount &&
+        personName !== undefined && (
+          <StyledMessageEditor
+            availableReceivers={[
+              {
+                accountId: financeAccount.account.id,
+                receivers: [
+                  {
+                    id: id,
+                    name: personName,
+                    type: 'CITIZEN'
+                  }
+                ]
+              }
+            ]}
+            defaultSender={{
+              value: financeAccount.account.id,
+              label: financeAccount.account.name
+            }}
+            draftContent={selectedDraft}
+            getAttachmentUrl={getAttachmentUrl}
+            initDraftRaw={(accountId) => initDraftMessageResult({ accountId })}
+            accounts={[financeAccount]}
+            folders={[]}
+            onClose={() => clearUiMode()}
+            onDiscard={(accountId, draftId) => {
+              clearUiMode()
+              void deleteDraftMessageResult({ accountId, draftId }).then(() => {
+                refreshMessages(accountId)
+              })
+            }}
+            onSend={onSend}
+            saveDraftRaw={(params) =>
+              updateDraftMessageResult({
+                accountId: params.accountId,
+                draftId: params.draftId,
+                body: params.content
+              })
+            }
+            saveMessageAttachment={messageAttachment}
+            sending={sending}
+            defaultTitle={thread ? thread.title : ''}
+          />
+        )}
+
       <CollapsibleContentArea
         title={<H2>{i18n.personProfile.financeNotesAndMessages.title}</H2>}
         open={open}
@@ -116,6 +296,18 @@ export default React.memo(function PersonFinanceNotesAndMessages({
                 uiMode.startsWith('edit-finance-note')
               }
             />
+            <Gap horizontal size="m" />
+            <AddButton
+              icon={faEnvelope}
+              text={i18n.personProfile.financeNotesAndMessages.sendMessage}
+              onClick={() => {
+                setText('')
+                setThread(undefined)
+                toggleUiMode('finance-message-editor')
+              }}
+              data-qa="send-finance-message"
+              disabled={false}
+            />
           </FlexRow>
         </BorderedContentArea>

@@ -154,112 +346,225 @@ export default React.memo(function PersonFinanceNotesAndMessages({
           </BorderedContentArea>
         )}

-        {renderResult(financeNotes, (notes) => (
-          // list note items eiter edit or view mode
-          <>
-            {notes.length > 0
-              ? notes.map(({ note, permittedActions }) => (
-                  <BorderedContentArea
-                    key={note.id}
-                    opaque
-                    paddingHorizontal="0"
-                    paddingVertical="s"
-                    data-qa="add-finance-note"
-                  >
-                    <FlexRow justifyContent="space-between">
-                      <FixedSpaceColumn spacing="xxs">
-                        <Label>
-                          {i18n.personProfile.financeNotesAndMessages.note}
-                        </Label>
-                        <Light style={{ fontSize: '14px' }}>
-                          {i18n.personProfile.financeNotesAndMessages.created}{' '}
-                          <span data-qa="finance-note-created-at">
-                            {note.createdAt.format()}
-                          </span>
-                          ,{note.createdByName}
-                        </Light>
-                        {uiMode === `edit-finance-note_${note.id}` && (
+        {renderResult(
+          combine(financeMessages, financeNotes),
+          ([threads, notes]) => (
+            // list note items eiter edit or view mode
+            <>
+              {threads.length > 0
+                ? threads.map((thread) => (
+                    <BorderedContentArea
+                      key={thread.id}
+                      opaque
+                      paddingHorizontal="0"
+                      paddingVertical="s"
+                      data-qa="finance-message-thread"
+                    >
+                      <FlexRow justifyContent="space-between">
+                        <FixedSpaceColumn spacing="xxs">
+                          <Label>
+                            <FontAwesomeIcon icon={faEnvelope} />
+                            <Gap horizontal size="xxs" />
+                            {thread.title} ({thread.messages.length}) (
+                            <UnderlinedLink
+                              data-qa="finance-message-thread-link"
+                              to={`/messages?accountId=${financeAccount?.account.id}&messageBox=thread&threadId=${thread.id}`}
+                              target="_blank"
+                            >
+                              {i18n.personProfile.financeNotesAndMessages.link}
+                            </UnderlinedLink>
+                            )
+                          </Label>
                           <Light style={{ fontSize: '14px' }}>
-                            {i18n.personProfile.financeNotesAndMessages.inEdit}
+                            {thread.messages[0].sentAt.format()},{' '}
+                            {thread.messages[0].sender.name}
                           </Light>
-                        )}
-                      </FixedSpaceColumn>
-
-                      {uiMode !== `edit-finance-note_${note.id}` && (
+                        </FixedSpaceColumn>
                         <FixedSpaceRow spacing="xs">
                           <IconOnlyButton
-                            icon={faPen}
+                            icon={faReply}
                             onClick={() => {
-                              setText(note.content)
-                              toggleUiMode(`edit-finance-note_${note.id}`)
+                              setThread(thread)
+                              toggleUiMode(`finance-message-editor`)
                             }}
-                            disabled={!permittedActions.includes('UPDATE')}
                             size="s"
-                            data-qa="edit-finance-note"
+                            data-qa="reply-finance-thread"
                             aria-label={i18n.common.edit}
                           />
                           <IconOnlyButton
                             icon={faTrash}
                             onClick={() => {
-                              setConfirmDelete(note.id)
-                              toggleUiMode(`delete-finance-note-${note.id}`)
+                              setConfirmDeleteThread(thread.id)
+                              toggleUiMode(`delete-finance-thread`)
                             }}
-                            disabled={!permittedActions.includes('DELETE')}
                             size="s"
-                            data-qa="delete-finance-note"
+                            data-qa={`delete-finance-thread-${thread.id}`}
                             aria-label={i18n.common.remove}
                           />
                         </FixedSpaceRow>
-                      )}
-                    </FlexRow>
-                    <Gap size="xs" />
+                      </FlexRow>
+                      <div>{formatParagraphs(thread.messages[0].content)}</div>

-                    {uiMode === `edit-finance-note_${note.id}` ? (
-                      <div key={note.id} data-qa="edit-finance-note">
-                        <StyledTextArea
-                          autoFocus
-                          value={text}
-                          rows={3}
-                          onChange={setText}
-                          data-qa="finance-note-text-area"
-                        />
-                        <Gap size="xs" />
-                        <FixedSpaceRow justifyContent="flex-start">
-                          <Button
-                            appearance="inline"
-                            onClick={() => clearUiMode()}
-                            text={i18n.common.cancel}
+                      {thread.messages.length > 1 && (
+                        <>
+                          {threadsOpen[thread.id] &&
+                            thread.messages
+                              .filter((_, i) => i !== 0)
+                              .map((m) => (
+                                <BorderedMessageArea
+                                  key={m.id}
+                                  opaque
+                                  paddingHorizontal="s"
+                                  paddingVertical="s"
+                                  data-qa="add-finance-note"
+                                >
+                                  <Light style={{ fontSize: '14px' }}>
+                                    {m.sentAt.format()}, {m.sender.name}
+                                  </Light>
+                                  <div>{formatParagraphs(m.content)}</div>
+                                </BorderedMessageArea>
+                              ))}
+
+                          <div
+                            onClick={() =>
+                              setThreadsOpen({
+                                ...threadsOpen,
+                                [thread.id]: !(threadsOpen[thread.id] ?? false)
+                              })
+                            }
+                          >
+                            <AccordionToggle>
+                              {threadsOpen[thread.id]
+                                ? i18n.personProfile.financeNotesAndMessages
+                                    .hideMessages
+                                : i18n.personProfile.financeNotesAndMessages
+                                    .showMessages}
+                              <Gap horizontal size="xs" />
+                              <FontAwesomeIcon
+                                icon={
+                                  threadsOpen[thread.id]
+                                    ? faChevronUp
+                                    : faChevronDown
+                                }
+                                color={theme.colors.main.m2}
+                              />
+                            </AccordionToggle>
+                          </div>
+                        </>
+                      )}
+                    </BorderedContentArea>
+                  ))
+                : null}
+
+              {notes.length > 0
+                ? notes.map(({ note, permittedActions }) => (
+                    <BorderedContentArea
+                      key={note.id}
+                      opaque
+                      paddingHorizontal="0"
+                      paddingVertical="s"
+                      data-qa="finance-note"
+                    >
+                      <FlexRow justifyContent="space-between">
+                        <FixedSpaceColumn spacing="xxs">
+                          <Label>
+                            {i18n.personProfile.financeNotesAndMessages.note}
+                          </Label>
+                          <Light style={{ fontSize: '14px' }}>
+                            {i18n.personProfile.financeNotesAndMessages.created}{' '}
+                            <span data-qa="finance-note-created-at">
+                              {note.createdAt.format()}
+                            </span>
+                            , {note.createdByName}
+                          </Light>
+                          {uiMode === `edit-finance-note_${note.id}` && (
+                            <Light style={{ fontSize: '14px' }}>
+                              {
+                                i18n.personProfile.financeNotesAndMessages
+                                  .inEdit
+                              }
+                            </Light>
+                          )}
+                        </FixedSpaceColumn>
+
+                        {uiMode !== `edit-finance-note_${note.id}` && (
+                          <FixedSpaceRow spacing="xs">
+                            <IconOnlyButton
+                              icon={faPen}
+                              onClick={() => {
+                                setText(note.content)
+                                toggleUiMode(`edit-finance-note_${note.id}`)
+                              }}
+                              disabled={!permittedActions.includes('UPDATE')}
+                              size="s"
+                              data-qa="edit-finance-note"
+                              aria-label={i18n.common.edit}
+                            />
+                            <IconOnlyButton
+                              icon={faTrash}
+                              onClick={() => {
+                                setConfirmDeleteNote(note.id)
+                                toggleUiMode(`delete-finance-note-${note.id}`)
+                              }}
+                              disabled={!permittedActions.includes('DELETE')}
+                              size="s"
+                              data-qa="delete-finance-note"
+                              aria-label={i18n.common.remove}
+                            />
+                          </FixedSpaceRow>
+                        )}
+                      </FlexRow>
+
+                      {uiMode === `edit-finance-note_${note.id}` ? (
+                        <div key={note.id} data-qa="edit-finance-note">
+                          <StyledTextArea
+                            autoFocus
+                            value={text}
+                            rows={3}
+                            onChange={setText}
+                            data-qa="finance-note-text-area"
                           />
-                          <MutateButton
-                            data-qa="update-finance-note"
-                            appearance="inline"
-                            text={i18n.common.save}
-                            mutation={updateFinanceNoteMutation}
-                            onClick={() => ({
-                              id,
-                              noteId: note.id,
-                              body: { content: text, personId: id }
-                            })}
-                            onSuccess={() => clearUiMode()}
-                            disabled={!text}
-                          />
-                        </FixedSpaceRow>
-                      </div>
-                    ) : (
-                      <div>{formatParagraphs(note.content)}</div>
-                    )}
-                  </BorderedContentArea>
-                ))
-              : null}
-          </>
-        ))}
+                          <Gap size="xs" />
+                          <FixedSpaceRow justifyContent="flex-start">
+                            <Button
+                              appearance="inline"
+                              onClick={() => clearUiMode()}
+                              text={i18n.common.cancel}
+                            />
+                            <MutateButton
+                              data-qa="update-finance-note"
+                              appearance="inline"
+                              text={i18n.common.save}
+                              mutation={updateFinanceNoteMutation}
+                              onClick={() => ({
+                                id,
+                                noteId: note.id,
+                                body: { content: text, personId: id }
+                              })}
+                              onSuccess={() => clearUiMode()}
+                              disabled={!text}
+                            />
+                          </FixedSpaceRow>
+                        </div>
+                      ) : (
+                        <div>{formatParagraphs(note.content)}</div>
+                      )}
+                    </BorderedContentArea>
+                  ))
+                : null}
+            </>
+          )
+        )}
       </CollapsibleContentArea>
     </>
   )
 })

 const BorderedContentArea = styled(ContentArea)`
-  border-bottom: solid 1px ${(p) => p.theme.colors.grayscale.g15};
+  border-bottom: solid 1px ${(p) => p.theme.colors.grayscale.g35};
+`
+const BorderedMessageArea = styled(ContentArea)`
+  border-top: solid 1px ${(p) => p.theme.colors.grayscale.g15};
 `
 const StyledTextArea = styled(TextArea)`
   width: 100%;
@@ -269,3 +574,16 @@ const StyledTextArea = styled(TextArea)`
   min-height: 100px;
   border: 1px solid ${(p) => p.theme.colors.grayscale.g70};
 `
+const AccordionToggle = styled.span`
+  color: ${(p) => p.theme.colors.main.m2};
+  cursor: pointer;
+`
+
+const UnderlinedLink = styled(Link)`
+  font-weight: normal;
+  text-decoration: underline;
+`
+const StyledMessageEditor = styled(MessageEditor)`
+  bottom: 0;
+  right: 0;
+`
diff --git c/frontend/src/employee-frontend/generated/api-clients/messaging.ts i/frontend/src/employee-frontend/generated/api-clients/messaging.ts
index 94019a6727..9ed4dce7e3 100644
--- c/frontend/src/employee-frontend/generated/api-clients/messaging.ts
+++ i/frontend/src/employee-frontend/generated/api-clients/messaging.ts
@@ -21,6 +21,7 @@ import { MessageThreadId } from 'lib-common/generated/api-types/shared'
 import { PagedMessageCopies } from 'lib-common/generated/api-types/messaging'
 import { PagedMessageThreads } from 'lib-common/generated/api-types/messaging'
 import { PagedSentMessages } from 'lib-common/generated/api-types/messaging'
+import { PersonId } from 'lib-common/generated/api-types/shared'
 import { PostMessageBody } from 'lib-common/generated/api-types/messaging'
 import { PostMessagePreflightBody } from 'lib-common/generated/api-types/messaging'
 import { PostMessagePreflightResponse } from 'lib-common/generated/api-types/messaging'
@@ -166,6 +167,22 @@ export async function getDraftMessages(
 }

+/**
+* Generated from fi.espoo.evaka.messaging.MessageController.getFinanceMessagesWithPerson
+*/
+export async function getFinanceMessagesWithPerson(
+  request: {
+    personId: PersonId
+  }
+): Promise<MessageThread[]> {
+  const { data: json } = await client.request<JsonOf<MessageThread[]>>({
+    url: uri`/employee/messages/finance/${request.personId}`.toString(),
+    method: 'GET'
+  })
+  return json.map(e => deserializeJsonMessageThread(e))
+}
+
+
 /**
 * Generated from fi.espoo.evaka.messaging.MessageController.getFolders
 */
diff --git c/frontend/src/lib-common/generated/api-types/messaging.ts i/frontend/src/lib-common/generated/api-types/messaging.ts
index 688159a2e7..d6b9062573 100644
--- c/frontend/src/lib-common/generated/api-types/messaging.ts
+++ i/frontend/src/lib-common/generated/api-types/messaging.ts
@@ -32,6 +32,7 @@ export type AccountType =
   | 'CITIZEN'
   | 'MUNICIPAL'
   | 'SERVICE_WORKER'
+  | 'FINANCE'

 /**
 * Generated from fi.espoo.evaka.messaging.AuthorizedMessageAccount
@@ -45,6 +46,7 @@ export interface AuthorizedMessageAccount {
 * Generated from fi.espoo.evaka.messaging.MessageControllerCitizen.ChildMessageAccountAccess
 */
 export interface ChildMessageAccountAccess {
+  childId: PersonId | null
   newMessage: MessageAccountId[]
   reply: MessageAccountId[]
 }
@@ -124,7 +126,7 @@ export interface DraftContent {
 * Generated from fi.espoo.evaka.messaging.MessageControllerCitizen.GetReceiversResponse
 */
 export interface GetReceiversResponse {
-  childrenToMessageAccounts: Partial<Record<PersonId, ChildMessageAccountAccess>>
+  childrenToMessageAccounts: ChildMessageAccountAccess[]
   messageAccounts: MessageAccountWithPresence[]
 }

diff --git c/frontend/src/lib-components/messages/types.ts i/frontend/src/lib-components/messages/types.ts
index df97d51f1d..145e00659d 100644
--- c/frontend/src/lib-components/messages/types.ts
+++ i/frontend/src/lib-components/messages/types.ts
@@ -33,6 +33,10 @@ export const isServiceWorkerMessageAccount = (
   acc: AuthorizedMessageAccount
 ): acc is AuthorizedMessageAccount => acc.account.type === 'SERVICE_WORKER'

+export const isFinanceMessageAccount = (
+  acc: AuthorizedMessageAccount
+): acc is AuthorizedMessageAccount => acc.account.type === 'FINANCE'
+
 export interface SaveDraftParams {
   accountId: MessageAccountId
   draftId: MessageDraftId
diff --git c/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx i/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx
index b70d8b0c7a..20cf891493 100755
--- c/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx
+++ i/frontend/src/lib-customizations/defaults/employee/i18n/fi.tsx
@@ -1699,7 +1699,13 @@ export const fi = {
     financeNotesAndMessages: {
       title: 'Talouden muistiinpanot ja viestit',
       addNote: 'Lisää muistiinpano',
-      confirmDelete: 'Haluatko varmasti poistaa muistiinpanon',
+      sendMessage: 'Lähetä eVaka-viesti',
+      link: 'Linkki alkuperäiseen viestiin',
+      showMessages: 'Näytä kaikki viestit',
+      hideMessages: 'Piilota kaikki viestit',
+      confirmDeleteNote: 'Haluatko varmasti poistaa muistiinpanon',
+      confirmDeleteMessage: 'Haluatko varmasti poistaa viestin',
+      confirmDeleteThread: 'Haluatko varmasti poistaa viestiketjun',
       note: 'Muistiinpano',
       created: 'Luotu',
       inEdit: 'Muokattavana'
@@ -4673,6 +4679,7 @@ export const fi = {
       municipalMessages: 'Kunnan tiedotteet',
       serviceWorkerMessages: 'Palveluohjauksen viestit',
       serviceWorkerFolders: 'Palveluohjauksen kansiot',
+      financeMessages: 'Taloushallinnon viestit',
       ownMessages: 'Omat viestit',
       groupsMessages: 'Ryhmien viestit',
       noAccountAccess:
diff --git c/service/src/integrationTest/kotlin/fi/espoo/evaka/messaging/MessageAccountQueriesTest.kt i/service/src/integrationTest/kotlin/fi/espoo/evaka/messaging/MessageAccountQueriesTest.kt
index 6c5ab0367c..1fd9eda770 100644
--- c/service/src/integrationTest/kotlin/fi/espoo/evaka/messaging/MessageAccountQueriesTest.kt
+++ i/service/src/integrationTest/kotlin/fi/espoo/evaka/messaging/MessageAccountQueriesTest.kt
@@ -138,6 +138,7 @@ class MessageAccountQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                     ),
                     "Espoo",
                     "Espoo palveluohjaus",
+                    "Espoo asiakasmaksut",
                 )
             }
         assertEquals(3, accounts2.size)
@@ -191,6 +192,7 @@ class MessageAccountQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                     ),
                     "Espoo",
                     "Espoo palveluohjaus",
+                    "Espoo asiakasmaksut",
                 )
             }
         assertEquals(1, accounts2.size)
@@ -230,6 +232,7 @@ class MessageAccountQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                     ),
                     "Espoo",
                     "Espoo palveluohjaus",
+                    "Espoo asiakasmaksut",
                 )
             }
         assertEquals(0, accounts2.size)
@@ -310,6 +313,7 @@ class MessageAccountQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                     recipientNames = allAccounts.map { it.name },
                     municipalAccountName = "Espoo",
                     serviceWorkerAccountName = "Espoo palveluohjaus",
+                    financeAccountName = "Espoo asiakasmaksut",
                 )
             tx.insertRecipients(listOf(messageId to allAccounts.map { it.id }.toSet()))
         }
diff --git c/service/src/integrationTest/kotlin/fi/espoo/evaka/messaging/MessageQueriesTest.kt i/service/src/integrationTest/kotlin/fi/espoo/evaka/messaging/MessageQueriesTest.kt
index d800f6a8a2..917d651f77 100644
--- c/service/src/integrationTest/kotlin/fi/espoo/evaka/messaging/MessageQueriesTest.kt
+++ i/service/src/integrationTest/kotlin/fi/espoo/evaka/messaging/MessageQueriesTest.kt
@@ -151,13 +151,23 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                         1,
                         "Espoo",
                         "Espoon palveluohjaus",
+                        "Espoon asiakasmaksut",
                     )
                 }
                 .data
                 .size,
         )
         val personResult =
-            db.read { it.getThreads(accounts.employee1.id, 10, 1, "Espoo", "Espoon palveluohjaus") }
+            db.read {
+                it.getThreads(
+                    accounts.employee1.id,
+                    10,
+                    1,
+                    "Espoo",
+                    "Espoon palveluohjaus",
+                    "Espoon asiakasmaksut",
+                )
+            }
         assertEquals(2, personResult.data.size)

         val thread = personResult.data.first()
@@ -169,7 +179,16 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {

         // then the message has correct readAt
         val person1Threads =
-            db.read { it.getThreads(accounts.person1.id, 10, 1, "Espoo", "Espoon palveluohjaus") }
+            db.read {
+                it.getThreads(
+                    accounts.person1.id,
+                    10,
+                    1,
+                    "Espoo",
+                    "Espoon palveluohjaus",
+                    "Espoon asiakasmaksut",
+                )
+            }
         assertEquals(2, person1Threads.data.size)
         val readMessages = person1Threads.data.flatMap { it.messages.mapNotNull { m -> m.readAt } }
         assertEquals(1, readMessages.size)
@@ -178,7 +197,16 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
         // then person 2 threads are not affected
         assertEquals(
             0,
-            db.read { it.getThreads(accounts.person2.id, 10, 1, "Espoo", "Espoon palveluohjaus") }
+            db.read {
+                    it.getThreads(
+                        accounts.person2.id,
+                        10,
+                        1,
+                        "Espoo",
+                        "Espoon palveluohjaus",
+                        "Espoon asiakasmaksut",
+                    )
+                }
                 .data
                 .flatMap { it.messages.mapNotNull { m -> m.readAt } }
                 .size,
@@ -197,7 +225,14 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
         // then employee sees the thread
         val employeeResult =
             db.read {
-                it.getReceivedThreads(accounts.employee1.id, 10, 1, "Espoo", "Espoon palveluohjaus")
+                it.getReceivedThreads(
+                    accounts.employee1.id,
+                    10,
+                    1,
+                    "Espoo",
+                    "Espoon palveluohjaus",
+                    "Espoon asiakasmaksut",
+                )
             }
         assertEquals(1, employeeResult.data.size)
         assertEquals("Newest thread", employeeResult.data[0].title)
@@ -205,7 +240,16 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {

         // person 1 is recipient in both threads
         val person1Result =
-            db.read { it.getThreads(accounts.person1.id, 10, 1, "Espoo", "Espoon palveluohjaus") }
+            db.read {
+                it.getThreads(
+                    accounts.person1.id,
+                    10,
+                    1,
+                    "Espoo",
+                    "Espoon palveluohjaus",
+                    "Espoon asiakasmaksut",
+                )
+            }
         assertEquals(2, person1Result.data.size)

         val newestThread = person1Result.data[0]
@@ -227,7 +271,16 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {

         // person 2 is recipient in the oldest thread only
         val person2Result =
-            db.read { it.getThreads(accounts.person2.id, 10, 1, "Espoo", "Espoon palveluohjaus") }
+            db.read {
+                it.getThreads(
+                    accounts.person2.id,
+                    10,
+                    1,
+                    "Espoo",
+                    "Espoon palveluohjaus",
+                    "Espoon asiakasmaksut",
+                )
+            }
         assertEquals(1, person2Result.data.size)
         assertEquals(oldestThread.id, person2Result.data[0].id)
         assertEquals(0, person2Result.data.flatMap { it.messages }.mapNotNull { it.readAt }.size)
@@ -235,7 +288,14 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
         // employee 2 is participating with himself
         val employee2Result =
             db.read {
-                it.getReceivedThreads(accounts.employee2.id, 10, 1, "Espoo", "Espoon palveluohjaus")
+                it.getReceivedThreads(
+                    accounts.employee2.id,
+                    10,
+                    1,
+                    "Espoo",
+                    "Espoon palveluohjaus",
+                    "Espoon asiakasmaksut",
+                )
             }
         assertEquals(1, employee2Result.data.size)
         assertEquals(1, employee2Result.data[0].messages.size)
@@ -249,7 +309,16 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
         createThread("t2", "c2", accounts.employee1, listOf(accounts.person1))

         val messages =
-            db.read { it.getThreads(accounts.person1.id, 10, 1, "Espoo", "Espoon palveluohjaus") }
+            db.read {
+                it.getThreads(
+                    accounts.person1.id,
+                    10,
+                    1,
+                    "Espoo",
+                    "Espoon palveluohjaus",
+                    "Espoon asiakasmaksut",
+                )
+            }
         assertEquals(2, messages.total)
         assertEquals(2, messages.data.size)
         assertEquals(setOf("t1", "t2"), messages.data.map { it.title }.toSet())
@@ -257,8 +326,22 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
         val (page1, page2) =
             db.read {
                 listOf(
-                    it.getThreads(accounts.person1.id, 1, 1, "Espoo", "Espoon palveluohjaus"),
-                    it.getThreads(accounts.person1.id, 1, 2, "Espoo", "Espoon palveluohjaus"),
+                    it.getThreads(
+                        accounts.person1.id,
+                        1,
+                        1,
+                        "Espoo",
+                        "Espoon palveluohjaus",
+                        "Espoon asiakasmaksut",
+                    ),
+                    it.getThreads(
+                        accounts.person1.id,
+                        1,
+                        2,
+                        "Espoo",
+                        "Espoon palveluohjaus",
+                        "Espoon asiakasmaksut",
+                    ),
                 )
             }
         assertEquals(2, page1.total)
@@ -366,9 +449,11 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                             tx.getAccountNames(
                                 setOf(accounts.employee1.id),
                                 testFeatureConfig.serviceWorkerMessageAccountName,
+                                testFeatureConfig.financeMessageAccountName,
                             ),
                         municipalAccountName = "Espoo",
                         serviceWorkerAccountName = "Espoon palveluohjaus",
+                        financeAccountName = "Espoon asiakasmaksut",
                     )
                 tx.insertRecipients(listOf(messageId to setOf(accounts.employee1.id)))
                 tx.getThreadByMessageId(messageId)
@@ -747,6 +832,7 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                         1,
                         "Espoo",
                         "Espoon palveluohjaus",
+                        "Espoon asiakasmaksut",
                         archiveFolderId,
                     )
                     .total
@@ -770,6 +856,7 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                         1,
                         "Espoo",
                         "Espoon palveluohjaus",
+                        "Espoon asiakasmaksut",
                         archiveFolderId,
                     )
                     .total
@@ -787,6 +874,7 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                         1,
                         "Espoo",
                         "Espoon palveluohjaus",
+                        "Espoon asiakasmaksut",
                         null,
                     )
                     .total
@@ -802,6 +890,7 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                         1,
                         "Espoo",
                         "Espoon palveluohjaus",
+                        "Espoon asiakasmaksut",
                         archiveFolderId,
                     )
                     .total
@@ -905,9 +994,11 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                         tx.getAccountNames(
                             recipientIds,
                             testFeatureConfig.serviceWorkerMessageAccountName,
+                            testFeatureConfig.financeMessageAccountName,
                         ),
                     municipalAccountName = "Espoo",
                     serviceWorkerAccountName = "Espoon palveluohjaus",
+                    financeAccountName = "Espoon asiakasmaksut",
                 )
             tx.insertRecipients(listOf(messageId to recipientAccounts.map { it.id }.toSet()))
             tx.upsertSenderThreadParticipants(sender.id, listOf(threadId), now)
@@ -941,6 +1032,7 @@ class MessageQueriesTest : PureJdbiTest(resetDbBeforeEach = true) {
                     recipientNames = listOf(),
                     municipalAccountName = "Espoo",
                     serviceWorkerAccountName = "Espoon palveluohjaus",
+                    financeAccountName = "Espoon asiakasmaksut",
                 )
             tx.insertRecipients(listOf(messageId to recipientIds))
             tx.upsertSenderThreadParticipants(sender.id, listOf(threadId), now)
diff --git c/service/src/integrationTest/kotlin/fi/espoo/evaka/pis/InactivePeopleCleanupIntegrationTest.kt i/service/src/integrationTest/kotlin/fi/espoo/evaka/pis/InactivePeopleCleanupIntegrationTest.kt
index 48586f8f64..96dca0b757 100644
--- c/service/src/integrationTest/kotlin/fi/espoo/evaka/pis/InactivePeopleCleanupIntegrationTest.kt
+++ i/service/src/integrationTest/kotlin/fi/espoo/evaka/pis/InactivePeopleCleanupIntegrationTest.kt
@@ -365,6 +365,7 @@ class InactivePeopleCleanupIntegrationTest : PureJdbiTest(resetDbBeforeEach = tr
                     recipientNames = listOf("recipient name"),
                     municipalAccountName = "Espoo",
                     serviceWorkerAccountName = "Espoon palveluohjaus",
+                    financeAccountName = "Espoon asiakasmaksut",
                 )
             tx.insertRecipients(listOf(messageId to setOf(personAccount)))
         }
@@ -404,6 +405,7 @@ class InactivePeopleCleanupIntegrationTest : PureJdbiTest(resetDbBeforeEach = tr
                     recipientNames = listOf("employee name"),
                     municipalAccountName = "Espoo",
                     serviceWorkerAccountName = "Espoon palveluohjaus",
+                    financeAccountName = "Espoon asiakasmaksut",
                 )
             tx.insertRecipients(listOf(messageId to setOf(employeeAccount)))
         }
diff --git c/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/config/SharedIntegrationTestConfig.kt i/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/config/SharedIntegrationTestConfig.kt
index 6d43a06a86..6cbc852844 100755
--- c/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/config/SharedIntegrationTestConfig.kt
+++ i/service/src/integrationTest/kotlin/fi/espoo/evaka/shared/config/SharedIntegrationTestConfig.kt
@@ -244,6 +244,8 @@ val testFeatureConfig =
         municipalMessageAccountName = "Espoon kaupunki - Esbo stad - City of Espoo",
         serviceWorkerMessageAccountName =
             "Varhaiskasvatuksen palveluohjaus - Småbarnspedagogikens servicehandledning - Early childhood education service guidance",
+        financeMessageAccountName =
+            "Varhaiskasvatuksen asiakasmaksut - Småbarnspedagogikens avgifter - Early childhood education fees",
         applyPlacementUnitFromDecision = false,
         preferredStartRelativeApplicationDueDate = false,
         fiveYearsOldDaycareEnabled = true,
diff --git c/service/src/main/kotlin/fi/espoo/evaka/EspooConfig.kt i/service/src/main/kotlin/fi/espoo/evaka/EspooConfig.kt
index c6c8bbc200..20c5a00364 100644
--- c/service/src/main/kotlin/fi/espoo/evaka/EspooConfig.kt
+++ i/service/src/main/kotlin/fi/espoo/evaka/EspooConfig.kt
@@ -200,6 +200,8 @@ class EspooConfig {
             municipalMessageAccountName = "Espoon kaupunki - Esbo stad - City of Espoo",
             serviceWorkerMessageAccountName =
                 "Varhaiskasvatuksen palveluohjaus - Småbarnspedagogikens servicehandledning - Early childhood education service guidance",
+            financeMessageAccountName =
+                "Varhaiskasvatuksen asiakasmaksut - Småbarnspedagogikens avgifter - Early childhood education fees",
             applyPlacementUnitFromDecision = false,
             preferredStartRelativeApplicationDueDate = false,
             fiveYearsOldDaycareEnabled = true,
diff --git c/service/src/main/kotlin/fi/espoo/evaka/messaging/Message.kt i/service/src/main/kotlin/fi/espoo/evaka/messaging/Message.kt
index e8f40f6743..7a5abab34b 100644
--- c/service/src/main/kotlin/fi/espoo/evaka/messaging/Message.kt
+++ i/service/src/main/kotlin/fi/espoo/evaka/messaging/Message.kt
@@ -182,7 +182,8 @@ enum class AccountType : DatabaseEnum {
     GROUP,
     CITIZEN,
     MUNICIPAL,
-    SERVICE_WORKER;
+    SERVICE_WORKER,
+    FINANCE;

     fun isPrimaryRecipientForCitizenMessage(): Boolean =
         when (this) {
@@ -191,6 +192,7 @@ enum class AccountType : DatabaseEnum {
             CITIZEN -> false
             MUNICIPAL -> false
             SERVICE_WORKER -> false
+            FINANCE -> false
         }

     override val sqlType: String = "message_account_type"
diff --git c/service/src/main/kotlin/fi/espoo/evaka/messaging/MessageAccountQueries.kt i/service/src/main/kotlin/fi/espoo/evaka/messaging/MessageAccountQueries.kt
index bc04482840..71f92389d9 100644
--- c/service/src/main/kotlin/fi/espoo/evaka/messaging/MessageAccountQueries.kt
+++ i/service/src/main/kotlin/fi/espoo/evaka/messaging/MessageAccountQueries.kt
@@ -46,10 +46,12 @@ private fun messageAccountName(
     accountName: String?,
     municipalAccountName: String,
     serviceWorkerAccountName: String,
+    financeAccountName: String,
 ): String {
     return when (accountType) {
         AccountType.MUNICIPAL -> municipalAccountName
         AccountType.SERVICE_WORKER -> serviceWorkerAccountName
+        AccountType.FINANCE -> financeAccountName
         else -> accountName!!
     }
 }
@@ -58,6 +60,7 @@ fun Database.Read.getAuthorizedMessageAccountsForEmployee(
     idFilter: AccessControlFilter<MessageAccountId>,
     municipalAccountName: String,
     serviceWorkerAccountName: String,
+    financeAccountName: String,
 ): List<AuthorizedMessageAccount> {
     return createQuery {
             sql(
@@ -82,6 +85,7 @@ AND (
     OR 'MESSAGING' = ANY(supervisor_dc.enabled_pilot_features)
     OR acc.type = 'MUNICIPAL'
     OR acc.type = 'SERVICE_WORKER'
+    OR acc.type = 'FINANCE'
 )
 """
             )
@@ -98,6 +102,7 @@ AND (
                                     column("account_name"),
                                     municipalAccountName = municipalAccountName,
                                     serviceWorkerAccountName = serviceWorkerAccountName,
+                                    financeAccountName = financeAccountName,
                                 ),
                             type = accountType,
                         )
@@ -118,12 +123,17 @@ AND (
 fun Database.Read.getAccountNames(
     accountIds: Set<MessageAccountId>,
     serviceWorkerAccountName: String,
+    financeAccountName: String,
 ): List<String> {

     return createQuery {
             sql(
                 """
-SELECT CASE mav.type WHEN 'SERVICE_WORKER' THEN ${bind(serviceWorkerAccountName)} ELSE mav.name END as name
+SELECT CASE mav.type
+    WHEN 'SERVICE_WORKER' THEN ${bind(serviceWorkerAccountName)}
+    WHEN 'FINANCE' THEN ${bind(financeAccountName)}
+    ELSE mav.name
+END as name
 FROM message_account_view mav
 WHERE mav.id = ANY(${bind(accountIds)})
 """
@@ -136,6 +146,7 @@ fun Database.Read.getMessageAccount(
     accountId: MessageAccountId,
     municipalAccountName: String,
     serviceWorkerAccountName: String,
+    financeAccountName: String,
 ): MessageAccount {
     return createQuery {
             sql(
@@ -159,6 +170,7 @@ WHERE acc.id = ${bind(accountId)}
                             column("name"),
                             municipalAccountName = municipalAccountName,
                             serviceWorkerAccountName = serviceWorkerAccountName,
+                            financeAccountName = financeAccountName,
                         ),
                     type = accountType,
                 )
@@ -270,3 +282,7 @@ WHERE content.id = ${bind(id)}
 fun Database.Read.getServiceWorkerAccountId(): MessageAccountId? =
     createQuery { sql("SELECT id FROM message_account WHERE type = 'SERVICE_WORKER'") }
         .exactlyOneOrNull<MessageAccountId>()
+
+fun Database.Read.getFinanceAccountId(): MessageAccountId? =
+    createQuery { sql("SELECT id FROM message_account WHERE type = 'FINANCE'") }
+        .exactlyOneOrNull<MessageAccountId>()
diff --git c/service/src/main/kotlin/fi/espoo/evaka/messaging/MessageController.kt i/service/src/main/kotlin/fi/espoo/evaka/messaging/MessageController.kt
index faee4b0360..d609b57608 100644
--- c/service/src/main/kotlin/fi/espoo/evaka/messaging/MessageController.kt
+++ i/service/src/main/kotlin/fi/espoo/evaka/messaging/MessageController.kt
@@ -7,6 +7,7 @@ package fi.espoo.evaka.messaging
 import fi.espoo.evaka.Audit
 import fi.espoo.evaka.AuditId
 import fi.espoo.evaka.application.personHasSentApplicationWithId
+import fi.espoo.evaka.invoicing.controller.SortDirection
 import fi.espoo.evaka.shared.*
 import fi.espoo.evaka.shared.auth.AuthenticatedUser
 import fi.espoo.evaka.shared.db.Database
@@ -70,6 +71,7 @@ class MessageController(
                         filter,
                         featureConfig.municipalMessageAccountName,
                         featureConfig.serviceWorkerMessageAccountName,
+                        featureConfig.financeMessageAccountName,
                     )
                 }
             }
@@ -101,6 +103,7 @@ class MessageController(
                                 filter,
                                 featureConfig.municipalMessageAccountName,
                                 featureConfig.serviceWorkerMessageAccountName,
+                                featureConfig.financeMessageAccountName,
                             )
                             // Only return group accounts of the requested unit
                             .filter {
@@ -160,6 +163,7 @@ class MessageController(
                     page,
                     featureConfig.municipalMessageAccountName,
                     featureConfig.serviceWorkerMessageAccountName,
+                    featureConfig.financeMessageAccountName,
                     accountAccessLimit = accountAccessLimit,
                 )
             }
@@ -194,6 +198,7 @@ class MessageController(
                             page,
                             featureConfig.municipalMessageAccountName,
                             featureConfig.serviceWorkerMessageAccountName,
+                            featureConfig.financeMessageAccountName,
            …
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Uusi toiminnallisuus tai parannus
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant