Skip to content

Commit

Permalink
feat: review application edit changes before submit (hl-1062) (#2838)
Browse files Browse the repository at this point in the history
* refactor: rename application api view for easier editor search

* feat: application history to use text field (from varchar 100)

* feat: update change reason historical record from payload

* feat: add review for edit changes

* refactor: add modal customizations to theme files

* fix: add validation to paper application date

* chore: e2e tests for handler edit/review

* refactor: make date function more generic with parameter
  • Loading branch information
sirtawast authored Feb 21, 2024
1 parent b2fec39 commit 2d9c08e
Show file tree
Hide file tree
Showing 39 changed files with 1,227 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from simple_history.utils import update_change_reason
from sql_util.aggregates import SubqueryCount

from applications.api.v1.serializers.application import (
Expand Down Expand Up @@ -190,6 +191,12 @@ def perform_update(self, serializer):
# Update the object.
serializer.instance = self.get_queryset().get(pk=serializer.instance.pk)

# Update change reason if provided
if self.request.data.get("change_reason") and self.request.user.is_staff:
update_change_reason(
serializer.instance, str(self.request.data.get("change_reason"))
)

@action(methods=["get"], detail=False, url_path="simplified_list")
def simplified_application_list(self, request):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.18 on 2024-01-24 09:47

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("applications", "0057_attachment_type_decision_text"),
]

operations = [
migrations.AlterField(
model_name="historicalapplication",
name="history_change_reason",
field=models.TextField(null=True),
),
]
1 change: 1 addition & 0 deletions backend/benefit/applications/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ def get_available_benefit_types(self):
bases = models.ManyToManyField("ApplicationBasis", related_name="applications")

history = HistoricalRecords(
history_change_reason_field=models.TextField(null=True),
table_name="bf_applications_application_history",
cascade_delete_history=True,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from PIL import Image
from rest_framework.reverse import reverse

from applications.api.v1.application_views import BaseApplicationViewSet
from applications.api.v1.serializers.application import (
ApplicantApplicationSerializer,
HandlerApplicationSerializer,
Expand All @@ -24,7 +25,6 @@
from applications.api.v1.status_transition_validator import (
ApplicantApplicationStatusValidator,
)
from applications.api.v1.views import BaseApplicationViewSet
from applications.enums import (
AhjoStatus,
ApplicationBatchStatus,
Expand Down
2 changes: 1 addition & 1 deletion backend/benefit/applications/tests/test_view_sets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.contrib.auth.models import AnonymousUser
from django.db.models import QuerySet

from applications.api.v1.views import ApplicantApplicationViewSet
from applications.api.v1.application_views import ApplicantApplicationViewSet
from applications.models import Application


Expand Down
2 changes: 1 addition & 1 deletion backend/benefit/helsinkibenefit/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)
from rest_framework_nested import routers

from applications.api.v1 import application_batch_views, views as application_views
from applications.api.v1 import application_batch_views, application_views
from applications.api.v1.ahjo_decision_views import DecisionProposalTemplateSectionList
from applications.api.v1.ahjo_integration_views import (
AhjoAttachmentView,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
getMaxEndDate,
getMinEndDate,
validateDateWithinFourMonths,
validateDateWithinMonths,
} from '@frontend/benefit-shared/src/utils/dates';
import { BENEFIT_TYPES } from 'benefit-shared/constants';

Expand Down Expand Up @@ -70,29 +70,28 @@ describe('dates', () => {
const fourMonthsAgo = new Date();
fourMonthsAgo.setMonth(fourMonthsAgo.getMonth() - 4);

expect(validateDateWithinFourMonths(fourMonthsAgo)).toBe(true);
expect(validateDateWithinMonths(fourMonthsAgo, 4)).toBe(true);
});

it('should return true when date is within four months', () => {
const twoMonthsAgo = new Date();
twoMonthsAgo.setMonth(twoMonthsAgo.getMonth() - 2);

expect(validateDateWithinFourMonths(twoMonthsAgo)).toBe(true);
expect(validateDateWithinMonths(twoMonthsAgo, 4)).toBe(true);
});

it('should return true when date is two months in the future', () => {
const twoMonthsInTheFuture = new Date();
twoMonthsInTheFuture.setMonth(twoMonthsInTheFuture.getMonth() + 2);

expect(validateDateWithinFourMonths(twoMonthsInTheFuture)).toBe(true);
expect(validateDateWithinMonths(twoMonthsInTheFuture, 4)).toBe(true);
});


it('should return false when date is more than four months in the past', () => {
const fiveMonthsAgo = new Date();
fiveMonthsAgo.setMonth(fiveMonthsAgo.getMonth() - 5);

expect(validateDateWithinFourMonths(fiveMonthsAgo)).toBe(false);
expect(validateDateWithinMonths(fiveMonthsAgo, 4)).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
PAY_SUBSIDY_GRANTED,
VALIDATION_MESSAGE_KEYS,
} from 'benefit-shared/constants';
import { validateDateWithinFourMonths } from 'benefit-shared/utils/dates';
import { validateDateWithinMonths } from 'benefit-shared/utils/dates';
import subMonths from 'date-fns/subMonths';
import { FinnishSSN } from 'finnish-ssn';
import { TFunction } from 'next-i18next';
Expand Down Expand Up @@ -45,9 +45,9 @@ export const getValidationSchema = (
.required(t(VALIDATION_MESSAGE_KEYS.REQUIRED))
.test({
message: t(VALIDATION_MESSAGE_KEYS.DATE_MIN, {
min: convertToUIDateFormat(subMonths(new Date(), 4)),
min: convertToUIDateFormat(subMonths(new Date(), 6)),
}),
test: (value = '') => validateDateWithinFourMonths(value),
test: (value = '') => validateDateWithinMonths(value, 6),
}),
[APPLICATION_FIELDS_STEP2_KEYS.END_DATE]: Yup.string().required(
t(VALIDATION_MESSAGE_KEYS.REQUIRED)
Expand Down
54 changes: 54 additions & 0 deletions frontend/benefit/handler/browser-tests/constants/forms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export const NEW_FORM_DATA = {
company: {
id: '0201256-6',
bankAccountNumber: 'FI81 4975 4587 0004 02',
firstName: 'Kuura',
lastName: 'Massi-Päällikkö',
phone: '050 000 0000',
email: '[email protected]',
coOperationNegotiationsDescription: 'Lorem ipsum dolor sit amet',
},
employee: {
firstName: 'Ruu',
lastName: 'Rättisitikka',
ssn: '050632-8912',
monthlyPay: '1800',
vacationMoney: '100',
otherExpenses: '300',
jobTitle: 'Verkkosamooja',
workingHours: '30',
collectiveBargainingAgreement: 'Yleinen TES',
},
deminimis: {
granter: 'Valtio',
amount: '2000',
grantedAt: `1.1.${new Date().getFullYear()}`,
},
};

export const EDIT_FORM_DATA = {
company: {
bankAccountNumber: 'FI65 4712 8581 0006 05',
firstName: 'Malla',
lastName: 'Jout-Sen',
phone: '040 123 4567',
email: '[email protected]',
coOperationNegotiationsDescription: 'Aenean fringilla lorem tellus',
},
employee: {
firstName: 'Cool',
lastName: 'Kanerva',
ssn: '211081-2043',
monthlyPay: '1111',
vacationMoney: '222',
otherExpenses: '333',
jobTitle: 'Some-asiantuntija',
workingHours: '18',
collectiveBargainingAgreement: '-',
},
deminimis: {
granter: 'Hyvän tekijät Inc.',
amount: '3000',
grantedAt: `3.3.${new Date().getFullYear() - 1}`,
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,11 @@ import { format } from 'date-fns';
import { Selector } from 'testcafe';

import fi from '../../public/locales/fi/common.json';
import { NEW_FORM_DATA as form } from '../constants/forms';
import MainIngress from '../page-model/MainIngress';
import handlerUser from '../utils/handlerUser';
import { getFrontendUrl } from '../utils/url.utils';

const form = {
company: {
id: '0201256-6',
bankAccountNumber: 'FI81 4975 4587 0004 02',
firstName: 'Kuura',
lastName: 'Massi-Päällikkö',
phone: '050 000 0000',
email: '[email protected]',
coOperationNegotiationsDescription: 'Lorem ipsum dolor sit amet',
},
employee: {
firstName: 'Ruu',
lastName: 'Rättisitikka',
ssn: '050632-8912',
monthlyPay: '1800',
vacationMoney: '100',
otherExpenses: '300',
jobTitle: 'Verkkosamooja',
workingHours: '30',
collectiveBargainingAgreement: 'Yleinen TES',
},
deminimis: {
granter: 'Valtio',
amount: '2000',
grantedAt: `1.1.${new Date().getFullYear()}`,
},
};

const url = getFrontendUrl(`/`);

const uploadFileAttachment = async (
Expand All @@ -48,7 +21,7 @@ const uploadFileAttachment = async (
await t.wait(100);
};

fixture('New application')
fixture('Create new application')
.page(url)
.beforeEach(async (t) => {
clearDataToPrintOnFailure(t);
Expand Down Expand Up @@ -127,9 +100,13 @@ test('Fill form and submit', async (t: TestController) => {
);

await uploadFileAttachment(t, '#upload_attachment_full_application');
await t.wait(1000);
await uploadFileAttachment(t, '#upload_attachment_employment_contract');
await uploadFileAttachment(t, '#upload_attachment_pay_subsidy_decision');
await t.wait(1000);
await uploadFileAttachment(t, '#upload_attachment_education_contract');
await t.wait(1000);
await uploadFileAttachment(t, '#upload_attachment_pay_subsidy_decision');
await t.wait(1000);

/**
* Click through all applicant terms.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,11 @@ import { format } from 'date-fns';
import { Selector } from 'testcafe';

import fi from '../../public/locales/fi/common.json';
import { EDIT_FORM_DATA as form, NEW_FORM_DATA } from '../constants/forms';
import MainIngress from '../page-model/MainIngress';
import handlerUser from '../utils/handlerUser';
import { getFrontendUrl } from '../utils/url.utils';

const form = {
company: {
bankAccountNumber: 'FI81 4975 4587 0004 02',
firstName: 'Malla',
lastName: 'Jout-Sen',
phone: '040 123 4567',
email: '[email protected]',
coOperationNegotiationsDescription: 'Aenean fringilla lorem tellus',
},
employee: {
firstName: 'Cool',
lastName: 'Kanerva',
ssn: '211081-2043',
monthlyPay: '1111',
vacationMoney: '222',
otherExpenses: '333',
jobTitle: 'Some-asiantuntija',
workingHours: '18',
collectiveBargainingAgreement: '-',
},
deminimis: {
granter: 'Hyvän tekijät Inc.',
amount: '3000',
grantedAt: `3.3.${new Date().getFullYear() - 1}`,
},
};

const url = getFrontendUrl(`/`);

const uploadFileAttachment = async (
Expand All @@ -52,32 +26,35 @@ const clearAndFill = async (
selector: string,
value: string
) => {
await t.click(selector);
await t.selectText(selector);
await t.pressKey('delete');
await t.typeText(selector, value ?? '');
};

fixture('New application')
fixture('Edit existing application')
.page(url)
.beforeEach(async (t) => {
clearDataToPrintOnFailure(t);
await t.useRole(handlerUser);
await t.navigateTo('/');
});

test('Fill form and submit', async (t: TestController) => {
test('Open form and edit fields, then submit', async (t: TestController) => {
const mainIngress = new MainIngress(fi.mainIngress.heading, 'h1');
await mainIngress.isLoaded();

// Open already created form in index page
// Open already created application in index page
const applicationLink = Selector('td')
.withText('Ruu Rättisitikka')
.withText(
`${NEW_FORM_DATA.employee.firstName} ${NEW_FORM_DATA.employee.lastName}`
)
.sibling('td')
.nth(0)
.find('a');
await t.click(applicationLink);

// Start handling the application
// // Start handling the application
const buttonSelector = 'main button';
const handleButton = Selector(buttonSelector).withText(
fi.review.actions.handle
Expand Down Expand Up @@ -181,13 +158,15 @@ test('Fill form and submit', async (t: TestController) => {
await t.click(Selector('[name="application_consent_3"]'));

// Validate form and submit
const nextButton = Selector(buttonSelector).withText(
fi.applications.actions.save
const validationButton = Selector(buttonSelector).withText(
fi.applications.actions.continue
);
await t.click(nextButton);
await t.click(validationButton);

const submitButton = Selector(buttonSelector).withText(
fi.review.actions.handlingPanel
);
await t.expect(submitButton.visible).ok();
await t.typeText('#changeReason', 'Testing edit application');

const submitButton = Selector('[data-testid="confirm-ok"]');
await t.click(submitButton);

await t.expect(editButton.visible).ok();
});
Loading

0 comments on commit 2d9c08e

Please sign in to comment.