From cd05a9f7abdd4aa763381a25ae50c5293afc5a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Viga=C5=A1?= Date: Wed, 7 Feb 2024 14:06:44 -0800 Subject: [PATCH] Child bonus partner (#841) * danovy bonus na deti partner * clean up ts * add all required inputs * remove debug * map partner data to output * add riadok 116a * add calc for partner bonus * fix validation of rodne cislo * fix 116a ak nie je partner * skip partner bonus if not in tesst spec * fix cypress for partner child bonus --- cypress/e2e/executeCase.ts | 14 ++- cypress/e2e/pages.spec.ts | 5 ++ src/lib/calculation.ts | 58 +++++++++++-- src/lib/initialValues.ts | 5 ++ src/lib/xml/outputBasis.ts | 1 + src/lib/xml/xmlConverter.ts | 23 ++++- src/pages/deti.tsx | 159 +++++++++++++++++++++++++++++++--- src/types/OutputJson.ts | 1 + src/types/PageUserInputs.ts | 7 ++ src/types/TaxForm.ts | 35 ++++---- src/types/TaxFormUserInput.ts | 7 ++ 11 files changed, 278 insertions(+), 37 deletions(-) diff --git a/cypress/e2e/executeCase.ts b/cypress/e2e/executeCase.ts index d0fbde91..1f6109c9 100644 --- a/cypress/e2e/executeCase.ts +++ b/cypress/e2e/executeCase.ts @@ -153,12 +153,22 @@ const executeTestCase = (testCase: string) => { cy.get('[data-test="add-child"]').click() } }) + next() + cy.url().then(url => { + if (input.partner_bonus_na_deti) { + + } else { + if (!url.includes('/dochodok')) { + getInput('partner_bonus_na_deti', '-no').click() + next() + } + } + }); } else { getInput('hasChildren', '-no').click() + next() } - next() - /** SECTION Dochodok */ assertUrl('/dochodok') diff --git a/cypress/e2e/pages.spec.ts b/cypress/e2e/pages.spec.ts index c9cd3a43..50c43dd4 100644 --- a/cypress/e2e/pages.spec.ts +++ b/cypress/e2e/pages.spec.ts @@ -439,6 +439,9 @@ describe('Children page', () => { withChildrenInput.children?.[0]?.rodneCislo ?? '', ) + next() + getInput('partner_bonus_na_deti', '-no').click() + next() assertUrl('/dochodok') }) @@ -524,6 +527,8 @@ describe('Children page', () => { // Remove 2rd child cy.get('[data-test="remove-child-2"]').click() + next() + getInput('partner_bonus_na_deti', '-no').click() next() assertUrl('/dochodok') }) diff --git a/src/lib/calculation.ts b/src/lib/calculation.ts index b28268d1..842aa17d 100644 --- a/src/lib/calculation.ts +++ b/src/lib/calculation.ts @@ -11,6 +11,7 @@ import Decimal from 'decimal.js' import { validatePartnerBonusForm } from './validatePartnerBonusForm' import { Summary } from '../types/Summary' import { optionWithValue } from '../components/FormComponents' +import { ChildrenUserInput } from '../types/PageUserInputs' const NEZDANITELNA_CAST_ZAKLADU = new Decimal(4922.82) // NEZDANITELNA_CAST_JE_NULA_AK_JE_ZAKLAD_DANE_VYSSI_AKO @@ -83,6 +84,34 @@ const makeMapChild = } } +const mapPartnerChildBonus = (input: ChildrenUserInput) => { + const wholeYear = input.partner_bonus_na_deti_od === '0' && input.partner_bonus_na_deti_do === '11' + const monthFrom = Number.parseInt(input.partner_bonus_na_deti_od, 10) + const monthTo = Number.parseInt(input.partner_bonus_na_deti_do, 10) + + return { + priezviskoMeno: input.r034_priezvisko_a_meno, + rodneCislo: input.r034_rodne_cislo ? input.r034_rodne_cislo.replace(/\D/g, '') : '', + m00: wholeYear, + m01: !wholeYear && monthFrom === 0, + m02: !wholeYear && monthFrom <= 1 && monthTo >= 1, + m03: !wholeYear && monthFrom <= 2 && monthTo >= 2, + m04: !wholeYear && monthFrom <= 3 && monthTo >= 3, + m05: !wholeYear && monthFrom <= 4 && monthTo >= 4, + m06: !wholeYear && monthFrom <= 5 && monthTo >= 5, + m07: !wholeYear && monthFrom <= 6 && monthTo >= 6, + m08: !wholeYear && monthFrom <= 7 && monthTo >= 7, + m09: !wholeYear && monthFrom <= 8 && monthTo >= 8, + m10: !wholeYear && monthFrom <= 9 && monthTo >= 9, + m11: !wholeYear && monthFrom <= 10 && monthTo >= 10, + m12: !wholeYear && monthTo === 11, + druhaOsobaPodalaDPvSR: input.partner_bonus_na_deti_typ_prijmu === '1' || input.partner_bonus_na_deti_typ_prijmu === '2', + dokladRocZuct: input.partner_bonus_na_deti_typ_prijmu === '3', + dokladVyskaDane: input.partner_bonus_na_deti_typ_prijmu === '4', + pocetMesiacov: monthTo - monthFrom + 1 + } +} + export function calculate(input: TaxFormUserInput): TaxForm { /** Combine default vaules with user input */ return { @@ -151,14 +180,16 @@ export function calculate(input: TaxFormUserInput): TaxForm { return input.children.map((child) => mapChild(child)) }, - get r034() { - return null + get partner_bonus_na_deti() { + return input.partner_bonus_na_deti }, - get r034a() { - return new Decimal(0) + get r034() { + return mapPartnerChildBonus(input) }, + r034a: new Decimal(parseInputNumber(input?.r034a ?? '0')), + /** SECTION Mortgage NAMES ARE WRONG TODO*/ // r037_uplatnuje_uroky: input?.r037_uplatnuje_uroky ?? false, // r037_zaplatene_uroky: new Decimal( @@ -401,6 +432,18 @@ export function calculate(input: TaxFormUserInput): TaxForm { get r116_dan() { return round(this.r090.plus(this.r105)) }, + get r116a(){ + if (this.partner_bonus_na_deti) { + if (this.r034.pocetMesiacov === 12) { + return this.r034a.plus(this.r038).plus(this.r045) + } else { + const partner = round(this.r034a.dividedBy(12)).times(this.r034.pocetMesiacov) + return this.r038.plus(this.r045).plus(partner) + } + } else { + return new Decimal(0) + } + }, get danovyBonusNaDieta() { const months = [ Months.January, @@ -449,7 +492,12 @@ export function calculate(input: TaxFormUserInput): TaxForm { } } - let zakladDane = this.r038.plus(this.r045) + let zakladDane + if (this.partner_bonus_na_deti){ + zakladDane = this.r116a + } else { + zakladDane = this.r038.plus(this.r045) + } zakladDane = zakladDane.toDecimalPlaces(2, Decimal.ROUND_UP) const percentLimit = getPercentualnyLimitNaDeti(monthGroup[0].count) diff --git a/src/lib/initialValues.ts b/src/lib/initialValues.ts index 54f30922..a1628db0 100644 --- a/src/lib/initialValues.ts +++ b/src/lib/initialValues.ts @@ -68,6 +68,11 @@ export const makeEmptyChild = (): ChildInput => ({ export const childrenUserInputInitialValues: ChildrenUserInput = { hasChildren: undefined, + partner_bonus_na_deti: undefined, + partner_bonus_na_deti_typ_prijmu: "0", + partner_bonus_na_deti_od: "", + partner_bonus_na_deti_do: "", + r034a: '', children: [makeEmptyChild()] } diff --git a/src/lib/xml/outputBasis.ts b/src/lib/xml/outputBasis.ts index df900991..8a486cc3 100644 --- a/src/lib/xml/outputBasis.ts +++ b/src/lib/xml/outputBasis.ts @@ -455,6 +455,7 @@ const sampleSchema: OutputJson = { r114: '', r115: '', r116: '', + r116a: '', r117: '', r118: '', r119: '', diff --git a/src/lib/xml/xmlConverter.ts b/src/lib/xml/xmlConverter.ts index b406972b..7668ee4d 100644 --- a/src/lib/xml/xmlConverter.ts +++ b/src/lib/xml/xmlConverter.ts @@ -79,8 +79,28 @@ export function convertToJson(taxForm: TaxForm): OutputJson { ]), ) }) as Dieta[] + if (taxForm.partner_bonus_na_deti) { + form.dokument.telo.uplatnujemPar33Ods8 = boolToString(taxForm.partner_bonus_na_deti) + form.dokument.telo.r34a = decimalToString(taxForm.r034a) + form.dokument.telo.r34.priezviskoMeno = taxForm.r034.priezviskoMeno + form.dokument.telo.r34.rodneCislo = taxForm.r034.rodneCislo + form.dokument.telo.r34.m00 = boolToString(taxForm.r034.m00) + form.dokument.telo.r34.m01 = boolToString(taxForm.r034.m01) + form.dokument.telo.r34.m02 = boolToString(taxForm.r034.m02) + form.dokument.telo.r34.m03 = boolToString(taxForm.r034.m03) + form.dokument.telo.r34.m04 = boolToString(taxForm.r034.m04) + form.dokument.telo.r34.m05 = boolToString(taxForm.r034.m05) + form.dokument.telo.r34.m06 = boolToString(taxForm.r034.m06) + form.dokument.telo.r34.m07 = boolToString(taxForm.r034.m07) + form.dokument.telo.r34.m08 = boolToString(taxForm.r034.m08) + form.dokument.telo.r34.m09 = boolToString(taxForm.r034.m09) + form.dokument.telo.r34.m10 = boolToString(taxForm.r034.m10) + form.dokument.telo.r34.m11 = boolToString(taxForm.r034.m11) + form.dokument.telo.r34.dokladRocZuct = boolToString(taxForm.r034.dokladRocZuct) + form.dokument.telo.r34.dokladVyskaDane = boolToString(taxForm.r034.dokladVyskaDane) + form.dokument.telo.r34.druhaOsobaPodalaDPvSR = boolToString(taxForm.r034.druhaOsobaPodalaDPvSR) + } } - form.dokument.telo.r34a = decimalToString(taxForm.r034a) /** SECTION Mortgage */ // if (taxForm.r037_uplatnuje_uroky) { @@ -128,6 +148,7 @@ export function convertToJson(taxForm: TaxForm): OutputJson { form.dokument.telo.r106 = '0.00' form.dokument.telo.r115 = '0.00' form.dokument.telo.r116 = roundDecimal(taxForm.r116_dan) + form.dokument.telo.r116a = decimalToString(taxForm.r116a) form.dokument.telo.r117 = decimalToString(taxForm.r117) form.dokument.telo.r118 = roundDecimal(taxForm.r118) diff --git a/src/pages/deti.tsx b/src/pages/deti.tsx index dd45c34d..c54c6fb2 100644 --- a/src/pages/deti.tsx +++ b/src/pages/deti.tsx @@ -21,10 +21,12 @@ import { validateRodneCislo, maxChildAgeBonusMonth, minChildAgeBonusMonth, + numberInputRegexp, } from '../lib/utils' import { Page } from '../components/Page' import { ErrorSummary } from '../components/ErrorSummary' import { + calculate, CHILD_RATE_EIGHTEEN_AND_OLDER, CHILD_RATE_EIGHTEEN_AND_YOUNGER, MAX_CHILD_AGE_BONUS, @@ -36,6 +38,7 @@ import { Details } from '../components/Details' import RadioGroup from "../components/radio/RadioGroup"; import Radio from "../components/radio/Radio"; import RadioConditional from "../components/radio/RadioConditional"; +import Decimal from 'decimal.js' const Deti: Page = ({ setTaxFormUserInput, @@ -43,8 +46,7 @@ const Deti: Page = ({ taxFormUserInput, router, previousRoute, - nextRoute, - isDebug + nextRoute }) => { const previousPageLink = ( @@ -65,8 +67,24 @@ const Deti: Page = ({ ...childrenUserInputInitialValues, hasChildren: false, } + const { danovyBonusNaDieta } = calculate({...taxFormUserInput, ...userInput}) setTaxFormUserInput(userInput) - router.push(nextRoute) + if (values.hasChildren) { + if (danovyBonusNaDieta.nevyuzityDanovyBonus.equals(new Decimal(0))) { + router.push(nextRoute) + } else { + if (values.partner_bonus_na_deti === false) { + router.push(nextRoute) + } else if (values.partner_bonus_na_deti === true) { + const errors = validate(values) + if (Object.keys(errors).length === 0) { + router.push(nextRoute) + } + } + } + } else { + router.push(nextRoute) + } }} > {({ values, errors, setErrors, validateForm, setFieldValue }) => ( @@ -161,16 +179,81 @@ const Deti: Page = ({ )} + + {taxForm.danovyBonusNaDieta.nevyuzityDanovyBonus.greaterThan(new Decimal(0)) && ( + <> +

+ Podľa vaších príjmov a počtu detí máte nárok na daňový bonus na vyživované dieťa vo výške {formatCurrency(taxForm.danovyBonusNaDieta.danovyBonus.toNumber())}. Ešte máte nevzužitý daňový bonus vo výške {formatCurrency(taxForm.danovyBonusNaDieta.nevyuzityDanovyBonus.toNumber())}. +

+ + + {values.partner_bonus_na_deti && ( + <> +

+ Údaje o oprávnenej osobe +

+ + { + const rodneCislo = formatRodneCislo( + event.currentTarget.value, + values.r034_rodne_cislo, + ) + setFieldValue('r034_rodne_cislo', rodneCislo) + }} + /> + +

Na začiatku ktorých mesiacov spĺňala druhá oprávnená osoba podmienky na daňový bonus na vyživované dieťa?

+
+ +
+

Akým spôsobom vysporiada/la svoje zdaniteľné príjmy druhá oprávnená osoba za rok 2023?

+ + + )} + + )} )} - { - isDebug && ( -
-

Debug

-
{JSON.stringify(taxForm.danovyBonusNaDieta, null, 2)}
-
- ) - } @@ -181,6 +264,21 @@ const Deti: Page = ({ ) } +const getIncomeHint = (value: string): string => { + switch (value) { + case "0": + return '' + case "1": + return 'Formulár daňového priznania k dani z príjmov fyzickej osoby - typ A riadok 39' + case "2": + return 'Formulár daňového priznania k dani z príjmov fyzickej osoby - typ B riadok 72' + case "3": + return 'Ročné zúčtovanie preddavkov na daň riadok 3' + default: + break; + } +} + interface ChildFormProps { index: number savedValues: ChildInput @@ -273,8 +371,12 @@ interface ChildFormErrors { interface ChildrenFormErrors { hasChildren?: string children?: ChildFormErrors[] - zaciatokPrijmovDen?: string, - zaciatokPrijmovMesiac?: string, + partner_bonus_na_deti_od?: string + partner_bonus_na_deti_do?: string + partner_bonus_na_deti_typ_prijmu?: string + r034_priezvisko_a_meno?: string + r034_rodne_cislo?: string + r034a?: string } export const validate = (values: ChildrenUserInput) => { @@ -319,6 +421,37 @@ export const validate = (values: ChildrenUserInput) => { if (childrenErrors.some((err) => Object.keys(err).length > 0)) { errors.children = childrenErrors } + + if(values.partner_bonus_na_deti) { + if (!["1", "2", "3", "4"].includes(values.partner_bonus_na_deti_typ_prijmu)) { + errors.partner_bonus_na_deti_typ_prijmu = 'Vyberte jednu z možností spôsobu vysporiadania príjmov' + } + + if (values.partner_bonus_na_deti_od === "") { + errors.partner_bonus_na_deti_od = 'Zadajte začiatok' + } + + if (values.partner_bonus_na_deti_do === "") { + errors.partner_bonus_na_deti_do = 'Zadajte koniec' + } + + if (!values.r034_priezvisko_a_meno) { + errors.r034_priezvisko_a_meno = 'Zadajte meno a priezvisko' + } + + if(!values.r034_rodne_cislo) { + errors.r034_rodne_cislo = 'Zadajte rodné číslo' + } else if (!validateRodneCislo(values.r034_rodne_cislo)) { + errors.r034_rodne_cislo = 'Zadané rodné číslo nie je správne' + } + + if (!values.r034a) { + errors.r034a = + 'Zadajte vlastné príjmy manželky / manžela' + } else if (!values.r034a.match(numberInputRegexp)) { + errors.r034a = 'Zadajte príjmy vo formáte 123,45' + } + } } return errors diff --git a/src/types/OutputJson.ts b/src/types/OutputJson.ts index 234d7ef9..4e555128 100644 --- a/src/types/OutputJson.ts +++ b/src/types/OutputJson.ts @@ -757,6 +757,7 @@ export interface Telo { r114: string r115: string r116: string + r116a: string r117: string r118: string r119: string diff --git a/src/types/PageUserInputs.ts b/src/types/PageUserInputs.ts index d64c0af3..d62f9365 100644 --- a/src/types/PageUserInputs.ts +++ b/src/types/PageUserInputs.ts @@ -23,6 +23,13 @@ export type ChildrenUserInput = Pick< TaxFormUserInput, | 'hasChildren' | 'children' + | 'partner_bonus_na_deti' + | 'r034_priezvisko_a_meno' + | 'r034_rodne_cislo' + | 'r034a' + | 'partner_bonus_na_deti_typ_prijmu' + | 'partner_bonus_na_deti_od' + | 'partner_bonus_na_deti_do' > export type PartnerUserInput = Pick< diff --git a/src/types/TaxForm.ts b/src/types/TaxForm.ts index 634a725f..e901ba00 100644 --- a/src/types/TaxForm.ts +++ b/src/types/TaxForm.ts @@ -21,22 +21,23 @@ export interface Child { interface Partner { priezviskoMeno: string rodneCislo: string - m00: string - m01: string - m02: string - m03: string - m04: string - m05: string - m06: string - m07: string - m08: string - m09: string - m10: string - m11: string - m12: string - druhaOsobaPodalaDPvSR: string - dokladRocZuct: string - dokladVyskaDane: string + m00: boolean + m01: boolean + m02: boolean + m03: boolean + m04: boolean + m05: boolean + m06: boolean + m07: boolean + m08: boolean + m09: boolean + m10: boolean + m11: boolean + m12: boolean + druhaOsobaPodalaDPvSR: boolean + dokladRocZuct: boolean + dokladVyskaDane: boolean + pocetMesiacov: number } export interface TaxForm { @@ -90,6 +91,7 @@ export interface TaxForm { /** Deti*/ r033?: Child[] + partner_bonus_na_deti: boolean r034: Partner r034a: Decimal @@ -165,6 +167,7 @@ export interface TaxForm { * osobitného základu dane (r. 90 + r. 104 + r. 28 Prílohy č. 2) Zrkadlenie * r081*/ r116_dan: Decimal + r116a: Decimal /** Nárok na daňový bonus (na jedno dieťa alebo úhrn na viac vyživovaných detí) podľa § 33 zákona 22) */ r117: Decimal diff --git a/src/types/TaxFormUserInput.ts b/src/types/TaxFormUserInput.ts index 79f15cdf..c5d072f1 100644 --- a/src/types/TaxFormUserInput.ts +++ b/src/types/TaxFormUserInput.ts @@ -85,6 +85,13 @@ export interface TaxFormUserInput { /** SECTION Deti*/ hasChildren?: boolean + partner_bonus_na_deti?: boolean + r034_priezvisko_a_meno?: string + r034_rodne_cislo?: string + r034a?: string + partner_bonus_na_deti_typ_prijmu?: "1" | "2" | "3" | "4" | "0" + partner_bonus_na_deti_od?: string + partner_bonus_na_deti_do?: string children: ChildInput[] /** SECTION Dve percenta */