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

Oikaisulaskut, osa 3: Oikaisulaskujen merkitseminen siirretyksi (ei vielä käytössä) #5989

Merged
merged 3 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//
// SPDX-License-Identifier: LGPL-2.1-or-later

import { Page, Element } from '../../../utils/page'
import { InvoiceReplacementReason } from 'lib-common/generated/api-types/invoicing'

import { Page, Element, TextInput, Select } from '../../../utils/page'

export class InvoiceDetailsPage {
headOfFamilySection: InvoiceHeadOfFamilySection
Expand All @@ -11,6 +13,11 @@ export class InvoiceDetailsPage {
totalPrice: Element
previousTotalPrice: Element

replacementDraftForm: InvoiceReplacementDraftSection

// view
replacementInfo: InvoiceReplacementInfoSection

constructor(private page: Page) {
this.headOfFamilySection = new InvoiceHeadOfFamilySection(
page.findByDataQa('head-of-family')
Expand All @@ -22,6 +29,13 @@ export class InvoiceDetailsPage {
this.previousTotalPrice = this.page
.findByDataQa('total-sum')
.findByDataQa('previous-price')

this.replacementDraftForm = new InvoiceReplacementDraftSection(
page.findByDataQa('replacement-draft-form')
)
this.replacementInfo = new InvoiceReplacementInfoSection(
page.findByDataQa('replacement-info')
)
}

nthChild(index: number): InvoiceChildSection {
Expand Down Expand Up @@ -80,3 +94,20 @@ export class InvoiceRow extends Element {
unitPrice = this.findByDataQa('unit-price')
totalPrice = this.findByDataQa('total-price')
}

export class InvoiceReplacementDraftSection extends Element {
reason = new Select(this.findByDataQa('replacement-reason'))
notes = new TextInput(this.findByDataQa('replacement-notes'))
markSentButton = this.findByDataQa('mark-sent')

async selectReason(value: InvoiceReplacementReason) {
await this.reason.selectOption(value)
}
}

export class InvoiceReplacementInfoSection extends Element {
reason = this.findByDataQa('replacement-reason')
notes = this.findByDataQa('replacement-notes')
sentAt = this.findByDataQa('sent-at')
sentBy = this.findByDataQa('sent-by')
}
34 changes: 32 additions & 2 deletions frontend/src/e2e-test/specs/4_finance/invoices.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: LGPL-2.1-or-later

import { DevPlacement } from 'e2e-test/generated/api-types'
import { DevEmployee, DevPlacement } from 'e2e-test/generated/api-types'
import FiniteDateRange from 'lib-common/finite-date-range'
import HelsinkiDateTime from 'lib-common/helsinki-date-time'
import LocalDate from 'lib-common/local-date'
Expand Down Expand Up @@ -43,6 +43,8 @@ const codebtor = Fixture.person({
ssn: '010177-1234'
}).data

let financeAdmin: DevEmployee

beforeEach(async () => {
await resetServiceState()
await Fixture.careArea(testCareArea).save()
Expand Down Expand Up @@ -79,7 +81,7 @@ beforeEach(async () => {
async function openInvoicesPage(): Promise<InvoicesPage> {
page = await Page.open({ acceptDownloads: true, mockedTime: now })

const financeAdmin = await Fixture.employee().financeAdmin().save()
financeAdmin = await Fixture.employee().financeAdmin().save()
await employeeLogin(page, financeAdmin)

await page.goto(config.employeeUrl)
Expand Down Expand Up @@ -339,5 +341,33 @@ describe('Invoices', () => {

await invoicesPage.navigateBackToInvoices()
})

test('Replacement invoice can be marked as sent', async () => {
// Add an absence => replacement invoice is generated
await Fixture.absence({
childId: testChild2.id,
date: today.subMonths(1).withDate(1),
absenceType: 'FORCE_MAJEURE',
absenceCategory: 'BILLABLE'
}).save()
await generateReplacementDraftInvoices()

await invoicesPage.filterByStatus('REPLACEMENT_DRAFT')

const invoicePage = await invoicesPage.openFirstInvoice()
const form = invoicePage.replacementDraftForm

await form.selectReason('ABSENCE')
await form.notes.fill('Unohtunut päiväkirjamerkintä')
await form.markSentButton.click()

const view = invoicePage.replacementInfo
await view.reason.assertTextEquals('Päiväkirjamerkintä')
await view.notes.assertTextEquals('Unohtunut päiväkirjamerkintä')
await view.sentAt.assertTextEquals(now.format())
await view.sentBy.assertTextEquals(
`${financeAdmin.lastName} ${financeAdmin.firstName}`
)
})
})
})
27 changes: 12 additions & 15 deletions frontend/src/employee-frontend/components/invoice/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,21 @@ type Props = {
invoiceResponse: InvoiceDetailedResponse
}

const Actions = React.memo(function Actions({ invoiceResponse }: Props) {
export const MarkSent = React.memo(function MarkSent({
invoiceResponse
}: Props) {
const { i18n } = useTranslation()
const { invoice, permittedActions } = invoiceResponse

return (
return permittedActions.includes('MARK_SENT') ? (
<FixedSpaceRow justifyContent="flex-end">
{permittedActions.includes('MARK_SENT') &&
invoice.status === 'WAITING_FOR_SENDING' ? (
<MutateButton
primary
text={i18n.invoice.form.buttons.markSent}
mutation={markInvoicesSentMutation}
onClick={() => ({ body: [invoice.id] })}
data-qa="invoice-actions-mark-sent"
/>
) : null}
<MutateButton
primary
text={i18n.invoice.form.buttons.markSent}
mutation={markInvoicesSentMutation}
onClick={() => ({ body: [invoice.id] })}
data-qa="invoice-actions-mark-sent"
/>
</FixedSpaceRow>
)
) : null
})

export default Actions
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ import { TitleContext, TitleState } from '../../state/title'
import { renderResult } from '../async-rendering'
import { invoiceCodesQuery, invoiceDetailsQuery } from '../invoices/queries'

import Actions from './Actions'
import { MarkSent } from './Actions'
import InvoiceDetailsSection from './InvoiceDetailsSection'
import InvoiceHeadOfFamilySection from './InvoiceHeadOfFamilySection'
import InvoiceRowsSection from './InvoiceRowsSection'
import { ReplacementDraftForm, ReplacementInfo } from './ReplacementDraftInfo'
import Sum from './Sum'
import { formatInvoicePeriod } from './utils'

Expand Down Expand Up @@ -79,7 +80,13 @@ export default React.memo(function InvoiceDetailsPage() {
previousSum={response.replacedInvoice?.totalPrice}
data-qa="total-sum"
/>
<Actions invoiceResponse={response} />
{response.invoice.status === 'WAITING_FOR_SENDING' ? (
<MarkSent invoiceResponse={response} />
) : response.invoice.status === 'REPLACEMENT_DRAFT' ? (
<ReplacementDraftForm invoiceResponse={response} />
) : response.invoice.replacementReason !== null ? (
<ReplacementInfo invoiceResponse={response} />
) : null}
</ContentArea>
)
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-FileCopyrightText: 2017-2022 City of Espoo
//
// SPDX-License-Identifier: LGPL-2.1-or-later

import React from 'react'
import styled from 'styled-components'

import { string } from 'lib-common/form/fields'
import { object, oneOf, required } from 'lib-common/form/form'
import { useForm, useFormFields } from 'lib-common/form/hooks'
import {
InvoiceDetailedResponse,
InvoiceReplacementReason,
invoiceReplacementReasons
} from 'lib-common/generated/api-types/invoicing'
import { MutateButton } from 'lib-components/atoms/buttons/MutateButton'
import { SelectF } from 'lib-components/atoms/dropdowns/Select'
import { TextAreaF } from 'lib-components/atoms/form/TextArea'
import {
FixedSpaceColumn,
FixedSpaceRow
} from 'lib-components/layout/flex-helpers'
import { InfoBox } from 'lib-components/molecules/MessageBoxes'
import { H3, Label, P } from 'lib-components/typography'

import { useTranslation } from '../../state/i18n'
import { markReplacementDraftSentMutation } from '../invoices/queries'

const replacementDraftForm = object({
reason: required(oneOf<InvoiceReplacementReason>()),
notes: string()
})

export function ReplacementDraftForm({
invoiceResponse
}: {
invoiceResponse: InvoiceDetailedResponse
}) {
const { i18n } = useTranslation()

const form = useForm(
replacementDraftForm,
() => ({
reason: {
domValue: '',
options: invoiceReplacementReasons.map((r) => ({
domValue: r,
value: r,
label: i18n.invoice.form.replacement.reasons[r]
}))
},
notes: ''
}),
i18n.validationErrors
)
const { reason, notes } = useFormFields(form)

return (
<FixedSpaceColumn data-qa="replacement-draft-form">
<H3>{i18n.invoice.form.replacement.title}</H3>
<P>{i18n.invoice.form.replacement.info}</P>
<FixedSpaceRow>
<FixedSpaceColumn>
<Label>Oikaisun syy *</Label>
<SelectF
bind={reason}
placeholder={i18n.common.select}
hideErrorsBeforeTouched
data-qa="replacement-reason"
/>
</FixedSpaceColumn>
<FixedSpaceColumn>
<Label>Lisätiedot</Label>
<TextAreaWrapper>
<TextAreaF bind={notes} data-qa="replacement-notes" />
</TextAreaWrapper>
</FixedSpaceColumn>
</FixedSpaceRow>
<FixedSpaceRow justifyContent="flex-end">
<InfoBox message={i18n.invoice.form.replacement.sendInfo} />
</FixedSpaceRow>
<FixedSpaceRow justifyContent="flex-end">
<MutateButton
primary
mutation={markReplacementDraftSentMutation}
onClick={() => ({
invoiceId: invoiceResponse.invoice.id,
body: form.value()
})}
text={i18n.invoice.form.replacement.send}
disabled={!form.isValid()}
data-qa="mark-sent"
/>
</FixedSpaceRow>
</FixedSpaceColumn>
)
}

export function ReplacementInfo({
invoiceResponse
}: {
invoiceResponse: InvoiceDetailedResponse
}) {
const { i18n } = useTranslation()
const { invoice } = invoiceResponse

if (invoice.replacementReason === null) return null

return (
<FixedSpaceColumn data-qa="replacement-info">
<H3>{i18n.invoice.form.replacement.title}</H3>
<P>{i18n.invoice.form.replacement.info}</P>
<FixedSpaceRow spacing="L">
<FixedSpaceColumn>
<Label>Oikaisun syy</Label>
<div data-qa="replacement-reason">
{i18n.invoice.form.replacement.reasons[invoice.replacementReason]}
</div>
</FixedSpaceColumn>
<FixedSpaceColumn>
<Label>Lisätiedot</Label>
<NotesWrapper data-qa="replacement-notes">
{invoice.replacementNotes}
</NotesWrapper>
</FixedSpaceColumn>
</FixedSpaceRow>
<div>
<Label>Merkitty siirretyksi</Label>
<div>
<span data-qa="sent-at">{invoice.sentAt?.format()}</span>
{' ('}
<span data-qa="sent-by">{invoice.sentBy?.name}</span>)
</div>
</div>
</FixedSpaceColumn>
)
}

const TextAreaWrapper = styled.div`
min-width: 400px;
`

const NotesWrapper = styled.div`
white-space: pre-line;
`
10 changes: 9 additions & 1 deletion frontend/src/employee-frontend/components/invoices/Invoices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,15 @@ const InvoiceTableBody = React.memo(function InvoiceTableBody({
<Td>
<ChildrenCell people={item.children} />
</Td>
<Td>{YearMonth.ofDate(item.periodStart).format()}</Td>
<Td>
{YearMonth.ofDate(item.periodStart).format()}
{item.revisionNumber > 0 && (
<>
<br />({i18n.invoices.table.replacementInvoice}{' '}
{item.revisionNumber})
</>
)}
</Td>
<Td data-qa="invoice-created-at">
{item.createdAt?.toLocalDate().format() ?? ''}
</Td>
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/employee-frontend/components/invoices/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
sendInvoices,
sendInvoicesByDate
} from '../../generated/api-clients/invoicing'
import { markReplacementDraftSent } from '../../generated/api-clients/invoicing'
import { createQueryKeys } from '../../query'

const queryKeys = createQueryKeys('invoices', {
Expand Down Expand Up @@ -76,3 +77,8 @@ export const deleteDraftInvoicesMutation = mutation({
queryKeys.invoiceDetailsAll()
]
})

export const markReplacementDraftSentMutation = mutation({
api: markReplacementDraftSent,
invalidateQueryKeys: (arg) => [queryKeys.invoiceDetails(arg.invoiceId)]
})
19 changes: 19 additions & 0 deletions frontend/src/employee-frontend/generated/api-clients/invoicing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { InvoiceDetailedResponse } from 'lib-common/generated/api-types/invoicin
import { InvoicePayload } from 'lib-common/generated/api-types/invoicing'
import { JsonCompatible } from 'lib-common/json'
import { JsonOf } from 'lib-common/json'
import { MarkReplacementDraftSentRequest } from 'lib-common/generated/api-types/invoicing'
import { NoteUpdateBody } from 'lib-common/generated/api-types/invoicing'
import { PagedFeeDecisionSummaries } from 'lib-common/generated/api-types/invoicing'
import { PagedInvoiceSummaryResponses } from 'lib-common/generated/api-types/invoicing'
Expand Down Expand Up @@ -641,6 +642,24 @@ export async function markInvoicesSent(
}


/**
* Generated from fi.espoo.evaka.invoicing.controller.InvoiceController.markReplacementDraftSent
*/
export async function markReplacementDraftSent(
request: {
invoiceId: UUID,
body: MarkReplacementDraftSentRequest
}
): Promise<void> {
const { data: json } = await client.request<JsonOf<void>>({
url: uri`/employee/invoices/${request.invoiceId}/mark-replacement-draft-sent`.toString(),
method: 'POST',
data: request.body satisfies JsonCompatible<MarkReplacementDraftSentRequest>
})
return json
}


/**
* Generated from fi.espoo.evaka.invoicing.controller.InvoiceController.searchInvoices
*/
Expand Down
Loading
Loading