Skip to content

Commit 0417d68

Browse files
committed
Fix EPO control 1411 by deriving kc_zd6 from rounded prij6/danZah
Independent ToWholeCZK truncation of Section6GrossIncome, Section6ForeignTax and Section6TaxBase could produce kc_zd6 != kc_prij6 - kc_dan_zah at whole-CZK precision when source amounts had fractional CZK, breaking the EPO server-side check "Oddíl 2/ř.36 - hodnota položky se nerovná hodnotě ř.34". Compute zd6 from the already-rounded prij6/danZah so the invariant holds for every input.
1 parent 06c758c commit 0417d68

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

internal/annualtaxxml/income_tax_gen.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,15 @@ func GenerateIncomeTaxXML(itr *domain.IncomeTaxReturn, settings map[string]strin
209209
uhrn := zd7 + zd8 + zd10 // ř.41 (no §9 rental income tracked yet)
210210

211211
// §6 employment income inputs.
212-
// kc_zd6 (ř.34/36) is computed by the service as kc_prij6 - kc_dan_zah and stored
213-
// in itr.Section6TaxBase. We carry the precomputed value through to keep XML and
214-
// detail-page rendering consistent.
212+
// EPO control 1411 ("Oddíl 2/ř.36 - hodnota položky se nerovná hodnotě ř.34")
213+
// requires kc_zd6 == kc_prij6 - kc_dan_zah at whole-CZK precision. Computing
214+
// zd6 from the already-rounded prij6/danZah (instead of rounding the halere
215+
// Section6TaxBase independently) guarantees the equality even when the source
216+
// amounts have fractional CZK that would otherwise truncate inconsistently.
215217
prij6 := ToWholeCZK(itr.Section6GrossIncome) // ř.31
216218
prij6zahr := ToWholeCZK(itr.Section6IncomeWithoutAdvance) // ř.35
217219
danZah := ToWholeCZK(itr.Section6ForeignTax) // ř.33
218-
zd6 := ToWholeCZK(itr.Section6TaxBase) // ř.34/36
220+
zd6 := prij6 - danZah // ř.34/36 = ř.31 - ř.33
219221

220222
// XSD ř.42 critical control: "Pokud je ř.41 záporný, uveďte pouze hodnotu z ř.36"
221223
// — when §7+§8+§10 sum to a loss, the consolidated tax base is just §6 alone.

internal/annualtaxxml/income_tax_gen_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,39 @@ func TestIncomeTaxXML_Section6Withholding(t *testing.T) {
741741
}
742742
}
743743

744+
// TestIncomeTaxXML_Section6Control1411 verifies that EPO control 1411
745+
// ("Oddíl 2/ř.36 - hodnota položky se nerovná hodnotě ř.34 z 2.oddílu DAP")
746+
// holds when the source amounts have fractional CZK (halere). EPO computes
747+
// ř.34 as kc_prij6 - kc_dan_zah at whole-CZK precision and requires kc_zd6
748+
// to match exactly; rounding each halere value independently can break the
749+
// invariant, so the generator derives kc_zd6 from the rounded prij6/danZah.
750+
func TestIncomeTaxXML_Section6Control1411(t *testing.T) {
751+
// 240001.00 CZK gross, 1.50 CZK foreign tax: independent rounding of the
752+
// stored Section6TaxBase (239999.50 CZK -> 239999) would not equal
753+
// 240001 - 1 = 240000.
754+
itr := &domain.IncomeTaxReturn{
755+
Year: 2025,
756+
FilingType: domain.FilingTypeRegular,
757+
TotalRevenue: domain.NewAmount(0, 0),
758+
Section6GrossIncome: domain.NewAmount(240001, 0), // 240001.00 CZK
759+
Section6ForeignTax: domain.NewAmount(1, 50), // 1.50 CZK
760+
Section6TaxBase: domain.NewAmount(239999, 50), // 239999.50 CZK
761+
}
762+
xmlData, err := GenerateIncomeTaxXML(itr, section6BaseSettings(), nil)
763+
if err != nil {
764+
t.Fatalf("GenerateIncomeTaxXML: %v", err)
765+
}
766+
for _, want := range []string{
767+
`kc_prij6="240001"`,
768+
`kc_dan_zah="1"`,
769+
`kc_zd6="240000"`, // = kc_prij6 - kc_dan_zah, satisfies EPO control 1411
770+
} {
771+
if !bytes.Contains(xmlData, []byte(want)) {
772+
t.Errorf("expected XML to contain %q, got:\n%s", want, xmlData)
773+
}
774+
}
775+
}
776+
744777
// TestIncomeTaxXML_Section6OnlyNegativeSection7 covers the XSD critical control on
745778
// kc_zakldan23 (ř.42): "Pokud je ř.41 záporný, uveďte pouze hodnotu z ř.36". When
746779
// the §7+§8+§10 sum is negative, the consolidated tax base equals §6 alone.

0 commit comments

Comments
 (0)