diff --git a/dist/js/helsinki_benefit_amount_estimate.min.js b/dist/js/helsinki_benefit_amount_estimate.min.js new file mode 100644 index 000000000..8f0505712 --- /dev/null +++ b/dist/js/helsinki_benefit_amount_estimate.min.js @@ -0,0 +1 @@ +!function(){"use strict";var e={getFormData:function(e,t,a){return{form_id:e,has_required_fields:!0,items:[{heading:{text:t("heading_employer"),level:3}},{radio:{id:"company_type",label:t("label_company_type"),required:!0,radio_items:[{name:"company_type",item_id:"company_type_business",label:t("label_company_type_business"),value:"business"},{name:"company_type",item_id:"company_type_association",label:t("label_company_type_association"),value:"association"}]}},{group:{id:"association_has_business_activities_true_group",hide_group:!0,items:[{paragraph:{text:""}},{checkbox:{id:"association_has_business_activities",label:t("label_association_has_business_activities"),helper_text:t("helper_text_association_has_business_activities")}}]}},{heading:{text:t("heading_employee"),level:3}},{input_integer:{id:"monthly_pay",label:t("label_monthly_pay"),unit:t("unit_euro"),min:0,max:1e4,size:12,required:!0,strip:"[€eE ]",helper_text:t("helper_text_monthly_pay")}},{input_integer:{id:"vacation_money",label:t("label_vacation_money"),unit:t("unit_euro"),min:0,max:454,size:12,required:!1,strip:"[€eE ]",helper_text:t("helper_text_vacation_money")}},{heading:{text:t("heading_pay_subsidy_information"),level:3}},{paragraph:{text:t("text_pay_subsidy_information")}},{radio:{id:"pay_subsidy_granted",label:t("label_pay_subsidy_granted"),required:!0,radio_items:[{name:"pay_subsidy_granted",item_id:"pay_subsidy_granted_false",label:t("label_pay_subsidy_false"),value:"pay_subsidy_granted_false"},{name:"pay_subsidy_granted",item_id:"pay_subsidy_granted_true",label:t("label_pay_subsidy_true"),value:"pay_subsidy_granted_true"}]}},{group:{id:"pay_subsidy_granted_group",hide_group:!0,items:[{radio:{id:"pay_subsidy_percentage",label:t("label_pay_subsidy_percentage"),required:!0,helper_text:t("helper_text_pay_subsidy_percentage"),radio_items:[{name:"pay_subsidy_percentage",item_id:"pay_subsidy_percentage_1",label:t("label_pay_subsidy_percentage_1",{value:100*a.PAY_SUBSIDY_PERCENTAGES[1]}),value:1},{name:"pay_subsidy_percentage",item_id:"pay_subsidy_percentage_2",label:t("label_pay_subsidy_percentage_2",{value:100*a.PAY_SUBSIDY_PERCENTAGES[2]}),value:2}]}}]}}]}}};var t={heading_employer:{fi:"Työnantajan tiedot",sv:"Arbetsgivarens uppgifter",en:"Employer information"},heading_employee:{fi:"Työsuhteen tiedot",sv:"Arbetstagarens uppgifter",en:"Employee details"},label_company_type:{fi:"Työnantajan muoto",sv:"Arbetsgivarens form",en:"Type of employer"},label_company_type_business:{fi:"Työnantaja on yritys",sv:"Arbetsgivaren är ett företag",en:"The employer is a company"},label_company_type_association:{fi:"Työnantaja on yhteisö",sv:"Arbetsgivaren är en organisation",en:"The employer is an organization"},label_association_has_business_activities:{fi:"Yhteisö harjoittaa taloudellista toimintaa",sv:"Organisationen bedriver ekonomisk verksamhet",en:"The organization engages in economic activity"},helper_text_association_has_business_activities:{fi:"Taloudellinen toiminta tarkoittaa palveluiden ja tuotteiden myyntiä, joka on jatkuvaa, ansiotarkoituksessa ja kilpailuolosuhteissa tapahtuvaa. Kaikkien kolmen edellytyksen tulee täyttyä samanaikaisesti.",sv:"Ekonomisk verksamhet innebär försäljning av tjänster och produkter som är kontinuerlig, vinstdrivande och sker under konkurrensförhållanden. Alla tre villkoren måste uppfyllas samtidigt.",en:"Economic activity refers to the sale of services and products that is continuous, profitable and occurs under competitive conditions. All three criteria must be met at the same time."},label_monthly_pay:{fi:"Työntekijän tuleva bruttopalkka",sv:"Arbetstagarens framtida bruttolön",en:"Future gross salary of the employee"},helper_text_monthly_pay:{fi:"Euroa kuukaudessa. Arvioi bruttopalkan suuruus työsuhteeseen, johon Helsinki-lisää haetaan.",sv:"Euro per månad. Uppskatta bruttolönen för den anställning som Helsingforstillägget ansöks om.",en:"Euros per month. Estimate the gross salary for the employment for which the Helsinki benefit is being applied."},label_vacation_money:{fi:"Lomaraha",sv:"Semesterpenning",en:"Holiday pay"},helper_text_vacation_money:{fi:"Euroa kuukaudessa. Jos jätät kentän tyhjäksi, niin lomaraha jätetään huomioimatta laskelmassa.",sv:"Euro per månad. Om du lämnar fältet tomt kommer semesterpenningen inte att beaktas i beräkningen.",en:"Euros per month. If you leave this field empty, the holiday pay will not be included in the calculation."},heading_pay_subsidy_information:{fi:"Muut työsuhteeseen haettavat tai myönnetyt tuet",sv:"Andra stöd för anställningen som ansöks om eller beviljats",en:"Other subsidies applied for or granted for the employment"},text_pay_subsidy_information:{fi:"Helsinki-lisän lisäksi työsuhteeseen voidaan myöntää muita tukia kattamaan palkkaamisesta aiheutuvia kustannuksia.",sv:"Förutom Helsingforstillägget kan andra stöd beviljas för att täcka kostnaderna för anställningen.",en:"In addition to the Helsinki benefit, other subsidies may be granted to cover the costs of employment."},label_pay_subsidy_granted:{fi:"Työsuhteeseen myönnetyt tai haetut muut tuet, kuten palkkatuki",sv:"Andra stöd som beviljats eller ansökts om för anställningen",en:"Other subsidies applied for or granted for the employment"},label_pay_subsidy_false:{fi:"Työsuhteeseen ei ole myönnetty tai haettu muuta tukea",sv:"Inget annat stöd har beviljats eller ansökts om",en:"No other subsidy has been granted or applied"},label_pay_subsidy_true:{fi:"Palkkatuki tai 55 vuotta täyttäneiden työllistämistuki",sv:"Lönesubvention eller anställningsstöd för personer över 55 år",en:"Pay subsidy or employment subsidy for ages 55 and above"},label_pay_subsidy_percentage:{fi:"Palkkatuen prosentti tai myöntämisperuste",sv:"Lönesubvention procent eller beviljningsgrund",en:"Wage subsidy percentage or basis for granting"},label_pay_subsidy_percentage_1:{fi:"Tuki kattaa ${value} % palkkauskustannuksista (tuen perusteena ammatillisen osaamisen parantaminen)",sv:"${value} % av anställningskostnaderna (stödet baseras på förbättring av yrkeskompetensen)",en:"${value} % of employment costs (subsidy based on improving professional skills)"},label_pay_subsidy_percentage_2:{fi:"Tuki kattaa ${value} % palkkauskustannuksista (tuen perusteena alentunut työkyky tai 55 vuotta täyttäneiden työllistämistuki)",sv:"${value} % av anställningskostnaderna (stödet baseras på nedsatt arbetsförmåga eller anställningsstöd för personer över 55 år)",en:"${value} % of employment costs (subsidy based on disability or illness that reduces work capacity or employment subsidy for ages 55 and above)"},helper_text_pay_subsidy_percentage:{fi:"Valitse sopivin vaihtoehto joko tuen prosentin tai tukea haettavan perusteen mukaan.",sv:"Välj det mest lämpliga alternativet baserat på antingen stödbeloppets procent eller grunden för att ansöka om stödet.",en:"Select the most suitable option based on either the subsidy percentage or the reason for applying for the subsidy."},total_title:{fi:"Arvio Helsinki-lisästä",sv:"Uppskattning av Helsingforstillägget",en:"Estimate of the Helsinki benefit"},total_prefix:{fi:"Työnantajalle arvoitu Helsinki-lisä",sv:"Beräknat Helsingforstillägget för arbetsgivaren",en:"Estimated Helsinki benefit for the employer"},total_suffix:{fi:"euroa kuukaudessa",sv:"euro per månad",en:"euros per month"},total_explanation:{fi:"Arvio tuen määrästä on suuntaa antava eikä se takaa lopullista tuen määrää. Helsinki-lisää myönnetään työsuhteen ajaksi, kuitenkin enintään 12 kuukaudeksi. Tuen myöntäminen edellyttää, että työllistettävälle on myönnetty Helsinki-lisä-kortti, palkkatukea tai 55 vuotta täyttäneiden työllistämistukea.",sv:"Uppskattningen av stödets belopp är vägledande och garanterar inte det slutliga beloppet. Helsingforstilläggetet beviljas för anställningstiden, dock högst 12 månader. För att stödet ska beviljas krävs att den anställde har beviljats ett Helsingforstilläggskort, lönebidrag eller anställningsstöd för personer över 55 år.",en:"The estimate of the financial support amount is indicative and does not guarantee the final amount of the support. The Helsinki benefit is granted for the duration of the employment, maximum of 12 months. Granting the support requires that the employee has been issued the Helsinki benefit card, pay subsidy, or employment subsidy for ages 55 and above."},breakdown_title:{fi:"Arvio muodostuu seuraavista tiedoista:",sv:"Uppskattningen baseras på följande uppgifter:",en:"The estimate is based on the following information:"},subtotal_title:{fi:"Työsuhde",sv:"Anställning",en:"Employment relationship"},subtotal_details_1:{fi:"Työllistettävän bruttopalkka: ${value} euroa kuukaudessa.",sv:"Bruttolön: ${value} euro per månad.",en:"Gross salary: ${value} euros per month."},subtotal_details_2:{fi:"Lomaraha: ${value} euroa kuukaudessa.",sv:"Semesterpenning: ${value} euro per månad.",en:"Holiday pay: ${value} euros per month."},subtotal_details_3:{fi:"Sivukulut: ${value} euroa bruttopalkasta. Arviossa on käytetty keskimääräistä arviota sivukuluista.",sv:"Bikostnader: ${value} euro av bruttolönen. En genomsnittlig uppskattning av bikostnaderna har använts i beräkningen.",en:"Additional costs: ${value} euros per month. The calculation result uses an average estimate of additional costs."},subtotal_details_4:{fi:"Työsuhteeseen on myönnetty palkkatuki, joka kattaa ${value} % palkkakustannuksista.",sv:"Lönesubventionen som beviljats för anställningen täcker ${value} % av lönekostnaderna.",en:"The pay subsidy granted for the employment covers ${value} % of the salary costs."},additional_details_title:{fi:"Lisähuomiot",sv:"Ytterligare anmärkningar",en:"Additional information"},additional_details_text_1:{fi:"Oppisopimuksissa Helsinki-lisää voidaan myöntää koko oppisopimuksen ajaksi.",sv:"För lärlingsavtal kan Helsingforstillägget beviljas för hela lärlingstiden.",en:"For apprenticeships, the Helsinki benefit can be granted for the entire duration of the apprenticeship."},additional_details_text_2:{fi:"Laskuri ei tue tapausta, jossa palkkatuen määrä on 100 prosenttia työntekijän palkkakustannuksista 65 prosentin työajalla.",sv:"Beräknaren stöder inte fall där lönesubventionen är 100 procent av anställningskostnaderna vid 65 procents arbetstid.",en:"The calculator does not support cases where the pay subsidy amount is 100 percent of the employee's salary costs with 65 percent working time."},error_calculation_title:{fi:"Laskenta epäonnistui.",sv:"Uppskattningen misslyckades.",en:"Calculation failed."},error_calculation_message:{fi:"Ole hyvä ja tarkista syöttämäsi tiedot.",sv:"Var god och kontrollera de uppgifter du angett.",en:"Please check the information you have entered."}};class a{constructor(a,s){this.id=a;const i=JSON.parse(s);["HELSINKI_BENEFIT_MAX_AMOUNT_WITH_PAY_SUBSIDY","HELSINKI_BENEFIT_MAX_AMOUNT_WITHOUT_PAY_SUBSIDY","PAY_SUBSIDY_PERCENTAGES","PAY_SUBSIDY_AMOUNT_MAX_LIMIT","STATE_AID_PERCENTAGES","SALARY_OTHER_EXPENSES_PERCENTAGE"].every((e=>Object.keys(i).includes(e)))||console.error("Missing Drupal settings. Calculator won´t work!"),0!==i.PAY_SUBSIDY_PERCENTAGES[0]&&console.error("First index of setting PAY_SUBSIDY_PERCENTAGES must be 0!"),999999!==i.PAY_SUBSIDY_AMOUNT_MAX_LIMIT[0]&&console.error("First index of setting PAY_SUBSIDY_AMOUNT_MAX_LIMIT must be 999999!");const n=()=>"association"===this.calculator.getFieldValue("company_type"),l=()=>"business"===this.calculator.getFieldValue("company_type"),r=()=>n()&&this.calculator.getFieldValue("association_has_business_activities"),o=()=>parseInt(this.calculator.getFieldValue("monthly_pay"),10),u=()=>{const e=parseInt(this.calculator.getFieldValue("vacation_money"),10);return Number.isNaN(e)?0:e},_=()=>{this.calculator.getElement("vacation_money").dataset.max=Math.floor(o()/2/11)},d=()=>i.SALARY_OTHER_EXPENSES_PERCENTAGE*o(),p=()=>"pay_subsidy_granted_true"===this.calculator.getFieldValue("pay_subsidy_granted"),y=()=>this.calculator.getFieldValue("pay_subsidy_percentage")||0,c=()=>{const e=y();return p()?i.PAY_SUBSIDY_PERCENTAGES[e]:i.PAY_SUBSIDY_PERCENTAGES[0]},m=()=>(()=>{const e=c();if(l()||r()){if(!p())return i.STATE_AID_PERCENTAGES[1];if(e===i.PAY_SUBSIDY_PERCENTAGES[1])return i.STATE_AID_PERCENTAGES[1];if(e===i.PAY_SUBSIDY_PERCENTAGES[2])return i.STATE_AID_PERCENTAGES[2]}return i.STATE_AID_PERCENTAGES[0]})()*[o(),u()].reduce(((e,t)=>parseInt(e,10)+parseInt(t,10)),d()),h=()=>m()-(()=>{const e=p()?c()*o():0;return p()?((e,t)=>{const a=y();return e===i.PAY_SUBSIDY_PERCENTAGES[a]?Math.min(t,i.PAY_SUBSIDY_AMOUNT_MAX_LIMIT[a]):t})(c(),e):0})(),b=(e=!1)=>{e?(this.calculator.showGroup("pay_subsidy_granted_group"),this.calculator.getElement("pay_subsidy_percentage").querySelectorAll('input[type="radio"]:checked').length||(this.calculator.getElement("pay_subsidy_percentage_1").checked=!0)):(this.calculator.hideGroup("pay_subsidy_granted_group"),this.calculator.getElement("pay_subsidy_percentage_1").checked=!1,this.calculator.getElement("pay_subsidy_percentage_2").checked=!1)},g=e=>this.calculator.formatFinnishEuroCents(e),k=()=>{n()?this.calculator.showGroup("association_has_business_activities_true_group"):this.calculator.hideGroup("association_has_business_activities_true_group"),o()>0&&_(),b(p())},v=()=>{const e=[];if(e.push(...this.calculator.validateBasics("company_type")),e.push(...this.calculator.validateBasics("monthly_pay")),e.push(...this.calculator.validateBasics("vacation_money")),e.push(...this.calculator.validateBasics("pay_subsidy_granted")),p()&&e.push(...this.calculator.validateBasics("pay_subsidy_percentage")),e.length)return{error:{title:this.t("missing_input"),message:e}};const t=h();if(Number.isNaN(t))return{error:{title:this.t("error_calculation_title"),message:this.t("error_calculation_message")}};const a={title:this.t("subtotal_title"),has_details:!0,details:[this.t("subtotal_details_1",{value:g(o())}),this.t("subtotal_details_2",{value:g(u())}),this.t("subtotal_details_3",{value:g(d()),percentage:100*i.SALARY_OTHER_EXPENSES_PERCENTAGE})]};p()&&a.details.push(this.t("subtotal_details_4",{value:100*c()}));const s={id:this.id,title:this.t("total_title"),total_prefix:this.t("total_prefix"),total_value:g(Math.max(0,Math.min(t,p()?i.HELSINKI_BENEFIT_MAX_AMOUNT_WITH_PAY_SUBSIDY:i.HELSINKI_BENEFIT_MAX_AMOUNT_WITHOUT_PAY_SUBSIDY))),total_suffix:this.t("total_suffix"),total_explanation:this.t("total_explanation"),hr:!0,breakdown:[{title:this.t("breakdown_title"),subtotals:a},{title:null,subtotals:{title:this.t("additional_details_title"),has_details:!0,details:[this.t("additional_details_text_1"),this.t("additional_details_text_2")]}}]};return{receipt:this.calculator.getPartialRender("{{>receipt}}",s),ariaLive:this.t("receipt_aria_live",{payment:1234})}},f={submit:e=>{this.calculator.clearResult(),e.preventDefault();const t=v();this.calculator.renderResult(t)},keydown:()=>{k()},change:()=>{k()},reset:()=>{window.setTimeout(k,1),this.calculator.clearResult(),this.calculator.showAriaLiveText(this.t("reset_aria_live"))}};this.calculator=window.HelfiCalculator({name:"helsinki_benefit_amount_estimate",translations:t}),this.t=(e,t)=>this.calculator.translate(e,t),this.settings=this.calculator.parseSettings(s),this.calculator.init({id:a,formData:(()=>e.getFormData(this.id,this.t,i))(),eventHandlers:f})}}window.helfi_calculator=window.helfi_calculator||{},window.helfi_calculator.helsinki_benefit_amount_estimate=(e,t)=>new a(e,t)}(); \ No newline at end of file diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/_form.js b/src/js/calculator/helsinki_benefit_amount_estimate/_form.js new file mode 100644 index 000000000..a2cc9f3d7 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/_form.js @@ -0,0 +1,153 @@ +function getFormData(id, t, config) { + return { + form_id: id, + has_required_fields: true, + items: [ + { + heading: { + text: t('heading_employer'), + level: 3, + } + }, + { + radio: { + id: 'company_type', + label: t('label_company_type'), + required: true, + radio_items: [ + { + name: 'company_type', + item_id: 'company_type_business', + label: t('label_company_type_business'), + value: 'business', + }, + { + name: 'company_type', + item_id: 'company_type_association', + label: t('label_company_type_association'), + value: 'association', + }, + ], + }, + }, + + { + group: { + id: 'association_has_business_activities_true_group', + hide_group: true, + items: [ + { + paragraph: { + text: '' + } + }, + { + checkbox: { + id: 'association_has_business_activities', + label: t('label_association_has_business_activities'), + helper_text: t('helper_text_association_has_business_activities'), + + }, + }, + ], + }, + }, + { + heading: { + text: t('heading_employee'), + level: 3, + } + }, + { + input_integer: { + id: 'monthly_pay', + label: t('label_monthly_pay'), + unit: t('unit_euro'), + min: 0, + max: 10000, + size: 12, + required: true, + strip: '[€eE ]', + helper_text: t('helper_text_monthly_pay'), + }, + }, + { + input_integer: { + id: 'vacation_money', + label: t('label_vacation_money'), + unit: t('unit_euro'), + min: 0, + max: 454, // Vacation money per month is half monthly_pay divided with 11 months; also adjusted in code when monthly_pay changes + size: 12, + required: false, + strip: '[€eE ]', + helper_text: t('helper_text_vacation_money'), + }, + }, + { + heading: { + text: t('heading_pay_subsidy_information'), + level: 3, + } + }, + { + paragraph: { + text: t('text_pay_subsidy_information') + } + }, + { + radio: { + id: 'pay_subsidy_granted', + label: t('label_pay_subsidy_granted'), + required: true, + radio_items: [ + { + name: 'pay_subsidy_granted', + item_id: 'pay_subsidy_granted_false', + label: t('label_pay_subsidy_false'), + value: 'pay_subsidy_granted_false', + }, + { + name: 'pay_subsidy_granted', + item_id: 'pay_subsidy_granted_true', + label: t('label_pay_subsidy_true'), + value: 'pay_subsidy_granted_true', + }, + ] + } + }, + { + group: { + id: 'pay_subsidy_granted_group', + hide_group: true, + items: [ + { + radio: { + id: 'pay_subsidy_percentage', + label: t('label_pay_subsidy_percentage'), + required: true, + helper_text: t('helper_text_pay_subsidy_percentage'), + radio_items: [ + { + name: 'pay_subsidy_percentage', + item_id: 'pay_subsidy_percentage_1', + label: t('label_pay_subsidy_percentage_1', {value: config.PAY_SUBSIDY_PERCENTAGES[1] * 100}), + value: 1, + }, + { + name: 'pay_subsidy_percentage', + item_id: 'pay_subsidy_percentage_2', + label: t('label_pay_subsidy_percentage_2', {value: config.PAY_SUBSIDY_PERCENTAGES[2] * 100}), + value: 2, + }, + ], + }, + }, + ], + }, + }, + ] + }; +} + +export default { getFormData }; diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/_translations.js b/src/js/calculator/helsinki_benefit_amount_estimate/_translations.js new file mode 100644 index 000000000..860dca377 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/_translations.js @@ -0,0 +1,181 @@ +/* eslint-disable no-template-curly-in-string */ + +const translations = { + heading_employer: { + fi: 'Työnantajan tiedot', + sv: 'Arbetsgivarens uppgifter', + en: 'Employer information', + }, + heading_employee: { + fi: 'Työsuhteen tiedot', + sv: 'Arbetstagarens uppgifter', + en: 'Employee details', + }, + label_company_type: { + fi: 'Työnantajan muoto', + sv: 'Arbetsgivarens form', + en: 'Type of employer', + }, + label_company_type_business: { + fi: 'Työnantaja on yritys', + sv: 'Arbetsgivaren är ett företag', + en: 'The employer is a company', + }, + label_company_type_association: { + fi: 'Työnantaja on yhteisö', + sv: 'Arbetsgivaren är en organisation', + en: 'The employer is an organization', + }, + label_association_has_business_activities: { + fi: 'Yhteisö harjoittaa taloudellista toimintaa', + sv: 'Organisationen bedriver ekonomisk verksamhet', + en: 'The organization engages in economic activity', + }, + helper_text_association_has_business_activities: { + fi: 'Taloudellinen toiminta tarkoittaa palveluiden ja tuotteiden myyntiä, joka on jatkuvaa, ansiotarkoituksessa ja kilpailuolosuhteissa tapahtuvaa. Kaikkien kolmen edellytyksen tulee täyttyä samanaikaisesti.', + sv: 'Ekonomisk verksamhet innebär försäljning av tjänster och produkter som är kontinuerlig, vinstdrivande och sker under konkurrensförhållanden. Alla tre villkoren måste uppfyllas samtidigt.', + en: 'Economic activity refers to the sale of services and products that is continuous, profitable and occurs under competitive conditions. All three criteria must be met at the same time.', + }, + label_monthly_pay: { + fi: 'Työntekijän tuleva bruttopalkka', + sv: 'Arbetstagarens framtida bruttolön', + en: 'Future gross salary of the employee', + }, + helper_text_monthly_pay: { + fi: 'Euroa kuukaudessa. Arvioi bruttopalkan suuruus työsuhteeseen, johon Helsinki-lisää haetaan.', + sv: 'Euro per månad. Uppskatta bruttolönen för den anställning som Helsingforstillägget ansöks om.', + en: 'Euros per month. Estimate the gross salary for the employment for which the Helsinki benefit is being applied.', + }, + label_vacation_money: { + fi: 'Lomaraha', + sv: 'Semesterpenning', + en: 'Holiday pay', + }, + helper_text_vacation_money: { + fi: 'Euroa kuukaudessa. Jos jätät kentän tyhjäksi, niin lomaraha jätetään huomioimatta laskelmassa.', + sv: 'Euro per månad. Om du lämnar fältet tomt kommer semesterpenningen inte att beaktas i beräkningen.', + en: 'Euros per month. If you leave this field empty, the holiday pay will not be included in the calculation.', + }, + heading_pay_subsidy_information: { + fi: 'Muut työsuhteeseen haettavat tai myönnetyt tuet', + sv: 'Andra stöd för anställningen som ansöks om eller beviljats', + en: 'Other subsidies applied for or granted for the employment', + }, + text_pay_subsidy_information: { + fi: 'Helsinki-lisän lisäksi työsuhteeseen voidaan myöntää muita tukia kattamaan palkkaamisesta aiheutuvia kustannuksia.', + sv: 'Förutom Helsingforstillägget kan andra stöd beviljas för att täcka kostnaderna för anställningen.', + en: 'In addition to the Helsinki benefit, other subsidies may be granted to cover the costs of employment.', + }, + label_pay_subsidy_granted: { + fi: 'Työsuhteeseen myönnetyt tai haetut muut tuet, kuten palkkatuki', + sv: 'Andra stöd som beviljats eller ansökts om för anställningen', + en: 'Other subsidies applied for or granted for the employment', + }, + label_pay_subsidy_false: { + fi: 'Työsuhteeseen ei ole myönnetty tai haettu muuta tukea', + sv: 'Inget annat stöd har beviljats eller ansökts om', + en: 'No other subsidy has been granted or applied', + }, + label_pay_subsidy_true: { + fi: 'Palkkatuki tai 55 vuotta täyttäneiden työllistämistuki', + sv: 'Lönesubvention eller anställningsstöd för personer över 55 år', + en: 'Pay subsidy or employment subsidy for ages 55 and above', + }, + label_pay_subsidy_percentage: { + fi: 'Palkkatuen prosentti tai myöntämisperuste', + sv: 'Lönesubvention procent eller beviljningsgrund', + en: 'Wage subsidy percentage or basis for granting', + }, + label_pay_subsidy_percentage_1: { + fi: 'Tuki kattaa ${value} % palkkauskustannuksista (tuen perusteena ammatillisen osaamisen parantaminen)', + sv: '${value} % av anställningskostnaderna (stödet baseras på förbättring av yrkeskompetensen)', + en: '${value} % of employment costs (subsidy based on improving professional skills)', + }, + label_pay_subsidy_percentage_2: { + fi: 'Tuki kattaa ${value} % palkkauskustannuksista (tuen perusteena alentunut työkyky tai 55 vuotta täyttäneiden työllistämistuki)', + sv: '${value} % av anställningskostnaderna (stödet baseras på nedsatt arbetsförmåga eller anställningsstöd för personer över 55 år)', + en: '${value} % of employment costs (subsidy based on disability or illness that reduces work capacity or employment subsidy for ages 55 and above)', + }, + helper_text_pay_subsidy_percentage: { + fi: 'Valitse sopivin vaihtoehto joko tuen prosentin tai tukea haettavan perusteen mukaan.', + sv: 'Välj det mest lämpliga alternativet baserat på antingen stödbeloppets procent eller grunden för att ansöka om stödet.', + en: 'Select the most suitable option based on either the subsidy percentage or the reason for applying for the subsidy.', + }, + total_title: { + fi: 'Arvio Helsinki-lisästä', + sv: 'Uppskattning av Helsingforstillägget', + en: 'Estimate of the Helsinki benefit', + }, + total_prefix: { + fi: 'Työnantajalle arvoitu Helsinki-lisä', + sv: 'Beräknat Helsingforstillägget för arbetsgivaren', + en: 'Estimated Helsinki benefit for the employer', + }, + total_suffix: { + fi: 'euroa kuukaudessa', + sv: 'euro per månad', + en: 'euros per month', + }, + total_explanation: { + fi: 'Arvio tuen määrästä on suuntaa antava eikä se takaa lopullista tuen määrää. Helsinki-lisää myönnetään työsuhteen ajaksi, kuitenkin enintään 12 kuukaudeksi. Tuen myöntäminen edellyttää, että työllistettävälle on myönnetty Helsinki-lisä-kortti, palkkatukea tai 55 vuotta täyttäneiden työllistämistukea.', + sv: 'Uppskattningen av stödets belopp är vägledande och garanterar inte det slutliga beloppet. Helsingforstilläggetet beviljas för anställningstiden, dock högst 12 månader. För att stödet ska beviljas krävs att den anställde har beviljats ett Helsingforstilläggskort, lönebidrag eller anställningsstöd för personer över 55 år.', + en: 'The estimate of the financial support amount is indicative and does not guarantee the final amount of the support. The Helsinki benefit is granted for the duration of the employment, maximum of 12 months. Granting the support requires that the employee has been issued the Helsinki benefit card, pay subsidy, or employment subsidy for ages 55 and above.', + }, + breakdown_title: { + fi: 'Arvio muodostuu seuraavista tiedoista:', + sv: 'Uppskattningen baseras på följande uppgifter:', + en: 'The estimate is based on the following information:', + }, + subtotal_title: { + fi: 'Työsuhde', + sv: 'Anställning', + en: 'Employment relationship', + }, + subtotal_details_1: { + fi: 'Työllistettävän bruttopalkka: ${value} euroa kuukaudessa.', + sv: 'Bruttolön: ${value} euro per månad.', + en: 'Gross salary: ${value} euros per month.', + }, + subtotal_details_2: { + fi: 'Lomaraha: ${value} euroa kuukaudessa.', + sv: 'Semesterpenning: ${value} euro per månad.', + en: 'Holiday pay: ${value} euros per month.', + }, + subtotal_details_3: { + fi: 'Sivukulut: ${value} euroa bruttopalkasta. Arviossa on käytetty keskimääräistä arviota sivukuluista.', + sv: 'Bikostnader: ${value} euro av bruttolönen. En genomsnittlig uppskattning av bikostnaderna har använts i beräkningen.', + en: 'Additional costs: ${value} euros per month. The calculation result uses an average estimate of additional costs.', + }, + subtotal_details_4: { + fi: 'Työsuhteeseen on myönnetty palkkatuki, joka kattaa ${value} % palkkakustannuksista.', + sv: 'Lönesubventionen som beviljats för anställningen täcker ${value} % av lönekostnaderna.', + en: 'The pay subsidy granted for the employment covers ${value} % of the salary costs.', + }, + additional_details_title:{ + fi: 'Lisähuomiot', + sv: 'Ytterligare anmärkningar', + en: 'Additional information', + }, + additional_details_text_1: { + fi: 'Oppisopimuksissa Helsinki-lisää voidaan myöntää koko oppisopimuksen ajaksi.', + sv: 'För lärlingsavtal kan Helsingforstillägget beviljas för hela lärlingstiden.', + en: 'For apprenticeships, the Helsinki benefit can be granted for the entire duration of the apprenticeship.', + }, + additional_details_text_2: { + fi: 'Laskuri ei tue tapausta, jossa palkkatuen määrä on 100 prosenttia työntekijän palkkakustannuksista 65 prosentin työajalla.', + sv: 'Beräknaren stöder inte fall där lönesubventionen är 100 procent av anställningskostnaderna vid 65 procents arbetstid.', + en: 'The calculator does not support cases where the pay subsidy amount is 100 percent of the employee\'s salary costs with 65 percent working time.', + }, + error_calculation_title:{ + fi: 'Laskenta epäonnistui.', + sv: 'Uppskattningen misslyckades.', + en: 'Calculation failed.', + }, + error_calculation_message:{ + fi: 'Ole hyvä ja tarkista syöttämäsi tiedot.', + sv: 'Var god och kontrollera de uppgifter du angett.', + en: 'Please check the information you have entered.', + } +}; + +export default translations; diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/helsinki-benefit-test.html b/src/js/calculator/helsinki_benefit_amount_estimate/helsinki-benefit-test.html new file mode 100644 index 000000000..536d7d2a8 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/helsinki-benefit-test.html @@ -0,0 +1,75 @@ + + + + + + + Hel.fi calculator testbed + + + + + + + + +
+
+
+
+ Fi + Sv + En + +
+
+

Laske arvio Helsinki-lisästä

+
+
+ + + +
+
+
+
+ + diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/helsinki_benefit_amount_estimate.js b/src/js/calculator/helsinki_benefit_amount_estimate/helsinki_benefit_amount_estimate.js new file mode 100644 index 000000000..79ba49789 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/helsinki_benefit_amount_estimate.js @@ -0,0 +1,313 @@ +import form from './_form'; +import translations from './_translations'; + +class HelsinkiBenefitAmountEstimate { + constructor(id, settings) { + this.id = id; + const config = JSON.parse(settings); + + const checkConfiguration = () => { + const NEEDED_CONFIG_KEYS = [ + 'HELSINKI_BENEFIT_MAX_AMOUNT_WITH_PAY_SUBSIDY', + 'HELSINKI_BENEFIT_MAX_AMOUNT_WITHOUT_PAY_SUBSIDY', + 'PAY_SUBSIDY_PERCENTAGES', + 'PAY_SUBSIDY_AMOUNT_MAX_LIMIT', + 'STATE_AID_PERCENTAGES', + 'SALARY_OTHER_EXPENSES_PERCENTAGE' + ]; + if(!NEEDED_CONFIG_KEYS.every((setting) => + Object.keys(config).includes(setting) + )) { + // eslint-disable-next-line no-console + console.error('Missing Drupal settings. Calculator won´t work!'); + } + + if(config.PAY_SUBSIDY_PERCENTAGES[0] !== 0) { + // eslint-disable-next-line no-console + console.error('First index of setting PAY_SUBSIDY_PERCENTAGES must be 0!'); + } + + if(config.PAY_SUBSIDY_AMOUNT_MAX_LIMIT[0] !== 999999) { + // eslint-disable-next-line no-console + console.error('First index of setting PAY_SUBSIDY_AMOUNT_MAX_LIMIT must be 999999!'); + } + }; + + checkConfiguration(); + + const getFormData = () => form.getFormData(this.id, this.t, config); + + const isEmployeeAssociation = () => + this.calculator.getFieldValue('company_type') === 'association'; + + const isEmployeeBusiness = () => + this.calculator.getFieldValue('company_type') === 'business'; + + const hasBusinessActivities = () => + isEmployeeAssociation() && this.calculator.getFieldValue('association_has_business_activities'); + + const getMonthlyPay = () => + parseInt(this.calculator.getFieldValue('monthly_pay'), 10); + + const getVacationMoney = () => { + const vacationMoney = parseInt(this.calculator.getFieldValue('vacation_money'), 10); + return Number.isNaN(vacationMoney) ? 0 : vacationMoney; + }; + + const setVacationMoneyMax = () => { + this.calculator.getElement('vacation_money').dataset.max = Math.floor(getMonthlyPay() / 2 / 11); + }; + + const getOtherExpenses = () => + config.SALARY_OTHER_EXPENSES_PERCENTAGE * getMonthlyPay(); + + const getSalaryWithExpenses = () => + [ + getMonthlyPay(), + getVacationMoney(), + ].reduce( + (acc, value) => parseInt(acc, 10) + parseInt(value, 10), + getOtherExpenses() + ); + + const isPaySubsidyGranted = () => + this.calculator.getFieldValue('pay_subsidy_granted') === 'pay_subsidy_granted_true'; + + const getPaySubsidyPercentageOption = () => + this.calculator.getFieldValue('pay_subsidy_percentage') || 0; + + const getPaySubsidyPercentage = () => { + const valuePaySubsidyPercentage = getPaySubsidyPercentageOption(); + return isPaySubsidyGranted() ? config.PAY_SUBSIDY_PERCENTAGES[valuePaySubsidyPercentage] : config.PAY_SUBSIDY_PERCENTAGES[0]; + }; + + const getPaySubsidyAmount = () => { + const getLimitedPaySubsidyAmount = (paySubsidyPercentage, paySubsidyAmount) => { + const index = getPaySubsidyPercentageOption(); + if (paySubsidyPercentage === config.PAY_SUBSIDY_PERCENTAGES[index]) + return Math.min(paySubsidyAmount, config.PAY_SUBSIDY_AMOUNT_MAX_LIMIT[index]); + return paySubsidyAmount; + }; + const paySubsidyAmount = isPaySubsidyGranted() ? getPaySubsidyPercentage() * getMonthlyPay() : 0; + return isPaySubsidyGranted() ? getLimitedPaySubsidyAmount(getPaySubsidyPercentage(), paySubsidyAmount) : 0; + }; + + const getStateAidPercentage = () => { + const paySubsidyPercentage = getPaySubsidyPercentage(); + // Associations with business activities are treated like businesses + if(isEmployeeBusiness() || hasBusinessActivities()) { + // No pay subsidy, get lower state aid percentage + if(!isPaySubsidyGranted()) { + return config.STATE_AID_PERCENTAGES[1]; + } + if(paySubsidyPercentage === config.PAY_SUBSIDY_PERCENTAGES[1]){ + return config.STATE_AID_PERCENTAGES[1]; + } + if(paySubsidyPercentage === config.PAY_SUBSIDY_PERCENTAGES[2]){ + return config.STATE_AID_PERCENTAGES[2]; + }; + } + // Associations always get the highest state aid percentage + return config.STATE_AID_PERCENTAGES[0]; + }; + + const getStateAidAmount = () => + getStateAidPercentage() * getSalaryWithExpenses(); + + const getHelsinkiBenefitAmount = () => + getStateAidAmount() - getPaySubsidyAmount(); + + const togglePaySubsidyPercentageGroup = (showPercentageGroup = false) => { + if (showPercentageGroup) { + this.calculator.showGroup('pay_subsidy_granted_group'); + // Pre-select the first radio button for percentage option if pay subsidy is selected + if (!this.calculator.getElement('pay_subsidy_percentage').querySelectorAll('input[type="radio"]:checked').length) { + this.calculator.getElement('pay_subsidy_percentage_1').checked = true; + } + } else { + this.calculator.hideGroup('pay_subsidy_granted_group'); + this.calculator.getElement('pay_subsidy_percentage_1').checked = false; + this.calculator.getElement('pay_subsidy_percentage_2').checked = false; + } + }; + + const formatCurrency = (number) => + this.calculator.formatFinnishEuroCents(number); + + const debugUpdate = () => { + if (process.env.NODE_ENV === 'development') { + const data = { + isEmployeeAssociation: isEmployeeAssociation(), + isEmployeeBusiness: isEmployeeBusiness(), + hasBusinessActivities: hasBusinessActivities(), + monthlyPay: getMonthlyPay(), + vacationMoney: getVacationMoney(), + otherExpenses: getOtherExpenses(), + allExpenses: getSalaryWithExpenses(), + isPaySubsidyGranted: isPaySubsidyGranted(), + paySubsidyAmount: getPaySubsidyAmount(), + paySubsidyPercentage: getPaySubsidyPercentage(), + stateAidPercentage: getStateAidPercentage(), + stateAidAmount: getStateAidAmount(), + helsinkiBenefitAmount: getHelsinkiBenefitAmount() + }; + // eslint-disable-next-line no-console + console.log('\n\n###################'); + Object.keys(data).forEach(key => { + const value = data[key]; + // eslint-disable-next-line no-console + console.log(key, value); + }); + } + }; + + const update = () => { + if (isEmployeeAssociation()) { + this.calculator.showGroup('association_has_business_activities_true_group'); + } else { + this.calculator.hideGroup('association_has_business_activities_true_group'); + } + + if (getMonthlyPay() > 0) { + setVacationMoneyMax(); + } + + togglePaySubsidyPercentageGroup(isPaySubsidyGranted()); + + debugUpdate(); + }; + + const validate = () => { + const errorMessages = []; + + errorMessages.push(...this.calculator.validateBasics('company_type')); + errorMessages.push(...this.calculator.validateBasics('monthly_pay')); + errorMessages.push(...this.calculator.validateBasics('vacation_money')); + errorMessages.push(...this.calculator.validateBasics('pay_subsidy_granted')); + + // Pay subsidy percentage is only required when pay subsidy is granted + if(isPaySubsidyGranted()) { + errorMessages.push(...this.calculator.validateBasics('pay_subsidy_percentage')); + } + + if (errorMessages.length) { + return { + error: { + title: this.t('missing_input'), + message: errorMessages + }, + }; + } + const helsinkiBenefitResult = getHelsinkiBenefitAmount(); + + if (Number.isNaN(helsinkiBenefitResult)) { + return { + error: { + title: this.t('error_calculation_title'), + message: this.t('error_calculation_message') + } + }; + } + + const subtotals = { + title: this.t('subtotal_title'), + has_details: true, + details: [ + this.t('subtotal_details_1', {value: formatCurrency(getMonthlyPay())}), + this.t('subtotal_details_2', {value: formatCurrency(getVacationMoney())}), + this.t('subtotal_details_3', {value: formatCurrency(getOtherExpenses()), percentage: config.SALARY_OTHER_EXPENSES_PERCENTAGE * 100}), + ], + }; + + if(isPaySubsidyGranted()) { + subtotals.details.push(this.t('subtotal_details_4', {value: getPaySubsidyPercentage() * 100})); + } + + + const receiptData = { + id: this.id, + title: this.t('total_title'), + total_prefix: this.t('total_prefix'), + // Total value is always at least zero but no more than HELSINKI_BENEFIT_MAX_AMOUNT_W... + total_value: formatCurrency( + Math.max(0, + Math.min( + helsinkiBenefitResult, + isPaySubsidyGranted() ? + config.HELSINKI_BENEFIT_MAX_AMOUNT_WITH_PAY_SUBSIDY : config.HELSINKI_BENEFIT_MAX_AMOUNT_WITHOUT_PAY_SUBSIDY) + ) + ), + total_suffix: this.t('total_suffix'), + total_explanation: this.t('total_explanation'), + hr: true, + breakdown: [ + { + title: this.t('breakdown_title'), + subtotals, + }, + { + title: null, + subtotals: { + title: this.t('additional_details_title'), + has_details: true, + details: [ + this.t('additional_details_text_1'), + this.t('additional_details_text_2'), + ] + } + } + ], + }; + + const receipt = this.calculator.getPartialRender( + '{{>receipt}}', + receiptData, + ); + + return { + receipt, + ariaLive: this.t('receipt_aria_live', { payment: 1234 }), + }; + + }; + + const eventHandlers = { + submit: (event) => { + this.calculator.clearResult(); + event.preventDefault(); + const result = validate(); + this.calculator.renderResult(result); + }, + keydown: () => { + update(); + }, + change: () => { + update(); + }, + reset: () => { + window.setTimeout(update, 1); + this.calculator.clearResult(); + this.calculator.showAriaLiveText(this.t('reset_aria_live')); + }, + }; + + // Prepare calculator for translations + this.calculator = window.HelfiCalculator({ name: 'helsinki_benefit_amount_estimate', translations }); + + // Create shortcut for translations + this.t = (key, value) => this.calculator.translate(key, value); + + // Parse settings to js + this.settings = this.calculator.parseSettings(settings); + + // Initialize calculator + this.calculator.init({ + id, + formData: getFormData(), + eventHandlers, + }); + } +} + +window.helfi_calculator = window.helfi_calculator || {}; +window.helfi_calculator.helsinki_benefit_amount_estimate = (id, settings) => new HelsinkiBenefitAmountEstimate(id, settings); diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/tests/.gitignore b/src/js/calculator/helsinki_benefit_amount_estimate/tests/.gitignore new file mode 100644 index 000000000..368d336f2 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/tests/.gitignore @@ -0,0 +1 @@ +test-results/ \ No newline at end of file diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/tests/README.md b/src/js/calculator/helsinki_benefit_amount_estimate/tests/README.md new file mode 100644 index 000000000..195461ee1 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/tests/README.md @@ -0,0 +1,41 @@ +# Helsinki benefit calculator tests + +## Installation + +Needed: + +* node installed, tested to be working with at least v18 +* optionally, a display and a OS with GUI for headed tests + +To install playwright and it's dependencies: + +``` +npm install +npm run test:install +``` + +## Running the test suite + +Run tests headlessly: + +``` +npm run test +``` + +--- + +Run tests visually: + +``` +npm run test:ui +``` + +--- + +Run tests line-by-line: + +``` +npm run test:debug +``` + +--- \ No newline at end of file diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/tests/fixtures/input.js b/src/js/calculator/helsinki_benefit_amount_estimate/tests/fixtures/input.js new file mode 100644 index 000000000..618391852 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/tests/fixtures/input.js @@ -0,0 +1,90 @@ +import { test, expect } from '@playwright/test'; +// eslint-disable-next-line import/no-relative-packages +import translations from '../../_translations'; + +// eslint-disable-next-line no-template-curly-in-string +const replaceValue = '${value}'; +const PAY_SUBSIDY_PERCENTAGES = ['50', '70']; + +const fillVacationMoney = (page, value) => + test.step('Fill vacation money', async () => { + await page.getByLabel(translations.label_vacation_money.fi).fill(String(value)); + }); + +const fillMonthlyPay = (page, value) => + test.step('Fill monthly pay', async () => { + await page.getByLabel(translations.label_monthly_pay.fi).fill(String(value)); + }); + +const selectPaySubsidyGranted = (page) => + test.step('Select pay subsidy granted', async () => { + await page.getByText(translations.label_pay_subsidy_true.fi, { exact: true }).click(); + }); + +const selectPaySubsidyNotGranted = (page) => + test.step('Select pay subsidy not granted', async () => { + await page.getByText(translations.label_pay_subsidy_false.fi, { exact: true }).click(); + }); + +const selectPaySubsidyPercentageOption1 = (page) => + test.step('Select pay subsidy percentage #1', async () => { + await page + .getByText( + translations.label_pay_subsidy_percentage_1.fi.replace(replaceValue, PAY_SUBSIDY_PERCENTAGES.at(0), { + exact: true, + }), + ) + .click(); + }); + +const selectPaySubsidyPercentageOption2 = (page) => + test.step('Select pay subsidy percentage #2', async () => { + await page + .getByText( + translations.label_pay_subsidy_percentage_2.fi.replace(replaceValue, PAY_SUBSIDY_PERCENTAGES.at(1), { + exact: true, + }), + ) + .click(); + }); + +const selectCompanyTypeBusiness = (page) => + test.step('Select business', async () => { + await page.getByText(translations.label_company_type_business.fi, { exact: true }).click({ force: true }); + }); + +const selectCompanyTypeAssociation = (page) => + test.step('Select association', async () => { + await page.getByText(translations.label_company_type_association.fi, { exact: true }).click({ force: true }); + }); + +const checkAssociationHasBusinessActivities = (page) => + test.step('Check association has business activities', async () => { + await page.getByText(translations.label_association_has_business_activities.fi).isVisible(); + await page.getByText(translations.label_association_has_business_activities.fi, { exact: true }).click(); + }); + +const clickResultsButton = (page) => + test.step('Click results button', async () => { + await page.getByRole('button', { name: 'Laske arvio' }).click({ force: true }); + }); + +const resultSelector = '.helfi-calculator__receipt-total__value'; +const expectResult = (page, result) => + test.step('Click results button', async () => { + expect(await page.locator(resultSelector).textContent()).toBe(result); + }); + +export { + selectPaySubsidyPercentageOption1, + selectPaySubsidyPercentageOption2, + fillVacationMoney, + fillMonthlyPay, + selectCompanyTypeBusiness, + selectCompanyTypeAssociation, + checkAssociationHasBusinessActivities, + selectPaySubsidyGranted, + selectPaySubsidyNotGranted, + clickResultsButton, + expectResult, +}; diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/tests/package-lock.json b/src/js/calculator/helsinki_benefit_amount_estimate/tests/package-lock.json new file mode 100644 index 000000000..3be4914b1 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/tests/package-lock.json @@ -0,0 +1,91 @@ +{ + "name": "helsinki_benefit_amount_estimate", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "helsinki_benefit_amount_estimate", + "version": "1.0.0", + "devDependencies": { + "@playwright/test": "^1.46.1", + "playwright-firefox": "^1.46.1" + } + }, + "node_modules/@playwright/test": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz", + "integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==", + "dev": true, + "dependencies": { + "playwright": "1.46.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz", + "integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==", + "dev": true, + "dependencies": { + "playwright-core": "1.46.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz", + "integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright-firefox": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.46.1.tgz", + "integrity": "sha512-02ZCX0yvdXKHKRX16Ku/wH5Y+8JcfQobgr+WLUtM8vYxxcs9t9mnm5eV/MxhO3yEIFUzB5LY+kTgAxwpnjx6hQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "playwright-core": "1.46.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/tests/package.json b/src/js/calculator/helsinki_benefit_amount_estimate/tests/package.json new file mode 100644 index 000000000..8ea00e58e --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/tests/package.json @@ -0,0 +1,14 @@ +{ + "name": "helsinki_benefit_amount_estimate", + "version": "1.0.0", + "scripts": { + "test": "npm run test:install && playwright test", + "test:ui": "npm run test:install && playwright test --headed", + "test:debug": "npm run test:install && playwright test --debug", + "test:install": "playwright install firefox" + }, + "devDependencies": { + "@playwright/test": "^1.46.1", + "playwright-firefox": "^1.46.1" + } +} diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/tests/playwright.config.js b/src/js/calculator/helsinki_benefit_amount_estimate/tests/playwright.config.js new file mode 100644 index 000000000..5ac00759a --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/tests/playwright.config.js @@ -0,0 +1,26 @@ +import { devices } from '@playwright/test'; + +const config = { + forbidOnly: !!process.env.CI, + retries: 0, + maxFailures: process.env.CI ? 1 : undefined, + workers: 1, + timeout: 10000, + use: { + viewport: { width: 1280, height: 1280 }, + trace: 'on-first-retry', + video: 'retry-with-video', + }, + projects: [ + { + name: 'Firefox', + use: { + ...devices['Desktop Firefox'], + baseURL: 'http://localhost:3001/src/js/calculator/helsinki_benefit_amount_estimate/helsinki-benefit-test.html', +}, + testMatch: [/tests\/.*spec\.js/], + }, + ], +}; + +export default config; \ No newline at end of file diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/tests/specs/form.spec.js b/src/js/calculator/helsinki_benefit_amount_estimate/tests/specs/form.spec.js new file mode 100644 index 000000000..344eded79 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/tests/specs/form.spec.js @@ -0,0 +1,51 @@ +import { test, expect } from '@playwright/test'; +// eslint-disable-next-line import/no-relative-packages +import translations from '../../_translations'; + +test.beforeEach(async ({ page }) => { + await page.goto('http://localhost:3001/src/js/calculator/helsinki_benefit_amount_estimate/helsinki-benefit-test.html'); +}); + +test('On submit, unfilled fields give an error', async ({ page }) => { + await page.getByRole('button', { name: 'Laske arvio' }).click(); + + expect(await page.getByLabel('Virheilmoitus').isVisible()); + expect(await page.getByRole('link', { name: translations.label_monthly_pay.fi }).isVisible()); + expect(await page.getByRole('link', { name: translations.label_company_type.fi }).isVisible()); + expect(await page.getByRole('link', { name: translations.label_pay_subsidy_granted.fi }).isVisible()); +}); + +test('Monthly pay input must be positive and in range', async ({ page }) => { + await page.getByLabel(translations.label_monthly_pay.fi).fill('10001'); + await page.getByRole('button', { name: 'Laske arvio' }).click(); + expect (await page.getByText(`Arvon pitää olla väliltä 0 ja 10000: ${translations.label_monthly_pay.fi}.`).isVisible()).toBeTruthy(); + + await page.getByLabel(translations.label_monthly_pay.fi).fill('-1'); + await page.getByRole('button', { name: 'Laske arvio' }).click(); + expect (await page.getByText(`Arvon pitää olla väliltä 0 ja 10000: ${translations.label_monthly_pay.fi}.`).isVisible()).toBeTruthy(); + + await page.getByLabel(translations.label_monthly_pay.fi).fill('10000'); + await page.getByRole('button', { name: 'Laske arvio' }).click(); + + expect (await page.getByText(`Arvon pitää olla väliltä 0 ja 10000: ${translations.label_monthly_pay.fi}.`).isVisible()).toBeFalsy();; +}); + +test('Vacation money input must be positive and in range, or empty', async ({ page }) => { + await page.getByLabel(translations.label_monthly_pay.fi).fill('2000'); + + await page.getByLabel(translations.label_vacation_money.fi).fill('91'); + await page.getByRole('button', { name: 'Laske arvio' }).click(); + expect (await page.getByText(`Arvon pitää olla väliltä 0 ja 90: ${translations.label_vacation_money.fi}.`).isVisible()).toBeTruthy(); + + await page.getByLabel(translations.label_vacation_money.fi).fill('-1'); + await page.getByRole('button', { name: 'Laske arvio' }).click(); + expect (await page.getByText(`Arvon pitää olla väliltä 0 ja 90: ${translations.label_vacation_money.fi}.`).isVisible()).toBeTruthy(); + + await page.getByLabel(translations.label_vacation_money.fi).fill('90'); + await page.getByRole('button', { name: 'Laske arvio' }).click(); + expect (await page.getByText(`Arvon pitää olla väliltä 0 ja 90: ${translations.label_vacation_money.fi}.`).isVisible()).toBeFalsy(); + + await page.getByLabel(translations.label_vacation_money.fi).clear(); + await page.getByRole('button', { name: 'Laske arvio' }).click(); + expect (await page.getByText(`Arvon pitää olla väliltä 0 ja 90: ${translations.label_vacation_money.fi}.`).isVisible()).toBeFalsy(); +}); \ No newline at end of file diff --git a/src/js/calculator/helsinki_benefit_amount_estimate/tests/specs/results.spec.js b/src/js/calculator/helsinki_benefit_amount_estimate/tests/specs/results.spec.js new file mode 100644 index 000000000..3b61f7bd1 --- /dev/null +++ b/src/js/calculator/helsinki_benefit_amount_estimate/tests/specs/results.spec.js @@ -0,0 +1,171 @@ +import { test } from '@playwright/test'; +import { + selectPaySubsidyPercentageOption1, + selectPaySubsidyPercentageOption2, + checkAssociationHasBusinessActivities, + selectCompanyTypeBusiness, + selectCompanyTypeAssociation, + selectPaySubsidyGranted, + selectPaySubsidyNotGranted, + clickResultsButton, + fillVacationMoney, + fillMonthlyPay, + expectResult, +} from '../fixtures/input'; + +const TEST_CASES = [ + { + NAME: 'Low', + MONTLY_PAY: '400', + VACATION_MONEY: '16', + BUSINESS_ACTIVITIES: { + NONE: '252,00', + 50: '52,00', + 70: '224,00', + }, + ASSOCIATION: { + NONE: '504,00', + 50: '304,00', + 70: '224,00', + }, + }, + { + NAME: 'Medium', + MONTLY_PAY: '1000', + VACATION_MONEY: '42', + BUSINESS_ACTIVITIES: { + NONE: '631,00', + 50: '131,00', + 70: '562,00', + }, + ASSOCIATION: { + NONE: '800,00', + 50: '762,00', + 70: '562,00', + }, + }, + { + NAME: 'High', + MONTLY_PAY: '2000', + VACATION_MONEY: '88', + BUSINESS_ACTIVITIES: { + NONE: '800,00', + 50: '264,00', + 70: '800,00', + }, + ASSOCIATION: { + NONE: '800,00', + 50: '800,00', + 70: '800,00', + }, + }, +]; + +test.slow(); +test.describe.configure({ mode: 'parallel' }); +test.beforeEach(async ({ page }) => { + await page.goto( + 'http://localhost:3001/src/js/calculator/helsinki_benefit_amount_estimate/helsinki-benefit-test.html', + ); +}); + + +TEST_CASES.forEach((testCase) => { + test.describe(testCase.NAME, () => { + test.describe('Business', () => { + test('Fill in form and check results (none)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeBusiness(page); + await selectPaySubsidyNotGranted(page); + await clickResultsButton(page); + expectResult(page, testCase.BUSINESS_ACTIVITIES.NONE); + }); + + test('Fill in form and check results (50)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeBusiness(page); + await selectPaySubsidyGranted(page); + await selectPaySubsidyPercentageOption1(page); + await clickResultsButton(page); + expectResult(page, testCase.BUSINESS_ACTIVITIES['50']); + }); + + test('Fill in form and check results (70)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeBusiness(page); + await selectPaySubsidyGranted(page); + await selectPaySubsidyPercentageOption2(page); + await clickResultsButton(page); + expectResult(page, testCase.BUSINESS_ACTIVITIES['70']); + }); + }); + + test.describe('Association: business', () => { + test('Fill in form and check results (none)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeAssociation(page); + await checkAssociationHasBusinessActivities(page); + await selectPaySubsidyNotGranted(page); + await clickResultsButton(page); + expectResult(page, testCase.BUSINESS_ACTIVITIES.NONE); + }); + + test('Fill in form and check results (50)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeAssociation(page); + await checkAssociationHasBusinessActivities(page); + await selectPaySubsidyGranted(page); + await selectPaySubsidyPercentageOption1(page); + await clickResultsButton(page); + expectResult(page, testCase.BUSINESS_ACTIVITIES['50']); + }); + + test('Fill in form and check results (70)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeAssociation(page); + await checkAssociationHasBusinessActivities(page); + await selectPaySubsidyGranted(page); + await selectPaySubsidyPercentageOption2(page); + await clickResultsButton(page); + expectResult(page, testCase.BUSINESS_ACTIVITIES['70']); + }); + }); + + test.describe('Association', () => { + test('Fill in form and check results (none)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeAssociation(page); + await selectPaySubsidyNotGranted(page); + await clickResultsButton(page); + expectResult(page, testCase.ASSOCIATION.NONE); + }); + + test('[Association] Fill in form and check results (50)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeAssociation(page); + await selectPaySubsidyGranted(page); + await selectPaySubsidyPercentageOption1(page); + await clickResultsButton(page); + expectResult(page, testCase.ASSOCIATION['50']); + }); + + test('[Association] Fill in form and check results (70)', async ({ page }) => { + await fillMonthlyPay(page, testCase.MONTLY_PAY); + await fillVacationMoney(page, testCase.VACATION_MONEY); + await selectCompanyTypeAssociation(page); + await selectPaySubsidyGranted(page); + await selectPaySubsidyPercentageOption2(page); + await clickResultsButton(page); + expectResult(page, testCase.ASSOCIATION['70']); + }); + }); + }); +}); diff --git a/templates/paragraphs/paragraph--calculator.html.twig b/templates/paragraphs/paragraph--calculator.html.twig index 4952d1954..2b63d5d00 100644 --- a/templates/paragraphs/paragraph--calculator.html.twig +++ b/templates/paragraphs/paragraph--calculator.html.twig @@ -29,6 +29,7 @@ 'early_childhood_education_fee', 'continuous_housing_service_voucher', 'families_home_services_client_fee', + 'helsinki_benefit_amount_estimate', ] %} diff --git a/webpack.config.js b/webpack.config.js index 02bf14c66..bb3372485 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -50,6 +50,7 @@ const Entries = () => { 'src/js/accordion/state.js', 'src/js/accordion/translations.js', 'src/js/localStorageManager.js', + 'src/js/calculator/**/tests/**' ]; glob.sync(pattern, {ignore: ignore}).map((item) => {