diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..6ec07a05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,88 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +/.venv +/.pytest_cache +/.ruff_cache + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg +*.eggs + +# Windows installers +*.msi + +# Debian packages +*.deb + +# Redhat packages +*.rpm + +# MacOS packages +*.dmg +*.pkg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Pycharm +.idea + +# Eclipse +.settings + +# Visual Studio cache/options directory +.vs/ +.vscode + +# OSX Files +.DS_Store + +# Django stuff: +*.log + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Sphinx documentation +docs/_build/ + +# Backup files +*~ +*.swp + +# OCA rules +!static/lib/ diff --git a/README.md b/README.md index b5e66dc5..ee3cc638 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,269 @@ # cssk-public -**Generated — do not edit.** +Czech and Slovak localization for Odoo: statutory accounting, VAT and control-statement filings, payroll, banking formats and company-register lookups. Published by Data Dance s.r.o. as a contribution to the Odoo community. -Every module here is produced from the private source repository -`Data-Dance/l10n_cssk` by `tools/generate_distribution.py`. A change -committed here is overwritten by the next sync, without warning. -Make the change in the source repository instead. +- [Slovak modules](https://www.datadance.eu/en/moduly/slovensko) +- [Czech modules](https://www.datadance.eu/en/moduly/cesko) +- [Licensing FAQ](https://www.datadance.eu/en/moduly/licencia) -Modules: 151 +**Generated from a private source repository.** + +Every module here is produced from Data Dance's private source +repository, and each sync replaces what is here. A change committed +directly to this repository is lost, not merged. + +Pull requests are welcome **here**: we apply an accepted one to the +source, keeping your authorship, and it returns in the next sync. +See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the steps and the +contributor licence agreement. + +## Requirements + +These modules depend on 24 module(s) that are not in +this repository. Add them to the addons path as well: + +| Source | Modules | +|---|---| +| [OCA/account-closing](https://github.com/OCA/account-closing) | `account_cutoff_start_end_dates`, `account_fiscal_year_closing`, `account_multicurrency_revaluation` | +| [OCA/account-financial-tools](https://github.com/OCA/account-financial-tools) | `account_asset_management`, `account_leasing`, `account_move_template` | +| [OCA/account-fiscal-rule](https://github.com/OCA/account-fiscal-rule) | `account_ecotax`, `account_ecotax_sale` | +| [OCA/account-reconcile](https://github.com/OCA/account-reconcile) | `account_reconcile_oca` | +| [OCA/bank-payment](https://github.com/OCA/bank-payment) | `account_banking_sepa_credit_transfer`, `account_payment_mode`, `account_payment_order` | +| [OCA/bank-statement-import](https://github.com/OCA/bank-statement-import) | `account_statement_import_base`, `account_statement_import_camt`, `account_statement_import_file` | +| [OCA/community-data-files](https://github.com/OCA/community-data-files) | `l10n_eu_nace` | +| [OCA/currency](https://github.com/OCA/currency) | `currency_rate_update` | +| [OCA/intrastat-extrastat](https://github.com/OCA/intrastat-extrastat) | `intrastat_product`, `intrastat_product_hscodes_import` | +| [OCA/mis-builder](https://github.com/OCA/mis-builder) | `mis_builder` | +| [OCA/payroll](https://github.com/OCA/payroll) | `payroll`, `payroll_account` | +| [OCA/reporting-engine](https://github.com/OCA/reporting-engine) | `report_xlsx` | +| [OCA/web](https://github.com/OCA/web) | `web_timeline` | + +## Modules + +166 module(s), grouped by the country or domain they serve. Every one of them is licensed `AGPL-3`. + +### Czech Republic (34) + +| Module | Version | What it does | +|---|---|---| +| `l10n_cz_account_asset_tax` | 19.0.1.0.3 | Czech tax depreciation groups and coefficients (zákon 586/1992 Sb. §30–32) | +| `l10n_cz_account_cutoff` | 19.0.1.0.1 | Pre-fills the OCA cut-off / deferral default accounts with the Czech 381/383/384/385 accounts so časové rozlišení works out of the box. | +| `l10n_cz_base` | 19.0.2.0.2 | Czech IBAN <-> legacy account number conversion and variable/constant/specific symbols | +| `l10n_cz_cash_journal` | 19.0.1.0.4 | Czech cash journal for daňová evidence (§ 7b ZDP): the členění vendors settled on, the peněžní deník, and the figures for Příloha č. 1 including oddíl D. | +| `l10n_cz_currency_revaluation` | 19.0.1.1.0 | Pre-fills the OCA multicurrency-revaluation accounts with the Czech 563/663 (kurzové rozdiely) accounts. | +| `l10n_cz_datadance` | 19.0.1.1.1 | One-click Czech statutory localization: installs the core statutory reporting + invoice modules, with optional workflow pieces selectable in Settings. | +| `l10n_cz_dppo` | 19.0.1.3.0 | Czech corporate income-tax return on the income-tax framework: ř.10 from the P&L, manual adjustments, the II. oddíl tax spine, and EPO Pisemnost/DPPDP9 XML… | +| `l10n_cz_ec_sales` | 19.0.1.2.0 | Czech EC sales list / souhrnné hlášení (DPHSHV) on the shared EC-summary framework + the EPO Pisemnost/DPHSHV XML export. | +| `l10n_cz_epo` | 19.0.1.0.0 | Signs Czech statutory filings with the company's qualified certificate and sends them to EPO — to check (test mode) or to file — and follows the filing to… | +| `l10n_cz_fiscal_year_closing` | 19.0.1.0.0 | Make the Czech year-end closing postable: retype 701/702/710 from off_balance, which Odoo refuses to mix with any other account in one entry. | +| `l10n_cz_fs` | 19.0.1.1.0 | Czech balance sheet (Rozvaha), P&L (Výkaz zisku a ztráty), cash flow and changes in equity on the shared FS framework — account-code line mappings + XML. | +| `l10n_cz_hr_payroll_account_oca` | 19.0.1.0.1 | Post Czech payslips to the general ledger | +| `l10n_cz_hr_payroll_annual_tax_settlement` | 19.0.1.0.1 | Annual Czech income-tax reconciliation (annual tax settlement statement, DPZVD6) for the Finanční správa — advance income tax aggregated over the year, EPO XML… | +| `l10n_cz_hr_payroll_eldp` | 19.0.1.0.2 | Annual Czech pension-insurance record (ELDP) for the ČSSZ — per-employee assessment base and days aggregated over the calendar year, XML validated against the… | +| `l10n_cz_hr_payroll_health` | 19.0.1.1.0 | Czech health-insurance employer e-filings — monthly premium overview (PPPZ, payslip-aggregate) and bulk employee enrol/terminate notification (HOZ, employee… | +| `l10n_cz_hr_payroll_oca` | 19.0.1.11.1 | Czech payroll rules for the payroll engine | +| `l10n_cz_hr_payroll_onz` | 19.0.1.0.2 | Czech employment registration (ONZ) for the ČSSZ — per-employee start/end events built from the employee and hr.version lifecycle, XML validated against the… | +| `l10n_cz_hr_payroll_parity` | 19.0.1.2.1 | Cross-engine parity harness: drives one shared set of payroll scenarios through both the OCA payroll and the Enterprise hr_payroll Czech implementations and… | +| `l10n_cz_hr_payroll_pvpoj` | 19.0.1.1.1 | Monthly Czech social-insurance overview (PVPOJ) for the ČSSZ — employer-aggregate XML export validated against the official PVPOJ25.xsd. Engine-neutral… | +| `l10n_cz_hr_payroll_surcharges` | 19.0.1.0.0 | Engine-neutral statutory wage surcharges for overtime, public holiday, night, weekend and difficult-environment work (§§ 114–118 zákoníku práce), plus the… | +| `l10n_cz_hr_payroll_surcharges_oca` | 19.0.1.0.0 | Statutory Czech wage surcharges (§§ 114–118 zákoníku práce) as salary rules for the OCA payroll engine. | +| `l10n_cz_intrastat_oca` | 19.0.1.0.0 | INTRASTAT-CZ Celní správa InstatOnline CSV on the OCA intrastat_product engine (CE-clean). EE uses Odoo EE l10n_cz_intrastat instead. | +| `l10n_cz_invoice` | 19.0.1.0.2 | Czech invoice layout: customer DIČ, payment symbols and the mandatory statutory phrases (reverse charge §92a, exemptions §64/§66). | +| `l10n_cz_invoice_payment_mode` | 19.0.1.0.0 | Prints the payment mode as 'Forma úhrady' in the header of the Czech invoice. | +| `l10n_cz_kh` | 19.0.1.8.0 | Czech VAT control statement on the shared KV/KH framework: per-document A1–B3 sections with the 10 000 CZK split + the EPO Pisemnost/DPHKH1 XML export. | +| `l10n_cz_oss` | 19.0.1.0.0 | Czech layer of the OSS return: the EPO Pisemnost/OSSEI1 XML (daňové přiznání k DPH ve zvláštním režimu jednoho správního místa — režim Evropské unie)… | +| `l10n_cz_payment_reliability` | 19.0.1.0.2 | Czech provider for the CZ/SK supplier-reliability check: nespolehlivý plátce + zveřejněné účty from the MFČR ADIS web service (rozhraniCRPDPH). | +| `l10n_cz_purchase_order_advance_invoice` | 19.0.1.0.0 | Czech chart wiring for purchase_order_advance_invoice. | +| `l10n_cz_sale_order_advance_invoice` | 19.0.1.1.0 | Czech chart wiring for advance invoices (zálohové faktury) | +| `l10n_cz_statutory` | 19.0.1.3.0 | Czech statutory reference registries: finanční úřady (seeded from the official l10n_cz.tax_office codelist) and person types. | +| `l10n_cz_vat_return` | 19.0.1.10.0 | Czech VAT return (přiznání k DPH / DPHDP3) on the shared VAT-return framework: line set mapped to the l10n_cz tax tags + the EPO Pisemnost/DPHDP3 XML export. | +| `l10n_cz_vat_status` | 19.0.1.1.0 | The Czech company's own VAT registration status over time, and what it does to invoices, bills, DPHDP3 and the kontrolní hlášení. | +| `l10n_cz_vat_status_purchase` | 19.0.1.0.0 | Purchase order lines propose the taxes the company's VAT status allows on the order date (l10n_cz_vat_status). | +| `l10n_cz_vat_status_sale` | 19.0.1.0.0 | Sales order lines propose the taxes the company's VAT status allows on the order date (l10n_cz_vat_status). | + +### Slovakia (50) + +| Module | Version | What it does | +|---|---|---| +| `l10n_sk_account_asset_profile` | 19.0.1.1.0 | Starter accounting-depreciation profiles for a Slovak company, wired to the chart's asset and oprávky accounts. | +| `l10n_sk_account_asset_tax` | 19.0.1.0.3 | Slovak tax depreciation groups and coefficients (zákon 595/2003 Z.z. §26–28) | +| `l10n_sk_account_cutoff` | 19.0.1.0.2 | Pre-fills the OCA cut-off / deferral default accounts with the Slovak 381/383/384/385 accounts so časové rozlíšenie works out of the box. | +| `l10n_sk_account_loan_base` | 19.0.1.0.2 | Engine-neutral Slovak leasing/loan account defaults on the l10n_sk chart, shared by the Community and Enterprise bridges. | +| `l10n_sk_account_loan_oca` | 19.0.1.1.2 | Applies the Slovak leasing account defaults to the OCA account_loan / account_leasing engine. | +| `l10n_sk_account_move_template` | 19.0.1.0.2 | Slovak posting templates for INTERNAL accounting documents on the l10n_sk chart. Not a document-level predkontácia: invoices, bank and cash keep deriving their… | +| `l10n_sk_asset_protocol_base` | 19.0.1.0.4 | Engine-neutral Slovak asset-protocol data and templates, shared by the Community and Enterprise asset bridges. | +| `l10n_sk_asset_protocol_oca` | 19.0.2.0.1 | Zaraďovací and vyraďovací protokol as printable documents on the OCA asset register. | +| `l10n_sk_base` | 19.0.2.0.0 | The Slovak DIČ — the income-tax identifier, which is not the VAT number and not the company registry number. | +| `l10n_sk_cash_journal` | 19.0.1.1.0 | Slovak cash journal: the statutory členenie of opatrenie MF/27076/2007-74, the peňažný denník in its official column layout, and the DPFO typ B tabuľka 1 / 1a… | +| `l10n_sk_currency_revaluation` | 19.0.1.0.1 | Pre-fills the OCA multicurrency-revaluation accounts with the Slovak 563/663 (kurzové rozdiely) accounts. | +| `l10n_sk_datadance` | 19.0.1.4.3 | One-click Slovak statutory localization: installs the core statutory reporting + invoice modules, with optional workflow pieces selectable in Settings. | +| `l10n_sk_dppo` | 19.0.1.4.3 | Slovak DPPO return on the income-tax framework: full r-line set + header mapping + XSD-validated XML export against the official dppo2025 schema. | +| `l10n_sk_dppo_fs` | 19.0.1.1.2 | Tie the DPPO r100 to the VZS r56 of the účtovná závierka (UZPODv14) and flag a disagreement before filing. | +| `l10n_sk_ec_sales` | 19.0.1.2.2 | Slovak EC sales list (Súhrnný výkaz) — codes 0/1/2 (goods / triangulation / services), FS SR SDV XML export. Built on the shared l10n_cssk_ec_summary_base… | +| `l10n_sk_ekasa_receipt` | 19.0.1.0.0 | Read the QR code on a Slovak fiscal receipt and fetch the registered receipt — items, quantities and the VAT recap — from Finančná správa's… | +| `l10n_sk_fiscal_year_closing` | 19.0.1.0.1 | Slovak year-end closing template: costs and revenues to 710, balance-sheet accounts to 702, reopened through 701. | +| `l10n_sk_fs` | 19.0.1.7.0 | Slovak balance sheet (Súvaha) on the shared l10n_cssk_fs_base framework — account-code line mapping + comparison period + XML export. | +| `l10n_sk_hr_payroll_account_oca` | 19.0.1.0.1 | Post Slovak payslips to the general ledger | +| `l10n_sk_hr_payroll_annual_tax_report` | 19.0.2.0.0 | Annual Slovak employer income-tax report (Hlásenie o vyúčtovaní dane a o úhrne príjmov zo závislej činnosti, § 39 ods. 9) for the Finančná správa — aggregate… | +| `l10n_sk_hr_payroll_base` | 19.0.1.0.0 | Engine-neutral base for the Slovak payroll: the employment forms (pracovný pomer, DoVP, DoPČ, DoBPŠ) and one table saying which contributions and entitlements… | +| `l10n_sk_hr_payroll_eldp` | 19.0.1.0.2 | Annual per-employee Slovak pension record (ELDP) filed to the Sociálna poisťovňa — aggregates each year's pension assessment base and insured period per… | +| `l10n_sk_hr_payroll_health` | 19.0.1.1.0 | Monthly Slovak health-insurance advances statement (Mesačný výkaz preddavkov na poistné, dávka 514) for VšZP / Dôvera / Union — employer header + per-employee… | +| `l10n_sk_hr_payroll_monthly_tax_overview` | 19.0.2.0.0 | Monthly Slovak income-tax overview (Prehľad o zrazených a odvedených preddavkoch na daň) for the Finančná správa — withheld tax advances + daňový bonus recap… | +| `l10n_sk_hr_payroll_mvp` | 19.0.1.0.3 | Monthly Slovak social-insurance statement (MVP/MVPP) for the Sociálna poisťovňa — aggregate summary + per-employee annex, XML validated against MVPP-v2026.xsd… | +| `l10n_sk_hr_payroll_oca` | 19.0.1.18.3 | Slovak payroll rules for the payroll engine | +| `l10n_sk_hr_payroll_parity` | 19.0.1.8.0 | Cross-engine parity harness: drives one shared set of payroll scenarios through both the OCA payroll and the Enterprise hr_payroll Slovak implementations and… | +| `l10n_sk_hr_payroll_rlfo` | 19.0.1.1.1 | Slovak social-insurance registration of employees (RLFO / RLZEC) filed to the Sociálna poisťovňa — prihláška / odhláška events built from the employee +… | +| `l10n_sk_hr_payroll_surcharges` | 19.0.1.2.4 | Engine-neutral statutory wage surcharges for night, Saturday, Sunday, public-holiday, overtime, difficult-conditions and standby work (§§ 121–123 Zákonníka… | +| `l10n_sk_hr_payroll_surcharges_oca` | 19.0.1.0.2 | Statutory Slovak wage surcharges and the minimum-wage top-up as salary rules for the OCA payroll engine. | +| `l10n_sk_hr_payroll_vpp` | 19.0.1.0.1 | Slovak social-insurance statement for dohody / irregular income (VPP/VPP2026) filed to the Sociálna poisťovňa — aggregate summary + per-employee annex, XML… | +| `l10n_sk_intrastat` | 19.0.1.1.0 | INTRASTAT-SK INSTAT XML on the OCA intrastat_product engine (CE-clean). EE variant: l10n_sk_intrastat_ee. | +| `l10n_sk_inventory_verification` | 19.0.1.0.1 | Statutory Slovak inventory verification: inventúrne súpisy (§ 30 ods. 2) and the inventarizačný zápis (§ 30 ods. 3). | +| `l10n_sk_invoice` | 19.0.1.0.4 | Slovak invoice layout: DIČ/IČO/IČ DPH party block, payment symbols, supply/issue/due dates and mandatory §74 statutory phrases (reverse charge, exemptions)… | +| `l10n_sk_jcd` | 19.0.1.1.4 | Slovak import customs declaration: duty into stock value, import VAT onto the right DPH rows, deduction gated on the confirmed customs document. | +| `l10n_sk_kv_dph` | 19.0.2.3.0 | Slovak VAT control statement (Kontrolný výkaz DPH) — sections A.1–D.2 incl. D.1, with FS SR XML export. Built on the shared l10n_cssk_kv_kh_base framework. | +| `l10n_sk_mis_reports` | 19.0.1.0.0 | Management P&L on the Slovak chart, for Community — the counterpart to Enterprise's account_reports. | +| `l10n_sk_oss` | 19.0.1.0.0 | Slovak layer of the OSS return: the Finančná správa eForm DPOSS_EUv01 XML (daňové priznanie k DPH — osobitná úprava pre Úniu), on the EU OSSVATReturnMSCON… | +| `l10n_sk_payment_reliability` | 19.0.1.1.0 | Slovak provider for the CZ/SK supplier-reliability check: registered bank accounts (ds_dph_iban) and the tax-reliability index (ds_iz_ran) from the Financial… | +| `l10n_sk_purchase_order_advance_invoice` | 19.0.1.0.0 | Slovak chart wiring for purchase_order_advance_invoice. | +| `l10n_sk_sale_order_advance_invoice` | 19.0.1.1.0 | Slovak chart wiring for advance invoices (preddavkové faktúry) | +| `l10n_sk_single_entry_closing` | 19.0.1.0.2 | The closing a SZČO keeping jednoduché účtovníctvo files: výkaz o príjmoch a výdavkoch from the peňažný denník, výkaz o majetku a záväzkoch from the ledger. | +| `l10n_sk_statutory` | 19.0.1.0.0 | Slovak statutory reference registries: daňové úrady and person types. | +| `l10n_sk_trade_registry` | 19.0.2.0.0 | Keep the register, court and oddiel/vložka as data and compose the § 3a Obchodného zákonníka sentence from them, instead of typing it once into the company… | +| `l10n_sk_ubl_bis3` | 19.0.3.1.0 | Slovak e-invoicing: Peppol BIS Billing 3.0 / UBL 2.1 export | +| `l10n_sk_vat_registration` | 19.0.1.0.1 | Record which paragraph of the SK VAT Act a partner's IČ DPH was issued under, and warn when a § 69/12 reverse charge is aimed at a § 7 / § 7a registrant, who… | +| `l10n_sk_vat_return` | 19.0.1.12.0 | Slovak VAT return (DPHv25) — output/input lines wired to the l10n_sk tax tags, with FS SR DPH XML export. Built on the shared l10n_cssk_vat_return_base… | +| `l10n_sk_vehicle_expense` | 19.0.1.0.2 | 50 % VAT deduction on mixed-use vehicles and the 80 % fuel tax-expense paušál — two independent Slovak regimes. | +| `l10n_sk_vrp2_account` | 19.0.2.3.0 | Fiscalize customer invoices through the Slovak Virtual Cash Register (VRP 2) | +| `l10n_sk_vrp2_base` | 19.0.2.4.0 | Connection layer for the Slovak Virtual Cash Register (VRP 2) | + +### Czech Republic and Slovakia (29) + +| Module | Version | What it does | +|---|---|---| +| `l10n_cssk_accrual` | 19.0.1.0.4 | Estimated unbilled items — book an estimate to the accrual account (CZ 388/389, SK 326), then link the actual invoice to true it up. | +| `l10n_cssk_cash_journal_base` | 19.0.1.6.0 | Country-neutral single-entry cash journal derived from ordinary double-entry books: payments become dated, categorised denník rows, with the year-end balances… | +| `l10n_cssk_core` | 19.0.1.17.0 | Country-neutral foundation for the Czech & Slovak statutory localization: tax-authority registry, person types, shared company/partner fields, settings and… | +| `l10n_cssk_ec_summary_base` | 19.0.1.6.5 | Country-neutral framework for the Slovak Súhrnný výkaz and Czech Souhrnné hlášení (EC sales list): single-line aggregation per (country, VAT, transaction… | +| `l10n_cssk_ec_summary_vies` | 19.0.1.0.4 | Validate EC sales list partners against VIES at export and snapshot the consultation number onto each line. | +| `l10n_cssk_fs_base` | 19.0.1.9.0 | Country-neutral framework for the Slovak and Czech financial statements (balance sheet + P&L): line trees computed from account-code balances + aggregates… | +| `l10n_cssk_hr_payroll_garnishment_account` | 19.0.1.1.1 | Turn each wage-garnishment deduction into a payable to the bailiff, stamped with the case number as variable symbol, ready for the payment order and the bank… | +| `l10n_cssk_hr_payroll_garnishment_base` | 19.0.1.2.1 | Engine-neutral register of Czech and Slovak wage-garnishment orders (exekuční / exekučný príkaz) with the statutory multi-claim waterfall and the employer's… | +| `l10n_cssk_hr_payroll_garnishment_oca` | 19.0.1.0.1 | Apply the Czech/Slovak wage-garnishment waterfall on payslips computed by the OCA payroll engine. | +| `l10n_cssk_income_tax_base` | 19.0.1.5.5 | Country-neutral framework for the corporate income-tax return (DPPO): versioned line definitions, accounting-result + aggregate evaluator, manual-override UI… | +| `l10n_cssk_internal_document` | 19.0.1.0.0 | Print a journal entry as the interní / interný doklad the law requires | +| `l10n_cssk_intrastat_base` | 19.0.1.1.0 | Edition-neutral INTRASTAT-SK INSTAT (instat62) XML renderer shared by the CE (OCA) and EE (account_intrastat) adapters. | +| `l10n_cssk_intrastat_footprint` | 19.0.1.1.0 | A document says which Intrastat declaration reports it, alongside the VAT return, control statement and financial statements it feeds. | +| `l10n_cssk_kv_kh_base` | 19.0.2.2.1 | Abstract framework for the Slovak KV DPH and Czech KH DPH VAT control statements: versioned templates, section/summary/reconciliation row mixins, move-line… | +| `l10n_cssk_leave_expiry_reminder` | 19.0.1.0.3 | Proactively remind employees and HR about expiring carried-over leave | +| `l10n_cssk_oss_base` | 19.0.1.0.0 | Country-neutral engine for the quarterly One-Stop-Shop VAT return (Union scheme): OSS-tagged sales aggregated by member state of consumption, rate and supply… | +| `l10n_cssk_partner_balances` | 19.0.1.2.0 | Saldokonto / odsúhlasenie (potvrdenie) zostatkov pohľadávok a záväzkov — a stored, state-tracked confirmation of a partner's open AR/AP as of a date, with a… | +| `l10n_cssk_payment_reliability` | 19.0.1.1.0 | Before you pay a vendor, check the supplier's tax reliability and whether the bank account is the one registered with the tax authority — and snapshot the… | +| `l10n_cssk_payment_symbols` | 19.0.1.3.0 | Variable, constant and specific payment symbols on invoices, credit notes and bank statement lines. | +| `l10n_cssk_payment_symbols_payment_order` | 19.0.1.0.0 | Copy invoice VS/KS/SS onto OCA payment order lines. | +| `l10n_cssk_payroll_declaration_base` | 19.0.1.4.3 | Engine-neutral base for Czech/Slovak payroll authority e-submissions: XSD-validated XML export, a submission state machine and an hr.payslip adapter that works… | +| `l10n_cssk_receipt_capture` | 19.0.1.0.0 | Country-neutral framework for capturing cash-register receipts (bločky / účtenky) into Odoo: a provider registry, the captured receipt with its lines and… | +| `l10n_cssk_receipt_capture_expense` | 19.0.1.0.0 | Turn a captured fiscal receipt into employee expenses, and capture receipts from the expense attachment upload. | +| `l10n_cssk_recycling_fee` | 19.0.1.0.0 | Dated CZ/SK recycling-fee rates on OCA ecotax classifications, the statutory per-line 'z toho recyklační příspěvek' on the invoice, and a per-category period… | +| `l10n_cssk_recycling_fee_sale` | 19.0.1.0.0 | Price the CZ/SK recycling fee on sale order lines at the order date, carry it to the invoice, and optionally invoice it as its own line. | +| `l10n_cssk_registry_verify` | 19.0.1.0.0 | Answer whether an IČO exists in the state register, and say so in a way a gate can act on | +| `l10n_cssk_submission_base` | 19.0.1.0.1 | Channel-agnostic delivery framework for statutory filings: one record per delivery attempt-set, a pluggable channel contract, retry with backoff, and durable… | +| `l10n_cssk_vat_return_base` | 19.0.1.10.5 | Country-neutral framework for the Slovak and Czech VAT return: numbered report lines computed from tax tags + aggregate formulas, with an XSD-validated XML… | +| `l10n_cssk_vies` | 19.0.2.0.0 | Validate EU VAT numbers directly against the European Commission VIES service (no Odoo IAP) and store the official consultation number as proof of check. | + +### European Union (1) + +| Module | Version | What it does | +|---|---|---| +| `l10n_eu_nace_cssk` | 19.0.1.0.0 | NACE Rev. 2.1 industries with the CZ-NACE 2025 fifth digit, linked to the partner's NACE code | + +### EDI and e-invoicing (5) + +| Module | Version | What it does | +|---|---|---| +| `edi_base` | 19.0.1.8.0 | Shared infrastructure for EDI integrations (Editel, GRiT, ...) | +| `edi_base_peppol` | 19.0.1.5.1 | Provider-neutral Peppol BIS Billing 3.0: send/receive invoices and credit notes, and business responses (MLR / Invoice Response), over any EDI transport… | +| `edi_epostak_base` | 19.0.1.0.1 | Base module for the ePošťák (epostak.sk) Peppol access point: provider registration, environment/mode configuration and the Peppol addressing metadata every… | +| `edi_epostak_connector_sapi` | 19.0.1.1.1 | Concrete REST connector for the ePošťák Peppol access point (SAPI-SK 1.0): send and receive UBL, acknowledge, track delivery | +| `edi_epostak_peppol` | 19.0.1.0.0 | Send & receive Peppol BIS Billing 3.0 (invoices, credit notes, business responses) through the ePošťák access point | + +### Payroll (2) + +| Module | Version | What it does | +|---|---|---| +| `payroll_dashboard` | 19.0.1.1.0 | A lightweight graph + pivot dashboard of payroll headline figures (gross, net, employer cost, income tax) per period and employee, for the community payroll… | +| `payroll_worked_days_timeline` | 19.0.1.0.0 | Timeline and pivot views of payslip worked days and inputs, per employee over time — a community alternative to the Enterprise work-entries gantt for reviewing… | + +### Banking and payments (17) + +| Module | Version | What it does | +|---|---|---| +| `account_abo` | 19.0.2.0.0 | Export Czech/Slovak domestic credit transfers in the ABO file format | +| `account_fio` | 19.0.2.2.3 | Fio banka API tokens per bank journal, with the mandatory 30-second throttle and token-expiry watch. | +| `account_fio_base` | 19.0.1.2.0 | Shared Fio banka REST client, movement parser and payment-order builder. The statement puller and both payment-order bridges build on this. | +| `account_multicash` | 19.0.2.0.0 | Export payments in MultiCash formats (CFD/CFU/CFA/MT101) for Czech banks | +| `account_payment_fio` | 19.0.1.2.1 | Build Fio XML payment orders and send them to the bank from an OCA payment order. | +| `account_payment_kb_best` | 19.0.1.0.0 | Export payment orders in Komerční banka's BEST format (domestic transfers and direct debits, foreign and SEPA transfers). | +| `account_payment_kb_sepa` | 19.0.1.0.0 | Komerční banka's profile of the OCA pain.001.001.03 SEPA credit transfer: structured postal address, SWIFT character set. | +| `account_payment_mode_pay_on_post` | 19.0.1.0.0 | A payment mode can register the payment when the invoice is posted — cash and card sales paid on the spot. | +| `account_qr_code_frame_provider` | 19.0.1.1.0 | Shared QR-code frame parameters for the payment-QR providers (PAY by square, payme, QR Platba) that build on it. | +| `account_qr_code_pay_by_square_sk` | 19.0.1.2.0 | Slovak PAY by square payment QR code on invoices — the Slovak Banking Association standard every Slovak banking app reads, pre-filling IBAN, amount, currency… | +| `account_qr_code_payme_sk` | 19.0.1.2.0 | Slovak payme payment QR code on invoices — a payme.sk link that opens the customer's banking app with IBAN, amount, currency and payment identification… | +| `account_qr_code_qr_platba_cz` | 19.0.1.1.1 | Czech QR Platba payment QR code on invoices — the Czech Banking Association short payment descriptor (SPD) every Czech banking app reads, pre-filling IBAN… | +| `account_statement_fio` | 19.0.2.4.0 | Pull movements and official statements from the Fio banka API into bank journals. Community and Enterprise alike. | +| `account_statement_import_camt_balance` | 19.0.1.0.0 | Last daily closing balance; one line per entry when a bank splits its details | +| `account_statement_import_gpc` | 19.0.2.0.0 | Import Czech/Slovak GPC (ABO) bank statement files. | +| `account_statement_import_kb_best` | 19.0.1.0.0 | Import Komerční banka BEST electronic statements (*.OKM). | +| `account_statement_import_mt940` | 19.0.1.0.0 | Import SWIFT MT940 statements (MultiCash *.STA) with the Czech VS/KS/SS and counterparty subfields. | + +### Currency rates (3) + +| Module | Version | What it does | +|---|---|---| +| `currency_rate_sk_base` | 19.0.1.0.1 | Shared VÚB / Tatra banka exchange-rate fetch + parse. The CE (OCA currency_rate_update) and EE (currency_rate_live) SK providers both build on this. | +| `currency_rate_update_cz` | 19.0.1.3.0 | Czech National Bank (ČNB) daily exchange-rate provider for the OCA currency_rate_update framework. | +| `currency_rate_update_sk` | 19.0.1.1.0 | Slovak FX-rate providers for OCA currency_rate_update: ECB reference rate (NBS statutory) with previous-day fill, plus VÚB and Tatra banka commercial rate… | + +### Point of sale (1) + +| Module | Version | What it does | +|---|---|---| +| `pos_vrp2` | 19.0.5.0.0 | Integrate Odoo POS with Slovak Virtual Cash Register (VRP 2) | + +### Partner and company data (5) + +| Module | Version | What it does | +|---|---|---| +| `partner_autocomplete_ares_cz` | 19.0.1.2.1 | Completes Partner information from ARES (https://ares.gov.cz/) | +| `partner_autocomplete_dispatcher` | 19.0.1.1.0 | Technical dispatcher for the partner-autocomplete providers: assigns each dependent provider module to the companies that should use it. | +| `partner_autocomplete_orsf_sk` | 19.0.1.4.0 | Completes Partner information from the Slovak state registers via https://orsf.sk | +| `partner_autocomplete_orsf_sk_related_parties` | 19.0.2.0.0 | Pull one partner's ownership/officer graph from ORSF on demand, so § 17 ods. 5 related-party transactions can be recognised instead of remembered. | +| `partner_nace` | 19.0.1.0.0 | The partner's main economic activity as its NACE code | + +### Accounting (18) + +| Module | Version | What it does | +|---|---|---| +| `account_asset_board` | 19.0.1.0.0 | Interactive depreciation schedule and pivot/graph analysis for OCA assets (Community) | +| `account_asset_tax` | 19.0.1.9.4 | Country-neutral engine for a second, non-posted tax-depreciation board alongside accounting depreciation (daňové vs účetní/účtovné odpisy) | +| `account_asset_tax_oca` | 19.0.1.4.4 | Attach the CZ/SK tax-depreciation board to OCA account_asset_management (Community) | +| `account_cz_bankfile_base` | 19.0.1.3.1 | Shared ABO and MultiCash payment-file builders. The CE (account_payment_order) and EE (account_batch_payment) variants of account_abo / account_multicash both… | +| `account_debit_note_dd` | 19.0.1.0.0 | Names Odoo's core debit note as the Czech vrubopis (opravný daňový doklad zvyšující základ) on screen and on the printed document — a UI and report layer that… | +| `account_edi_isdoc` | 19.0.1.2.0 | Import and export ISDOC 6.0.2 electronic invoices (Czech national e-invoicing standard) | +| `account_edi_isdoc_purchase_advance` | 19.0.1.0.0 | Route imported ISDOC advance documents (DocumentType 4/5/6) into the received-advances flow. | +| `account_edi_isdoc_sale` | 19.0.1.0.0 | Export a sale order as an ISDOC proforma (zálohová faktura, DocumentType 4) | +| `account_edi_isdoc_sale_advance` | 19.0.1.0.0 | Map the Czech advance-payment documents to ISDOC DocumentType 4/5/6 | +| `account_gpc_base` | 19.0.1.0.0 | Shared GPC (ABO electronic statement) parser. The CE (account_statement_import_file) and EE (account_bank_statement_import) import shims both build on this. | +| `account_kb_best_base` | 19.0.1.0.0 | Komerční banka BEST client format: domestic and foreign/SEPA payment files out, electronic statements in. | +| `account_move_report_signed` | 19.0.1.0.0 | Renders negative (signed) values in Account Move note PDF reports | +| `account_mt940_base` | 19.0.1.0.0 | Shared SWIFT MT940 parser with the Czech ?NN layout of field :86: (MultiCash *.STA statements). | +| `account_reconcile_oca_counter_account` | 19.0.1.0.0 | Put the counterparty bank account back on the OCA bank statement reconciliation screen. | +| `purchase_order_advance_invoice` | 19.0.1.1.0 | Received advance invoices (vendor proformas) with tax documents on sent payments | +| `sale_order_advance_invoice` | 19.0.2.7.1 | Advance invoices (proforma) with tax documents on received payments | +| `sale_order_advance_invoice_payment_match` | 19.0.1.0.1 | Match incoming bank statement lines to advance invoices by variable symbol and register the payment automatically. | +| `sale_order_advance_invoice_payment_match_oca` | 19.0.1.0.1 | Runs the advance-invoice matching inside the OCA reconciliation widget's auto-reconcile. | + +### Other (1) + +| Module | Version | What it does | +|---|---|---| +| `advance_invoice_saldo_xlsx` | 19.0.1.0.1 | XLSX saldo of issued and received advance invoices: paid, tax-documented, deducted and open amounts. | diff --git a/account_debit_note_dd/__manifest__.py b/account_debit_note_dd/__manifest__.py index c2896445..d18b1615 100644 --- a/account_debit_note_dd/__manifest__.py +++ b/account_debit_note_dd/__manifest__.py @@ -2,6 +2,9 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "Account Debit Note DD", + "summary": "Names Odoo's core debit note as the Czech vrubopis (opravný daňový doklad zvyšující " + "základ) on screen and on the printed document — a UI and report layer that leaves " + "posting unchanged.", "category": "Tools", "license": "AGPL-3", "author": "Data Dance s.r.o.", diff --git a/account_edi_isdoc/CHANGELOG.rst b/account_edi_isdoc/CHANGELOG.rst new file mode 100644 index 00000000..315b83cf --- /dev/null +++ b/account_edi_isdoc/CHANGELOG.rst @@ -0,0 +1,62 @@ +========= +Changelog +========= + +All notable changes to **account_edi_isdoc** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +Fixed +~~~~~ + +- **TaxPointDate carried the issue date, not the DUZP.** The export wrote + ``invoice_date``, so a supply delivered on the 30th and invoiced on the 3rd + told the recipient to book it in the wrong VAT period. It now writes + ``taxable_supply_date``, falling back to the invoice date. +- **An imported vendor bill ignored TaxPointDate.** Only the advance tax + document (``account_edi_isdoc_purchase_advance``) read it; every ordinary + bill now takes its DUZP from it. The deduction period + (``cssk_vat_deduction_date``) is deliberately not set: it is when *we* + claim, which the supplier's document cannot know. + +Carry-over to 18.0 +~~~~~~~~~~~~~~~~~~ + +- TaxPointDate export/import (2026-09-28). On 18.0 ``taxable_supply_date`` + comes from the country modules, not core — check it exists before porting. + +Changed +~~~~~~~ + +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.1.1.0] — 2026-07-14 +------------------------- + +Added +~~~~~ + +- Import now also writes ``VariableSymbol`` / ``ConstantSymbol`` / + ``SpecificSymbol`` onto the invoice symbol fields (digits-sanitised); + previously the VS went into ``payment_reference`` only and KS/SS were + dropped. + +[19.0.1.0.0] — 2026-07-04 +------------------------- + +Added +~~~~~ + +- Adds the Czech national e-invoicing format **ISDOC 6.0.2** to Odoo's electronic + invoice import/export, alongside the standard UBL/CII formats, by hooking into + ``account_edi_ubl_cii`` (reuses existing send/print and bill-import plumbing). +- Export of customer invoices and credit notes as ISDOC; the format is selectable + per customer (Invoicing tab) and offered for companies established in the CZ. +- Import (decode) of incoming ISDOC documents into draft vendor bills. +- Supported representations: plain ``.isdoc`` XML, ``.isdocx`` archive + (ISDOC + manifest + PDF), and ISDOC embedded into a PDF/A-3 document. diff --git a/l10n_sk_hr_payroll_hlasenie/LICENSE b/account_edi_isdoc/LICENSE similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/LICENSE rename to account_edi_isdoc/LICENSE diff --git a/l10n_sk_hr_payroll_hlasenie/__init__.py b/account_edi_isdoc/__init__.py similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/__init__.py rename to account_edi_isdoc/__init__.py diff --git a/account_edi_isdoc/__manifest__.py b/account_edi_isdoc/__manifest__.py new file mode 100644 index 00000000..31def93d --- /dev/null +++ b/account_edi_isdoc/__manifest__.py @@ -0,0 +1,29 @@ +{ + 'name': "Import/Export Czech e-invoices (ISDOC)", + 'version': '19.0.1.2.0', + 'category': 'Accounting/Accounting', + 'summary': "Import and export ISDOC 6.0.2 electronic invoices (Czech national e-invoicing standard)", + 'description': """ +ISDOC electronic invoicing +========================== + +Adds the Czech national e-invoicing format **ISDOC 6.0.2** to Odoo's electronic +invoice import/export, alongside the standard UBL/CII formats. + +The format can be selected per customer (Invoicing tab) and is offered for +companies established in the Czech Republic. Supported representations: + +* plain ``.isdoc`` XML +* ``.isdocx`` archive (ISDOC + manifest + PDF) +* ISDOC embedded into a PDF/A-3 document + +This module hooks into ``account_edi_ubl_cii`` so it reuses the existing +send/print and bill-import plumbing. + """, + 'depends': ['account_edi_ubl_cii', 'l10n_cz_base'], + 'data': [], + 'installable': True, + 'auto_install': False, + 'author': "Data Dance s.r.o.", + "license": "AGPL-3", +} diff --git a/account_edi_isdoc/models/__init__.py b/account_edi_isdoc/models/__init__.py new file mode 100644 index 00000000..733c5939 --- /dev/null +++ b/account_edi_isdoc/models/__init__.py @@ -0,0 +1,4 @@ +from . import account_edi_xml_isdoc +from . import res_partner +from . import account_move +from . import account_move_send diff --git a/account_edi_isdoc/models/account_edi_xml_isdoc.py b/account_edi_isdoc/models/account_edi_xml_isdoc.py new file mode 100644 index 00000000..9803092c --- /dev/null +++ b/account_edi_isdoc/models/account_edi_xml_isdoc.py @@ -0,0 +1,696 @@ +import io +import re +import uuid +import zipfile + +from lxml import etree + +from odoo import _, models, Command +from odoo.exceptions import UserError +from odoo.tools import html2plaintext +from odoo.tools.float_utils import float_is_zero +from odoo.addons.account.tools import dict_to_xml +from odoo.addons.account_edi_ubl_cii.models.account_edi_common import FloatFmt +from odoo.addons.account_edi_isdoc.tools import isdoc_invoice + +# ISDOC 6.0.2 - Czech national e-invoicing standard. +# Schema: http://isdoc.cz/namespace/2013 (xsd/isdoc-invoice-6.0.2.xsd) +ISDOC_NS = "http://isdoc.cz/namespace/2013" +ISDOC_MANIFEST_NS = "http://isdoc.cz/namespace/2013/manifest" +ISDOC_VERSION = "6.0.2" + +# code list (isdoc-invoice-6.0.2.xsd, DocumentTypeType) +ISDOC_DOCUMENT_TYPE = { + 'invoice': '1', # daňový doklad - faktura + 'credit_note': '2', # opravný daňový doklad (dobropis) + 'debit_note': '3', # opravný daňový doklad (vrubopis) + 'proforma': '4', # zálohová faktura (neplátce / bez DPH) + 'advance': '5', # zálohový daňový doklad (s DPH) + 'advance_credit': '6', # opravný daňový doklad k zálohovému (s DPH) + 'simplified': '7', # zjednodušený daňový doklad +} + +# Order of the group inside a transfer
(after PaymentDueDate). +# These are min=1 in the schema, so they must all be present even when empty (e.g. IBAN/BIC). +ISDOC_BANK_GROUP = ('ID', 'BankCode', 'Name', 'IBAN', 'BIC') + + +class AccountEdiXmlISDOC(models.AbstractModel): + _name = 'account.edi.xml.isdoc' + _inherit = 'account.edi.common' + _description = "ISDOC 6.0.2 (CZ)" + + # ------------------------------------------------------------------------- + # HELPERS + # ------------------------------------------------------------------------- + + def format_float(self, amount, precision_digits=2): + if amount is None: + return None + return FloatFmt(amount, precision_digits) + + def _export_invoice_filename(self, invoice): + return f"{invoice.name.replace('/', '_')}.isdoc" + + @staticmethod + def _text(value): + """ Wrap a scalar as a leaf node for dict_to_xml (bare scalars become attributes). """ + return {'_text': value} + + @staticmethod + def _split_street(partner): + """ ISDOC wants StreetName + BuildingNumber separately; Odoo stores a combined street. + Split off a trailing house number (incl. forms like '1141/11'). """ + street = (partner.street or '').strip() + m = re.search(r'^(.*?)[\s,]+(\d+[\w/]*)$', street) + if m: + return m.group(1).strip(), m.group(2).strip() + return street, (partner.street2 or '').strip() + + def _get_document_type_code(self, invoice): + # Advance tax documents (DocumentType 5/6, daňový doklad k přijaté platbě) are emitted + # by the account_edi_isdoc_sale_advance bridge; the core knows only invoices/credit notes. + is_refund = invoice.move_type in ('out_refund', 'in_refund') + return ISDOC_DOCUMENT_TYPE['credit_note'] if is_refund else ISDOC_DOCUMENT_TYPE['invoice'] + + def _line_tax_percent(self, line): + # Both account.move.line and sale.order.line expose `tax_ids`. + return sum(line.tax_ids.filtered(lambda t: t.amount_type == 'percent').mapped('amount')) + + def _isdoc_line_node(self, index, name, qty, subtotal, total, percent, uom_name, product, dp): + """ Build one from primitives (shared by invoice and proforma export). """ + item = {'Description': self._text(html2plaintext(name) if name else product.name)} + if product.barcode: + item['CatalogueItemIdentification'] = {'ID': self._text(product.barcode)} + if product.default_code: + item['SellersItemIdentification'] = {'ID': self._text(product.default_code)} + unit = subtotal / qty if qty else subtotal + unit_incl = total / qty if qty else total + return { + 'ID': self._text(str(index)), + 'InvoicedQuantity': {'_text': self.format_float(qty, 6), 'unitCode': uom_name or 'ks'}, + 'LineExtensionAmount': self._text(self.format_float(subtotal, dp)), + 'LineExtensionAmountTaxInclusive': self._text(self.format_float(total, dp)), + 'LineExtensionTaxAmount': self._text(self.format_float(total - subtotal, dp)), + 'UnitPrice': self._text(self.format_float(unit, dp)), + 'UnitPriceTaxInclusive': self._text(self.format_float(unit_incl, dp)), + 'ClassifiedTaxCategory': { + 'Percent': self._text(self.format_float(percent, 2)), + 'VATCalculationMethod': self._text('0'), + 'VATApplicable': self._text('true' if percent else 'false'), + }, + 'Item': item, + } + + def _isdoc_split_lines(self, invoice): + """ (regular_lines, deposit_lines): deposit_lines are advance deductions reported as + already-claimed deposits (TaxedDeposits) instead of invoice lines. """ + product_lines = invoice.invoice_line_ids.filtered(lambda l: l.display_type == 'product') + deposit_lines = self._isdoc_deduction_lines(product_lines) + return product_lines - deposit_lines, deposit_lines + + def _isdoc_deduction_lines(self, product_lines): + """ Lines on a final invoice that deduct already-invoiced advances. Override point: + the core handles generic Odoo down payments (negative is_downpayment lines on a final + invoice); the CZ advance bridge adds is_advance_tracking deductions. """ + aml = self.env['account.move.line'] + if 'is_downpayment' not in aml._fields: + return aml + move = product_lines.move_id[:1] + if not move or move._is_downpayment(): # a down-payment invoice's own lines aren't deductions + return aml + return product_lines.filtered('is_downpayment') + + def _isdoc_group_by_rate(self, lines, currency, sign=1): + """ {percent: {'taxable','inclusive','tax'}} summed (and rounded) over `lines`. """ + groups = {} + for line in lines: + percent = round(self._line_tax_percent(line), 2) + g = groups.setdefault(percent, {'taxable': 0.0, 'inclusive': 0.0}) + g['taxable'] += sign * line.price_subtotal + g['inclusive'] += sign * line.price_total + for g in groups.values(): + g['taxable'] = currency.round(g['taxable']) + g['inclusive'] = currency.round(g['inclusive']) + g['tax'] = currency.round(g['inclusive'] - g['taxable']) + return groups + + # ------------------------------------------------------------------------- + # EXPORT + # ------------------------------------------------------------------------- + + def _export_invoice(self, invoice): + """ Generate an ISDOC 6.0.2 XML for a given invoice. Returns (bytes, set(errors)). """ + self._validate_taxes(invoice.invoice_line_ids.tax_ids) + + # ISDOC mandates a ; generate and persist a stable one on first export. + if not invoice.isdoc_uuid: + invoice.isdoc_uuid = str(uuid.uuid4()).upper() + + vals = {'invoice': invoice.with_context(lang=invoice.partner_id.lang)} + document_node = self._get_invoice_node(vals) + + errors = [c for c in self._export_invoice_constraints(invoice, vals).values() if c] + + xml_content = dict_to_xml(document_node, nsmap={None: ISDOC_NS}, template=isdoc_invoice.Invoice) + # dict_to_xml emits bare tags (ISDOC uses no element prefixes); move the whole tree + # into the ISDOC namespace so it is genuinely namespaced (not just declared) and our + # {ns}-qualified post-processing matches. + self._apply_namespace(xml_content) + self._postprocess_bank_groups(xml_content) + + return etree.tostring(xml_content, xml_declaration=True, encoding='UTF-8'), set(errors) + + @staticmethod + def _apply_namespace(root): + for el in list(root.iter()): + el.tag = f'{{{ISDOC_NS}}}{etree.QName(el).localname}' + + def _export_invoice_isdocx(self, invoice, pdf=None): + """ Build an ``.isdocx`` archive: a ZIP holding the ISDOC XML, a manifest.xml + (per isdoc-manifest-6.0.2.xsd) pointing at it, and optionally the PDF rendering. """ + xml_content, errors = self._export_invoice(invoice) + isdoc_filename = self._export_invoice_filename(invoice) + + manifest = etree.Element(f'{{{ISDOC_MANIFEST_NS}}}manifest', nsmap={None: ISDOC_MANIFEST_NS}) + etree.SubElement(manifest, f'{{{ISDOC_MANIFEST_NS}}}maindocument', filename=isdoc_filename) + manifest_bytes = etree.tostring(manifest, xml_declaration=True, encoding='UTF-8') + + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as archive: + archive.writestr(isdoc_filename, xml_content) + archive.writestr('manifest.xml', manifest_bytes) + if pdf: + archive.writestr(f"{invoice.name.replace('/', '_')}.pdf", pdf) + return buffer.getvalue(), errors + + def _postprocess_bank_groups(self, root): + """ Ensure every transfer
carries the full ordered BankAccount group. + + dict_to_xml drops empty leaves, but ID/BankCode/Name/IBAN/BIC are min=1 in the schema + (and may legitimately be empty, e.g. IBAN/BIC). Insert any missing ones in order, right + after . + """ + for details in root.iter(f'{{{ISDOC_NS}}}Details'): + present = {etree.QName(child).localname: child for child in details} + if 'PaymentDueDate' not in present: # cash branch, no bank group + continue + anchor = present['PaymentDueDate'] + insert_at = list(details).index(anchor) + 1 + for tag in ISDOC_BANK_GROUP: + if tag in present: + insert_at = list(details).index(present[tag]) + 1 + continue + el = etree.SubElement(details, f'{{{ISDOC_NS}}}{tag}') + details.remove(el) + details.insert(insert_at, el) + insert_at += 1 + + # ------------------------------------------------------------------------- + # EXPORT: node builders + # ------------------------------------------------------------------------- + + def _get_invoice_node(self, vals): + invoice = vals['invoice'] + company = invoice.company_id + currency = invoice.currency_id + dp = currency.decimal_places + is_refund = invoice.move_type in ('out_refund', 'in_refund') + sign = -1 if is_refund else 1 + has_tax = bool(invoice.amount_tax) or any( + self._line_tax_percent(l) for l in invoice.invoice_line_ids + ) + vals['regular_lines'], vals['deposit_lines'] = self._isdoc_split_lines(invoice) + + node = { + 'version': ISDOC_VERSION, + 'DocumentType': self._text(self._get_document_type_code(invoice)), + 'ID': self._text(invoice.name), + 'UUID': self._text(invoice.isdoc_uuid), + 'IssuingSystem': self._text("Odoo"), + 'IssueDate': self._text(invoice.invoice_date and invoice.invoice_date.isoformat()), + 'VATApplicable': self._text('true' if has_tax else 'false'), + 'ElectronicPossibilityAgreementReference': { + '_text': company.partner_id.website or 'http://', + 'languageID': 'cs', + }, + 'LocalCurrencyCode': self._text(currency.name), + 'CurrRate': self._text('1'), + 'RefCurrRate': self._text('1'), + 'AccountingSupplierParty': {'Party': self._get_party_node(company.partner_id)}, + 'AccountingCustomerParty': {'Party': self._get_party_node(invoice.commercial_partner_id)}, + 'InvoiceLines': {'InvoiceLine': self._get_invoice_line_nodes(vals)}, + 'TaxTotal': self._get_tax_total_node(vals), + 'LegalMonetaryTotal': self._get_monetary_total_node(vals), + } + # TaxPointDate is the DUZP (datum uskutečnění zdanitelného plnění), + # which is NOT the issue date: a supply delivered on the 30th and + # invoiced on the 3rd belongs to the earlier VAT period, and the + # recipient books the deduction by this element. + tax_point = invoice.taxable_supply_date or invoice.invoice_date + if tax_point: + node['TaxPointDate'] = self._text(tax_point.isoformat()) + + # Credit note -> reference to the reversed invoice. + origin = invoice.reversed_entry_id + if is_refund and origin: + node['OriginalDocumentReferences'] = {'OriginalDocumentReference': { + 'ID': self._text(origin.name), + 'IssueDate': self._text(origin.invoice_date and origin.invoice_date.isoformat()), + 'UUID': self._text(origin.isdoc_uuid) if origin.isdoc_uuid else None, + }} + + # Already-invoiced advance payments deducted on this final invoice. + if vals['deposit_lines']: + nontaxed, taxed = self._get_deposits_nodes(vals) + if nontaxed: + node['NonTaxedDeposits'] = {'NonTaxedDeposit': nontaxed} + if taxed: + node['TaxedDeposits'] = {'TaxedDeposit': taxed} + + payment_means = self._get_payment_means_node(vals, sign, dp) + if payment_means: + node['PaymentMeans'] = payment_means + return node + + def _get_party_node(self, partner): + ico = partner.company_registry or '' + street, building = self._split_street(partner) + party = { + 'PartyIdentification': { + 'ID': self._text(ico or (partner.vat or '').replace(partner.country_id.code or '', '')), + }, + 'PartyName': {'Name': self._text(partner.name)}, + 'PostalAddress': { + 'StreetName': self._text(street), + 'BuildingNumber': self._text(building), + 'CityName': self._text(partner.city), + 'PostalZone': self._text(partner.zip), + 'Country': { + 'IdentificationCode': self._text(partner.country_id.code), + 'Name': self._text(partner.country_id.name), + }, + }, + } + if ico: + party['PartyIdentification']['CatalogFirmIdentification'] = self._text(ico) + if partner.vat: + party['PartyTaxScheme'] = { + 'CompanyID': self._text(partner.vat), + 'TaxScheme': self._text('VAT'), + } + contact = {} + if partner.phone: + contact['Telephone'] = self._text(partner.phone) + if partner.email: + contact['ElectronicMail'] = self._text(partner.email) + if contact: + party['Contact'] = contact + return party + + def _get_invoice_line_nodes(self, vals): + dp = vals['invoice'].currency_id.decimal_places + return [ + self._isdoc_line_node( + index, line.name, line.quantity or 0.0, line.price_subtotal, line.price_total, + self._line_tax_percent(line), line.product_uom_id.name, line.product_id, dp) + for index, line in enumerate(vals['regular_lines'], 1) + ] + + def _get_tax_total_node(self, vals): + invoice = vals['invoice'] + currency = invoice.currency_id + dp = currency.decimal_places + # Full amounts come from the regular lines; the deducted advances are "already claimed". + full = self._isdoc_group_by_rate(vals['regular_lines'], currency) + claimed = self._isdoc_group_by_rate(vals['deposit_lines'], currency, sign=-1) + zero = {'taxable': 0.0, 'inclusive': 0.0, 'tax': 0.0} + + subtotals = [] + total_tax = 0.0 + for percent in sorted(set(full) | set(claimed)): + f = full.get(percent, zero) + c = claimed.get(percent, zero) + total_tax += f['tax'] + subtotals.append({ + 'TaxableAmount': self._text(self.format_float(f['taxable'], dp)), + 'TaxAmount': self._text(self.format_float(f['tax'], dp)), + 'TaxInclusiveAmount': self._text(self.format_float(f['inclusive'], dp)), + 'AlreadyClaimedTaxableAmount': self._text(self.format_float(c['taxable'], dp)), + 'AlreadyClaimedTaxAmount': self._text(self.format_float(c['tax'], dp)), + 'AlreadyClaimedTaxInclusiveAmount': self._text(self.format_float(c['inclusive'], dp)), + 'DifferenceTaxableAmount': self._text(self.format_float(f['taxable'] - c['taxable'], dp)), + 'DifferenceTaxAmount': self._text(self.format_float(f['tax'] - c['tax'], dp)), + 'DifferenceTaxInclusiveAmount': self._text(self.format_float(f['inclusive'] - c['inclusive'], dp)), + 'TaxCategory': { + 'Percent': self._text(self.format_float(percent, 2)), + 'VATApplicable': self._text('true' if percent else 'false'), + }, + }) + return { + 'TaxSubTotal': subtotals, + 'TaxAmount': self._text(self.format_float(currency.round(total_tax), dp)), + } + + def _get_monetary_total_node(self, vals): + invoice = vals['invoice'] + currency = invoice.currency_id + dp = currency.decimal_places + # Full = regular lines; claimed = deducted advances; difference = net (= invoice totals). + full = self._isdoc_group_by_rate(vals['regular_lines'], currency) + claimed = self._isdoc_group_by_rate(vals['deposit_lines'], currency, sign=-1) + full_untaxed = currency.round(sum(g['taxable'] for g in full.values())) + full_total = currency.round(sum(g['inclusive'] for g in full.values())) + claimed_untaxed = currency.round(sum(g['taxable'] for g in claimed.values())) + claimed_total = currency.round(sum(g['inclusive'] for g in claimed.values())) + return { + 'TaxExclusiveAmount': self._text(self.format_float(full_untaxed, dp)), + 'TaxInclusiveAmount': self._text(self.format_float(full_total, dp)), + 'AlreadyClaimedTaxExclusiveAmount': self._text(self.format_float(claimed_untaxed, dp)), + 'AlreadyClaimedTaxInclusiveAmount': self._text(self.format_float(claimed_total, dp)), + 'DifferenceTaxExclusiveAmount': self._text(self.format_float(invoice.amount_untaxed, dp)), + 'DifferenceTaxInclusiveAmount': self._text(self.format_float(invoice.amount_total, dp)), + 'PaidDepositsAmount': self._text(self.format_float(claimed_total, dp)), + 'PayableAmount': self._text(self.format_float(invoice.amount_total, dp)), + } + + def _get_deposits_nodes(self, vals): + """ Build (NonTaxedDeposit list, TaxedDeposit list) from the deducted advance lines. """ + dp = vals['invoice'].currency_id.decimal_places + nontaxed, taxed = [], [] + for line in vals['deposit_lines']: + percent = self._line_tax_percent(line) + doc_id, var_symbol = self._isdoc_deposit_source(line) + net = -line.price_subtotal + inclusive = -line.price_total + if percent: + taxed.append({ + 'ID': self._text(doc_id), + 'VariableSymbol': self._text(var_symbol), + 'TaxableDepositAmount': self._text(self.format_float(net, dp)), + 'TaxInclusiveDepositAmount': self._text(self.format_float(inclusive, dp)), + 'ClassifiedTaxCategory': { + 'Percent': self._text(self.format_float(percent, 2)), + 'VATCalculationMethod': self._text('0'), + 'VATApplicable': self._text('true'), + }, + }) + else: + nontaxed.append({ + 'ID': self._text(doc_id), + 'VariableSymbol': self._text(var_symbol), + 'DepositAmount': self._text(self.format_float(net, dp)), + }) + return nontaxed, taxed + + def _isdoc_deposit_source(self, line): + """ (document_id, variable_symbol) of the advance invoice this deduction settles. """ + doc_id = line.name or line.move_id.name + var_symbol = '' + if 'sale_line_ids' in line._fields: + sources = line.sale_line_ids.invoice_lines.move_id.filtered(lambda m: m._is_downpayment()) + if sources: + doc_id = sources[0].name or doc_id + var_symbol = sources[0].variable_symbol or '' + if not var_symbol: + var_symbol = re.sub(r'\D', '', doc_id or '') or '0' + return doc_id, var_symbol + + def _get_payment_means_node(self, vals, sign, dp): + invoice = vals['invoice'] + bank = invoice.partner_bank_id + if not bank: + return None + details = {} + if invoice.invoice_date_due: + details['PaymentDueDate'] = self._text(invoice.invoice_date_due.isoformat()) + account_id, bank_code, iban = bank._cz_account_parts() # from l10n_cz_base + details['ID'] = self._text(account_id) + details['BankCode'] = self._text(bank_code) + details['Name'] = self._text(bank.bank_id.name or '') + details['IBAN'] = self._text(iban) + details['BIC'] = self._text(bank.bank_id.bic or '') + if invoice.variable_symbol: # from l10n_cz_base + details['VariableSymbol'] = self._text(invoice.variable_symbol) + if invoice.constant_symbol: + details['ConstantSymbol'] = self._text(invoice.constant_symbol) + if invoice.specific_symbol: + details['SpecificSymbol'] = self._text(invoice.specific_symbol) + return {'Payment': { + 'PaidAmount': self._text(self.format_float(sign * invoice.amount_total, dp)), + 'PaymentMeansCode': self._text('42'), + 'Details': details, + }} + + # ------------------------------------------------------------------------- + # EXPORT: constraints + # ------------------------------------------------------------------------- + + def _export_invoice_constraints(self, invoice, vals): + constraints = self._invoice_constraints_common(invoice) + supplier = invoice.company_id.partner_id + customer = invoice.commercial_partner_id + for role, partner in (('supplier', supplier), ('customer', customer)): + constraints[f'isdoc_{role}_name'] = self._check_required_fields(partner, 'name') + constraints[f'isdoc_{role}_address'] = self._check_required_fields(partner, ['street', 'city', 'zip']) + constraints[f'isdoc_{role}_country'] = self._check_required_fields(partner, 'country_id') + if not partner.company_registry and not partner.vat: + constraints[f'isdoc_{role}_id'] = _( + "%(partner)s must have an ICO (Company ID) or a VAT number for ISDOC.", + partner=partner.display_name, + ) + if invoice.currency_id != invoice.company_id.currency_id: + constraints['isdoc_foreign_currency'] = _( + "ISDOC export currently supports only invoices in the company currency (%(cur)s).", + cur=invoice.company_id.currency_id.name, + ) + return constraints + + # ------------------------------------------------------------------------- + # IMPORT + # ------------------------------------------------------------------------- + + @staticmethod + def _q(tag): + # Namespace-agnostic on import: ISDOC 1.x-5.x use the ".../invoice" namespace, + # 6.x uses ".../2013"; the element names are identical, so match any namespace. + return f'{{*}}{tag}' + + def _get_import_document_amount_sign(self, tree): + """ Map to (move_type, qty_factor). ISDOC amounts are always + positive (credit notes carry DocumentType 2/6), so qty_factor is 1. """ + doc_type = (tree.findtext(self._q('DocumentType')) or '').strip() + if doc_type in ('2', '6'): # credit note / credit note for advance + return 'refund', 1 + if doc_type: # 1/3/4/5/7 -> handled as invoice (advance/proforma refined later) + return 'invoice', 1 + return None, 1 + + def _import_fill_invoice(self, invoice, tree, qty_factor): + q = self._q + logs = [] + invoice_values = {} + + # ==== Partner ==== + # Vendor bill -> the supplier is our partner; customer invoice -> the customer. + party_tag = 'AccountingSupplierParty' if invoice.move_type in ('in_invoice', 'in_refund') \ + else 'AccountingCustomerParty' + party_node = tree.find(f'{q(party_tag)}/{q("Party")}') + partner_logs = [] + if party_node is not None: + partner, partner_logs = self._import_partner( + invoice.company_id, **self._import_isdoc_party_vals(party_node)) + invoice.partner_id = partner.id # set before lines/bank so they resolve correctly + + # ==== Currency, dates ==== + invoice_values['currency_id'], currency_logs = self._import_currency(tree, q('LocalCurrencyCode')) + if issue_date := tree.findtext(q('IssueDate')): + invoice_values['invoice_date'] = issue_date + # The supplier's DUZP. The deduction period (cssk_vat_deduction_date) + # is deliberately left alone: it is when WE claim, which the + # supplier's document cannot know. + if tax_point := tree.findtext(q('TaxPointDate')): + invoice_values['taxable_supply_date'] = tax_point + if due_date := tree.findtext(f'{q("PaymentMeans")}/{q("Payment")}/{q("Details")}/{q("PaymentDueDate")}'): + invoice_values['invoice_date_due'] = due_date + + # ==== ref, uuid, payment_reference, origin, narration ==== + if ref := tree.findtext(q('ID')): + invoice_values['ref'] = ref + if isdoc_uuid := tree.findtext(q('UUID')): + invoice_values['isdoc_uuid'] = isdoc_uuid + if var_symbol := tree.findtext( + f'{q("PaymentMeans")}/{q("Payment")}/{q("Details")}/{q("VariableSymbol")}'): + invoice_values['payment_reference'] = var_symbol + # VS/KS/SS onto the symbol fields (legacy aliases of the canonical + # l10n_cssk_* fields), digits-sanitised to satisfy their validation + for element, field_name, size in ( + ('VariableSymbol', 'variable_symbol', 10), + ('ConstantSymbol', 'constant_symbol', 4), + ('SpecificSymbol', 'specific_symbol', 10), + ): + raw = tree.findtext( + f'{q("PaymentMeans")}/{q("Payment")}/{q("Details")}/{q(element)}') + if raw and (digits := re.sub(r'\D', '', raw)[-size:]): + invoice_values[field_name] = digits + invoice_values['invoice_origin'] = ( + tree.findtext(f'{q("OrderReferences")}/{q("OrderReference")}/{q("ExternalOrderID")}') + or tree.findtext(f'{q("OriginalDocumentReferences")}/{q("OriginalDocumentReference")}/{q("ID")}') + or None + ) + invoice_values['narration'] = self._import_description(tree, xpaths=[q('Note')]) + + # ==== Lines (+ deducted advance payments + payable rounding) ==== + line_vals, line_logs = self._import_isdoc_lines(invoice, tree, qty_factor) + line_vals += self._import_isdoc_deposits(invoice, tree, qty_factor) + line_vals += self._import_isdoc_rounding(invoice, tree, qty_factor) + invoice_values['invoice_line_ids'] = [Command.create(vals) for vals in line_vals] + + invoice.write(invoice_values) + + # ==== Bank account (normalised to IBAN via l10n_cz_base) ==== + # Done after the write: writing currency_id re-fires _compute_partner_bank_id, which + # would otherwise clobber a manually set partner_bank_id (for refunds bank_partner_id + # flips to the company, resolving to empty). + self._import_isdoc_bank(invoice, tree) + + return logs + partner_logs + currency_logs + line_logs + + def _import_isdoc_party_vals(self, party_node): + q = self._q + street = (party_node.findtext(f'{q("PostalAddress")}/{q("StreetName")}') or '').strip() + building = (party_node.findtext(f'{q("PostalAddress")}/{q("BuildingNumber")}') or '').strip() + return { + 'name': party_node.findtext(f'{q("PartyName")}/{q("Name")}'), + 'vat': party_node.findtext(f'{q("PartyTaxScheme")}/{q("CompanyID")}'), + 'phone': party_node.findtext(f'{q("Contact")}/{q("Telephone")}'), + 'email': party_node.findtext(f'{q("Contact")}/{q("ElectronicMail")}'), + 'postal_address': { + 'country_code': party_node.findtext( + f'{q("PostalAddress")}/{q("Country")}/{q("IdentificationCode")}'), + 'street': (street + ' ' + building).strip(), + 'city': party_node.findtext(f'{q("PostalAddress")}/{q("CityName")}'), + 'zip': party_node.findtext(f'{q("PostalAddress")}/{q("PostalZone")}'), + }, + } + + def _import_isdoc_bank(self, invoice, tree): + """ Attach the document's bank account (the issuer's) to the partner and the move. + The account is normalised to IBAN via l10n_cz_base; ISDOC may carry an explicit + or only the legacy /. """ + q = self._q + if not invoice.partner_id: + return + details = tree.find(f'{q("PaymentMeans")}/{q("Payment")}/{q("Details")}') + if details is None: + return + iban = (details.findtext(q('IBAN')) or '').strip() + if not iban: + account_id = (details.findtext(q('ID')) or '').strip() + bank_code = (details.findtext(q('BankCode')) or '').strip() + if account_id and bank_code: + iban = self.env['res.partner.bank']._cz_legacy_to_iban(f'{account_id}/{bank_code}') or '' + if not iban: + return + try: + bank = self.env['res.partner.bank']._find_or_create_bank_account( + account_number=iban, partner=invoice.partner_id, company=invoice.company_id) + except UserError as e: + invoice._message_log(body=_("The bank account couldn't be imported: %s", e)) + return + if bank: + invoice.partner_bank_id = bank.id + + def _import_isdoc_lines(self, invoice, tree, qty_factor): + q = self._q + logs = [] + tax_type = invoice.journal_id.type + currency = invoice.currency_id or invoice.company_id.currency_id + vals_list = [] + for line in tree.findall(f'{q("InvoiceLines")}/{q("InvoiceLine")}'): + name = (line.findtext(f'{q("Item")}/{q("Description")}') or '').strip() + qty = float(line.findtext(q('InvoicedQuantity')) or '1') + subtotal = float(line.findtext(q('LineExtensionAmount')) or '0') + unit = float(line.findtext(q('UnitPrice')) or '0') + default_code = line.findtext(f'{q("Item")}/{q("SellersItemIdentification")}/{q("ID")}') + barcode = line.findtext(f'{q("Item")}/{q("CatalogueItemIdentification")}/{q("ID")}') + product = self.env['product.product']._retrieve_product( + name=name, default_code=default_code or '', barcode=barcode or '') + + quantity = qty + price_unit = unit or (subtotal / qty if qty else subtotal) + # Infer a discount if the line subtotal differs from price_unit * qty. + discount = 0.0 + gross = price_unit * qty + if qty and not float_is_zero(gross, precision_rounding=currency.rounding) \ + and currency.compare_amounts(gross, subtotal): + discount = 100.0 * (1 - subtotal / gross) + elif not qty and not currency.is_zero(subtotal): + # Zero-quantity adjustment/rounding line that still carries an amount: keep it. + quantity = 1.0 + price_unit = subtotal + + line_values = { + 'name': name, + 'price_unit': price_unit, + 'tax_nodes': line.findall(f'{q("ClassifiedTaxCategory")}/{q("Percent")}'), + } + tax_ids, tax_logs = self._retrieve_taxes(invoice, line_values, tax_type) + logs += tax_logs + + vals_list.append({ + 'name': name or (product.display_name or _("Line")), + 'product_id': product.id, + 'quantity': quantity * qty_factor, + 'price_unit': line_values['price_unit'], + 'discount': discount, + 'tax_ids': [Command.set(tax_ids)], + }) + return vals_list, logs + + def _import_isdoc_deposits(self, invoice, tree, qty_factor): + """ Deducted advance payments (TaxedDeposits/NonTaxedDeposits) become negative lines, + so the imported total matches the document's PayableAmount. """ + q = self._q + tax_type = invoice.journal_id.type + vals_list = [] + for deposit in tree.findall(f'{q("TaxedDeposits")}/{q("TaxedDeposit")}'): + dep_id = deposit.findtext(q('ID')) or '' + net = float(deposit.findtext(q('TaxableDepositAmount')) or '0') + line_values = { + 'name': dep_id, + 'price_unit': -net * qty_factor, + 'tax_nodes': deposit.findall(f'{q("ClassifiedTaxCategory")}/{q("Percent")}'), + } + tax_ids, _logs = self._retrieve_taxes(invoice, line_values, tax_type) + vals_list.append({ + 'name': _("Advance payment %s", dep_id) if dep_id else _("Advance payment"), + 'quantity': 1, + 'price_unit': line_values['price_unit'], + 'tax_ids': [Command.set(tax_ids)], + }) + for deposit in tree.findall(f'{q("NonTaxedDeposits")}/{q("NonTaxedDeposit")}'): + dep_id = deposit.findtext(q('ID')) or '' + net = float(deposit.findtext(q('DepositAmount')) or '0') + vals_list.append({ + 'name': _("Advance payment %s", dep_id) if dep_id else _("Advance payment"), + 'quantity': 1, + 'price_unit': -net * qty_factor, + 'tax_ids': [Command.set([])], + }) + return vals_list + + def _import_isdoc_rounding(self, invoice, tree, qty_factor): + """ Czech invoices are often rounded to whole crowns via ; + add it as a line so the imported total matches . """ + q = self._q + currency = invoice.currency_id or invoice.company_id.currency_id + rounding = float(tree.findtext(f'{q("LegalMonetaryTotal")}/{q("PayableRoundingAmount")}') or '0') + if currency.is_zero(rounding): + return [] + return [{ + 'name': _("Rounding"), + 'quantity': 1, + 'price_unit': rounding * qty_factor, + 'tax_ids': [Command.set([])], + }] diff --git a/account_edi_isdoc/models/account_move.py b/account_edi_isdoc/models/account_move.py new file mode 100644 index 00000000..ad4cd4d0 --- /dev/null +++ b/account_edi_isdoc/models/account_move.py @@ -0,0 +1,101 @@ +import io +import zipfile + +from lxml import etree + +from odoo import fields, models +from odoo.tools.mimetypes import guess_mimetype + +ISDOC_NS = "http://isdoc.cz/namespace/2013" +# ISDOC namespaces across versions: ".../invoice" (1.x-5.x) and ".../2013" (6.x). Imports are +# accepted from any of them — the element structure is stable — while exports stay 6.0.2. +ISDOC_NS_PREFIX = "http://isdoc.cz/namespace" + + +class AccountMove(models.Model): + _inherit = 'account.move' + + # ISDOC mandates a document . We persist the one we generate on export and + # the one we read on import so the value round-trips and stays stable across re-exports. + isdoc_uuid = fields.Char(string="ISDOC UUID", copy=False) + + def _get_import_file_type(self, file_data): + """ Identify ISDOC invoice files (flat root in any ISDOC namespace). """ + # EXTENDS account_edi_ubl_cii + if (tree := file_data['xml_tree']) is not None: + qname = etree.QName(tree) + if qname.localname == 'Invoice' and (qname.namespace or '').startswith(ISDOC_NS_PREFIX): + return 'account.edi.xml.isdoc' + return super()._get_import_file_type(file_data) + + def _get_edi_decoder(self, file_data, new=False): + """ Route ISDOC files to our builder. + + The base implementation only dispatches models that inherit + ``account.edi.xml.ubl_20`` or ``account.edi.xml.cii``. Our builder inherits + ``account.edi.common`` directly, so it must be wired here explicitly. + """ + # EXTENDS account_edi_ubl_cii + if file_data['import_file_type'] == 'account.edi.xml.isdoc': + return { + 'priority': 20, + 'decoder': self.env['account.edi.xml.isdoc']._import_invoice_ubl_cii, + } + return super()._get_edi_decoder(file_data, new) + + def _get_xml_tree(self, file_data): + """ Also parse bare ``.isdoc`` files: their mimetype is often not ``*/xml`` (e.g. + ``application/octet-stream`` when uploaded/emailed), so the base parser skips them. """ + tree = super()._get_xml_tree(file_data) + if tree is not None: + return tree + raw = file_data.get('raw') or b'' + name = (file_data.get('name') or '').lower() + looks_isdoc = name.endswith('.isdoc') or ( + raw[:200].lstrip().startswith(b' element and uses a single + # default namespace, so the UBL PDF-embedding postprocessing would corrupt it. + if invoice_data.get('ubl_cii_xml_options', {}).get('ubl_cii_format') == 'isdoc': + return False + return super()._needs_ubl_postprocessing(invoice_data) + + def _hook_invoice_document_after_pdf_report_render(self, invoice, invoice_data): + # EXTENDS account_edi_ubl_cii + # For ISDOC, embed the ISDOC XML into the invoice PDF (the "ISDOC.PDF" / PDF-A3 + # representation) instead of running the UBL postprocessing + Factur-X embedding. + if invoice_data.get('ubl_cii_xml_options', {}).get('ubl_cii_format') == 'isdoc': + attachment_values = invoice_data.get('ubl_cii_xml_attachment_values') + if tools.config['test_enable'] or not attachment_values: + return + pdf_values = ( + invoice.invoice_pdf_report_id + or invoice_data.get('pdf_attachment_values') + or invoice_data.get('proforma_pdf_attachment_values') + ) + if pdf_values: + pdf_values['raw'] = self._isdoc_embed_in_pdf( + pdf_values['raw'], attachment_values['raw'], attachment_values['name']) + return + super()._hook_invoice_document_after_pdf_report_render(invoice, invoice_data) + + def _isdoc_embed_in_pdf(self, pdf_bytes, isdoc_xml, filename): + """ Embed the ISDOC XML into the PDF as an attachment and convert to PDF/A-3. """ + reader = OdooPdfFileReader(io.BytesIO(pdf_bytes), strict=False) + writer = OdooPdfFileWriter() + writer.cloneReaderDocumentRoot(reader) + writer.addAttachment(filename, isdoc_xml, subtype='text/xml') + try: + if not writer.is_pdfa: + writer.convert_to_pdfa() + except Exception: # noqa: BLE001 — embedding already succeeded; PDF/A is best-effort + _logger.exception("ISDOC: PDF/A-3 conversion failed; the XML is still embedded") + output = io.BytesIO() + writer.write(output) + return output.getvalue() diff --git a/account_edi_isdoc/models/res_partner.py b/account_edi_isdoc/models/res_partner.py new file mode 100644 index 00000000..491ce103 --- /dev/null +++ b/account_edi_isdoc/models/res_partner.py @@ -0,0 +1,25 @@ +from odoo import api, fields, models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + invoice_edi_format = fields.Selection( + selection_add=[ + ('isdoc', "Czech Republic (ISDOC)"), + ], + ) + + @api.model + def _get_ubl_cii_formats_info(self): + # EXTENDS account_edi_ubl_cii — offer ISDOC for Czech companies. + return { + **super()._get_ubl_cii_formats_info(), + 'isdoc': {'countries': ['CZ'], 'on_peppol': False, 'sequence': 200}, + } + + @api.model + def _get_edi_builder(self, invoice_edi_format): + if invoice_edi_format == 'isdoc': + return self.env['account.edi.xml.isdoc'] + return super()._get_edi_builder(invoice_edi_format) diff --git a/account_edi_isdoc/static/description/icon.png b/account_edi_isdoc/static/description/icon.png new file mode 100644 index 00000000..1ca2c7d5 Binary files /dev/null and b/account_edi_isdoc/static/description/icon.png differ diff --git a/account_edi_isdoc/tests/__init__.py b/account_edi_isdoc/tests/__init__.py new file mode 100644 index 00000000..3caea2f6 --- /dev/null +++ b/account_edi_isdoc/tests/__init__.py @@ -0,0 +1,4 @@ +from . import test_isdoc_export +from . import test_isdoc_import +from . import test_isdoc_containers +from . import test_isdoc_advance diff --git a/account_edi_isdoc/tests/schema/isdoc-invoice-6.0.2.xsd b/account_edi_isdoc/tests/schema/isdoc-invoice-6.0.2.xsd new file mode 100644 index 00000000..a84c7260 --- /dev/null +++ b/account_edi_isdoc/tests/schema/isdoc-invoice-6.0.2.xsd @@ -0,0 +1,2780 @@ + + + + + + + Digitální podpis dokladu ve formátu Enveloped XML Signature (http://www.w3.org/TR/xmldsig-core/) + Document digital signature in the Enveloped XML Signature format (http://www.w3.org/TR/xmldsig-core/) + + + + + + + + + Číslo verze ISDOC + ISDOC version number + + + + + + + + + Typ dokumentu + Document type + + + + + Faktura - daňový doklad + invoice + + + + + Opravný daňový doklad (dobropis) + credit note + + + + + Opravný daňový doklad (vrubopis) + debit note + + + + + Zálohová faktura (nedaňový zálohový list) + proforma invoice (no VAT) + + + + + Daňový doklad při přijetí platby (daňový zálohový list) + advance invoice (with VAT) + + + + + Opravný daňový doklad při přijetí platby (dobropis DZL) + credit note for advance invoice (with VAT) + + + + + Zjednodušený daňový doklad + simplified tax document + + + + + + + + Podrobnější typ dokumentu. Číselník udržuje a zveřejňuje subjekt identifikovaný v SubDocumentTypeOrigin. + Document subtype. Codelist is maintained and published by subject specified in SubDocumentTypeOrigin. + + + + + + + Správce číselníku podrobnějšího typu dokumentu. + Maintainer of subdocument type code list. + + + + + + + Identifikace cílového konsolidátora. Číselník je rozšířený číselník kódů bank a udržuje a zveřejňuje ho ČBA, používá se převážně v B2C systémech velkých výstavců faktur. + Identification of target consolidator. Values are extended list of bank codes maintained and published by ČBA. + + + + + + + Identifikace klienta v systému výstavce, používá se převážně v B2C systémech velkých výstavců faktur. + Client identification in the issuer system. Used mainly in B2C systems of companies issuing large volume of invoices. + + + + + + + Kompletní číslo bankovního účtu příjemce faktury včetně kódu banky. Používá se převážně v B2C systémech velkých výstavců faktur. + Complete bank account number of invoice receiver. Used mainly in B2C systems of companies issuing large volume of invoices. + + + + + + + Unikátní identifikátor + Unique identifier + + + + + + + Unikátní identifikátor s maximální délkou 36 znaků + Unique identifier limited to 36 characters + + + + + + + + + Unikátní identifikátor GUID + Unique GUID identifier + + + + + + + + + Identifikace schématu použitého při tvorbě identifikátoru + Identification of schema used for identifier construction + + + + + + + Identifikace systému, který odesílá/generuje fakturu + Identification of issuing system + + + + + + + + + Datum vystavení + Issue date + + + + + + + Datum zdanitelného plnění + Tax point date + + + + + + + Je předmětem DPH + VAT is applicable + + + + + + + Poznámka + Note + + + + + + Identifikátor použitého jazyka (např. "cs" pro češtinu) + Language identifier (e.g. "en" for English) + + + + + + + + + Hlavičková kolekce objednávek pro případnou vazbu + Header collection of referenced purchase order(s) + + + + + Odkaz na související objednávku + Information about referenced purchase order + + + + + + + + Odkaz na související objednávku + Information about referenced purchase order + + + + + Vlastní identifikátor objednávky přijaté u dodavatele + Private identifier of purchase order received at supplier + + + + + Externí číslo objednávky přijaté, typicky objednávka vydaná u odběratele + External number of accepted purchase order, usually purchase order issued at purchaser + + + + + Datum vystavení objednávky přijaté u dodavatele + Issue date of purchase order received at supplier + + + + + Datum vystavení objednávky vydané (u objednávající strany) + Issue date of purchase order + + + + + Unikátní identifikátor GUID + Unique GUID identifier + + + + + ID datové zprávy v ISDS, které vrátilo rozhraní ISDS při prvotním poslání této objednávky + Message ID inside ISDS system + + + + + Spisová značka, pod kterým byla vypravena písemnost Objednávka ve spisové službě objednatele + File number + + + + + Číslo jednací, pod kterým byla vypravena písemnost Objednávka ve spisové službě objednatele + Reference number + + + + + + + + + Odkaz na řádku související objednávky + Reference to line on a related purchase order + + + + + Číslo řádky na dokladu + Line number + + + + + + + + + Vlastní identifikátor objednávky přijaté u dodavatele + Private identifier of purchase order received at supplier + + + + + + + Číslo řádky na dokladu + Line number + + + + + + + Identifikace odstavce smlouvy + Identifier of paragraph in an agreement + + + + + + + Hlavičková kolekce dodacích listů pro případnou vazbu + Header collection of referenced delivery notes + + + + + Odkaz na související dodací list + Information about referenced delivery note + + + + + + + + Odkaz na související dodací list + Information about referenced delivery note + + + + + Vlastní identifikátor dodacího listu u dodavatele + Private identifier of delivery note at supplier + + + + + Datum vystavení + Issue date + + + + + Unikátní identifikátor GUID + Unique GUID identifier + + + + + + + + + Odkaz na související dodací list + Information about referenced line on delivery note + + + + + Číslo řádky na dokladu + Line number + + + + + + + + + Související smlouvy + Related contracts + + + + + Informace o smlouvě související s dokladem + Information about related contract + + + + + + + + Informace o smlouvě související s dokladem + Information about related contract + + + + + Identifikace smlouvy lidsky čitelným označením + Human readable contract identifier + + + + + Identifikace smlouvy jejím unikátním id, které vznikne v okamžiku jejího zaevidování v systému OVM vystavitele + Contract identifier supplied at the time of contract registration inside file system + + + + + Datum podpisu smlouvy stranou OVM + Date of contract signature + + + + + + Datum konce platnosti smlouvy + Date until contract is valid + + + + + Smlouva je uzavřena na dobu neurčitou + Contract for indefinite period + + + + + + Jednoznačný identifikátor Datové zprávy v systému ISDS, pokud byl dokument odeslán touto cestou + Unique identifier inside ISDS system + + + + + Spisová značka, pod kterou byl vypraven tento doklad od emitenta, pokud emitent používá na tuto agendu spisovou službu + File number + + + + + Číslo jednací, pod kterým byl vypraven tento doklad od emitenta, pokud emitent používá na tuto agendu spisovou službu + Reference number + + + + + + + + + Odkaz na související smlouvu + Reference to a related contract + + + + + Identifikace odstavce smlouvy + Identifier of paragraph in an agreement + + + + + + + + + Datum konce platnosti smlouvy + Date until contract is valid + + + + + + + Smlouva je uzavřena na dobu neurčitou + Contract for indefinite period + + + + + + Kurz cizí měny, pokud je použita, jinak 1 + Foreign currency exchange rate (if foreign currency is used), otherwise 1 + + + + + + + Vztažný kurz cizí měny, většinou 1 + Reference foreign currency exchange rate, usually 1 + + + + + + + Hlavičková kolekce odkazů na původní doklady + Header collection of referenced original documents + + + + + Odkaz na původní doklad, který tento aktuální doklad opravuje (jen pro typy dokumentů 2, 3 a 6) + Reference to an original document which is being corrected by this document (only for document types 2, 3 and 6) + + + + + + + + Odkaz na původní doklad, který tento aktuální doklad opravuje (jen pro typy dokumentů 2, 3 a 6) + Reference to an original document which is being corrected by this document (only for document types 2, 3 and 6) + + + + + Lidsky čitelné číslo původního dokladu + Human readable number of original document + + + + + Datum vystavení původního dokladu + Issue date of original document + + + + + Unikátní identifikátor GUID + Unique GUID identifier + + + + + + + + + Odkaz na původní doklad, který tento aktuální doklad opravuje (jen pro typy dokumentů 2, 3 a 6) + Line reference to an original document which is being corrected by this document (only for document types 2, 3 and 6) + + + + + Číslo řádky na dokladu + Line number + + + + + + + + + Kolekce příloh dokladu. Jedna z nich může být náhled, je označena pomocí preview="true" + Collection of document attachments. Exactly one attachment can be document preview marked by preview="true" + + + + + Příloha dokladu + Document attachment + + + + + + + + Příloha dokladu + Document attachment + + + + + Jméno a cesta k souboru + File name and path + + + + + Identifikace metody použité při výpočtu otisku přílohy + Attachment digest method identification + + + + + Hodnota otisku přílohy + Attachment digest value + + + + + + Je daná příloha náhledem dokladu + Is this attachment document preview + + + + + + + Jméno a cesta k souboru + File name and path + + + + + + + Identifikace metody použité při výpočtu otisku přílohy + Attachment digest method identification + + + + Identifikátory jednotlivých algoritmů jsou definovány v http://www.w3.org/TR/xmldsig-core/#sec-AlgID + Algorithm identifiers are defined in http://www.w3.org/TR/xmldsig-core/#sec-AlgID + + + + + + + Hodnota otisku přílohy + Attachment digest value + + + + + + + Typ boolean, dovolené hodnoty jsou jen "true" a "false", tj. není podporováno "0" a "1" jako u zabudovaného typu xs:boolean + Boolean datatype. Permitted values are only "true" and "false". Values "0" and "1" which are in a value space of built-in xs:boolean type are not supported + + + + + + + + + Jakákoliv struktura uživatelských elementů. Elementy musí používat vlastní jmenný prostor. + Arbitrary fragment of user-defined elements. Elements must use their own namespace. + + + + + + + + + Dodavatel, účetní jednotka + Supplier, accounting entity in Commercial Register + + + + + Identifikace subjektu + Information about an organization, sub-organization, or individual fulfilling a role in a business process. + + + + + + + + Identifikace subjektu + Information about an organization, sub-organization, or individual fulfilling a role in a business process. + + + + + Element identifikačních položek subjektu (firmy) + Information about a party's identification. + + + + + Název subjektu + Information about a party's name. + + + + + Poštovní adresa + Postal address + + + + + Daňové údaje. Element je možné použít vícekrát a určit více identifikátorů, např. DIČ a IČ DPH na Slovensku. + Information about a party's tax scheme + + + + + Identifikace zápisu v rejstříku + Commercial Register record identification (in the Czech Republic) + + + + + Kontaktní osoba + Information about a contactable person or organization department + + + + + + + + Element identifikačních položek subjektu (firmy) + Information about a party's identification. + + + + + Uživatelské číslo firmy/provozovny + User defined company/workplace number + + + + + Mezinárodní číslo firmy/provozovny, např. EAN + International company/workplace number, e.g. EAN + + + + + IČ + Company identification number + + + + + + + + Uživatelské číslo firmy/provozovny + User defined company/workplace number + + + + + + + Mezinárodní číslo firmy/provozovny, např. EAN + International company/workplace number, e.g. EAN + + + + + + + Název subjektu + Information about a party's name. + + + + + Název + A character string that constitutes the distinctive designation of a person, place, thing or concept + + + + + + + + Název + A character string that constitutes the distinctive designation of a person, place, thing or concept + + + + + + + Poštovní adresa + Postal address + + + + + Ulice + Street + + + + + Číslo popisné/orientační + Building number + + + + + Město + City + + + + + PSČ + ZIP/postal zone + + + + + Země + Country + + + + + + + + Ulice + Street + + + + + + + Číslo popisné/orientační + Building number + + + + + + + Město + City + + + + + + + PSČ + ZIP/postal zone + + + + + + + Země + Country + + + + + Kód země podle ISO 3166 + ISO 3166 country code + + + + + Název země + Country name + + + + + + + + Kód země podle ISO 3166 + ISO 3166 country code + + + + + + + Daňové údaje. Element je možné použít vícekrát a určit více identifikátorů, např. DIČ a IČ DPH na Slovensku. + Information about a party's tax scheme + + + + + DIČ + VAT number + + + + + Daňové schéma. Nejpoužívanější schémata jsou: VAT (daň z přidané hodnoty, používá se v ČR pro DIČ a na Slovensku pro IČ DPH) a TIN (používá se na Slovensku pro DIČ). + Information about a tax scheme. The most common values are VAT (Value Added Tax) and TIN (Tax Identification Number). + + + + + + + + DIČ + VAT number + + + + + + + Daňové schéma. Nejpoužívanější schémata jsou: VAT (daň z přidané hodnoty, používá se v ČR pro DIČ a na Slovensku pro IČ DPH) a TIN (používá se na Slovensku pro DIČ). + Information about a tax scheme. The most common values are VAT (Value Added Tax) and TIN (Tax Identification Number). + + + + + + + Identifikace zápisu v rejstříku + Commercial Register record identification (in the Czech Republic) + + + + + + Správce rejstříku + Commercial Register administrator + + + + + Číslo registrace + Commercial Register number + + + + + Datum registrace + Registration date + + + + + + Předformátovaná informace o zápisu do rejstříku + Preformatted information about registration in the Commerical Register + + + + + + + + Správce rejstříku + Commercial Register administrator + + + + + + + Číslo registrace + Commercial Register number + + + + + + + Datum registrace + Registration date + + + + + + + Předformátovaná informace o zápisu do rejstříku + Preformatted information about registration in the Commerical Register + + + + + + + Kontaktní osoba + Information about a contactable person or organization department + + + + + Jméno kontaktu + Contact name + + + + + Telefonní číslo + Phone number + + + + + E-mailová adresa + E-mail address + + + + + + + + Telefonní číslo + Phone number + + + + + + + E-mailová adresa + E-mail address + + + + + + + Dodavatel, fakturační adresa + Supplier, invoicing address + + + + + Identifikace subjektu + Information about an organization, sub-organization, or individual fulfilling a role in a business process. + + + + + + + + Příjemce, účetní jednotka + Customer, accounting entity in Commercial Register + + + + + Identifikace subjektu + Information about an organization, sub-organization, or individual fulfilling a role in a business process. + + + + + + + + Anonymní příjemce zjednodušeného daňového dokladu + Anonymous receiver of simplified tax document + + + + + Unikátní identifikátor + Unique identifier + + + + + Identifikace schématu použitého při tvorbě identifikátoru + Identification of schema used for identifier construction + + + + + + + + Odběratel, fakturační adresa + Purchaser, invoicing address + + + + + Identifikace subjektu + Information about an organization, sub-organization, or individual fulfilling a role in a business process. + + + + + + + + Místo určení, adresa dodání + Information about delivery + + + + + Identifikace subjektu + Information about an organization, sub-organization, or individual fulfilling a role in a business process. + + + + + + + + Kolekce jednotlivých řádků faktury + Invoice lines collection + + + + + Řádek faktury + Information about an invoice line + + + + + + + + Řádek faktury + Information about an invoice line + + + + + Unikátní alfanumerický identifikátor řádku dokladu + Unique alphanumeric line identifier + + + + + Odkaz na řádku související objednávky + Reference to line on a related purchase order + + + + + Odkaz na související dodací list + Information about referenced line on delivery note + + + + + Odkaz na původní doklad, který tento aktuální doklad opravuje (jen pro typy dokumentů 2, 3 a 6) + Line reference to an original document which is being corrected by this document (only for document types 2, 3 and 6) + + + + + Odkaz na související smlouvu + Reference to a related contract + + + + + Klasifikátor egovernmentu pro výkonové účetnictví státu nebo agendové rozlišení + Egoverment accounting classifier + + + + + Účtované množství + Invoiced quantity + + + + + Celková cena bez daně na řádku v cizí měně + Total line amount without tax in a foreign currency + + + + + Celková cena bez daně na řádku v tuzemské měně + Total line amount without tax in a local currency + + + + + Celková cena bez daně na řádku v tuzemské měně před slevou + Total line amount without tax in a local currency without discount + + + + + Celková cena včetně daně na řádku v cizí měně + Total line amount including tax in a foreign currency + + + + + Celková cena včetně daně na řádku v tuzemské měně + Total line amount including tax in a local currency + + + + + Celková cena včetně daně na řádku v tuzemské měně před slevou + Total line amount including tax in a local currency without discount + + + + + Částka daně na řádku v tuzemské měně + Line tax amount in a local currency + + + + + Jednotková cena bez daně na řádku v tuzemské měně + Unit price without tax in a local currency + + + + + Jednotková cena s daní na řádku v tuzemské měně + Unit price including tax in a local currency + + + + + Složená položka DPH + Compound VAT field + + + + + Poznámka + Note + + + + + Citace paragrafu zákona, podle kterého je řádka osvobozena od DPH + Legislation citation which defines VAT exception for this line + + + + + Informace o položce faktury + Information directly relating to an item + + + + + Jakákoliv struktura uživatelských elementů. Elementy musí používat vlastní jmenný prostor. + Arbitrary fragment of user-defined elements. Elements must use their own namespace. + + + + + + + + Množství + Amount + + + + + + Jednotka + Unit + + + + + + + + + Kód měny + Currency code + + + + + + + + + Částka + Amount + + + + + + + Složená položka DPH + Compound VAT field + + + + + Procentní sazba DPH + Percentage VAT rate + + + + + Způsob výpočtu DPH + VAT calculation method (there are two types in the Czech Republic) + + + + + Je předmětem DPH + VAT is applicable + + + + + Lokální režim přenesení daňové povinnosti + Local reverse charge mode + + + + + + + + Procenta + Percentage + + + + + + + Způsob výpočtu DPH + VAT calculation method (there are two types in the Czech Republic) + + + + + Zdola + From the bottom + + + + + Shora + From the top + + + + + + + + Lokální režim přenesení daňové povinnosti + Local reverse charge mode + + + + + Kód předmětu plnění DPH pro lokální režim přenesení daňové povinnosti + VAT subject code for local reverse charge mode + + + + + Množství + Amount + + + + + + + + Kód předmětu plnění DPH pro lokální režim přenesení daňové povinnosti + VAT subject code for local reverse charge mode + + + + + + + dodání zlata + delivery of gold + + + + + obchodování s povolenkami na emise skleníkových plynů + trade with emission allowances + + + + + poskytnutí stavebních nebo montážních prací + delivery of developer or assembly work + + + + + odpady (viz příloha 5 zákona o DPH) + waste (see appendix 5 of VAT bill) + + + + + + + + + + + + + Informace o položce faktury + Information directly relating to an item + + + + + Popis položky + Item description + + + + + Kód EAN + EAN code + + + + + Identifikace zboží dle prodejce + Seller's item identification + + + + + Sekundární identifikace zboží dle prodejce + Secondary seller's item identification + + + + + Terciální identifikace zboží dle prodejce + Tertiary seller's item identification + + + + + Identifikace zboží dle kupujícího + Buyer's item identification + + + + + Kolekce šarží nebo sériových čísel + Batch or serial number collection + + + + + + + + Popis položky + Item description + + + + + + + Kód EAN + EAN code + + + + + Unikátní identifikátor + Unique identifier + + + + + + + + Identifikace zboží dle prodejce + Seller's item identification + + + + + Unikátní identifikátor + Unique identifier + + + + + + + + Sekundární identifikace zboží dle prodejce + Secondary seller's item identification + + + + + Unikátní identifikátor + Unique identifier + + + + + + + + Terciální identifikace zboží dle prodejce + Tertiary seller's item identification + + + + + Unikátní identifikátor + Unique identifier + + + + + + + + Identifikace zboží dle kupujícího + Buyer's item identification + + + + + Unikátní identifikátor + Unique identifier + + + + + + + + Kolekce šarží nebo sériových čísel + Batch or serial number collection + + + + + Šarže/sériové číslo + Batch/serial number + + + + + + + + Šarže/sériové číslo + Batch/serial number + + + + + Název šarže/Sériové číslo + Batch name/serial number + + + + + Poznámka + Note + + + + + Datum expirace + Expiration date + + + + + Specifikace + Specification + + + + + Množství + Amount + + + + + Rozlišení mezi šarží a sériovým číslem + Differentiation between batch and serial number + + + + + Externí číslo/série kolku, pro případ obchodu s chráněnými komoditami + External number of duty stamp + + + + + + + + Datum expirace + Expiration date + + + + + + + Specifikace + Specification + + + + + + + Rozlišení mezi šarží a sériovým číslem + Differentiation between batch and serial number + + + + + Šarže + Batch + + + + + Sériové číslo + Serial number + + + + + + + + Externí číslo/série kolku, pro případ obchodu s chráněnými komoditami + External number of duty stamp + + + + + + + Kolekce nedaňových zálohových listů + Collection of proforma invoices (without VAT) + + + + + Informace o konkrétním zaplaceném nedaňovém zálohovém listu + Information about a particular paid proforma invoice + + + + + + + + Informace o konkrétním zaplaceném nedaňovém zálohovém listu + Information about a particular paid proforma invoice + + + + + Jméno dokladu, identifikace zálohového listu u vystavitele + Document name, issuer identification of proforma invoice + + + + + Variabilní symbol, pod kterým byl zálohový list zaplacen + Variable symbol, used when proforma invoice was paid, typically number of the proforma invoice + + + + + Kladná částka zálohy v cizí měně + Deposit in a foreign currency + + + + + Kladná částka zálohy v tuzemské měně + Deposit in a local currency + + + + + + + + Variabilní symbol + Variable symbol (distinctive symbol of payment, typically number of invoice). Used for payment inside of the Czech Republic. + + + + + + + Kolekce odúčtování zdaněných zálohových listů + Collection of taxed deposits (advance invoices with VAT) + + + + + Informace o konkrétní částce v sazbě na odúčtovaném daňovém zálohovém listu + Information about amount and rate on taxed deposit (advance invoice) + + + + + + + + Informace o konkrétní částce v sazbě na odúčtovaném daňovém zálohovém listu + Information about amount and rate on taxed deposit (advance invoice) + + + + + Jméno dokladu, identifikace daňového zálohového listu u vystavitele + Document name, issuer identification of taxed advance invoice + + + + + Variabilní symbol + Variable symbol (distinctive symbol of payment, typically number of invoice). Used for payment inside of the Czech Republic. + + + + + Kladná část zálohy bez daně v cizí měně + Deposit amount without tax in a foreign currency + + + + + Kladná část zálohy bez daně v tuzemské měně + Deposit amount without tax in a local currency + + + + + Kladná část zálohy s daní v cizí měně + Deposit amount including tax in a foreign currency + + + + + Kladná část zálohy s daní v tuzemské měně + Deposit amount including tax in a local currency + + + + + Složená položka DPH + Compound VAT field + + + + + + + + Daňová rekapitulace + Information about a total amount of a particular type of tax + + + + + Sumář jedné daňové sazby + Subtotals for one tax rate + + + + + Částka + Amount + + + + + Částka + Amount + + + + + + + + Sumář jedné daňové sazby + Subtotals for one tax rate + + + + + Základ v sazbě v cizí měně + Tax base for rate in a foreign currency + + + + + Základ v sazbě v tuzemské měně + Tax base for rate in a local currency + + + + + Daň v sazbě v cizí měně + Tax for rate in a foreign currency + + + + + Daň v sazbě v tuzemské měně + Tax for rate in a local currency + + + + + Částka s daní v sazbě v cizí měně + Amount including tax for rate in a foreign currency + + + + + Částka s daní v sazbě v tuzemské měně + Amount including tax for rate in a local currency + + + + + Na záloze již uplatněno, základ v sazbě v cizí měně + Already claimed amount for rate in a foreign currency + + + + + Na záloze již uplatněno, základ v sazbě v tuzemské měně + Already claimed amount for rate in a local currency + + + + + Na záloze již uplatněno, daň v sazbě v cizí měně + Already claimed tax for rate in a foreign currency + + + + + Na záloze již uplatněno, daň v sazbě v tuzemské měně + Already claimed tax for rate in a local currency + + + + + Na záloze již uplatněno, s daní v sazbě v cizí měně + Already claimed amount including tax for rate in a foreign currency + + + + + Na záloze již uplatněno, s daní v sazbě v tuzemské měně + Already claimed amount including tax for rate in a local currency + + + + + Rozdíl, základ v cizí měně + Difference in the amount in a foreign currency + + + + + Rozdíl, základ v tuzemské měně + Difference in the amount in a local currency + + + + + Rozdíl, daň v cizí měně + Difference in the tax in a foreign currency + + + + + Rozdíl, daň v tuzemské měně + Difference in the tax in a local currency + + + + + Rozdíl, s daní v cizí měně + Difference including tax in a foreign currency + + + + + Rozdíl, s daní v tuzemské měně + Difference including tax in a local currency + + + + + Daňová sazba + Information about a tax rate + + + + + + + + Daňová sazba + Information about a tax rate + + + + + Procentní sazba daně + Tax rate expressed as a percentage + + + + + Daňové schéma. Nejpoužívanější schémata jsou: VAT (daň z přidané hodnoty, používá se v ČR pro DIČ a na Slovensku pro IČ DPH) a TIN (používá se na Slovensku pro DIČ). + Information about a tax scheme. The most common values are VAT (Value Added Tax) and TIN (Tax Identification Number). + + + + + Je předmětem DPH + VAT is applicable + + + + + Je daňová sazba v režimu přenesení daňové povinnosti? + Is tax rate included in a local reverse charge mode? + + + + + + + + Kolekce celkových částek na dokladu končící položkou částka k zaplacení + Collection of total amounts on document ending with payable amount + + + + + Celková částka bez daně v místní měně + Total amount without tax in a local currency + + + + + Celková částka bez daně v cizí měně + Total amount without tax in a foreign currency + + + + + Celková částka s daní daně v místní měně + Total amount including tax in a local currency + + + + + Celková částka s daní daně v cizí měně + Total amount including tax in a foreign currency + + + + + Celková částka bez daně v místní měně ze všech uplatněných daňových zálohových listů + Total amount of all already claimed advance invoices without tax in a local currency + + + + + Celková částka bez daně v cizí měně ze všech uplatněných daňových zálohových listů + Total amount of all already claimed advance invoices without tax in a foreign currency + + + + + Celková částka s daní v místní měně ze všech uplatněných daňových zálohových listů + Total amount of all already claimed advance invoices including tax in a local currency + + + + + Celková částka s daní v cizí měně ze všech uplatněných daňových zálohových listů + Total amount of all already claimed advance invoices including tax in a foreign currency + + + + + Rozdíl mezi předpisem a "na záloze již uplatněno", bez daně v místní měně + Difference between precept and already claimed amount without tax in a local currency + + + + + Rozdíl mezi předpisem a "na záloze již uplatněno", bez daně v cizí měně + Difference between precept and already claimed amount without tax in a foreign currency + + + + + Rozdíl mezi předpisem a "na záloze již uplatněno", s daní v místní měně + Difference between precept and already claimed amount including tax in a local currency + + + + + Rozdíl mezi předpisem a "na záloze již uplatněno", s daní v cizí měně + Difference between precept and already claimed amount including tax in a foreign currency + + + + + Zaokrouhlení celé částky v tuzemské měně + Rounding of the total amount in a local currency + + + + + Zaokrouhlení celé částky v cizí měně + Rounding of the total amount in a foreign currency + + + + + Na nedaňové záloze zaplaceno v tuzemské měně + Paid non-taxable deposit in a local currency + + + + + Na nedaňové záloze zaplaceno v cizí měně + Paid non-taxable deposit in a foreign currency + + + + + K zaplacení v tuzemské měně + Payable amount in a local currency + + + + + K zaplacení v cizí měně + Payable amount in a foreign currency + + + + + + + + Kolekce plateb + Information about payment means + + + + + Platba + Information directly relating to a specific payment + + + + + Kolekce dalších bankovních účtů, na které je možno také platit + Collection of alternative bank accounts + + + + + + + + Platba + Information directly relating to a specific payment + + + + + Částka k zaplacení + Amount to be paid + + + + + Způsob platby + The set of valid means of paying the debt incurred + + + + + Podrobnosti o platbě + Payment details + + + + + + Je povolena částečná platba + Flag indicating that partial payment is permitted + + + + + + + Způsob platby + The set of valid means of paying the debt incurred + + + + + Platba v hotovosti + Cash payment + + + + + Platba šekem + Cheque payment + + + + + Uskutečněná kreditní transakce + Credit transfer + + + + + Převod na účet + Money transfer to an account + + + + + Platba kartou + Card payment + + + + + Inkaso + Direct debit + + + + + Platba dobírkou + Payment by postgiro + + + + + Zaúčtování mezi partnery + Composition between partners + + + + + + + + Podrobnosti o platbě + Payment details + + + + + + Platba v hotovosti + Cash payment + + + + Identifikátor svázaného dokladu, například pokladní účtenky + Identifier of paired document, for example of bill + + + + + Datum vystavení + Issue date + + + + + + Platba převodem + Money transfer + + + + Datum splatnosti + Due date + + + + + + Variabilní symbol + Variable symbol (distinctive symbol of payment, typically number of invoice). Used for payment inside of the Czech Republic. + + + + + Konstantní symbol nebo platební titul + Constant symbol (used for payment inside of the Czech Republic) + + + + + Specifický symbol + Specific symbol (used for payment inside of the Czech Republic) + + + + + + + + + + Identifikátor svázaného dokladu, například pokladní účtenky + Identifier of paired document, for example of bill + + + + + + + Datum splatnosti + Due date + + + + + + + Kód lokální banky + Bank code + + + + + + + Mezinárodní číslo účtu (IBAN) + International bank account number (IBAN) + + + + + + + Kód banky podle ISO 9362, tzv. SWIFT kód + Bank identifier code as defined in ISO 9362 + + + + + + + Konstantní symbol nebo platební titul + Constant symbol (used for payment inside of the Czech Republic) + + + + + + + Specifický symbol + Specific symbol (used for payment inside of the Czech Republic) + + + + + + + Kolekce dalších bankovních účtů, na které je možno také platit + Collection of alternative bank accounts + + + + + Informace o bankovním účtu + Information about a bank account + + + + + + + + Informace o bankovním účtu + Information about a bank account + + + + + + + Informace o bankovním účtu + Information about a bank account + + + + + Číslo účtu lokální banky + Account number + + + + + Kód lokální banky + Bank code + + + + + Název + A character string that constitutes the distinctive designation of a person, place, thing or concept + + + + + Mezinárodní číslo účtu (IBAN) + International bank account number (IBAN) + + + + + Kód banky podle ISO 9362, tzv. SWIFT kód + Bank identifier code as defined in ISO 9362 + + + + + + + + + Odkaz na elementem s daným Id + Reference to element with a particular Id + + + + + + + + Identifikátor elementu pro tvorbu odkazů + Element identifier used as a target for linking + + + + + + + Příznak režimu "kontrolovaného státem" + Flag for state governed documents + + + + + + + Spisová značka, pod kterou byl vypraven tento doklad od emitenta, pokud emitent používá na tuto agendu spisovou službu + File number + + + + + + + Číslo jednací, pod kterým byl vypraven tento doklad od emitenta, pokud emitent používá na tuto agendu spisovou službu + Reference number + + + + + + + Jednoznačný identifikátor Datové zprávy v systému ISDS, pokud byl dokument odeslán touto cestou + Unique identifier inside ISDS system + + + + + + + Klasifikátor egovernmentu pro výkonové účetnictví státu nebo agendové rozlišení + Egoverment accounting classifier + + + + + + + Kolekce klasifikátorů popisující dokument + Collection of classifiers + + + + + Klasifikátor egovernmentu pro výkonové účetnictví státu nebo agendové rozlišení + Egoverment accounting classifier + + + + + + + + Hlavní element dokumentu, jeho subtyp viz položka DocumentType + Document root element, subtype is stored in DocumentType element + + + + + + Typ dokumentu + Document type + + + + + + Podrobnější typ dokumentu. Číselník udržuje a zveřejňuje subjekt identifikovaný v SubDocumentTypeOrigin. + Document subtype. Codelist is maintained and published by subject specified in SubDocumentTypeOrigin. + + + + + Správce číselníku podrobnějšího typu dokumentu. + Maintainer of subdocument type code list. + + + + + + Identifikace cílového konsolidátora. Číselník je rozšířený číselník kódů bank a udržuje a zveřejňuje ho ČBA, používá se převážně v B2C systémech velkých výstavců faktur. + Identification of target consolidator. Values are extended list of bank codes maintained and published by ČBA. + + + + + Identifikace klienta v systému výstavce, používá se převážně v B2C systémech velkých výstavců faktur. + Client identification in the issuer system. Used mainly in B2C systems of companies issuing large volume of invoices. + + + + + Kompletní číslo bankovního účtu příjemce faktury včetně kódu banky. Používá se převážně v B2C systémech velkých výstavců faktur. + Complete bank account number of invoice receiver. Used mainly in B2C systems of companies issuing large volume of invoices. + + + + + Lidsky čitelné číslo dokladu + Human readable document number + + + + + GUID identifikace od emitujícího systému + GUID identifier produced by emitting system + + + + + Příznak režimu "kontrolovaného státem" + Flag for state governed documents + + + + + Jednoznačný identifikátor Datové zprávy v systému ISDS, pokud byl dokument odeslán touto cestou + Unique identifier inside ISDS system + + + + + Spisová značka, pod kterou byl vypraven tento doklad od emitenta, pokud emitent používá na tuto agendu spisovou službu + File number + + + + + Číslo jednací, pod kterým byl vypraven tento doklad od emitenta, pokud emitent používá na tuto agendu spisovou službu + Reference number + + + + + Kolekce klasifikátorů popisující dokument + Collection of classifiers + + + + + Identifikace systému, který odesílá/generuje fakturu + Identification of issuing system + + + + + Datum vystavení + Issue date + + + + + Datum zdanitelného plnění + Tax point date + + + + + Je předmětem DPH + VAT is applicable + + + + + Označení dokumentu, kterým dal příjemce vystaviteli souhlas s elektronickou formou faktury + Reference to agreement about acceptance of electronic invoices + + + + + Poznámka + Note + + + + + Kód měny + Currency code + + + + + Kód měny + Currency code + + + + + Kurz cizí měny, pokud je použita, jinak 1 + Foreign currency exchange rate (if foreign currency is used), otherwise 1 + + + + + Vztažný kurz cizí měny, většinou 1 + Reference foreign currency exchange rate, usually 1 + + + + + Jakákoliv struktura uživatelských elementů. Elementy musí používat vlastní jmenný prostor. + Arbitrary fragment of user-defined elements. Elements must use their own namespace. + + + + + Dodavatel, účetní jednotka + Supplier, accounting entity in Commercial Register + + + + + Dodavatel, fakturační adresa + Supplier, invoicing address + + + + + + Příjemce, účetní jednotka + Customer, accounting entity in Commercial Register + + + + + + Anonymní příjemce zjednodušeného daňového dokladu + Anonymous receiver of simplified tax document + + + + + Příjemce, účetní jednotka + Customer, accounting entity in Commercial Register + + + + + + + Odběratel, fakturační adresa + Purchaser, invoicing address + + + + + Hlavičková kolekce objednávek pro případnou vazbu + Header collection of referenced purchase order(s) + + + + + Hlavičková kolekce dodacích listů pro případnou vazbu + Header collection of referenced delivery notes + + + + + Hlavičková kolekce odkazů na původní doklady + Header collection of referenced original documents + + + + + Související smlouvy + Related contracts + + + + + Místo určení, adresa dodání + Information about delivery + + + + + Kolekce jednotlivých řádků faktury + Invoice lines collection + + + + + Kolekce nedaňových zálohových listů + Collection of proforma invoices (without VAT) + + + + + Kolekce odúčtování zdaněných zálohových listů + Collection of taxed deposits (advance invoices with VAT) + + + + + Daňová rekapitulace + Information about a total amount of a particular type of tax + + + + + Kolekce celkových částek na dokladu končící položkou částka k zaplacení + Collection of total amounts on document ending with payable amount + + + + + Kolekce plateb + Information about payment means + + + + + Kolekce příloh dokladu. Jedna z nich může být náhled, je označena pomocí preview="true" + Collection of document attachments. Exactly one attachment can be document preview marked by preview="true" + + + + + + + Číslo verze ISDOC + ISDOC version number + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/account_edi_isdoc/tests/test_files/import/doklad.isdoc b/account_edi_isdoc/tests/test_files/import/doklad.isdoc new file mode 100644 index 00000000..a50627c8 --- /dev/null +++ b/account_edi_isdoc/tests/test_files/import/doklad.isdoc @@ -0,0 +1,850 @@ + + + + + + 1 + + + + Identifikace cílového konsolidátora + + identifikace příjemce dokladu u cíl.konsolidátora + + FV-111999/2008 + + 7B4C5BE0-288C-11D2-8E62-004095452B84 + + 2008-02-29 + + 2008-02-29 + + Nepovinná poznámka k dokladu + + + + + + OP-111222/2008 + + OV-123111/2008 + + 2008-01-03 + + + + + OP-111223/2008 + OV-123112/2008 + 2008-01-20 + + + + + + + + + DL-45678/2008 + + 2008-02-29 + + + + + + CZK + + + EUR + + 25.10 + + 1 + + + + + FV-111999/2008 + + 2008-02-29 + + 7B4C5BE0-288C-11D2-8E62-004095452B84 + + + + + + + Logo.bmp + + t1y5ADJ5+2IM/oCnvvlo/g+Gnvo= + + + Notebook.jpg + + r32gUQt4vqSBnw401tL7QhjH2xs= + + + OBCHODNI_PODMINKY.DOC + + Gm9hyD0uRKiUN3VOY7X0OIB1+DU= + + + Dodany nahled FV.pdf + + FTIz8XtuiVXefMmoGBUlkC2Uslo= + + + + stare doklady\dalsi.isdoc + + e2yeqeUXMAxst6rS92+2XPIvDjQ= + + + + stare doklady\logo2.bmp + + t1y5ADJ5+2IM/oCnvvlo/g+Gnvo= + + + + + + my user data + 10 + + + + + + + 315 + + 0 + + 25097563 + + + + ABRA Software a.s. + + + Jeremiášova + 1422 + Praha 13 + 15300 + + CZ + Česká republika + + + + CZ25097563 + VAT + + + Městského soudu v Praze + odd. B, vl. 4475 + 1996-12-31 + + + Josef Novák + 123456789 + novak@abra.eu + + + + + + + + + 315 + 0 + 25097563 + + + ABRA Software a.s. + + + Jeremiášova + 1422 + Praha 13 + 15300 + + CZ + Česká republika + + + + CZ25097563 + VAT + + + Městského soudu v Praze + odd. B, vl. 4475 + 1996-12-31 + + + Josef Novák + 123456789 + novak@abra.eu + + + + + + + + + 000001 + 0 + 00006947 + + + Ministerstvo financí ČR + + + Letenská + 15 + Praha 1 + 11810 + + CZ + Česká republika + + + + CZ00006947 + VAT + + + Jan Novák + 987654321 + novotny@mfcr.cz + + + + + + + + + 000001 + 0 + 00006947 + + + Ministerstvo financí ČR + + + Letenská + 15 + Praha 1 + 11810 + + CZ + Česká republika + + + + CZ00006947 + VAT + + + Jan Novák + 987654321 + novotny@mfcr.cz + + + + + + + + + 000001 + 0 + 00006947 + + + Ministerstvo financí ČR + + + Letenská + 15 + Praha 1 + 11810 + + CZ + Česká republika + + + + CZ00006947 + VAT + + + Jan Novák + 987654321 + novotny@mfcr.cz + + + + + + + + + 1 + + + + OP-111222/2008 + + OV-123111/2008 + + 2008-01-03 + + 1 + + + + + DL-45678/2008 + + 2008-02-29 + + 1 + + + + + + + + FV-111999/2008 + + 2008-02-29 + + 7B4C5BE0-288C-11D2-8E62-004095452B84 + + 1 + + + 100 + + 4.30 + + 107.93 + + 5.20 + 130.52 + 22.59 + + 1.0793 + + 1.3052 + + + 19 + + 0 + + + Parfémovaný, vůně strawberry + + Kapesník papírový NAPPAX NP458 + + + 1000000000013 + + + + 10000001 + + + + DE555444333 + + + + NAPPAX 458-1028-30 + + + + 999988887777766666 + + + + + + Šarže 3A5/2008 + + Pozor doprodej staré řady parfémů + + 2010-12-31 + + Dávkování 35 + + 80 + + B + + + + Šarže 3A9/2008 + Nová řada + 2015-12-31 + Dávkování 35 + 20 + B + + + + + + my user data + + + + + 2 + + + + OP-111223/2008 + + OV-123112/2008 + + 2008-01-20 + + 3 + + + + + DL-45678/2008 + + 2008-02-29 + + 2 + + + 1000 + 8.70 + 218.37 + 9.50 + 238.45 + 20.08 + .21837 + .23845 + + 9 + + 0 + + S úpravou LA1 + + Papír barevný XERTEC 120g/L + + 0000000000013 + + + 10000002 + + + ABFN78956546 + + + XRT 1054/222/100020 + + + 5555444433332222 + + + + + + 3 + + + + DL-45678/2008 + + 2008-02-29 + + 3 + + + 1 + 1 + 25.1 + 1.05 + 26.355 + 1.255 + 25.1 + 26.355 + + 5 + + 0 + + Expresní zásilka + + Služba ve zvláštní sazbě, ekologická přeprava + + + + + + + + + ZLV-111888/2008 + + 111888 + + 1 + + 25.1 + + + + + + + + + DZV-111555/2008 + + 111555 + + 4.30 + + 107.93 + + 5.20 + + 130.52 + + + 19 + 0 + + + + DZV-111666/2008 + + 8.70 + + 218.37 + + 9.50 + + 238.45 + + + 9 + 0 + + + + + + + + + 4.30 + + 107.93 + + 5.20 + + 130.52 + + 0.9 + + 22.59 + + 4.30 + + 107.93 + + 5.20 + + 130.52 + + 0.9 + + 22.59 + + 0 + + 0 + + 0 + + 0 + + 0 + + 0 + + + 19 + + + + + + 8.70 + + 218.37 + + 9.50 + + 238.45 + + 0.8 + + 20.08 + + 8.70 + + 218.37 + + 9.50 + + 238.45 + + 0.8 + + 20.08 + + 0 + + 0 + + 0 + + 0 + + 0 + + 0 + + + 9 + + + + + + 1 + + 25.10 + + 1.10 + + 27.61 + + 0.1 + + 2.51 + + 0 + + 0 + + 0 + + 0 + + 0 + + 0 + + 1 + + 25.10 + + 1.10 + + 27.61 + + 0.1 + + 2.51 + + + 5 + + + + 0.1 + + 2.51 + + + + + + 351.40 + 14 + 396.58 + 15.80 + 326.30 + + 13 + 368.97 + 14.70 + 25.10 + + 1 + 27.61 + 1.10 + -2.51 + + -0.10 + + -25.10 + + -1 + + 0 + + 0 + + + + + + + + + + 0 + + 10 + +
+ + PP-455123/2008 + + 2008-05-27 + +
+
+ + + 0 + + 42 + +
+ + 2008-03-13 + + 1234567890 + + 0800 + + Česká spořitelna, a.s. + + CZ12345678901234567890 + + GIBACZPX + + 111999 + + 0308 + + 105512 + +
+
+ + + 0 + 42 +
+ 2008-06-30 + 1234567890 + 0800 + Česká spořitelna, a.s. + CZ12345678901234567890 + GIBACZPX + 111999 + 0308 + 105512 +
+
+ + + + + 45798465465465 + + 0100 + Komerční banka, a.s. + CZ1546556565454687987 + KOBACZPX + + + 98654623265464 + 0300 + Československá obchodní banka, a.s. + CZ1546556565454687987 + CEKOCZPP + + +
+ + + + + + + + + +29hg/161shG5T0cuwyUEQZiHmn4= + + +rJgOKTSyaGb5WPL0T6SRYFIIPTm17+ZemKbYjA8LFLaYg70T4srjamuEuS4Bw8fv +lr67ZEWM48kEZVNJGNwaQ4j1aif0lnkL8zmYFAyrPo6XxUPQqSIEma6VHbJNZ0Hv +/Jvh4ocQoMV6681Yi9dLZlPXLETmOK0B8ixLwflJdjhJCBq1PInYwEV2DdrJ2jvd +fy0e4yd0CHW8JV2KoJmqs99jiRAPLS9tMnbbSL9WDWXFdM9vmC01PdCcMoEHHitf +IudK+fSpjMgu+c70+JJCp2uSiFCxpjzK7UUzRw4wMf4tGIBwzm9j/muDbFk2W4Py +/Qp4rDcUO6vKUBYMduLALg== + + +MIIEVzCCAz+gAwIBAgICS/4wDQYJKoZIhvcNAQEFBQAwaDELMAkGA1UEBhMCQ1ox +LjAsBgNVBAMTJUkuQ0EgLSBUZXN0IHN0YW5kYXJkIHJvb3QgY2VydGlmaWNhdGUx +KTAnBgNVBAoTIFBydm5pIGNlcnRpZmlrYWNuaSBhdXRvcml0YSBhLnMuMB4XDTA5 +MDQxNTE0NDM1MloXDTA5MDUxNTE0NDM1MlowgZAxCzAJBgNVBAYTAkNaMRUwEwYD +VQQDDAxQZXRyIEt1Y2hhxZkxHjAcBgNVBAgMFUplcmVtacOhxaFvdmEgMTQyMi83 +YjERMA8GA1UEBwwIUHJhaGEgMTMxITAfBgkqhkiG9w0BCQEWEnBlZHJvczc2QHNl +em5hbS5jejEUMBIGA1UEBRMLSUNBIC0gMTQyNzMwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDPG1MxyHykSzYgePuR4Ww4afODFdTzx++8yh5dneb+VT1B +dDblQeFAwtsrIs0V83i1TAFMyMX6cuI6iYbaX/Ifvy3b39riDSFz4qW2RzyVGJz1 +B1JSmkj6fIqfQ3xbCNK4HAZCgOHc75SZPnBLfcmXkCw+v7pHIhYhqcc3RvPmUvi8 +jXcQiWCvVYGgaQDrDwgzj6/89b81RHn95feoFvnAWckvvWD0ve85Jv9BTrARfmDI +GKlLtAN1r7dZh1hDEOf3hCny+H3Rk5eNpN5oR2dWPnK92lW0XAwLQNUFE2tvIuBS +Id1nOTLYbOK5nWh9nJpLXxOHnZyVELqSA+89skvJAgMBAAGjgeEwgd4wMAYDVR0f +BCkwJzAloCOgIYYfaHR0cDovL3Rlc3RzLmljYS5jei90c2ljYTA4LmNybDAfBgNV +HSMEGDAWgBRXvxJOn7Qrxd6vZb8KF+SiVgLIzjAdBgNVHQ4EFgQUp6DSncosDHQ2 +L3ktSAk3nMHEJ1UwXQYDVR0gBFYwVDBSBgsrBgEEAYG4SAFlATBDMEEGCCsGAQUF +BwICMDUaM1RlbnRvIGNlcnRpZmlrYXQgKFNDKSBqZSB2eWRhbiBwcm8gdGVzdG92 +YWNpIHVjZWx5LjALBgNVHQ8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAHsCylO7 +MvCh0zPXx2c4/gtdoo7sO+kkr0j3BSium4CPu3N0hjn3VjKAqm3iyanfESR/1Bi3 +ziDr8scTSK5ztCl/K0C1qRjkVgLMyr0YkEoh5zatnG3Cb+dnyzLHBH5mWWq9Nqx2 +gNHMycAwRfUtTs6B4BmfTVcFXC2inTXHpzrgvN8r7baHaJOay9snzaMJhVzwJ6E1 +/FY9PMM81wli5IjfPndpC0kvr9a4aox8J9vkYBd1NlwAJquFVoTjCETO1QKFfUEs +lMMc7F1gjtWsUCWEEhpyVVSP+RjVKj7MjIxHla6jsqBNrtPkdGPjNYJY27MJKUI9 +qoF4YlIqJOw16ws= + + +
diff --git a/account_edi_isdoc/tests/test_files/import/doklad_s_prilohami.isdocx b/account_edi_isdoc/tests/test_files/import/doklad_s_prilohami.isdocx new file mode 100644 index 00000000..6e48a3a7 Binary files /dev/null and b/account_edi_isdoc/tests/test_files/import/doklad_s_prilohami.isdocx differ diff --git a/account_edi_isdoc/tests/test_isdoc_advance.py b/account_edi_isdoc/tests/test_isdoc_advance.py new file mode 100644 index 00000000..76eca257 --- /dev/null +++ b/account_edi_isdoc/tests/test_isdoc_advance.py @@ -0,0 +1,147 @@ +from lxml import etree + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo.tools import file_open + +ISDOC_NS = "http://isdoc.cz/namespace/2013" + +# A final invoice (DocumentType 1) settling one 21% taxed advance of 500 + VAT: +# full line 1000 + 21% = 1210 ; advance already claimed 500 + 105 = 605 ; net payable 605. +DEPOSIT_INVOICE = f""" + + 1 + FV-DEP-1 + 11111111-1111-1111-1111-111111111111 + 2024-07-10 + true + http:// + CZK + 1 + 1 + + 29261783 + Dodavatel s.r.o. + Hlavní1 + Praha11000 + CZČeská republika + CZ29261783VAT + + + 25663585 + Odberatel s.r.o. + Vedlejší2 + Brno60200 + CZČeská republika + CZ25663585VAT + + + 1 + 1 + 1000 + 1210 + 210 + 1000 + 1210 + 210true + Zboží + + + ZAL-1 + 12345 + 500 + 605 + 210true + + + 10002101210 + 500105605 + 500105605 + 21true + 210 + + 10001210 + 500605 + 500605 + 605605 + +""" + + +@tagged('post_install', '-at_install') +class TestISDOCAdvance(AccountTestInvoicingCommon): + + @classmethod + def _create_company(cls, **create_values): + create_values.setdefault('currency_id', cls.env.ref('base.CZK').id) + company = super()._create_company(**create_values) + company.country_id = cls.env.ref('base.cz') + company.tax_calculation_rounding_method = 'round_globally' # Czech VAT is rounded globally + return company + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data['company'] + cls.purchase_journal = cls.company_data['default_journal_purchase'] + cls.tax_sale_21 = cls.env['account.tax'].create({ + 'name': "DPH 21%", 'amount': 21.0, 'amount_type': 'percent', + 'type_tax_use': 'sale', 'company_id': cls.company.id}) + cls.tax_purchase_21 = cls.env['account.tax'].create({ + 'name': "DPH 21% (nákup)", 'amount': 21.0, 'amount_type': 'percent', + 'type_tax_use': 'purchase', 'company_id': cls.company.id}) + cls.cz_customer = cls.env['res.partner'].create({ + 'name': "Odběratel", 'street': "Vedlejší 2", 'zip': "60200", 'city': "Brno", + 'vat': 'CZ25663585', 'company_registry': '25663585', + 'country_id': cls.env.ref('base.cz').id}) + cls.company.partner_id.write({ + 'street': "Hlavní 1", 'zip': "11000", 'city': "Praha", + 'vat': 'CZ46342958', 'company_registry': '46342958'}) + with file_open('account_edi_isdoc/tests/schema/isdoc-invoice-6.0.2.xsd', 'rb') as f: + cls.schema = etree.XMLSchema(etree.parse(f)) + + def _find(self, root, path): + return root.findtext('/'.join(f'{{{ISDOC_NS}}}{p}' for p in path.split('/'))) + + # ----- Import: deducted advance becomes a negative line ----- + + def test_import_taxed_deposit_nets_to_payable(self): + invoice = self.env['account.move'].with_company(self.company).create({ + 'move_type': 'in_invoice', 'journal_id': self.purchase_journal.id}) + self.env['account.edi.xml.isdoc'].with_company(self.company)._import_invoice_ubl_cii( + invoice, {'xml_tree': etree.fromstring(DEPOSIT_INVOICE.encode()), + 'attachment': self.env['ir.attachment']}) + lines = invoice.invoice_line_ids.filtered(lambda l: l.display_type == 'product') + self.assertEqual(len(lines), 2) # full line + advance deduction + # full 1000 + 21% = 1210, less advance 500 + 105 = 605 -> net 605 + self.assertAlmostEqual(invoice.amount_untaxed, 500.0, places=2) + self.assertAlmostEqual(invoice.amount_total, 605.0, places=2) + + # ----- Export: advance deduction -> AlreadyClaimed + TaxedDeposits ----- + + def test_export_advance_deduction(self): + if 'is_downpayment' not in self.env['account.move.line']._fields: + self.skipTest("`sale` not installed; down-payment lines unavailable") + invoice = self.env['account.move'].with_company(self.company).create({ + 'move_type': 'out_invoice', 'partner_id': self.cz_customer.id, 'invoice_date': '2024-07-10', + 'invoice_line_ids': [ + Command.create({'name': "Zboží", 'quantity': 1, 'price_unit': 1000.0, + 'tax_ids': [Command.set(self.tax_sale_21.ids)]}), + Command.create({'name': "Záloha", 'quantity': 1, 'price_unit': -500.0, 'is_downpayment': True, + 'tax_ids': [Command.set(self.tax_sale_21.ids)]}), + ]}) + invoice.action_post() + self.assertEqual(invoice.amount_total, 605.0) # net + + xml, errors = self.env['account.edi.xml.isdoc']._export_invoice(invoice) + self.assertFalse(errors) + root = etree.fromstring(xml) + self.schema.assertValid(root) + # Only the regular line is in InvoiceLines. + self.assertEqual(len(root.findall(f'{{{ISDOC_NS}}}InvoiceLines/{{{ISDOC_NS}}}InvoiceLine')), 1) + # The advance shows up as a TaxedDeposit and in the already-claimed totals. + self.assertEqual(self._find(root, 'TaxedDeposits/TaxedDeposit/TaxableDepositAmount'), '500.00') + self.assertEqual(self._find(root, 'LegalMonetaryTotal/AlreadyClaimedTaxExclusiveAmount'), '500.00') + self.assertEqual(self._find(root, 'LegalMonetaryTotal/PayableAmount'), '605.00') + self.assertEqual(self._find(root, 'TaxTotal/TaxSubTotal/DifferenceTaxInclusiveAmount'), '605.00') diff --git a/account_edi_isdoc/tests/test_isdoc_containers.py b/account_edi_isdoc/tests/test_isdoc_containers.py new file mode 100644 index 00000000..5f423f39 --- /dev/null +++ b/account_edi_isdoc/tests/test_isdoc_containers.py @@ -0,0 +1,142 @@ +import io +import zipfile + +from lxml import etree + +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo.tools import file_open + +ISDOC_NS = "http://isdoc.cz/namespace/2013" +ISDOC_MANIFEST_NS = "http://isdoc.cz/namespace/2013/manifest" + + +@tagged('post_install', '-at_install') +class TestISDOCContainers(AccountTestInvoicingCommon): + + @classmethod + def _create_company(cls, **create_values): + create_values.setdefault('currency_id', cls.env.ref('base.CZK').id) + company = super()._create_company(**create_values) + company.country_id = cls.env.ref('base.cz') + company.tax_calculation_rounding_method = 'round_globally' # Czech VAT is rounded globally + return company + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data['company'] + cls.purchase_journal = cls.company_data['default_journal_purchase'] + for rate in (12.0, 21.0): + cls.env['account.tax'].create({ + 'name': f"DPH {rate:.0f}% (nákup)", 'amount': rate, 'amount_type': 'percent', + 'type_tax_use': 'purchase', 'company_id': cls.company.id, + }) + cls.cz_customer = cls.env['res.partner'].create({ + 'name': "Zákazník s.r.o.", 'street': "Hlavní 1", 'zip': "11000", 'city': "Praha", + 'vat': 'CZ25663585', 'country_id': cls.env.ref('base.cz').id, + 'invoice_edi_format': 'isdoc', + }) + cls.company.partner_id.write({ + 'street': "Příhon 943", 'zip': "696 15", 'city': "Čejkovice", + 'vat': 'CZ46342958', 'company_registry': '46342958', + }) + with file_open('account_edi_isdoc/tests/schema/isdoc-invoice-6.0.2.xsd', 'rb') as f: + cls.invoice_schema = etree.XMLSchema(etree.parse(f)) + + # ----- Export pipeline wiring ----- + + def test_isdoc_registered_and_engages_pipeline(self): + self.assertIn('isdoc', self.env['res.partner']._get_ubl_cii_formats()) + invoice = self.init_invoice( + 'out_invoice', partner=self.cz_customer, invoice_date='2024-07-10', + amounts=[100.0], taxes=self.company_data['default_tax_sale'], post=True) + self.assertTrue(invoice._need_ubl_cii_xml('isdoc')) + + def test_isdoc_skips_ubl_postprocessing(self): + send = self.env['account.move.send'] + # ISDOC must NOT go through the UBL AdditionalDocumentReference postprocessing. + self.assertFalse(send._needs_ubl_postprocessing( + {'ubl_cii_xml_options': {'ubl_cii_format': 'isdoc'}})) + # ... but a UBL format still does. + self.assertTrue(send._needs_ubl_postprocessing( + {'ubl_cii_xml_options': {'ubl_cii_format': 'ubl_bis3'}})) + + # ----- Container import ----- + + def _new_bill(self): + return self.env['account.move'].with_company(self.company).create({ + 'move_type': 'in_invoice', 'journal_id': self.purchase_journal.id}) + + def test_get_xml_tree_parses_bare_isdoc(self): + # A .isdoc uploaded with a non-xml mimetype must still be recognised. + with file_open('account_edi_isdoc/tests/test_files/import/doklad.isdoc', 'rb') as f: + raw = f.read() + attachment = self.env['ir.attachment'].create({ + 'name': 'invoice.isdoc', 'raw': raw, 'mimetype': 'application/octet-stream'}) + file_data = self.env['account.move']._to_files_data(attachment)[0] + self.assertIsNotNone(file_data['xml_tree']) + self.assertEqual(file_data['import_file_type'], 'account.edi.xml.isdoc') + + def test_import_isdocx_archive(self): + # Official .isdocx (doklad + attachments + nested old docs). Unwrap finds the ISDOC + # and decodes it (the inner doklad is the ISDOC 5.1 ABRA demo). + with file_open('account_edi_isdoc/tests/test_files/import/doklad_s_prilohami.isdocx', 'rb') as f: + raw = f.read() + attachment = self.env['ir.attachment'].create({ + 'name': 'doklad_s_prilohami.isdocx', 'raw': raw, 'mimetype': 'application/zip'}) + bill = self._new_bill() + files_data = bill._to_files_data(attachment) + files_data += bill._unwrap_attachments(files_data) + isdoc_fd = next(fd for fd in files_data if fd.get('import_file_type') == 'account.edi.xml.isdoc') + self.assertIsNotNone(isdoc_fd['xml_tree']) + + bill._get_edi_decoder(isdoc_fd)['decoder'](bill, isdoc_fd) + self.assertEqual(bill.partner_id.name, "ABRA Software a.s.") + self.assertEqual(bill.ref, 'FV-111999/2008') + self.assertGreater(len(bill.invoice_line_ids.filtered(lambda l: l.display_type == 'product')), 3) + + # ----- Export container forms ----- + + def test_export_isdocx_archive(self): + invoice = self.init_invoice( + 'out_invoice', partner=self.cz_customer, invoice_date='2024-07-10', + amounts=[100.0, 200.0], taxes=self.company_data['default_tax_sale'], post=True) + raw, errors = self.env['account.edi.xml.isdoc']._export_invoice_isdocx(invoice, pdf=b'%PDF-1.4 test') + self.assertFalse(errors) + + archive = zipfile.ZipFile(io.BytesIO(raw)) + names = archive.namelist() + self.assertIn('manifest.xml', names) + isdoc_name = next(n for n in names if n.endswith('.isdoc')) + self.assertTrue(any(n.endswith('.pdf') for n in names)) + # Inner ISDOC is schema-valid. + self.invoice_schema.assertValid(etree.fromstring(archive.read(isdoc_name))) + # Manifest points at the ISDOC. + manifest = etree.fromstring(archive.read('manifest.xml')) + main = manifest.find(f'{{{ISDOC_MANIFEST_NS}}}maindocument') + self.assertEqual(main.get('filename'), isdoc_name) + # The archive round-trips through our importer's unwrap. + attachment = self.env['ir.attachment'].create({ + 'name': 'invoice.isdocx', 'raw': raw, 'mimetype': 'application/zip'}) + files_data = self._new_bill()._unwrap_attachments( + self.env['account.move']._to_files_data(attachment)) + self.assertTrue(any(fd.get('import_file_type') == 'account.edi.xml.isdoc' for fd in files_data)) + + def test_embed_isdoc_in_pdf_roundtrips(self): + # Take a real PDF + ISDOC from the official archive, embed, then re-detect on import. + with file_open('account_edi_isdoc/tests/test_files/import/doklad_s_prilohami.isdocx', 'rb') as f: + archive = zipfile.ZipFile(io.BytesIO(f.read())) + pdf = archive.read(next(n for n in archive.namelist() if n.lower().endswith('.pdf'))) + isdoc_xml = archive.read(next(n for n in archive.namelist() if n.lower().endswith('.isdoc'))) + + new_pdf = self.env['account.move.send']._isdoc_embed_in_pdf(pdf, isdoc_xml, 'invoice.isdoc') + + attachment = self.env['ir.attachment'].create({ + 'name': 'invoice.pdf', 'raw': new_pdf, 'mimetype': 'application/pdf'}) + bill = self._new_bill() + files_data = bill._to_files_data(attachment) + files_data += bill._unwrap_attachments(files_data) + isdoc_fd = next((fd for fd in files_data if fd.get('import_file_type') == 'account.edi.xml.isdoc'), None) + self.assertIsNotNone(isdoc_fd, "Embedded ISDOC not found back in the PDF") + self.assertTrue((etree.QName(isdoc_fd['xml_tree']).namespace or '').startswith('http://isdoc.cz/namespace')) diff --git a/account_edi_isdoc/tests/test_isdoc_export.py b/account_edi_isdoc/tests/test_isdoc_export.py new file mode 100644 index 00000000..dbe6e675 --- /dev/null +++ b/account_edi_isdoc/tests/test_isdoc_export.py @@ -0,0 +1,170 @@ +from lxml import etree + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo.tools import file_open + +ISDOC_NS = "http://isdoc.cz/namespace/2013" + + +@tagged('post_install', '-at_install') +class TestISDOCExport(AccountTestInvoicingCommon): + + @classmethod + def _create_company(cls, **create_values): + # EXTENDS 'account' — ISDOC is a Czech, CZK format. + create_values.setdefault('currency_id', cls.env.ref('base.CZK').id) + company = super()._create_company(**create_values) + company.country_id = cls.env.ref('base.cz') + company.tax_calculation_rounding_method = 'round_globally' # Czech VAT is rounded globally + return company + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company_data['company'].partner_id.write({ + 'street': "Příhon 943", + 'zip': "696 15", + 'city': "Čejkovice", + 'vat': 'CZ46342958', + 'company_registry': '46342958', + 'phone': '+420511447174', + 'email': 'fakturace@example.cz', + }) + cls.cz_customer = cls.env['res.partner'].create({ + 'name': "Odběratel s.r.o.", + 'street': "Diabasová 1141/11", + 'zip': "15500", + 'city': "Praha 13", + 'vat': 'CZ25663585', + 'company_registry': '25663585', + 'country_id': cls.env.ref('base.cz').id, + }) + cls.company_data['company'].partner_id.bank_ids = [Command.create({ + 'acc_number': '2631172369/0800', + 'allow_out_payment': True, # mark trusted so out_invoice posting isn't blocked + })] + cls.tax_21 = cls.env['account.tax'].create({ + 'name': "DPH 21%", + 'amount': 21.0, + 'amount_type': 'percent', + 'type_tax_use': 'sale', + 'company_id': cls.company_data['company'].id, + }) + cls.tax_12 = cls.env['account.tax'].create({ + 'name': "DPH 12%", + 'amount': 12.0, + 'amount_type': 'percent', + 'type_tax_use': 'sale', + 'company_id': cls.company_data['company'].id, + }) + with file_open('account_edi_isdoc/tests/schema/isdoc-invoice-6.0.2.xsd', 'rb') as f: + cls.schema = etree.XMLSchema(etree.parse(f)) + + def _export(self, invoice): + builder = self.env['account.edi.xml.isdoc'] + xml, errors = builder._export_invoice(invoice) + self.assertFalse(errors, "ISDOC export reported constraints: %s" % errors) + root = etree.fromstring(xml) + self.schema.assertValid(root) # raises etree.DocumentInvalid with details on failure + return root + + def _find(self, root, path): + return root.findtext('/'.join(f'{{{ISDOC_NS}}}{p}' for p in path.split('/'))) + + def test_out_invoice_two_tax_rates(self): + invoice = self.init_invoice( + 'out_invoice', partner=self.cz_customer, invoice_date='2024-07-10', + amounts=[201.48, 300.00], taxes=self.tax_12, post=True, + ) + root = self._export(invoice) + self.assertEqual(root.get('version'), '6.0.2') + self.assertEqual(self._find(root, 'DocumentType'), '1') + self.assertEqual(self._find(root, 'ID'), invoice.name) + self.assertTrue(invoice.isdoc_uuid) + self.assertEqual(self._find(root, 'UUID'), invoice.isdoc_uuid) + self.assertEqual(self._find(root, 'LegalMonetaryTotal/PayableAmount'), + f'{invoice.amount_total:.2f}') + lines = root.findall(f'{{{ISDOC_NS}}}InvoiceLines/{{{ISDOC_NS}}}InvoiceLine') + self.assertEqual(len(lines), 2) + + def test_tax_point_is_the_duzp_not_the_issue_date(self): + invoice = self.init_invoice( + 'out_invoice', partner=self.cz_customer, invoice_date='2024-08-03', + amounts=[100.0], taxes=self.tax_21) + invoice.taxable_supply_date = '2024-07-31' + invoice.action_post() + root = self._export(invoice) + self.assertEqual(self._find(root, 'IssueDate'), '2024-08-03') + self.assertEqual(self._find(root, 'TaxPointDate'), '2024-07-31') + + def test_out_invoice_mixed_rates_and_bank(self): + invoice = self.init_invoice( + 'out_invoice', partner=self.cz_customer, invoice_date='2024-07-10', + amounts=[100.0, 200.0], taxes=self.tax_21, post=True, + ) + # second line at the 12% rate -> two TaxSubTotal groups + invoice.button_draft() + invoice.invoice_line_ids[1].tax_ids = self.tax_12 + invoice.partner_bank_id = self.company_data['company'].partner_id.bank_ids[0] + invoice.action_post() + root = self._export(invoice) + subtotals = root.findall(f'{{{ISDOC_NS}}}TaxTotal/{{{ISDOC_NS}}}TaxSubTotal') + self.assertEqual(len(subtotals), 2) + self.assertEqual(self._find(root, 'PaymentMeans/Payment/Details/BankCode'), '0800') + self.assertEqual(self._find(root, 'PaymentMeans/Payment/Details/ID'), '2631172369') + + def test_out_refund(self): + invoice = self.init_invoice( + 'out_refund', partner=self.cz_customer, invoice_date='2024-07-11', + amounts=[1209.0], taxes=self.tax_21, post=True, + ) + root = self._export(invoice) + self.assertEqual(self._find(root, 'DocumentType'), '2') + + def test_cz_bank_account_conversion(self): + # Conversion lives in l10n_cz_base (res.partner.bank._cz_account_parts). + # Stored as a Czech IBAN -> derive legacy account number + bank code, incl. computed fields. + bank_iban = self.env['res.partner.bank'].create({ + 'acc_number': 'CZ43 0800 0000 0026 3117 2369', + 'partner_id': self.company_data['company'].partner_id.id, + }) + self.assertEqual( + bank_iban._cz_account_parts(), + ('2631172369', '0800', 'CZ4308000000002631172369'), + ) + self.assertEqual(bank_iban.bank_local_code, '0800') + self.assertEqual(bank_iban.acc_legacy_number, '2631172369') + # Stored in legacy form (incl. a prefix) -> compute a valid IBAN. + bank_legacy = self.env['res.partner.bank'].create({ + 'acc_number': '19-2000145399/0100', + 'partner_id': self.cz_customer.id, + }) + self.assertEqual( + bank_legacy._cz_account_parts(), + ('19-2000145399', '0100', 'CZ0801000000192000145399'), + ) + # Fio bank code 2010 — the case the [6:14] slice got wrong. + bank_fio = self.env['res.partner.bank'].create({ + 'acc_number': '2801589782/2010', + 'partner_id': self.cz_customer.id, + }) + self.assertEqual( + bank_fio._cz_account_parts(), + ('2801589782', '2010', 'CZ8120100000002801589782'), + ) + # legacy -> IBAN model helper (used on import to store IBAN as primary) + self.assertEqual( + self.env['res.partner.bank']._cz_legacy_to_iban('2631172369/0800'), + 'CZ4308000000002631172369', + ) + + def test_missing_customer_address_is_flagged(self): + self.cz_customer.write({'street': False, 'city': False, 'zip': False}) + invoice = self.init_invoice( + 'out_invoice', partner=self.cz_customer, invoice_date='2024-07-10', + amounts=[100.0], taxes=self.tax_21, post=True, + ) + _xml, errors = self.env['account.edi.xml.isdoc']._export_invoice(invoice) + self.assertTrue(errors) diff --git a/account_edi_isdoc/tests/test_isdoc_import.py b/account_edi_isdoc/tests/test_isdoc_import.py new file mode 100644 index 00000000..801c46c7 --- /dev/null +++ b/account_edi_isdoc/tests/test_isdoc_import.py @@ -0,0 +1,175 @@ +from lxml import etree + +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo.tools import file_open + +NS = "http://isdoc.cz/namespace/2013" + + +def _party(role_name, vat): + return f""" + {vat[2:]} + {role_name} + Hlavní1 + Praha11000 + CZČeská republika + {vat}VAT + """ + + +# A plain invoice: two 12% lines -> untaxed 500, tax 60, total 560. +INVOICE = f""" + + 1FA-2024-001 + AAAA1111-1111-1111-1111-111111111111 + 2024-07-10true + http:// + CZK11 + {_party("Dodavatel s.r.o.", "CZ12345679")} + {_party("Odběratel s.r.o.", "CZ25663585")} + + 12 + 200224 + 24100112 + 120true + Zboží A + 21 + 300336 + 36300336 + 120true + Zboží B + + + 50060560 + 000 + 50060560 + 12true + 60 + + 500560 + 00 + 500560 + 0560 + +""" + +# A credit note (DocumentType 2) with a legacy bank account and a variable symbol. +CREDIT_NOTE = f""" + + 2OD-2024-007 + BBBB2222-2222-2222-2222-222222222222 + 2024-07-11true + http:// + CZK11 + {_party("Dodavatel Plus s.r.o.", "CZ29261783")} + {_party("Odběratel s.r.o.", "CZ25663585")} + 11 + 10001210 + 21010001210 + 210true + Vrácené zboží + + 10002101210 + 000 + 10002101210 + 21true + 210 + + 10001210 + 00 + 10001210 + 01210 + + -121042 +
2024-07-25 + 26311723690800Česká spořitelna + 20240007
+
""" + + +@tagged('post_install', '-at_install') +class TestISDOCImport(AccountTestInvoicingCommon): + + @classmethod + def _create_company(cls, **create_values): + create_values.setdefault('currency_id', cls.env.ref('base.CZK').id) + company = super()._create_company(**create_values) + company.country_id = cls.env.ref('base.cz') + company.tax_calculation_rounding_method = 'round_globally' # Czech VAT is rounded globally + return company + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data['company'] + cls.purchase_journal = cls.company_data['default_journal_purchase'] + cls.taxes = {} + for rate in (5.0, 9.0, 12.0, 19.0, 21.0): + cls.taxes[rate] = cls.env['account.tax'].create({ + 'name': f"DPH {rate:.0f}% (nákup)", 'amount': rate, 'amount_type': 'percent', + 'type_tax_use': 'purchase', 'company_id': cls.company.id}) + + def _import(self, source): + tree = source if isinstance(source, etree._Element) else etree.fromstring(source) + invoice = self.env['account.move'].with_company(self.company).create({ + 'move_type': 'in_invoice', 'journal_id': self.purchase_journal.id}) + self.env['account.edi.xml.isdoc'].with_company(self.company)._import_invoice_ubl_cii( + invoice, {'xml_tree': tree, 'attachment': self.env['ir.attachment']}) + return invoice + + def _import_file(self, filename): + with file_open(f'account_edi_isdoc/tests/test_files/import/{filename}', 'rb') as f: + return self._import(etree.fromstring(f.read())) + + def test_import_invoice(self): + invoice = self._import(INVOICE.encode()) + self.assertEqual(invoice.move_type, 'in_invoice') + self.assertEqual(invoice.ref, 'FA-2024-001') + self.assertTrue(invoice.isdoc_uuid) + self.assertEqual(invoice.currency_id, self.env.ref('base.CZK')) + self.assertEqual(invoice.partner_id.name, "Dodavatel s.r.o.") + product_lines = invoice.invoice_line_ids.filtered(lambda l: l.display_type == 'product') + self.assertEqual(len(product_lines), 2) + self.assertTrue(all(l.tax_ids == self.taxes[12.0] for l in product_lines)) + self.assertAlmostEqual(invoice.amount_untaxed, 500.0, places=2) + self.assertAlmostEqual(invoice.amount_tax, 60.0, places=2) + self.assertAlmostEqual(invoice.amount_total, 560.0, places=2) + + def test_import_reads_the_duzp(self): + """An ordinary vendor bill takes its DUZP from TaxPointDate; only the + advance tax document used to.""" + xml = INVOICE.replace( + '2024-07-10', + '2024-07-102024-06-28') + self.assertIn('TaxPointDate', xml) + invoice = self._import(xml.encode()) + self.assertEqual(str(invoice.invoice_date), '2024-07-10') + self.assertEqual(str(invoice.taxable_supply_date), '2024-06-28') + if 'cssk_vat_deduction_date' in invoice._fields: + # The deduction period is ours to set, not the supplier's. + self.assertFalse(invoice.cssk_vat_deduction_date) + + def test_import_credit_note_with_legacy_bank(self): + invoice = self._import(CREDIT_NOTE.encode()) + self.assertEqual(invoice.move_type, 'in_refund') + self.assertEqual(invoice.ref, 'OD-2024-007') + self.assertEqual(invoice.partner_id.name, "Dodavatel Plus s.r.o.") + self.assertEqual(invoice.payment_reference, '20240007') # VariableSymbol + # Legacy / normalised to IBAN (via l10n_cz_base), legacy parts computed. + self.assertTrue(invoice.partner_bank_id) + self.assertEqual(invoice.partner_bank_id.sanitized_acc_number, 'CZ4308000000002631172369') + self.assertEqual(invoice.partner_bank_id.bank_local_code, '0800') + self.assertAlmostEqual(invoice.amount_total, 1210.0, places=2) + + def test_import_real_isdoc_5_1_document(self): + # The official ABRA demo is ISDOC 5.1 (namespace .../invoice) — the importer is + # version-agnostic, so it must still decode it (a settlement invoice with deposits). + invoice = self._import_file('doklad.isdoc') + self.assertEqual(invoice.move_type, 'in_invoice') + self.assertEqual(invoice.ref, 'FV-111999/2008') + self.assertEqual(invoice.partner_id.name, "ABRA Software a.s.") + self.assertTrue(invoice.isdoc_uuid) + # 3 product lines + advance deductions (TaxedDeposits/NonTaxedDeposits) + rounding line. + product_lines = invoice.invoice_line_ids.filtered(lambda l: l.display_type == 'product') + self.assertGreater(len(product_lines), 3) diff --git a/account_edi_isdoc/tools/__init__.py b/account_edi_isdoc/tools/__init__.py new file mode 100644 index 00000000..7fba8907 --- /dev/null +++ b/account_edi_isdoc/tools/__init__.py @@ -0,0 +1,3 @@ +from . import isdoc_invoice + +from .isdoc_invoice import Invoice diff --git a/account_edi_isdoc/tools/isdoc_invoice.py b/account_edi_isdoc/tools/isdoc_invoice.py new file mode 100644 index 00000000..f3fd0a8a --- /dev/null +++ b/account_edi_isdoc/tools/isdoc_invoice.py @@ -0,0 +1,192 @@ +# Ordering templates for ISDOC 6.0.2 invoices. +# +# Each dict mirrors an from xsd/isdoc-invoice-6.0.2.xsd: the KEY ORDER +# is the element order the schema mandates. Passed as the `template` argument to +# odoo.addons.account.tools.dict_to_xml, which does `dict.fromkeys(template) | node` +# to enforce that order and (with render_empty_nodes=False) drop any optional node the +# builder left unset. +# +# ISDOC has a single default namespace and no element prefixes, so keys are bare tags. + +# -- Parties --------------------------------------------------------------- +Country = { + 'IdentificationCode': {}, + 'Name': {}, +} +PostalAddress = { + 'StreetName': {}, + 'BuildingNumber': {}, + 'CityName': {}, + 'PostalZone': {}, + 'Country': Country, +} +PartyIdentification = { + 'UserID': {}, + 'CatalogFirmIdentification': {}, + 'ID': {}, +} +PartyName = { + 'Name': {}, +} +PartyTaxScheme = { + 'CompanyID': {}, + 'TaxScheme': {}, +} +RegisterIdentification = { + 'RegisterKeptAt': {}, + 'RegisterFileRef': {}, + 'RegisterDate': {}, + 'Preformatted': {}, +} +Contact = { + 'Name': {}, + 'Telephone': {}, + 'ElectronicMail': {}, +} +Party = { + 'PartyIdentification': PartyIdentification, + 'PartyName': PartyName, + 'PostalAddress': PostalAddress, + 'PartyTaxScheme': PartyTaxScheme, + 'RegisterIdentification': RegisterIdentification, + 'Contact': Contact, +} +SupplierParty = {'Party': Party} +CustomerParty = {'Party': Party} +Delivery = {'Party': Party} + +# -- References ------------------------------------------------------------ +OriginalDocumentReferences = { + 'OriginalDocumentReference': { + 'ID': {}, + 'IssueDate': {}, + 'UUID': {}, + }, +} + +# -- Invoice lines --------------------------------------------------------- +ClassifiedTaxCategory = { + 'Percent': {}, + 'VATCalculationMethod': {}, + 'VATApplicable': {}, + 'LocalReverseCharge': {}, +} +Item = { + 'Description': {}, + 'CatalogueItemIdentification': {'ID': {}}, + 'SellersItemIdentification': {'ID': {}}, + 'SecondarySellersItemIdentification': {'ID': {}}, + 'TertiarySellersItemIdentification': {'ID': {}}, + 'BuyersItemIdentification': {'ID': {}}, +} +InvoiceLine = { + 'ID': {}, + 'InvoicedQuantity': {}, + 'LineExtensionAmount': {}, + 'LineExtensionAmountTaxInclusive': {}, + 'LineExtensionTaxAmount': {}, + 'UnitPrice': {}, + 'UnitPriceTaxInclusive': {}, + 'ClassifiedTaxCategory': ClassifiedTaxCategory, + 'Note': {}, + 'Item': Item, +} +InvoiceLines = {'InvoiceLine': InvoiceLine} + +# -- Deposits (advance payments already invoiced) -------------------------- +NonTaxedDeposit = { + 'ID': {}, + 'VariableSymbol': {}, + 'DepositAmount': {}, +} +NonTaxedDeposits = {'NonTaxedDeposit': NonTaxedDeposit} +TaxedDeposit = { + 'ID': {}, + 'VariableSymbol': {}, + 'TaxableDepositAmount': {}, + 'TaxInclusiveDepositAmount': {}, + 'ClassifiedTaxCategory': ClassifiedTaxCategory, +} +TaxedDeposits = {'TaxedDeposit': TaxedDeposit} + +# -- Tax & monetary totals ------------------------------------------------- +TaxCategory = { + 'Percent': {}, + 'VATApplicable': {}, +} +TaxSubTotal = { + 'TaxableAmount': {}, + 'TaxAmount': {}, + 'TaxInclusiveAmount': {}, + 'AlreadyClaimedTaxableAmount': {}, + 'AlreadyClaimedTaxAmount': {}, + 'AlreadyClaimedTaxInclusiveAmount': {}, + 'DifferenceTaxableAmount': {}, + 'DifferenceTaxAmount': {}, + 'DifferenceTaxInclusiveAmount': {}, + 'TaxCategory': TaxCategory, +} +TaxTotal = { + 'TaxSubTotal': TaxSubTotal, + 'TaxAmount': {}, +} +LegalMonetaryTotal = { + 'TaxExclusiveAmount': {}, + 'TaxInclusiveAmount': {}, + 'AlreadyClaimedTaxExclusiveAmount': {}, + 'AlreadyClaimedTaxInclusiveAmount': {}, + 'DifferenceTaxExclusiveAmount': {}, + 'DifferenceTaxInclusiveAmount': {}, + 'PayableRoundingAmount': {}, + 'PaidDepositsAmount': {}, + 'PayableAmount': {}, +} + +# -- Payment means --------------------------------------------------------- +# Transfer-payment branch of DetailsType (cash branch = DocumentID + IssueDate). +# Bank fields come from : ID, BankCode, Name, IBAN, BIC. +Details = { + 'PaymentDueDate': {}, + 'ID': {}, + 'BankCode': {}, + 'Name': {}, + 'IBAN': {}, + 'BIC': {}, + 'VariableSymbol': {}, + 'ConstantSymbol': {}, + 'SpecificSymbol': {}, +} +Payment = { + 'PaidAmount': {}, + 'PaymentMeansCode': {}, + 'Details': Details, +} +PaymentMeans = {'Payment': Payment} + +# -- Root ------------------------------------------------------------------ +Invoice = { + '_tag': 'Invoice', + 'DocumentType': {}, + 'ID': {}, + 'UUID': {}, + 'IssuingSystem': {}, + 'IssueDate': {}, + 'TaxPointDate': {}, + 'VATApplicable': {}, + 'ElectronicPossibilityAgreementReference': {}, + 'Note': {}, + 'LocalCurrencyCode': {}, + 'ForeignCurrencyCode': {}, + 'CurrRate': {}, + 'RefCurrRate': {}, + 'AccountingSupplierParty': SupplierParty, + 'AccountingCustomerParty': CustomerParty, + 'OriginalDocumentReferences': OriginalDocumentReferences, + 'Delivery': Delivery, + 'InvoiceLines': InvoiceLines, + 'NonTaxedDeposits': NonTaxedDeposits, + 'TaxedDeposits': TaxedDeposits, + 'TaxTotal': TaxTotal, + 'LegalMonetaryTotal': LegalMonetaryTotal, + 'PaymentMeans': PaymentMeans, +} diff --git a/account_edi_isdoc_purchase_advance/CHANGELOG.rst b/account_edi_isdoc_purchase_advance/CHANGELOG.rst new file mode 100644 index 00000000..bdb1f135 --- /dev/null +++ b/account_edi_isdoc_purchase_advance/CHANGELOG.rst @@ -0,0 +1,31 @@ +========= +Changelog +========= + +All notable changes to **account_edi_isdoc_purchase_advance** are documented +here. Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows +Keep a Changelog. + +[Unreleased] +------------ + +Changed +~~~~~~~ + +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.1.0.0] — 2026-07-18 +------------------------- + +Added +~~~~~ + +- Initial release. ISDOC DocumentType 4 imports create a received advance + ``purchase.order`` (a proforma is never posted as a bill); DocumentType + 5/6 imports are routed to the advance journal with + ``taxable_supply_date`` from ``TaxPointDate``, lines rewritten to the + paid-advances / clearing accounts, and linked to the matching open + advance by order reference, variable symbol or unique paid amount. diff --git a/l10n_sk_hr_payroll_prehlad/LICENSE b/account_edi_isdoc_purchase_advance/LICENSE similarity index 100% rename from l10n_sk_hr_payroll_prehlad/LICENSE rename to account_edi_isdoc_purchase_advance/LICENSE diff --git a/l10n_sk_hr_payroll_prehlad/__init__.py b/account_edi_isdoc_purchase_advance/__init__.py similarity index 100% rename from l10n_sk_hr_payroll_prehlad/__init__.py rename to account_edi_isdoc_purchase_advance/__init__.py diff --git a/account_edi_isdoc_purchase_advance/__manifest__.py b/account_edi_isdoc_purchase_advance/__manifest__.py new file mode 100644 index 00000000..5c1f69a3 --- /dev/null +++ b/account_edi_isdoc_purchase_advance/__manifest__.py @@ -0,0 +1,43 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "ISDOC Import — Received Advance Invoices", + "version": "19.0.1.0.0", + "summary": "Route imported ISDOC advance documents (DocumentType 4/5/6) " + "into the received-advances flow.", + "description": """ +ISDOC Import — Received Advance Invoices +======================================== + +Wires the ISDOC import into ``purchase_order_advance_invoice``: + +* **DocumentType 4** (zálohová faktura — non-tax proforma): a received + advance ``purchase.order`` is created from the document (supplier, + gross amount, supplier's number) instead of leaving only a vendor + bill — a proforma must not be posted as a bill. The draft bill the + import framework creates is kept for reference, linked to the advance + and flagged in the chatter. +* **DocumentType 5** (daňový doklad k přijaté platbě): the imported bill + is registered as an advance tax document — moved to the advance + journal, ``taxable_supply_date`` taken from ``TaxPointDate``, product + lines rewritten to the paid-advances account, the payable leg to the + advance clearing account, and linked to the matching open advance + (by order reference, variable symbol, or unique paid amount — with an + ambiguity bail-out). +* **DocumentType 6** (opravný doklad k zálohovému): same journal/date/ + account treatment as 5, as a vendor credit note. + +Auto-installs when both ``account_edi_isdoc`` and +``purchase_order_advance_invoice`` are present. +""", + "author": "Data Dance s.r.o.", + "website": "https://www.datadance.eu", + "license": "AGPL-3", + "category": "Accounting/Localizations", + "depends": ["account_edi_isdoc", "purchase_order_advance_invoice"], + "data": [ + "data/product_data.xml", + ], + "auto_install": True, + "installable": True, +} diff --git a/account_edi_isdoc_purchase_advance/data/product_data.xml b/account_edi_isdoc_purchase_advance/data/product_data.xml new file mode 100644 index 00000000..945aca66 --- /dev/null +++ b/account_edi_isdoc_purchase_advance/data/product_data.xml @@ -0,0 +1,16 @@ + + + + + + Advance payment (imported) + service + + + 0.0 + 0.0 + + + + diff --git a/account_edi_isdoc_purchase_advance/models/__init__.py b/account_edi_isdoc_purchase_advance/models/__init__.py new file mode 100644 index 00000000..7703f6ef --- /dev/null +++ b/account_edi_isdoc_purchase_advance/models/__init__.py @@ -0,0 +1 @@ +from . import account_edi_xml_isdoc diff --git a/account_edi_isdoc_purchase_advance/models/account_edi_xml_isdoc.py b/account_edi_isdoc_purchase_advance/models/account_edi_xml_isdoc.py new file mode 100644 index 00000000..46ad0e44 --- /dev/null +++ b/account_edi_isdoc_purchase_advance/models/account_edi_xml_isdoc.py @@ -0,0 +1,190 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import re + +from odoo import Command, models + + +def _digits(value, size=10): + return re.sub(r"\D", "", value or "")[-size:] + + +class AccountEdiXmlISDOC(models.AbstractModel): + _inherit = "account.edi.xml.isdoc" + + def _import_fill_invoice(self, invoice, tree, qty_factor): + logs = super()._import_fill_invoice(invoice, tree, qty_factor) + if invoice.move_type not in ("in_invoice", "in_refund"): + return logs + doc_type = (tree.findtext(self._q("DocumentType")) or "").strip() + if doc_type == "4": + logs += self._isdoc_import_proforma_advance(invoice, tree) + elif doc_type in ("5", "6"): + logs += self._isdoc_import_advance_tax_document(invoice, tree) + return logs + + def _import_invoice_ubl_cii(self, invoice, file_data, new=False): + res = super()._import_invoice_ubl_cii(invoice, file_data, new=new) + # account rewrites must run AFTER the whole import settles: the + # payment-term lines are regenerated by the dynamic-line sync, so a + # rewrite inside _import_fill_invoice would be lost + tree = file_data.get("xml_tree") if isinstance(file_data, dict) else None + if tree is not None and invoice.move_type in ("in_invoice", "in_refund"): + doc_type = (tree.findtext(self._q("DocumentType")) or "").strip() + if doc_type in ("5", "6"): + advance, how = self._isdoc_find_advance_order(invoice, tree) + if advance: + invoice.advance_purchase_order_id = advance + invoice.message_post(body=self.env._( + "ISDOC advance tax document: linked to received " + "advance %(order)s (matched by %(how)s).", + order=advance.name, how=how, + )) + else: + invoice.message_post(body=self.env._( + "ISDOC advance tax document: no unique matching " + "received advance found — link it manually." + )) + self._isdoc_apply_advance_accounts(invoice) + return res + + def _isdoc_apply_advance_accounts(self, invoice): + company = invoice.company_id + advance = invoice.advance_purchase_order_id + net_account = ( + company.advance_paid_account_lt_id + if advance and advance.advance_invoice_long_term + and company.advance_paid_account_lt_id + else company.advance_paid_account_id + ) + if net_account: + invoice.invoice_line_ids.filtered( + lambda line: line.display_type == "product" + ).account_id = net_account + clearing = company.advance_paid_clearing_account_id + if clearing: + invoice.line_ids.filtered( + lambda line: line.display_type == "payment_term" + ).account_id = clearing + + # ------------------------------------------------------------------ + # DocumentType 4 — zálohová faktura (non-tax proforma) + # ------------------------------------------------------------------ + def _isdoc_import_proforma_advance(self, invoice, tree): + q = self._q + gross = tree.findtext( + f'{q("LegalMonetaryTotal")}/{q("TaxInclusiveAmount")}' + ) or tree.findtext(f'{q("LegalMonetaryTotal")}/{q("PayableAmount")}') + try: + gross_amount = float(gross) + except (TypeError, ValueError): + return [self.env._( + "ISDOC proforma (DocumentType 4): could not read the " + "document total; no received advance was created." + )] + product = self.env.ref( + "account_edi_isdoc_purchase_advance.product_imported_advance" + ) + order = self.env["purchase.order"].create({ + "partner_id": invoice.partner_id.id, + "company_id": invoice.company_id.id, + "currency_id": invoice.currency_id.id, + "is_advance_invoice": True, + "partner_ref": invoice.ref or False, + "order_line": [ + Command.create({ + "product_id": product.id, + "name": self.env._( + "Advance per %s", invoice.ref or "ISDOC" + ), + "product_qty": 1, + "price_unit": gross_amount, + "tax_ids": [Command.clear()], + }) + ], + }) + invoice.advance_purchase_order_id = order + invoice.message_post(body=self.env._( + "This ISDOC is a proforma advance invoice (DocumentType 4) — " + "it must not be posted as a vendor bill. Received advance " + "%s was created; pay and track it there, then delete this " + "draft.", order.name, + )) + return [self.env._( + "ISDOC proforma (DocumentType 4): received advance %s created " + "over %s %s. Do not post this draft bill.", + order.name, f"{gross_amount:.2f}", invoice.currency_id.name, + )] + + # ------------------------------------------------------------------ + # DocumentType 5/6 — daňový doklad k přijaté platbě (+ its refund) + # ------------------------------------------------------------------ + def _isdoc_import_advance_tax_document(self, invoice, tree): + """Journal and tax-point routing; the advance matching and account + rewrites run later in _import_invoice_ubl_cii, once the amounts and + dynamic lines are final.""" + company = invoice.company_id + journal = company.advance_purchase_journal_id + if journal and invoice.journal_id != journal: + invoice.journal_id = journal + tax_point = tree.findtext(self._q("TaxPointDate")) + if tax_point and "taxable_supply_date" in invoice._fields: + invoice.taxable_supply_date = tax_point + return [self.env._( + "ISDOC advance tax document (DocumentType %s) — routed to the " + "advance journal.", + (tree.findtext(self._q("DocumentType")) or "").strip(), + )] + + def _isdoc_find_advance_order(self, invoice, tree): + """Unique open received advance for this document, or (None, ""). + Precedence: supplier's order/original-document reference naming the + advance, then variable symbol digits, then a single candidate whose + paid amount equals the document total. Ambiguity bails out.""" + q = self._q + candidates = self.env["purchase.order"].search([ + ("company_id", "=", invoice.company_id.id), + ("is_advance_invoice", "=", True), + ("partner_id", "child_of", + invoice.partner_id.commercial_partner_id.id), + ("state", "not in", ("cancel",)), + ]) + if not candidates: + return None, "" + references = [ + tree.findtext( + f'{q("OrderReferences")}/{q("OrderReference")}' + f'/{q("ExternalOrderID")}' + ), + tree.findtext( + f'{q("OriginalDocumentReferences")}' + f'/{q("OriginalDocumentReference")}/{q("ID")}' + ), + ] + for reference in filter(None, references): + matched = candidates.filtered( + lambda o: o.name == reference.strip() + ) + if len(matched) == 1: + return matched, self.env._("order reference") + var_symbol = _digits(tree.findtext( + f'{q("PaymentMeans")}/{q("Payment")}/{q("Details")}' + f'/{q("VariableSymbol")}' + )) + if var_symbol: + matched = candidates.filtered( + lambda o: _digits(o.name).lstrip("0") + == var_symbol.lstrip("0") + ) + if len(matched) == 1: + return matched, self.env._("variable symbol") + waiting = candidates.filtered( + lambda o: o.advance_invoice_accounting_status == "waiting" + and o.currency_id.compare_amounts( + o.amount_paid, invoice.amount_total + ) == 0 + ) + if len(waiting) == 1: + return waiting, self.env._("paid amount") + return None, "" diff --git a/account_edi_isdoc_purchase_advance/tests/__init__.py b/account_edi_isdoc_purchase_advance/tests/__init__.py new file mode 100644 index 00000000..fdfadff9 --- /dev/null +++ b/account_edi_isdoc_purchase_advance/tests/__init__.py @@ -0,0 +1 @@ +from . import test_isdoc_purchase_advance diff --git a/account_edi_isdoc_purchase_advance/tests/test_isdoc_purchase_advance.py b/account_edi_isdoc_purchase_advance/tests/test_isdoc_purchase_advance.py new file mode 100644 index 00000000..760cc440 --- /dev/null +++ b/account_edi_isdoc_purchase_advance/tests/test_isdoc_purchase_advance.py @@ -0,0 +1,236 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from lxml import etree + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged + +ISDOC_NS = "http://isdoc.cz/namespace/2013" + +_SUPPLIER_PARTY = """ + + 29261783 + Dodavatel s.r.o. + Hlavni1 + Praha11000 + CZCZ + CZ29261783VAT + + + 46342958 + My Company + Vedlejsi2 + Brno60200 + CZCZ + CZ46342958VAT + +""" + + +def _proforma(ref="ZF-2026-77", gross="1210"): + return f""" + + 4 + {ref} + 44444444-4444-4444-4444-444444444444 + 2026-07-01 + false + http:// + CZK + 11 + {_SUPPLIER_PARTY} + + 1 + 1 + {gross} + {gross} + 0 + {gross} + {gross} + 00false + Zaloha + + + {gross}0{gross} + 0false + 0 + + {gross}{gross} + {gross} + +""" + + +def _tax_document(ref="DD-2026-55", order_ref="", vs="", net="1000", + tax="210", gross="1210"): + order_refs = ( + f"" + f"{order_ref}" + f"" + if order_ref else "" + ) + payment_means = ( + f"" + f"{gross}42" + f"
2026-07-15" + f"" + f"{vs}" + f"
" + if vs else "" + ) + return f""" + + 5 + {ref} + 55555555-5555-5555-5555-555555555555 + 2026-07-10 + 2026-07-08 + true + http:// + CZK + 11 + {order_refs} + {_SUPPLIER_PARTY} + + 1 + 1 + {net} + {gross} + {tax} + {net} + {gross} + 210true + Danovy doklad k platbe + + {payment_means} + + {net}{tax}{gross} + 21true + {tax} + + {net}{gross} + {gross} + +""" + + +@tagged("post_install", "-at_install") +class TestIsdocPurchaseAdvance(AccountTestInvoicingCommon): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env.user.group_ids |= cls.env.ref("purchase.group_purchase_manager") + cls.company = cls.env.company + Account = cls.env["account.account"] + cls.clearing = Account.create({ + "code": "314001", "name": "Paid advances clearing", + "account_type": "liability_payable", "reconcile": True, + }) + cls.net_advance = Account.create({ + "code": "314000", "name": "Paid advances", + "account_type": "asset_current", + }) + cls.adv_journal = cls.env["account.journal"].create({ + "name": "Advance Tax Documents (Purchase)", + "code": "PDADV", "type": "purchase", + }) + cls.company.advance_purchase_journal_id = cls.adv_journal + cls.company.advance_paid_clearing_account_id = cls.clearing + cls.company.advance_paid_account_id = cls.net_advance + cls.supplier = cls.env["res.partner"].create({ + "name": "Dodavatel s.r.o.", "vat": "CZ29261783", + "country_id": cls.env.ref("base.cz").id, + }) + cls.tax_purchase_21 = cls.env["account.tax"].create({ + "name": "DPH 21% (nákup)", "amount": 21.0, + "amount_type": "percent", "type_tax_use": "purchase", + "company_id": cls.company.id, + }) + + def _import(self, xml, move_type="in_invoice"): + invoice = self.env["account.move"].create({ + "move_type": move_type, + "journal_id": self.company_data["default_journal_purchase"].id, + }) + self.env["account.edi.xml.isdoc"]._import_invoice_ubl_cii( + invoice, + { + "xml_tree": etree.fromstring(xml.encode()), + "attachment": self.env["ir.attachment"], + }, + ) + return invoice + + def _paid_advance(self, price=1210.0): + order = self.env["purchase.order"].create({ + "partner_id": self.supplier.id, + "is_advance_invoice": True, + "order_line": [ + Command.create({ + "product_id": self.env.ref( + "account_edi_isdoc_purchase_advance" + ".product_imported_advance" + ).id, + "product_qty": 1, + "price_unit": price, + "tax_ids": [Command.clear()], + }) + ], + }) + bank_journal = self.company_data["default_journal_bank"] + method_line = bank_journal.outbound_payment_method_line_ids[:1] + if not method_line.payment_account_id: + method_line.payment_account_id = self.env["account.account"].create({ + "code": "OUTPAY", "name": "Outstanding Payments", + "account_type": "asset_current", "reconcile": True, + }) + wizard = self.env["purchase.advance.payment.wizard"].with_context( + active_model="purchase.order", active_id=order.id + ).create({ + "journal_id": bank_journal.id, + "payment_method_line_id": method_line.id, + }) + wizard.action_create_payment() + return order + + def test_proforma_creates_received_advance(self): + invoice = self._import(_proforma(ref="ZF-2026-77", gross="1210")) + order = invoice.advance_purchase_order_id + self.assertTrue(order) + self.assertTrue(order.is_advance_invoice) + self.assertTrue(order.name.startswith("PADV")) + self.assertEqual(order.partner_ref, "ZF-2026-77") + self.assertEqual(order.partner_id, self.supplier) + self.assertAlmostEqual(order.amount_total, 1210.0) + self.assertFalse(order.order_line.tax_ids) + + def test_tax_document_matched_by_order_reference(self): + advance = self._paid_advance() + invoice = self._import(_tax_document(order_ref=advance.name)) + self.assertEqual(invoice.journal_id, self.adv_journal) + self.assertEqual(invoice.advance_purchase_order_id, advance) + self.assertEqual(str(invoice.taxable_supply_date), "2026-07-08") + product_lines = invoice.invoice_line_ids.filtered( + lambda line: line.display_type == "product" + ) + self.assertEqual(product_lines.account_id, self.net_advance) + term_lines = invoice.line_ids.filtered( + lambda line: line.display_type == "payment_term" + ) + self.assertEqual(term_lines.account_id, self.clearing) + + def test_tax_document_matched_by_amount(self): + advance = self._paid_advance(price=1210.0) + invoice = self._import(_tax_document()) + self.assertEqual(invoice.advance_purchase_order_id, advance) + + def test_tax_document_ambiguous_bails(self): + self._paid_advance(price=1210.0) + self._paid_advance(price=1210.0) + invoice = self._import(_tax_document()) + self.assertFalse(invoice.advance_purchase_order_id) + # journal/accounts still routed — it IS an advance tax document + self.assertEqual(invoice.journal_id, self.adv_journal) diff --git a/account_edi_isdoc_sale/CHANGELOG.rst b/account_edi_isdoc_sale/CHANGELOG.rst new file mode 100644 index 00000000..e5b1aafa --- /dev/null +++ b/account_edi_isdoc_sale/CHANGELOG.rst @@ -0,0 +1,30 @@ +========= +Changelog +========= + +All notable changes to **account_edi_isdoc_sale** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +Changed +~~~~~~~ + +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.1.0.0] — 2026-07-04 +------------------------- + +Added +~~~~~ + +- Bridges sale orders to ISDOC: a sale order rendered as a pro-forma can be + exported as an ISDOC 6.0.2 proforma — **DocumentType 4** (zálohová faktura, a + request for advance payment). +- Incoming proformas are decoded by the base module (``account_edi_isdoc``) into a + draft vendor bill, since a proforma is not a bookable tax document. +- Auto-installs when both ``account_edi_isdoc`` and ``sale`` are present. diff --git a/account_edi_isdoc_sale/LICENSE b/account_edi_isdoc_sale/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/account_edi_isdoc_sale/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/account_edi_isdoc_sale/__init__.py b/account_edi_isdoc_sale/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/account_edi_isdoc_sale/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_edi_isdoc_sale/__manifest__.py b/account_edi_isdoc_sale/__manifest__.py new file mode 100644 index 00000000..9c8a863c --- /dev/null +++ b/account_edi_isdoc_sale/__manifest__.py @@ -0,0 +1,23 @@ +{ + 'name': "ISDOC for Sales (proforma)", + 'version': '19.0.1.0.0', + 'category': 'Accounting/Accounting', + 'summary': "Export a sale order as an ISDOC proforma (zálohová faktura, DocumentType 4)", + 'description': """ +ISDOC proforma from sale orders +============================== + +In Odoo a pro-forma is not a separate record but a sale order rendered as a +pro-forma document. This bridge lets such a sale order be exported as an ISDOC +6.0.2 proforma (DocumentType 4 — zálohová faktura, a request for advance payment). + +Incoming proformas are decoded by the base module (account_edi_isdoc) into a +draft vendor bill, since a proforma is not a bookable tax document. +""", + 'depends': ['account_edi_isdoc', 'sale'], + 'data': [], + 'installable': True, + 'auto_install': True, + 'author': "Data Dance s.r.o.", + "license": "AGPL-3", +} diff --git a/account_edi_isdoc_sale/models/__init__.py b/account_edi_isdoc_sale/models/__init__.py new file mode 100644 index 00000000..1a2c41ed --- /dev/null +++ b/account_edi_isdoc_sale/models/__init__.py @@ -0,0 +1,2 @@ +from . import account_edi_xml_isdoc +from . import sale_order diff --git a/account_edi_isdoc_sale/models/account_edi_xml_isdoc.py b/account_edi_isdoc_sale/models/account_edi_xml_isdoc.py new file mode 100644 index 00000000..31a1cd12 --- /dev/null +++ b/account_edi_isdoc_sale/models/account_edi_xml_isdoc.py @@ -0,0 +1,104 @@ +import re +import uuid + +from lxml import etree + +from odoo import fields, models +from odoo.addons.account.tools import dict_to_xml +from odoo.addons.account_edi_isdoc.models.account_edi_xml_isdoc import ( + ISDOC_NS, ISDOC_VERSION, ISDOC_DOCUMENT_TYPE, +) +from odoo.addons.account_edi_isdoc.tools import isdoc_invoice + + +class AccountEdiXmlISDOC(models.AbstractModel): + _inherit = 'account.edi.xml.isdoc' + + def _export_order_proforma(self, order): + """ Generate an ISDOC 6.0.2 proforma (DocumentType 4) from a sale order. """ + self._validate_taxes(order.order_line.tax_ids) + if not order.isdoc_uuid: + order.isdoc_uuid = str(uuid.uuid4()).upper() + + company = order.company_id + currency = order.currency_id + dp = currency.decimal_places + lines = order.order_line.filtered(lambda line: not line.display_type) + + # The shared total/monetary builders only read currency_id + amount_* (present on + # sale.order too), so we duck-type the order as the 'invoice' with no deposits. + vals = { + 'invoice': order, + 'regular_lines': lines, + 'deposit_lines': order.order_line.browse(), + } + line_nodes = [ + self._isdoc_line_node( + index, line.name, line.product_uom_qty or 0.0, line.price_subtotal, line.price_total, + self._line_tax_percent(line), line.product_uom_id.name, line.product_id, dp) + for index, line in enumerate(lines, 1) + ] + issue_date = (order.date_order or fields.Datetime.now()).date().isoformat() + + node = { + 'version': ISDOC_VERSION, + 'DocumentType': self._text(ISDOC_DOCUMENT_TYPE['proforma']), + 'ID': self._text(order.name), + 'UUID': self._text(order.isdoc_uuid), + 'IssuingSystem': self._text("Odoo"), + 'IssueDate': self._text(issue_date), + 'VATApplicable': self._text('true' if order.amount_tax else 'false'), + 'ElectronicPossibilityAgreementReference': { + '_text': company.partner_id.website or 'http://', + 'languageID': 'cs', + }, + 'LocalCurrencyCode': self._text(currency.name), + 'CurrRate': self._text('1'), + 'RefCurrRate': self._text('1'), + 'AccountingSupplierParty': {'Party': self._get_party_node(company.partner_id)}, + 'AccountingCustomerParty': {'Party': self._get_party_node(order.partner_id.commercial_partner_id)}, + 'InvoiceLines': {'InvoiceLine': line_nodes}, + 'TaxTotal': self._get_tax_total_node(vals), + 'LegalMonetaryTotal': self._get_monetary_total_node(vals), + } + payment_means = self._get_order_payment_means(order, dp) + if payment_means: + node['PaymentMeans'] = payment_means + + xml_content = dict_to_xml(node, nsmap={None: ISDOC_NS}, template=isdoc_invoice.Invoice) + self._apply_namespace(xml_content) + self._postprocess_bank_groups(xml_content) + + errors = [c for c in self._export_order_constraints(order).values() if c] + return etree.tostring(xml_content, xml_declaration=True, encoding='UTF-8'), set(errors) + + def _get_order_payment_means(self, order, dp): + bank = order.company_id.partner_id.bank_ids[:1] + if not bank: + return None + details = {} + if order.validity_date: + details['PaymentDueDate'] = self._text(order.validity_date.isoformat()) + account_id, bank_code, iban = bank._cz_account_parts() # from l10n_cz_base + details['ID'] = self._text(account_id) + details['BankCode'] = self._text(bank_code) + details['Name'] = self._text(bank.bank_id.name or '') + details['IBAN'] = self._text(iban) + details['BIC'] = self._text(bank.bank_id.bic or '') + var_symbol = re.sub(r'\D', '', order.name or '') + if var_symbol: + details['VariableSymbol'] = self._text(var_symbol) + return {'Payment': { + 'PaidAmount': self._text(self.format_float(order.amount_total, dp)), + 'PaymentMeansCode': self._text('42'), + 'Details': details, + }} + + def _export_order_constraints(self, order): + constraints = {} + for role, partner in (('supplier', order.company_id.partner_id), + ('customer', order.partner_id.commercial_partner_id)): + constraints[f'isdoc_{role}_name'] = self._check_required_fields(partner, 'name') + constraints[f'isdoc_{role}_address'] = self._check_required_fields(partner, ['street', 'city', 'zip']) + constraints[f'isdoc_{role}_country'] = self._check_required_fields(partner, 'country_id') + return constraints diff --git a/account_edi_isdoc_sale/models/sale_order.py b/account_edi_isdoc_sale/models/sale_order.py new file mode 100644 index 00000000..246ed60b --- /dev/null +++ b/account_edi_isdoc_sale/models/sale_order.py @@ -0,0 +1,14 @@ +from odoo import fields, models + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + # ISDOC mandates a document ; persist the one we generate so a re-exported + # proforma keeps a stable identifier. + isdoc_uuid = fields.Char(string="ISDOC UUID", copy=False) + + def _export_isdoc_proforma(self): + """ Return (xml_bytes, errors) for this order as an ISDOC proforma (DocumentType 4). """ + self.ensure_one() + return self.env['account.edi.xml.isdoc']._export_order_proforma(self) diff --git a/account_edi_isdoc_sale/static/description/icon.png b/account_edi_isdoc_sale/static/description/icon.png new file mode 100644 index 00000000..1ca2c7d5 Binary files /dev/null and b/account_edi_isdoc_sale/static/description/icon.png differ diff --git a/account_edi_isdoc_sale/tests/__init__.py b/account_edi_isdoc_sale/tests/__init__.py new file mode 100644 index 00000000..8cf681eb --- /dev/null +++ b/account_edi_isdoc_sale/tests/__init__.py @@ -0,0 +1 @@ +from . import test_isdoc_proforma diff --git a/account_edi_isdoc_sale/tests/test_isdoc_proforma.py b/account_edi_isdoc_sale/tests/test_isdoc_proforma.py new file mode 100644 index 00000000..127e4f72 --- /dev/null +++ b/account_edi_isdoc_sale/tests/test_isdoc_proforma.py @@ -0,0 +1,130 @@ +from lxml import etree + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo.tools import file_open + +ISDOC_NS = "http://isdoc.cz/namespace/2013" + +# A minimal incoming proforma (DocumentType 4): one 1000 + 21% line, payable 1210. +PROFORMA = f""" + + 4 + ZAL-2024-001 + 22222222-2222-2222-2222-222222222222 + 2024-07-10 + true + http:// + CZK + 1 + 1 + + 29261783 + Dodavatel s.r.o. + Hlavní1 + Praha11000 + CZČeská republika + CZ29261783VAT + + + 25663585 + Odberatel s.r.o. + Vedlejší2 + Brno60200 + CZČeská republika + CZ25663585VAT + + + 1 + 1 + 1000 + 1210 + 210 + 1000 + 1210 + 210true + Záloha na dodávku + + + 10002101210 + 000 + 10002101210 + 21true + 210 + + 10001210 + 00 + 10001210 + 01210 + +""" + + +@tagged('post_install', '-at_install') +class TestISDOCProforma(AccountTestInvoicingCommon): + + @classmethod + def _create_company(cls, **create_values): + create_values.setdefault('currency_id', cls.env.ref('base.CZK').id) + company = super()._create_company(**create_values) + company.country_id = cls.env.ref('base.cz') + company.tax_calculation_rounding_method = 'round_globally' + return company + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data['company'] + cls.purchase_journal = cls.company_data['default_journal_purchase'] + cls.company.partner_id.write({ + 'street': "Příhon 943", 'zip': "696 15", 'city': "Čejkovice", + 'vat': 'CZ46342958', 'company_registry': '46342958'}) + cls.company.partner_id.bank_ids = [Command.create({'acc_number': '2631172369/0800'})] + cls.cz_customer = cls.env['res.partner'].create({ + 'name': "Odběratel s.r.o.", 'street': "Vedlejší 2", 'zip': "60200", 'city': "Brno", + 'vat': 'CZ25663585', 'company_registry': '25663585', 'country_id': cls.env.ref('base.cz').id}) + cls.tax_sale_21 = cls.env['account.tax'].create({ + 'name': "DPH 21%", 'amount': 21.0, 'amount_type': 'percent', + 'type_tax_use': 'sale', 'company_id': cls.company.id}) + cls.tax_purchase_21 = cls.env['account.tax'].create({ + 'name': "DPH 21% (nákup)", 'amount': 21.0, 'amount_type': 'percent', + 'type_tax_use': 'purchase', 'company_id': cls.company.id}) + with file_open('account_edi_isdoc/tests/schema/isdoc-invoice-6.0.2.xsd', 'rb') as f: + cls.schema = etree.XMLSchema(etree.parse(f)) + + def _find(self, root, path): + return root.findtext('/'.join(f'{{{ISDOC_NS}}}{p}' for p in path.split('/'))) + + def test_export_proforma_from_sale_order(self): + order = self.env['sale.order'].with_company(self.company).sudo().create({ + 'partner_id': self.cz_customer.id, + 'order_line': [Command.create({ + 'product_id': self.product_a.id, 'product_uom_qty': 2, 'price_unit': 500.0, + 'tax_ids': [Command.set(self.tax_sale_21.ids)], + })], + }) + xml, errors = order._export_isdoc_proforma() + self.assertFalse(errors) + root = etree.fromstring(xml) + self.schema.assertValid(root) + self.assertEqual(self._find(root, 'DocumentType'), '4') # proforma + self.assertEqual(self._find(root, 'ID'), order.name) + self.assertTrue(order.isdoc_uuid) + self.assertEqual(self._find(root, 'LegalMonetaryTotal/PayableAmount'), + f'{order.amount_total:.2f}') + self.assertEqual(len(root.findall(f'{{{ISDOC_NS}}}InvoiceLines/{{{ISDOC_NS}}}InvoiceLine')), 1) + # Bank account on the proforma (the company's, where the advance is paid). + self.assertEqual(self._find(root, 'PaymentMeans/Payment/Details/BankCode'), '0800') + + def test_import_proforma_as_draft_bill(self): + invoice = self.env['account.move'].with_company(self.company).create({ + 'move_type': 'in_invoice', 'journal_id': self.purchase_journal.id}) + self.env['account.edi.xml.isdoc'].with_company(self.company)._import_invoice_ubl_cii( + invoice, {'xml_tree': etree.fromstring(PROFORMA.encode()), + 'attachment': self.env['ir.attachment']}) + # A proforma is not a tax document -> it lands as a DRAFT vendor bill. + self.assertEqual(invoice.state, 'draft') + self.assertEqual(invoice.move_type, 'in_invoice') + self.assertEqual(invoice.ref, 'ZAL-2024-001') + self.assertAlmostEqual(invoice.amount_total, 1210.0, places=2) diff --git a/account_edi_isdoc_sale_advance/CHANGELOG.rst b/account_edi_isdoc_sale_advance/CHANGELOG.rst new file mode 100644 index 00000000..3dce1fe7 --- /dev/null +++ b/account_edi_isdoc_sale_advance/CHANGELOG.rst @@ -0,0 +1,37 @@ +========= +Changelog +========= + +All notable changes to **account_edi_isdoc_sale_advance** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +Changed +~~~~~~~ + +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.1.0.0] — 2026-07-04 +------------------------- + +Added +~~~~~ + +- Maps the Czech advance-payment document chain (``sale_order_advance_invoice``) to + ISDOC document types: + + - zálohová faktura (``is_advance_invoice`` sale order) → **DocumentType 4** + (non-VAT proforma), via ``account_edi_isdoc_sale``. + - daňový doklad k přijaté platbě (``is_advance_invoice_tax_document``) → + **DocumentType 5** (with VAT); its credit note → **DocumentType 6**. + - final invoice: ``is_advance_tracking`` deduction lines are reported as + ``TaxedDeposits`` / already-claimed amounts on a normal **DocumentType 1** + invoice, each referencing its tax document. + +- Auto-installs when both ``account_edi_isdoc_sale`` and ``sale_order_advance_invoice`` + are present. diff --git a/account_edi_isdoc_sale_advance/LICENSE b/account_edi_isdoc_sale_advance/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/account_edi_isdoc_sale_advance/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/account_edi_isdoc_sale_advance/__init__.py b/account_edi_isdoc_sale_advance/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/account_edi_isdoc_sale_advance/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_edi_isdoc_sale_advance/__manifest__.py b/account_edi_isdoc_sale_advance/__manifest__.py new file mode 100644 index 00000000..e4c79d67 --- /dev/null +++ b/account_edi_isdoc_sale_advance/__manifest__.py @@ -0,0 +1,27 @@ +{ + 'name': "ISDOC for Czech Advance Invoices", + 'version': '19.0.1.0.0', + 'category': 'Accounting/Accounting', + 'summary': "Map the Czech advance-payment documents to ISDOC DocumentType 4/5/6", + 'description': """ +ISDOC mapping for Czech advance invoices +======================================= + +Integrates `sale_order_advance_invoice` (zálohová faktura) with ISDOC: + +* zálohová faktura — an ``is_advance_invoice`` sale order — exports as ISDOC + **DocumentType 4** (non-VAT proforma), via account_edi_isdoc_sale. +* daňový doklad k přijaté platbě — the tax document created in the advance journal + (``is_advance_invoice_tax_document``) — exports as **DocumentType 5** (with VAT), + its credit note as **6**. +* the final invoice deducts the advances: the ``is_advance_tracking`` deduction + lines are reported as ``TaxedDeposits`` / already-claimed amounts on a normal + **DocumentType 1** invoice, each referencing its tax document. +""", + 'depends': ['account_edi_isdoc_sale', 'sale_order_advance_invoice'], + 'data': [], + 'installable': True, + 'auto_install': True, + 'author': "Data Dance s.r.o.", + "license": "AGPL-3", +} diff --git a/account_edi_isdoc_sale_advance/models/__init__.py b/account_edi_isdoc_sale_advance/models/__init__.py new file mode 100644 index 00000000..7703f6ef --- /dev/null +++ b/account_edi_isdoc_sale_advance/models/__init__.py @@ -0,0 +1 @@ +from . import account_edi_xml_isdoc diff --git a/account_edi_isdoc_sale_advance/models/account_edi_xml_isdoc.py b/account_edi_isdoc_sale_advance/models/account_edi_xml_isdoc.py new file mode 100644 index 00000000..6ae8fea0 --- /dev/null +++ b/account_edi_isdoc_sale_advance/models/account_edi_xml_isdoc.py @@ -0,0 +1,36 @@ +import re + +from odoo import models +from odoo.addons.account_edi_isdoc.models.account_edi_xml_isdoc import ISDOC_DOCUMENT_TYPE + + +class AccountEdiXmlISDOC(models.AbstractModel): + _inherit = 'account.edi.xml.isdoc' + + def _get_document_type_code(self, invoice): + # daňový doklad k přijaté platbě = tax document for a received advance (with VAT). + if invoice.is_advance_invoice_tax_document: + is_refund = invoice.move_type in ('out_refund', 'in_refund') + return ISDOC_DOCUMENT_TYPE['advance_credit'] if is_refund else ISDOC_DOCUMENT_TYPE['advance'] + return super()._get_document_type_code(invoice) + + def _isdoc_deduction_lines(self, product_lines): + # EXTENDS core: a Czech settlement invoice deducts advances through tracking lines + # (is_advance_tracking) rather than generic is_downpayment lines. + lines = super()._isdoc_deduction_lines(product_lines) + lines |= product_lines.filtered(lambda l: l.sale_line_ids.filtered('is_advance_tracking')) + return lines + + def _isdoc_deposit_source(self, line): + # Resolve the advance's identity from its tax document (daňový doklad k přijaté platbě), + # falling back to the advance order (zálohová faktura) if no tax document is posted yet. + tracking = line.sale_line_ids.filtered('is_advance_tracking') + advance = tracking.advance_source_order_id[:1] + if not advance: + return super()._isdoc_deposit_source(line) + tax_docs = advance.invoice_ids.filtered( + lambda m: m.move_type in ('out_invoice', 'out_refund') and m.state == 'posted') + source = tax_docs[:1] or advance + doc_id = source.name + var_symbol = source.variable_symbol if 'variable_symbol' in source._fields else '' + return doc_id, (var_symbol or re.sub(r'\D', '', doc_id or '') or '0') diff --git a/account_edi_isdoc_sale_advance/static/description/icon.png b/account_edi_isdoc_sale_advance/static/description/icon.png new file mode 100644 index 00000000..1ca2c7d5 Binary files /dev/null and b/account_edi_isdoc_sale_advance/static/description/icon.png differ diff --git a/account_edi_isdoc_sale_advance/tests/__init__.py b/account_edi_isdoc_sale_advance/tests/__init__.py new file mode 100644 index 00000000..b259a2e1 --- /dev/null +++ b/account_edi_isdoc_sale_advance/tests/__init__.py @@ -0,0 +1 @@ +from . import test_isdoc_advance_cz diff --git a/account_edi_isdoc_sale_advance/tests/test_isdoc_advance_cz.py b/account_edi_isdoc_sale_advance/tests/test_isdoc_advance_cz.py new file mode 100644 index 00000000..7e87651d --- /dev/null +++ b/account_edi_isdoc_sale_advance/tests/test_isdoc_advance_cz.py @@ -0,0 +1,114 @@ +from lxml import etree + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo.tools import file_open + +ISDOC_NS = "http://isdoc.cz/namespace/2013" + + +@tagged('post_install', '-at_install') +class TestISDOCAdvanceCZ(AccountTestInvoicingCommon): + + @classmethod + def _create_company(cls, **create_values): + create_values.setdefault('currency_id', cls.env.ref('base.CZK').id) + # sale_order_advance_invoice defaults this required field to the main company's + # journal, which breaks the fresh test company (cross-company). Skip the default, + # then assign a journal that belongs to the new company. + create_values['advance_invoice_journal_id'] = False + company = super()._create_company(**create_values) + company.country_id = cls.env.ref('base.cz') + company.tax_calculation_rounding_method = 'round_globally' + company.advance_invoice_journal_id = cls.env['account.journal'].create({ + 'name': "Tax Documents for Advance Invoices", 'code': 'TDADV', + 'type': 'sale', 'company_id': company.id}) + return company + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data['company'] + cls.builder = cls.env['account.edi.xml.isdoc'] + cls.company.partner_id.write({ + 'street': "Příhon 943", 'zip': "696 15", 'city': "Čejkovice", + 'vat': 'CZ46342958', 'company_registry': '46342958'}) + cls.cz_customer = cls.env['res.partner'].create({ + 'name': "Odběratel s.r.o.", 'street': "Vedlejší 2", 'zip': "60200", 'city': "Brno", + 'vat': 'CZ25663585', 'company_registry': '25663585', 'country_id': cls.env.ref('base.cz').id}) + cls.tax_sale_21 = cls.env['account.tax'].create({ + 'name': "DPH 21%", 'amount': 21.0, 'amount_type': 'percent', + 'type_tax_use': 'sale', 'company_id': cls.company.id}) + with file_open('account_edi_isdoc/tests/schema/isdoc-invoice-6.0.2.xsd', 'rb') as f: + cls.schema = etree.XMLSchema(etree.parse(f)) + + def _find(self, root, path): + return root.findtext('/'.join(f'{{{ISDOC_NS}}}{p}' for p in path.split('/'))) + + def _advance_order(self): + return self.env['sale.order'].with_company(self.company).sudo().create({ + 'partner_id': self.cz_customer.id, + 'is_advance_invoice': True, + 'order_line': [Command.create({ + 'product_id': self.product_a.id, 'product_uom_qty': 1, 'price_unit': 1000.0, + 'is_downpayment': True, 'tax_ids': [Command.set(self.tax_sale_21.ids)], + })], + }) + + def test_advance_order_exports_as_proforma_type_4(self): + # The zálohová faktura (an is_advance_invoice sale order) -> DocumentType 4. + order = self._advance_order() + xml, errors = order._export_isdoc_proforma() + self.assertFalse(errors) + root = etree.fromstring(xml) + self.schema.assertValid(root) + self.assertEqual(self._find(root, 'DocumentType'), '4') + + def test_tax_document_exports_as_type_5(self): + # A move linked to an advance order's line is the daňový doklad k přijaté platbě -> 5. + advance = self._advance_order() + move = self.env['account.move'].with_company(self.company).sudo().create({ + 'move_type': 'out_invoice', 'partner_id': self.cz_customer.id, 'invoice_date': '2024-07-10', + 'invoice_line_ids': [Command.create({ + 'product_id': self.product_a.id, 'quantity': 1, 'price_unit': 1000.0, + 'tax_ids': [Command.set(self.tax_sale_21.ids)], + 'sale_line_ids': [Command.set(advance.order_line.ids)], + })], + }) + self.assertTrue(move.is_advance_invoice_tax_document) + self.assertEqual(self.builder._get_document_type_code(move), '5') + + move.action_post() # gives the move a name so is populated + xml, errors = self.builder._export_invoice(move) + self.assertFalse(errors) + root = etree.fromstring(xml) + self.schema.assertValid(root) + self.assertEqual(self._find(root, 'DocumentType'), '5') + + def test_settlement_deduction_routed_to_deposit(self): + # On a final invoice, an is_advance_tracking deduction line -> TaxedDeposit, not a line. + advance = self._advance_order() + parent = self.env['sale.order'].with_company(self.company).sudo().create({ + 'partner_id': self.cz_customer.id}) + tracking = self.env['sale.order.line'].sudo().create({ + 'order_id': parent.id, 'product_id': self.product_a.id, 'product_uom_qty': 0.0, + 'price_unit': 500.0, 'name': "Advance deduction", + 'is_advance_tracking': True, 'advance_source_order_id': advance.id}) + + move = self.env['account.move'].with_company(self.company).sudo().create({ + 'move_type': 'out_invoice', 'partner_id': self.cz_customer.id, 'invoice_date': '2024-07-10', + 'invoice_line_ids': [ + Command.create({'product_id': self.product_a.id, 'quantity': 1, 'price_unit': 1000.0, + 'tax_ids': [Command.set(self.tax_sale_21.ids)]}), + Command.create({'product_id': self.product_a.id, 'quantity': 1, 'price_unit': -500.0, + 'tax_ids': [Command.set(self.tax_sale_21.ids)], + 'sale_line_ids': [Command.set(tracking.ids)]}), + ], + }) + regular_lines, deposit_lines = self.builder._isdoc_split_lines(move) + self.assertEqual(len(regular_lines), 1) + self.assertEqual(len(deposit_lines), 1) + # Deposit identity falls back to the advance order (no posted tax document yet). + doc_id, _vs = self.builder._isdoc_deposit_source(deposit_lines) + self.assertEqual(doc_id, advance.name) diff --git a/account_fio/CHANGELOG.rst b/account_fio/CHANGELOG.rst index 05f30db2..fc9a03b9 100644 --- a/account_fio/CHANGELOG.rst +++ b/account_fio/CHANGELOG.rst @@ -128,8 +128,8 @@ Carry-over to 18.0 release is the 18.0 -> 19.0 port that settles it. Original note, kept for the record: everything under 19.0.1.1.0 plus the - unreleased entries landed on ``19.0-fio-api`` only; DURWEN runs the 18.0 - copies vendored into ``DURWEN_CZ/addons``, which already carried the + unreleased entries landed on ``19.0-fio-api`` only; a customer runs the 18.0 + copies vendored into its own repository, which already carried the 19.0.1.1.0 fixes while the ``18.0-fio-api`` branch did not. [19.0.2.1.0] — 2026-09-01 @@ -239,7 +239,7 @@ Fixed ``Exception``, not a ``UserError``, so over RPC the diagnosis — which is the entire value of that button — was stripped and the dialog said nothing. It now raises ``UserError`` the way the upload and pull paths already did. - Found on DURWEN staging with a stale token. + Found on a customer's staging database with a stale token. [19.0.1.1.0] — 2026-08-24 ------------------------- diff --git a/account_fio/tests/test_fio_journal.py b/account_fio/tests/test_fio_journal.py index 52f7c1aa..39d9e84c 100644 --- a/account_fio/tests/test_fio_journal.py +++ b/account_fio/tests/test_fio_journal.py @@ -40,7 +40,7 @@ def test_test_connection_records_what_the_bank_says(self): def test_a_rejected_token_reaches_the_user_as_a_message(self): """FioError is a plain Exception, so an unwrapped one leaves the button showing a bare "Internal server error" — losing the only diagnosis the - user gets. Found on DURWEN staging with a stale token.""" + user gets. Found on a customer's staging database with a stale token.""" from odoo.addons.account_fio_base.utils.client import FioError with patch.object( diff --git a/account_fio_base/CHANGELOG.rst b/account_fio_base/CHANGELOG.rst index fef3f582..41d09f85 100644 --- a/account_fio_base/CHANGELOG.rst +++ b/account_fio_base/CHANGELOG.rst @@ -25,8 +25,8 @@ Carry-over to 18.0 release is the 18.0 -> 19.0 port that settles it. Original note, kept for the record: everything under 19.0.1.1.0 plus the - unreleased entries landed on ``19.0-fio-api`` only; DURWEN runs the 18.0 - copies vendored into ``DURWEN_CZ/addons``, which already carried the + unreleased entries landed on ``19.0-fio-api`` only; a customer runs the 18.0 + copies vendored into its own repository, which already carried the 19.0.1.1.0 fixes while the ``18.0-fio-api`` branch did not. [19.0.1.2.0] — 2026-09-01 diff --git a/account_payment_fio/CHANGELOG.rst b/account_payment_fio/CHANGELOG.rst index 540dea6d..95902044 100644 --- a/account_payment_fio/CHANGELOG.rst +++ b/account_payment_fio/CHANGELOG.rst @@ -40,8 +40,8 @@ Carry-over to 18.0 release is the 18.0 -> 19.0 port that settles it. Original note, kept for the record: everything under 19.0.1.1.0 plus the - unreleased entries landed on ``19.0-fio-api`` only; DURWEN runs the 18.0 - copies vendored into ``DURWEN_CZ/addons``, which already carried the + unreleased entries landed on ``19.0-fio-api`` only; a customer runs the 18.0 + copies vendored into its own repository, which already carried the 19.0.1.1.0 fixes while the ``18.0-fio-api`` branch did not. [19.0.1.2.0] — 2026-09-01 diff --git a/account_qr_code_frame_provider/__manifest__.py b/account_qr_code_frame_provider/__manifest__.py index faec5d5a..7d5d6372 100644 --- a/account_qr_code_frame_provider/__manifest__.py +++ b/account_qr_code_frame_provider/__manifest__.py @@ -2,6 +2,8 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "QR Code Frame Provider", + "summary": "Shared QR-code frame parameters for the payment-QR providers (PAY by square, payme, " + "QR Platba) that build on it.", "version": "19.0.1.1.0", "category": "Accounting/Payment", "author": "Data Dance s.r.o.", diff --git a/account_qr_code_pay_by_square_sk/__manifest__.py b/account_qr_code_pay_by_square_sk/__manifest__.py index 21c10c4b..f9a1db1e 100644 --- a/account_qr_code_pay_by_square_sk/__manifest__.py +++ b/account_qr_code_pay_by_square_sk/__manifest__.py @@ -2,6 +2,9 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "PAY by square (Slovakia)", + "summary": "Slovak PAY by square payment QR code on invoices — the Slovak Banking Association " + "standard every Slovak banking app reads, pre-filling IBAN, amount, currency, " + "beneficiary and message.", "version": "19.0.1.2.0", "category": "Accounting/Payment", "author": "Data Dance s.r.o.", diff --git a/account_qr_code_payme_sk/__manifest__.py b/account_qr_code_payme_sk/__manifest__.py index a70cfbf2..bcd89830 100644 --- a/account_qr_code_payme_sk/__manifest__.py +++ b/account_qr_code_payme_sk/__manifest__.py @@ -2,6 +2,9 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "payme (Slovakia)", + "summary": "Slovak payme payment QR code on invoices — a payme.sk link that opens the " + "customer's banking app with IBAN, amount, currency and payment identification " + "pre-filled.", "version": "19.0.1.2.0", "category": "Accounting/Payment", "author": "Data Dance s.r.o.", diff --git a/account_qr_code_qr_platba_cz/__manifest__.py b/account_qr_code_qr_platba_cz/__manifest__.py index f494e389..49befc3d 100644 --- a/account_qr_code_qr_platba_cz/__manifest__.py +++ b/account_qr_code_qr_platba_cz/__manifest__.py @@ -2,6 +2,9 @@ # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). { "name": "QR Platba (Czech Republic)", + "summary": "Czech QR Platba payment QR code on invoices — the Czech Banking Association short " + "payment descriptor (SPD) every Czech banking app reads, pre-filling IBAN, amount, " + "currency, message and the variable symbol.", "version": "19.0.1.1.1", "category": "Accounting/Payment", "author": "Data Dance s.r.o.", diff --git a/account_statement_fio/CHANGELOG.rst b/account_statement_fio/CHANGELOG.rst index 5953c616..3fc7dc53 100644 --- a/account_statement_fio/CHANGELOG.rst +++ b/account_statement_fio/CHANGELOG.rst @@ -160,8 +160,8 @@ Carry-over to 18.0 recorded in the 18.0 CHANGELOGs under "Carry-over to 19.0". Original note, kept for the record: everything under 19.0.1.1.0 plus the - unreleased entries landed on ``19.0-fio-api`` only; DURWEN runs the 18.0 - copies vendored into ``DURWEN_CZ/addons``, which already carried the + unreleased entries landed on ``19.0-fio-api`` only; a customer runs the 18.0 + copies vendored into its own repository, which already carried the 19.0.1.1.0 fixes while the ``18.0-fio-api`` branch did not. [19.0.2.2.0] — 2026-09-01 diff --git a/edi_base/CHANGELOG.rst b/edi_base/CHANGELOG.rst index e3103d42..8dc02bfa 100644 --- a/edi_base/CHANGELOG.rst +++ b/edi_base/CHANGELOG.rst @@ -78,7 +78,7 @@ Migration definitions to ``edi_base_gs1`` and schedules that module for install where ``edi_editel_base``, ``edi_grit_base`` or ``edi_base_purchase`` is present. **Upgrade with ``-u edi_base`` (or ``-u all``).** -- Tested on a database mirroring Zlaté Zrnko's installed set (one partner +- Tested on a database mirroring a customer's installed set (one partner with a communication endpoint, four with SSCC mode, one warehouse GLN): the migration reassigns the three definitions, ``edi_base_gs1`` installs in the same run, and all three values survive. diff --git a/edi_base_peppol/CHANGELOG.rst b/edi_base_peppol/CHANGELOG.rst index c492215b..a0c5b26e 100644 --- a/edi_base_peppol/CHANGELOG.rst +++ b/edi_base_peppol/CHANGELOG.rst @@ -5,6 +5,32 @@ Changelog All notable changes to **edi_base_peppol** are documented here. Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. +[Unreleased] +------------ + +Fixed +~~~~~ + +- **The 19.0.1.4.0 upgrade could not run on a Method A database.** Its + post-migrate set the new company settings through ``company.write()``, and + core's ``res.company.write()`` calls ``_set_category_defaults()``, which + ``stock_account`` overrides to write an ``ir.default`` for + ``product.category.property_valuation``. An upgrade builds the registry one + module at a time, and this one loads after ``stock_account`` but before + ``stock_account_method_a`` — the module that adds ``perpetual`` to that + selection — so the write raised *Invalid value for + product.category.property_valuation: perpetual* and rolled the whole upgrade + back. Retrying re-ran the same migration and failed the same way, so no + module on such a database could be upgraded at all. The three company + columns are now set in SQL, which never reaches that hook. Found on a + customer database, and verified both ways on a scratch 19.0 database with + Method A installed and a ``perpetual`` company: the ORM version reproduces + the failure, this version upgrades cleanly and writes the same values. + There is no automated test — an Odoo migration runs outside the test + framework, and the failure needs a real partial registry. +- A ``peppol.purchase_journal_id`` parameter holding a non-ASCII digit passed + ``str.isdigit()`` and then raised in ``int()``, aborting the upgrade. + [19.0.1.5.1] — 2026-10-01 ------------------------- diff --git a/edi_base_peppol/migrations/19.0.1.4.0/post-migrate.py b/edi_base_peppol/migrations/19.0.1.4.0/post-migrate.py index 7670a335..d652bb2c 100644 --- a/edi_base_peppol/migrations/19.0.1.4.0/post-migrate.py +++ b/edi_base_peppol/migrations/19.0.1.4.0/post-migrate.py @@ -5,6 +5,27 @@ Peppol address"), the auto-send flag applies to every company as it did, and the purchase journal belongs to its own company. The parameters are left in place, unread. + +The company columns are set in SQL deliberately, and must stay that way. +``res.company.write()`` calls ``_set_category_defaults()``, which +``stock_account`` overrides to write an ``ir.default`` for +``product.category.property_valuation`` taken from the company's +``inventory_valuation``. That value is validated against the field's +selection, and an upgrade builds the registry one module at a time: this +migration runs after ``stock_account`` but before ``stock_account_method_a``, +which is what adds ``perpetual`` to the selection. So on a Method A database +an ORM write of ANY company field raises + + Invalid value for product.category.property_valuation: perpetual + +and rolls the whole upgrade back -- after which this migration runs again and +fails again, so retrying never gets anywhere. Setting the columns directly +goes nowhere near that hook. Found on a customer database 2026-10-02 and +reproduced both ways on a scratch database with Method A installed: the ORM +version fails, this one upgrades cleanly and writes the same values. Not +covered by an automated test -- a migration runs outside the test framework +and the failure needs a real partial registry -- so do not "simplify" this +back to the ORM without rebuilding that scratch database. """ from odoo import SUPERUSER_ID, api @@ -15,14 +36,39 @@ def migrate(cr, version): ICP = env["ir.config_parameter"] auto_send = (ICP.get_param("peppol.auto_send") or "").lower() not in ( "", "0", "false") - for company in env["res.company"].with_context(active_test=False).search([]): - partner = company.partner_id - company.write({ - "peppol_send_enabled": bool(partner.peppol_eas and partner.peppol_endpoint), - "peppol_auto_send": auto_send, - }) + + # Every company, archived ones included, as the ORM loop did with + # active_test=False: a column update sees no active filter at all. + # A correlated subquery rather than UPDATE ... FROM res_partner: the join + # form skips a company whose partner row is missing, where the ORM loop + # resolved an empty partner and wrote False. COALESCE keeps that. + cr.execute( + """ + UPDATE res_company c + SET peppol_send_enabled = COALESCE(( + SELECT COALESCE(p.peppol_eas, '') <> '' + AND COALESCE(p.peppol_endpoint, '') <> '' + FROM res_partner p + WHERE p.id = c.partner_id), FALSE), + peppol_auto_send = %s + """, + (auto_send,), + ) + journal_id = ICP.get_param("peppol.purchase_journal_id") - if journal_id and journal_id.isdigit(): - journal = env["account.journal"].browse(int(journal_id)).exists() - if journal: - journal.company_id.peppol_purchase_journal_id = journal + if journal_id and journal_id.isascii() and journal_id.isdigit(): + # No existence check needed: an id that is not a journal joins to no + # company, and the statement then updates nothing. + cr.execute( + """ + UPDATE res_company + SET peppol_purchase_journal_id = j.id + FROM account_journal j + WHERE j.id = %s + AND j.company_id = res_company.id + """, + (int(journal_id),), + ) + + # The ORM has these companies cached from the reads above. + env.invalidate_all() diff --git a/edi_base_peppol/models/res_company.py b/edi_base_peppol/models/res_company.py index a37ea106..23583321 100644 --- a/edi_base_peppol/models/res_company.py +++ b/edi_base_peppol/models/res_company.py @@ -1,7 +1,7 @@ """Which company sends through Peppol, to whom, and books what it receives where. -One database can hold a company that must send e-invoices (hascon, a Slovak -VAT payer under the 2027 mandate) and one that must not (SmarterHOME CZ). +One database can hold a company that must send e-invoices (a Slovak VAT +payer under the 2027 mandate) and one that must not (a Czech sister company). Everything that used to be a database-wide parameter is therefore the company's; the migration to 19.0.1.4.0 carried the former values over. """ diff --git a/edi_base_peppol/tests/test_peppol_route.py b/edi_base_peppol/tests/test_peppol_route.py index e4699d60..b79f2fe9 100644 --- a/edi_base_peppol/tests/test_peppol_route.py +++ b/edi_base_peppol/tests/test_peppol_route.py @@ -14,7 +14,7 @@ def setUpClass(cls): cls.sk = cls.env.ref("base.sk") cls.cz = cls.env.ref("base.cz") cls.company = cls.env["res.company"].create({ - "name": "hascon", "country_id": cls.sk.id, + "name": "alfa_sk", "country_id": cls.sk.id, "account_fiscal_country_id": cls.sk.id, "peppol_send_enabled": True, "peppol_scope": "sk_mandate"}) cls.company.partner_id.write({ @@ -64,9 +64,9 @@ def test_the_mandate_needs_a_slovak_company(self): # -- company settings ----------------------------------------------- def test_a_company_that_does_not_send_never_does(self): - smarterhome = self.env["res.company"].create({ - "name": "SmarterHOME CZ", "country_id": self.cz.id}) - route, _reason = self._route(self._customer(self.sk), company=smarterhome) + beta_cz = self.env["res.company"].create({ + "name": "Beta CZ s.r.o.", "country_id": self.cz.id}) + route, _reason = self._route(self._customer(self.sk), company=beta_cz) self.assertEqual(route, "pdf") def test_a_company_without_an_address_is_blocked(self): diff --git a/edi_epostak_connector_sapi/CHANGELOG.rst b/edi_epostak_connector_sapi/CHANGELOG.rst index d5cb4dad..9c834fa1 100644 --- a/edi_epostak_connector_sapi/CHANGELOG.rst +++ b/edi_epostak_connector_sapi/CHANGELOG.rst @@ -5,6 +5,53 @@ Changelog All notable changes to **edi_epostak_connector_sapi** are documented here. Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. +[19.0.1.1.1] — 2026-10-03 +------------------------- + +Fixed +~~~~~ + +- **A 404 from the Peppol directory was treated as a transport failure.** + *Check Peppol reachability* showed a red failure dialog for the one case it + exists to report. ePošťák answers an unregistered participant with HTTP + **404** carrying a complete negative body (``found``/``accepts`` false, a + ``reason``, and ``capability.routingStatus``); the call passed no + ``expected``, so it defaulted to ``(200,)`` and raised ``EpostakApiError`` + over a perfectly good answer — while the caller below it was already written + for exactly that body and never saw one. + + A 404 *without* ``found`` still raises: a changed route is not "participant + not registered", and reporting it as one would diagnose the wrong thing. + +- **``networkReady`` and ``routingStatus`` were never read.** Probing the + sandbox to confirm the body shape turned this up as a separate defect: both + the positive and the negative body nest them under ``capability``, never at + the top level, so ``result.get("networkReady")`` returned ``None`` even on a + successful lookup and the not-network-ready note could never fire. They are + lifted in ``_check_capabilities`` now, so its documented return contract + finally holds. + +- The negative outcome is a sticky **warning** quoting the provider's own + ``reason`` and ``routingStatus`` rather than a red error — the commonest + cause is a counterparty who has not registered, the second commonest our own + EAS being wrong, and the message names both. Suppressed on a clean success, + where ``ready`` only restates the title. + +Reported from the field 2026-10-03: the customer's company partner carried +``9950:SK2022913409`` (EAS 9950 is the IČ DPH scheme) while ePošťák knows them +as ``0245:4025578881`` (0245 is the DIČ), so the lookup was correctly negative +— and the dialog hid that behind a traceback. + +Verified live against the ePošťák sandbox on both paths. The seven new tests +were negative-controlled against the unfixed source: four fail there, +including the positive-path one, while the three guard tests hold both ways. + +Ported to 18.0 on 2026-10-03 as ``0b8eae57`` (``[18.0.1.1.0]``) — **do not +port this again.** The connector change there is byte-identical; that commit +also took down the ``UserError`` wrapper from ``[19.0.1.1.0]``, whose +remaining parts 18.0 still owes and records in its own Carry-over section. + + [19.0.1.1.0] — 2026-10-01 ------------------------- diff --git a/edi_epostak_connector_sapi/__manifest__.py b/edi_epostak_connector_sapi/__manifest__.py index 5aae3959..c9b38af3 100644 --- a/edi_epostak_connector_sapi/__manifest__.py +++ b/edi_epostak_connector_sapi/__manifest__.py @@ -3,7 +3,7 @@ "summary": "Concrete REST connector for the ePošťák Peppol access point " "(SAPI-SK 1.0): send and receive UBL, acknowledge, track delivery", "author": "Data Dance s.r.o.", - "version": "19.0.1.1.0", + "version": "19.0.1.1.1", "depends": [ "edi_epostak_base", ], diff --git a/edi_epostak_connector_sapi/models/epostak_connector.py b/edi_epostak_connector_sapi/models/epostak_connector.py index b9ece4c5..9b7c775f 100644 --- a/edi_epostak_connector_sapi/models/epostak_connector.py +++ b/edi_epostak_connector_sapi/models/epostak_connector.py @@ -1151,10 +1151,24 @@ def _parse_firms(data): def _check_capabilities(self, participant_id, document_types=None): """Ask the Peppol directory whether a participant can receive a type. - Returns the raw API answer ({'found', 'accepts', 'networkReady', ...}). - Cheap preflight against the commonest support case — a counterparty - whose EAS/endpoint is wrong or who is not registered at all, which - otherwise only surfaces as a 422 at send time. + Returns the answer flattened to {'found', 'accepts', 'networkReady', + 'routingStatus', 'reason', ...}. Cheap preflight against the commonest + support case — a counterparty whose EAS/endpoint is wrong or who is not + registered at all, which otherwise only surfaces as a 422 at send time. + + Two things about the wire format, both of which cost a bug: + + * "not registered" is answered with HTTP **404** carrying a complete + negative body (``found``/``accepts`` false, ``reason``, + ``capability.routingStatus``). That is an answer, not a transport + failure, so 404 is accepted and rendered. A 404 WITHOUT ``found`` + is something else entirely — a changed route, say — and still + raises, because reporting it as "not registered" would diagnose the + wrong thing. + * ``networkReady`` and ``routingStatus`` are nested under + ``capability`` in **both** the positive and the negative body, never + at the top level, so they are lifted here. Reading them off the top + level yielded ``None`` even on a successful lookup. """ cfg = self._validate_config() participant_id = self._epostak_normalize_participant(participant_id) @@ -1170,10 +1184,28 @@ def _check_capabilities(self, participant_id, document_types=None): body = {"participant": {"scheme": scheme, "identifier": identifier}} if document_types: body["documentTypes"] = list(document_types) - return self._request( + result = self._request( cfg, "POST", "%s/peppol/capabilities" % cfg["api_url"], participant_id=self._epostak_own_participant_id(), json_body=body, + expected=(200, 404), ) + if "found" not in result: + envelope = result.get("error") or {} + raise EpostakApiError( + _( + "ePošťák did not answer the capability lookup for " + "%(participant)s: %(detail)s", + participant=participant_id, + detail=envelope.get("message") or _("empty body"), + ), + status=404, + code=envelope.get("code"), + ) + capability = result.get("capability") or {} + for key in ("networkReady", "routingStatus", "documentTypeId", "processId"): + if key not in result and key in capability: + result[key] = capability[key] + return result diff --git a/edi_epostak_connector_sapi/models/res_partner.py b/edi_epostak_connector_sapi/models/res_partner.py index 7af01396..9b8fa6aa 100644 --- a/edi_epostak_connector_sapi/models/res_partner.py +++ b/edi_epostak_connector_sapi/models/res_partner.py @@ -65,6 +65,10 @@ def action_epostak_check_peppol(self): found = bool(result.get("found")) accepts = bool(result.get("accepts")) ready = result.get("networkReady") + # The provider's own words for WHY, which is the actionable part and + # the one thing we cannot phrase better ourselves. + reason = (result.get("reason") or "").strip() + routing = (result.get("routingStatus") or "").strip() if found and accepts: kind, title = "success", _("Reachable on Peppol") @@ -82,15 +86,29 @@ def action_epostak_check_peppol(self): participant=participant, ) else: - kind, title = "danger", _("Not found on Peppol") + # Not a failure of ours: the directory answered, and the answer is + # no. A warning rather than a red error, because the commonest + # cause is a counterparty who simply has not registered yet — and + # the second commonest is our own EAS being wrong, which the + # message names. + kind, title = "warning", _("Not registered on Peppol") message = _( - "%(participant)s is not registered in the Peppol directory. " - "Check the EAS scheme and the endpoint value.", + "%(participant)s is not registered in the Peppol directory, " + "so Peppol cannot deliver to it. Check the EAS scheme and the " + "endpoint value — a Slovak DIČ is scheme 0245, an IČ DPH is " + "9950 — or ask the counterparty for their Peppol address.", participant=participant, ) if ready is False: message += " " + _("(The provider reports the participant as not " "network-ready.)") + # Only where it tells the user something. On a clean success the + # routing status is "ready", which the title already said. + if reason or (routing and not (found and accepts)): + detail = reason or _("no reason given") + if routing: + detail = "%s (%s)" % (detail, routing) + message += "\n\n" + _("ePošťák reports: %s", detail) return { "type": "ir.actions.client", diff --git a/edi_epostak_connector_sapi/tests/test_epostak_sapi.py b/edi_epostak_connector_sapi/tests/test_epostak_sapi.py index 9191b011..6d0e2ada 100644 --- a/edi_epostak_connector_sapi/tests/test_epostak_sapi.py +++ b/edi_epostak_connector_sapi/tests/test_epostak_sapi.py @@ -13,6 +13,7 @@ from odoo.addons.edi_epostak_connector_sapi.models.epostak_connector import ( PERMANENT_CODES, RETRYABLE_STATUS, + EpostakApiError, ) @@ -682,3 +683,168 @@ def test_threshold_zero_disables_the_activity(self): for _i in range(5): c._record_poll(error="boom") self.assertFalse(self._open_activities()) + + +class TestEpostakCapabilities(TransactionCase): + """A participant who is not registered is answered with HTTP 404. + + Reported from the field 2026-10-03. ePošťák answers an unregistered + participant with 404 and a COMPLETE negative body; the call passed no + ``expected``, so it defaulted to (200,) and raised EpostakApiError over a + perfectly good answer. "Check Peppol reachability" therefore showed a red + failure dialog for the one case it exists to report. + + Both bodies below were captured from the sandbox on 2026-10-03, not + guessed — which is how the second defect showed up: ``networkReady`` and + ``routingStatus`` are nested under ``capability`` in BOTH of them, so + reading them off the top level returned None even on a 200. + """ + + DOC_TYPE = ( + "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice" + "##urn:cen.eu:en16931:2017#compliant#" + "urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1" + ) + + NOT_REGISTERED = { + "found": False, + "accepts": False, + "reason": "Participant not registered in Peppol network", + "capability": { + "documentTypeId": DOC_TYPE, + "processId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0", + "routingStatus": "participant_not_found", + "networkReady": False, + }, + } + + REGISTERED = { + "found": True, + "accepts": True, + "participant": { + "scheme": "0245", + "identifier": "4024626329", + "id": "0245:4024626329", + }, + "accessPoint": { + "url": "https://dev.epostak.sk/as4", + "transportProfile": "peppol-transport-as4-v2_0", + }, + "internal": False, + "supportedDocumentTypes": [DOC_TYPE], + "matchedDocumentType": DOC_TYPE, + "source": "sml", + "capability": { + "documentTypeId": DOC_TYPE, + "processId": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0", + "routingStatus": "ready", + "networkReady": True, + }, + } + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.connector = cls.env["epostak.connector"] + ICP = cls.env["ir.config_parameter"].sudo() + ICP.set_param("epostak.mode", "sandbox") + ICP.set_param("epostak.sapi.sandbox.client_id", "a-client-id") + ICP.set_param("epostak.sapi.sandbox.client_secret", "sk_live_test_x") + ICP.set_param("epostak.firm_id", "") + + def _lookup(self, status, payload, participant="9950:SK2022913409"): + """Run _check_capabilities against a canned HTTP response. + + requests.request is patched rather than _request, so the status code + really does travel through the expected/raise decision under test. + """ + from unittest.mock import patch + + from odoo.addons.edi_epostak_connector_sapi.models import ( + epostak_connector as mod, + ) + + connector_cls = type(self.connector) + with patch.object(mod.requests, "request", + return_value=_FakeResponse( + status_code=status, payload=payload, text="{}")), \ + patch.object(connector_cls, "_get_token", return_value="tok"), \ + patch.object(connector_cls, "_epostak_own_participant_id", + return_value="0245:4024626329"): + return self.connector._check_capabilities( + participant, [self.DOC_TYPE] + ) + + def test_404_not_registered_is_an_answer_not_a_failure(self): + result = self._lookup(404, self.NOT_REGISTERED) + self.assertFalse(result["found"]) + self.assertFalse(result["accepts"]) + self.assertIn("not registered", result["reason"]) + + def test_nested_capability_fields_are_lifted(self): + """The caller reads result['networkReady']; the wire nests it.""" + result = self._lookup(404, self.NOT_REGISTERED) + self.assertIs(result["networkReady"], False) + self.assertEqual(result["routingStatus"], "participant_not_found") + + def test_nested_fields_are_lifted_on_the_positive_answer_too(self): + """This one was silently broken even before the 404: a successful + lookup reported networkReady as None.""" + result = self._lookup(200, self.REGISTERED) + self.assertIs(result["networkReady"], True) + self.assertEqual(result["routingStatus"], "ready") + + def test_a_404_without_a_body_still_raises(self): + """A routing 404 is NOT 'participant not registered'. Rendering it as + one would diagnose the wrong thing entirely.""" + with self.assertRaises(EpostakApiError): + self._lookup(404, {"error": {"code": "NOT_FOUND", + "message": "Cannot POST /v1/wrong"}}) + + def test_a_500_still_raises(self): + with self.assertRaises(EpostakApiError): + self._lookup(500, {"error": {"code": "INTERNAL"}}) + + def test_the_button_warns_instead_of_raising(self): + """What the customer actually sees: a sticky warning quoting the + provider's reason, not a red traceback dialog.""" + from unittest.mock import patch + + partner = self.env["res.partner"].create({ + "name": "Unregistered s.r.o.", + "peppol_eas": "9950", + "peppol_endpoint": "SK2022913409", + }) + connector_cls = type(self.connector) + flat = dict(self.NOT_REGISTERED, + networkReady=False, + routingStatus="participant_not_found") + with patch.object(connector_cls, "_check_capabilities", + return_value=flat): + action = partner.action_epostak_check_peppol() + params = action["params"] + self.assertEqual(params["type"], "warning") + self.assertTrue(params["sticky"], "a warning that vanishes is unread") + self.assertIn("not registered", params["message"]) + self.assertIn("Participant not registered in Peppol network", + params["message"]) + self.assertIn("participant_not_found", params["message"]) + + def test_the_button_reports_a_reachable_participant(self): + from unittest.mock import patch + + partner = self.env["res.partner"].create({ + "name": "Registered s.r.o.", + "peppol_eas": "0245", + "peppol_endpoint": "4024626329", + }) + connector_cls = type(self.connector) + flat = dict(self.REGISTERED, networkReady=True, routingStatus="ready") + with patch.object(connector_cls, "_check_capabilities", + return_value=flat): + action = partner.action_epostak_check_peppol() + self.assertEqual(action["params"]["type"], "success") + # "ePošťák reports: no reason given (ready)" on a clean success is + # noise restating the title. + self.assertNotIn("ePošťák reports", action["params"]["message"]) + self.assertNotIn("no reason given", action["params"]["message"]) diff --git a/l10n_cssk_cash_journal_base/CHANGELOG.rst b/l10n_cssk_cash_journal_base/CHANGELOG.rst index ee35aa0c..0e2f48a6 100644 --- a/l10n_cssk_cash_journal_base/CHANGELOG.rst +++ b/l10n_cssk_cash_journal_base/CHANGELOG.rst @@ -8,6 +8,25 @@ Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Cha [Unreleased] ------------ +[19.0.1.6.0] — 2026-10-04 +------------------------- + +Fixed +~~~~~ + +- **An expense outside an invoice is one row**, carrying both its base and its + VAT, as a paid bill already was. An Expenses-app expense or a hand-made entry + with a tax used to give two rows, one with the base and one with the VAT + under the VAT account's category. Each VAT line is now folded into the base + line it was computed on. The money is unchanged: it only moves from a row of + its own into the base row's VAT. Reported by a customer's accountant. + +Added +~~~~~ + +- ``_cssk_write_national``: a hook through which the country module puts its + statutory layout first in the spreadsheet. + [19.0.1.5.0] — 2026-09-30 ------------------------- diff --git a/l10n_cssk_cash_journal_base/__manifest__.py b/l10n_cssk_cash_journal_base/__manifest__.py index 307a4906..44b9c74e 100644 --- a/l10n_cssk_cash_journal_base/__manifest__.py +++ b/l10n_cssk_cash_journal_base/__manifest__.py @@ -1,6 +1,6 @@ { "name": "CZ/SK Cash Journal — Shared Framework (peňažný denník / peněžní deník)", - "version": "19.0.1.5.0", + "version": "19.0.1.6.0", "summary": "Country-neutral single-entry cash journal derived from ordinary " "double-entry books: payments become dated, categorised denník " "rows, with the year-end balances the tax return asks for.", diff --git a/l10n_cssk_cash_journal_base/models/cssk_cash_journal_line.py b/l10n_cssk_cash_journal_base/models/cssk_cash_journal_line.py index 0aa14e5f..b1ce5abf 100644 --- a/l10n_cssk_cash_journal_base/models/cssk_cash_journal_line.py +++ b/l10n_cssk_cash_journal_base/models/cssk_cash_journal_line.py @@ -498,12 +498,8 @@ def _cssk_money_row_vals(self, money_line): _("The entry has no counterpart amount to attribute the money to."), )] - rows = [] - for line, share in parts: - rows += self._cssk_expand( - line, share, self._cssk_kind(line), base, - visited={money_line.id}, depth=0) - return rows + return self._cssk_expand_parts( + parts, base, lambda line: {money_line.id}, depth=0) @api.model def _cssk_payment_kind(self, money_line): @@ -523,6 +519,73 @@ def _cssk_payment_kind(self, money_line): # -- following the money to what it paid for ----------------------- + def _cssk_expand_parts(self, parts, base, visited_for, depth): + """Expand the allocated counterparts of one entry into rows. + + **Each VAT line is folded into the base line it was computed on**, so + an expense entered outside an invoice — the Expenses app, a hand-made + journal entry with a tax — is one row carrying its base and its VAT, + the way a paid invoice already was (``_cssk_split_document``). Expanded + line by line instead, the VAT line became a row of its own, under + whatever category the VAT account had. Reported by a customer's accountant: + "Rozdelilo mi to výdavok do dvoch riadkov — zvlášť základ, zvlášť DPH." + + A VAT line is folded only into lines of the same direction that carry + its tax and that are a direct category (not a receivable, payable or + money account, which are followed further and are not one row). With + no such line it stays a row of its own, as before — nothing is lost + from the book either way, the money only moves between the amount and + the VAT of the rows. + """ + tax_on = {} # base line -> VAT share folded into it + folded = set() + for tax_line, tax_share in parts: + if tax_line.display_type != "tax" or not tax_line.tax_line_id: + continue + targets = [ + (other, share) for other, share in parts + if share and other.balance + and other.display_type in BASE_DISPLAY_TYPES + and self._cssk_kind(other) == self._cssk_kind(tax_line) + and self._cssk_is_direct_category(other) + and tax_line.tax_line_id in other.tax_ids.flatten_taxes_hierarchy() + ] + if not targets: + continue + currency = tax_line.company_currency_id + weight = sum(abs(other.balance) for other, _share in targets) + remaining = tax_share + for index, (other, _share) in enumerate(targets): + part = remaining if index == len(targets) - 1 else currency.round( + tax_share * abs(other.balance) / weight) + remaining = currency.round(remaining - part) + tax_on[other] = currency.round(tax_on.get(other, 0.0) + part) + folded.add(tax_line) + + rows = [] + for line, share in parts: + if line in folded: + continue + new = self._cssk_expand( + line, share, self._cssk_kind(line), base, visited_for(line), depth) + if tax_on.get(line): + # A direct category line is always one row; should it ever be + # none, the folded VAT still needs a row to stand on, or it + # would leave the book. + new = new or [self._cssk_category_row( + base, self._cssk_kind(line), 0.0, line)] + new[0]["amount_tax"] = new[0].get("amount_tax", 0.0) + tax_on[line] + rows += new + return rows + + def _cssk_is_direct_category(self, line): + """Would ``_cssk_expand`` make this line ONE category row?""" + return ( + line.account_id.account_type not in ( + "asset_receivable", "liability_payable", *LIQUIDITY_TYPES) + and line.account_id not in self._cssk_transit_accounts(line.company_id) + ) + def _cssk_expand(self, line, amount, kind, base, visited, depth): """Attribute ``amount`` of money to what ``line`` stands for. @@ -574,12 +637,9 @@ def _cssk_expand_transit(self, line, amount, kind, base, visited, depth): lambda other: other.id not in visited and other != line) parts = self._cssk_allocate(siblings, amount, line.company_currency_id) if parts: - rows = [] - for other, share in parts: - rows += self._cssk_expand( - other, share, self._cssk_kind(other), base, - visited | {line.id, other.id}, depth + 1) - return rows + return self._cssk_expand_parts( + parts, base, lambda other: visited | {line.id, other.id}, + depth + 1) return self._cssk_through_reconciliation( line, amount, kind, base, visited, depth, documents=False) diff --git a/l10n_cssk_cash_journal_base/report/cssk_cash_journal_xlsx.py b/l10n_cssk_cash_journal_base/report/cssk_cash_journal_xlsx.py index e73b2356..b814043d 100644 --- a/l10n_cssk_cash_journal_base/report/cssk_cash_journal_xlsx.py +++ b/l10n_cssk_cash_journal_base/report/cssk_cash_journal_xlsx.py @@ -11,7 +11,8 @@ class CsskCashJournalXlsx(models.AbstractModel): - """The denník as a spreadsheet: the rows, then the totals per category. + """The denník as a spreadsheet: the rows, then the totals per category, + preceded by the country's statutory layout where its module provides one. Two sheets on purpose. The first is every row as the book has it, so the accountant can filter and pivot it herself — which is what she was doing by @@ -35,6 +36,11 @@ def generate_xlsx_report(self, workbook, data, wizards): date_fmt = workbook.add_format({"num_format": "yyyy-mm-dd"}) rows = wizard._cssk_rows() + # The national book first, when the country module lays one out: it + # is the layout the accountant checks against the PDF. The flat sheet + # stays, because it is the one she pivots. + self._cssk_write_national(workbook, wizard, { + "bold": bold, "money": money, "date": date_fmt}) self._cssk_write_rows(workbook, wizard, rows, bold, money, date_fmt) self._cssk_write_totals(workbook, wizard, rows, bold, money) @@ -72,6 +78,14 @@ def _cssk_write_rows(self, workbook, wizard, rows, bold, money, date_fmt): sheet.write(index, 14, self.env._("yes") if row.needs_review else "") sheet.write(index, 15, row.review_reason or "") + def _cssk_write_national(self, workbook, wizard, formats): + """Hook: the statutory column layout of the country's book. + + Nothing here — the columns of a denník are a national matter, as they + are for the PDF (``_cssk_pdf_report``). A country module adds its own + sheet; ``formats`` carries ``bold``, ``money`` and ``date``. + """ + def _cssk_write_totals(self, workbook, wizard, rows, bold, money): sheet = workbook.add_worksheet(self.env._("Totals per category")) sheet.set_column(0, 0, 32) diff --git a/l10n_cssk_cash_journal_base/tests/test_cash_journal.py b/l10n_cssk_cash_journal_base/tests/test_cash_journal.py index 64b60e4c..21667df3 100644 --- a/l10n_cssk_cash_journal_base/tests/test_cash_journal.py +++ b/l10n_cssk_cash_journal_base/tests/test_cash_journal.py @@ -257,6 +257,63 @@ def test_direct_bank_expense_takes_the_account_category(self): self.assertEqual(row.amount, 12.0) self.assertEqual(row.payment_kind, "bank") + def _taxed_entry(self, lines, journal=None): + """A journal entry paying ``lines`` [(account, amount, taxes)] out of + the bank — the shape the Expenses app and a hand-made entry share.""" + journal = journal or self.bank_journal + move = self.env["account.move"].create({ + "journal_id": journal.id, + "date": fields.Date.to_date("2026-05-10"), + "line_ids": [ + (0, 0, {"account_id": account.id, "name": "Výdavok", + "debit": amount, "credit": 0.0, + "tax_ids": [(6, 0, taxes.ids)]}) + for account, amount, taxes in lines + ], + }) + # Odoo adds the VAT lines; the money leg balances whatever they are. + total = sum(move.line_ids.mapped("debit")) - sum(move.line_ids.mapped("credit")) + move.write({"line_ids": [(0, 0, { + "account_id": journal.default_account_id.id, "name": "Výdavok", + "debit": 0.0, "credit": total})]}) + move.action_post() + return move + + def test_an_expense_entry_with_vat_is_one_row(self): + """"Rozdelilo mi to výdavok do dvoch riadkov — zvlášť základ, zvlášť + DPH." An entry outside an invoice is one row with its VAT, as a paid + bill is.""" + tax = self.tax_purchase_a + move = self._taxed_entry([(self.expense_account, 100.0, tax)]) + tax_lines = move.line_ids.filtered(lambda l: l.display_type == "tax") + self.assertTrue(tax_lines, "fixture: Odoo computed a VAT line") + self._generate() + rows = self._rows() + self.assertEqual(len(rows), 1) + self.assertEqual(rows.category_id, self.cat_goods) + self.assertAlmostEqual(rows.amount, 100.0, 2) + self.assertAlmostEqual(rows.amount_tax, sum(tax_lines.mapped("debit")), 2) + bank_out = -sum(move.line_ids.filtered( + lambda l: l.account_id == self.bank_journal.default_account_id).mapped("balance")) + self.assertAlmostEqual(rows.amount + rows.amount_tax, bank_out, 2, + msg="the book still adds up to the money") + + def test_vat_follows_each_base_line_of_a_split_entry(self): + tax = self.tax_purchase_a + move = self._taxed_entry([(self.expense_account, 100.0, tax), + (self.services_account, 300.0, tax)]) + self._generate() + rows = self._rows() + self.assertEqual(len(rows), 2) + by_cat = {r.category_id: r for r in rows} + rate = tax.amount / 100.0 + self.assertAlmostEqual(by_cat[self.cat_goods].amount_tax, 100.0 * rate, 2) + self.assertAlmostEqual(by_cat[self.cat_services].amount_tax, 300.0 * rate, 2) + bank_out = -sum(move.line_ids.filtered( + lambda l: l.account_id == self.bank_journal.default_account_id).mapped("balance")) + self.assertAlmostEqual( + sum(rows.mapped("amount")) + sum(rows.mapped("amount_tax")), bank_out, 2) + def test_transfer_between_bank_and_cash_is_a_transit_row(self): """Money between one's own pockets is a priebežná položka, not income.""" move = self.env["account.move"].create({ diff --git a/l10n_cssk_core/CHANGELOG.rst b/l10n_cssk_core/CHANGELOG.rst index c237b010..2fc14b58 100644 --- a/l10n_cssk_core/CHANGELOG.rst +++ b/l10n_cssk_core/CHANGELOG.rst @@ -8,6 +8,28 @@ Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Cha [Unreleased] ------------ +[19.0.1.17.0] — 2026-10-02 +-------------------------- + +Added +~~~~~ + +- **Statement account mapping** (``cssk.statement.account.map``, Configuration + → CZ/SK Localization). Statutory statements read balances by the account + codes their rows name, so an analytic carried over from another system — + 461001 / 461002 where Úč POD names 461100 / 461200 — reached no row and the + súvaha stopped balancing. A company can now report such an account under the + code the form expects, for any balance or only for a debit or credit balance + at the period end (an overdrawn bank account shown as a krátkodobý bankový + úver). Applied where the balances are read, so the preview, the filed XML, + the income-tax return and the unmapped check all see it. A mapped account's + tag filters (``665&IX_1``) still follow its own code. +- The shared matcher ``_cssk_eval_formula`` can report which codes added to a + total (``contributors``), and the balance query is exposed with the account + ids kept (``_cssk_account_balance_groups``) and as a domain + (``_cssk_account_balance_domain``), for drill-downs that open exactly what a + figure was summed from. + [19.0.1.16.0] — 2026-09-29 -------------------------- @@ -703,20 +725,20 @@ Added - **The company registry is now validated as an IČO on CZ/SK partners.** ``company_registry`` is core's field and core validates nothing in it. On the Data Dance production base that left **12 of 96** CZ/SK values unusable: - ``Test``, ``12345``, ``-``, ``JUSTICE.CZ``, ``cepatay111`` — and three cases + ``Test``, ``12345``, ``-``, ``JUSTICE.CZ``, ``office111`` — and three cases of the company *name* pasted into the number field, two of which reached **posted invoices**, where a company name in place of the IČO is a defect in a statutory document rather than untidy data. ``is_valid_ico`` applies the mod-11 check both countries share; measured against that base it rejects all 12 and accepts all 84 genuine companies. -- ``normalize_registry`` canonicalises storage (``00 585 441`` → ``00585441``, - ``614556`` → ``00614556``), keeping the leading zeros a Czech IČO is printed +- ``normalize_registry`` canonicalises storage (``00 580 007`` → ``00580007``, + ``610003`` → ``00610003``), keeping the leading zeros a Czech IČO is printed with. It rewrites **only values that are already valid**, so it can never turn a typo into a different-looking typo, and it returns anything it does not recognise unchanged so the error can quote what was typed. This is also what makes core's own ``same_company_registry_partner_id`` duplicate warning work: core compares the two strings exactly, so without a - canonical stored form ``00 585 441`` and ``00585441`` never see each other. + canonical stored form ``00 580 007`` and ``00580007`` never see each other. - ``registry_key`` is the separate COMPARISON form — digits only, padding dropped — for matching ``00682811`` against ``682811``. Using the storage form to match misses half the matches; using the match form to store puts an @@ -732,15 +754,15 @@ Notes would have been accepted, which is precisely the junk a signup form collects. ``ICO_MIN_DIGITS = 6`` rejects all 10 000 and breaks no real number: every genuine IČO is written with at least six digits, the oldest - carrying leading zeros (``00614556`` printed as ``614556``). + carrying leading zeros (``00610003`` printed as ``610003``). - **The check is scoped by** ``country_code``, **not** ``_deduce_country_code``. The latter also reads the VAT prefix, so a foreign company VAT-registered in - Czechia — a ``CZ…`` VAT number over a German ``HRB 6089`` company registry — + Czechia — a ``CZ…`` VAT number over a German ``HRB 12345`` company registry — would be deduced as CZ and have its legitimate home-register number rejected. Core scopes this field's own uniqueness by country too. - The mod-11 scheme is Czechoslovak; an unscoped check rejects Hetzner's - ``HRB 6089``, a US EIN ``93-1564675``, an 11-digit Latvian registry and a - Papua New Guinean ``1-120979442`` — all legitimate, all present on that + The mod-11 scheme is Czechoslovak; an unscoped check rejects a German supplier's + ``HRB 12345``, a US EIN ``12-3456789``, an 11-digit Latvian registry and a + Papua New Guinean ``1-123456789`` — all legitimate, all present on that same base. - Because the registry is a **commercial field**, a bad value on a parent is synced onto every child and the constraint fires for those too. The error diff --git a/l10n_cssk_core/__manifest__.py b/l10n_cssk_core/__manifest__.py index 6e30fd84..80809312 100644 --- a/l10n_cssk_core/__manifest__.py +++ b/l10n_cssk_core/__manifest__.py @@ -1,6 +1,6 @@ { "name": "CZ/SK Localization — Shared Core", - "version": "19.0.1.16.0", + "version": "19.0.1.17.0", "summary": "Country-neutral foundation for the Czech & Slovak statutory " "localization: tax-authority registry, person types, shared " "company/partner fields, settings and security.", @@ -37,6 +37,7 @@ "data": [ "security/ir.model.access.csv", "security/cssk_tax_authority_rules.xml", + "security/cssk_statement_account_map_rules.xml", "views/cssk_tax_authority_views.xml", "views/cssk_person_type_views.xml", "views/res_company_views.xml", @@ -46,6 +47,7 @@ "views/cssk_filing_comparison_views.xml", "views/cssk_filing_discrepancy_views.xml", "views/res_config_settings_views.xml", + "views/cssk_statement_account_map_views.xml", "views/cssk_core_menus.xml", ], "installable": True, diff --git a/l10n_cssk_core/i18n/cs.po b/l10n_cssk_core/i18n/cs.po index 7d9b747f..567a0778 100644 --- a/l10n_cssk_core/i18n/cs.po +++ b/l10n_cssk_core/i18n/cs.po @@ -278,6 +278,9 @@ msgstr "Společnosti" msgid "Companies that refers to partner" msgstr "Společnosti odkazující na partnera" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__company_id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__company_id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__company_id #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_tax_authority__company_id msgid "Company" @@ -357,6 +360,9 @@ msgstr "Země" msgid "Country code" msgstr "Kód země" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__create_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__create_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__create_uid #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__create_uid #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__create_uid @@ -365,6 +371,9 @@ msgstr "Kód země" msgid "Created by" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__create_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__create_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__create_date #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__create_date #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__create_date @@ -456,6 +465,9 @@ msgstr "Zobrazit formát EDI faktury" msgid "Display Invoice Template Pdf Report" msgstr "Zobrazit šablonu PDF reportu faktury" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__display_name +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__display_name +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__display_name #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move__display_name #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move_line__display_name @@ -614,6 +626,9 @@ msgstr "Globální lokalizační číslo (GLN)" msgid "Has Message" msgstr "Má zprávu" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__id #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move__id #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move_line__id @@ -809,6 +824,9 @@ msgstr "Položka deníku" msgid "Language" msgstr "Jazyk" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__write_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__write_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__write_uid #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__write_uid #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__write_uid @@ -817,6 +835,9 @@ msgstr "Jazyk" msgid "Last Updated by" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__write_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__write_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__write_date #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__write_date #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__write_date @@ -1883,6 +1904,7 @@ msgstr "" msgid "No longer differs" msgstr "Již se neliší" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__note #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__note msgid "Note" @@ -2560,3 +2582,111 @@ msgstr "" #: model:ir.ui.menu,name:l10n_cssk_core.menu_cssk_filing_discrepancy msgid "Rows to review" msgstr "Řádky k posouzení" + +#. module: l10n_cssk_core +#. odoo-python +#: code:addons/l10n_cssk_core/models/cssk_statement_account_map.py:0 +msgid "" +"Account %s is mapped for any balance AND for one side. Use either one 'any " +"balance' mapping, or a debit and/or a credit mapping." +msgstr "" +"Účet %s je namapován pro jakýkoli zůstatek A ZÁROVEŇ pro jednu stranu. " +"Použijte buď jedno mapování „jakýkoli zůstatek“, nebo debetní a/nebo " +"kreditní mapování." + +#. module: l10n_cssk_core +#: model:ir.model.constraint,message:l10n_cssk_core.constraint_cssk_statement_account_map_company_account_side_uniq +msgid "An account can be mapped only once per balance side." +msgstr "Účet lze namapovat jen jednou pro každou stranu zůstatku." + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__any +msgid "Any balance" +msgstr "Jakýkoli zůstatek" + +#. module: l10n_cssk_core +#: model:ir.model.fields,help:l10n_cssk_core.field_cssk_statement_account_map__balance_side +msgid "" +"Any balance: always reported under the code.\n" +"Debit / credit balance only: reported under the code only when the account's balance at the period end has that sign (an overdrawn bank account reported as a bank loan); otherwise it keeps its own code. Applies to balance-sheet balances only." +msgstr "" +"Jakýkoli zůstatek: účet se vždy vykazuje pod tímto kódem.\n" +"Jen debetní / kreditní zůstatek: pod tímto kódem se vykazuje, jen pokud má zůstatek účtu ke konci období toto znaménko (přečerpaný bankovní účet vykázaný jako bankovní úvěr); jinak si ponechává vlastní kód. Platí jen pro zůstatky v rozvaze." + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__balance_side +msgid "Balance Side" +msgstr "Strana zůstatku" + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__credit +msgid "Credit balance only" +msgstr "Jen kreditní zůstatek" + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__debit +msgid "Debit balance only" +msgstr "Jen debetní zůstatek" + +#. module: l10n_cssk_core +#: model_terms:ir.ui.view,arch_db:l10n_cssk_core.cssk_statement_account_map_view_search +msgid "Depends on balance side" +msgstr "Závisí na straně zůstatku" + +#. module: l10n_cssk_core +#: model_terms:ir.actions.act_window,help:l10n_cssk_core.cssk_statement_account_map_action +msgid "Report an account under another code" +msgstr "Vykázat účet pod jiným kódem" + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__reported_code +msgid "Reported Code" +msgstr "Vykazovaný kód" + +#. module: l10n_cssk_core +#: model:ir.actions.act_window,name:l10n_cssk_core.cssk_statement_account_map_action +#: model:ir.model,name:l10n_cssk_core.model_cssk_statement_account_map +#: model:ir.ui.menu,name:l10n_cssk_core.menu_cssk_statement_account_map +msgid "Statement account mapping" +msgstr "Mapování účtů do výkazů" + +#. module: l10n_cssk_core +#: model_terms:ir.actions.act_window,help:l10n_cssk_core.cssk_statement_account_map_action +msgid "" +"Statutory statements read balances by the account codes their\n" +" rows name. An account of your own chart that no row names —\n" +" an analytic carried over from another system — lands on no row.\n" +" Map it to the code the form expects, for any balance or only\n" +" for a debit or credit balance at the period end." +msgstr "" +"Zákonné výkazy čtou zůstatky podle kódů účtů, které uvádějí jejich řádky. " +"Účet vašeho účtového rozvrhu, který žádný řádek neuvádí — analytika převzatá" +" z jiného systému — nepatří do žádného řádku. Namapujte jej na kód, který " +"formulář očekává, pro jakýkoli zůstatek nebo jen pro debetní či kreditní " +"zůstatek ke konci období." + +#. module: l10n_cssk_core +#: model:ir.model.fields,help:l10n_cssk_core.field_cssk_statement_account_map__reported_code +msgid "" +"The account code the statutory statements read this account as — a code the " +"form's rows name, e.g. 461200 for a short-term bank loan on the Slovak Úč " +"POD." +msgstr "" +"Kód účtu, pod kterým zákonné výkazy tento účet čtou — kód, který uvádějí " +"řádky formuláře, např. 461200 pro krátkodobý bankovní úvěr na slovenském Úč " +"POD." + +#. module: l10n_cssk_core +#. odoo-python +#: code:addons/l10n_cssk_core/models/cssk_statement_account_map.py:0 +msgid "" +"The reported code must be a plain account code, without spaces, commas, " +"signs or tag operators." +msgstr "" +"Vykazovaný kód musí být samotný kód účtu, bez mezer, čárek, znamének a " +"operátorů štítků." + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__account_id +msgid "Account" +msgstr "Účet" diff --git a/l10n_cssk_core/i18n/l10n_cssk_core.pot b/l10n_cssk_core/i18n/l10n_cssk_core.pot index 17b1f1a6..2e37edf6 100644 --- a/l10n_cssk_core/i18n/l10n_cssk_core.pot +++ b/l10n_cssk_core/i18n/l10n_cssk_core.pot @@ -315,6 +315,9 @@ msgstr "" msgid "Companies that refers to partner" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__company_id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__company_id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__company_id #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_tax_authority__company_id msgid "Company" @@ -403,6 +406,9 @@ msgstr "" msgid "Country code" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__create_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__create_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__create_uid #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__create_uid #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__create_uid @@ -411,6 +417,9 @@ msgstr "" msgid "Created by" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__create_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__create_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__create_date #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__create_date #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__create_date @@ -512,6 +521,9 @@ msgstr "" msgid "Display Invoice Template Pdf Report" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__display_name +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__display_name +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__display_name #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move__display_name #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move_line__display_name @@ -696,6 +708,9 @@ msgstr "" msgid "Has Message" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__id #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move__id #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move_line__id @@ -919,6 +934,9 @@ msgstr "" msgid "Language" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__write_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__write_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__write_uid #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__write_uid #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__write_uid @@ -927,6 +945,9 @@ msgstr "" msgid "Last Updated by" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__write_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__write_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__write_date #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__write_date #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__write_date @@ -1982,6 +2003,7 @@ msgstr "" msgid "No longer differs" msgstr "" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__note #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__note msgid "Note" @@ -2585,3 +2607,96 @@ msgstr "" #: model:ir.ui.menu,name:l10n_cssk_core.menu_cssk_filing_discrepancy msgid "Rows to review" msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__account_id +msgid "Account" +msgstr "" + +#. module: l10n_cssk_core +#. odoo-python +#: code:addons/l10n_cssk_core/models/cssk_statement_account_map.py:0 +msgid "" +"Account %s is mapped for any balance AND for one side. Use either one 'any " +"balance' mapping, or a debit and/or a credit mapping." +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.constraint,message:l10n_cssk_core.constraint_cssk_statement_account_map_company_account_side_uniq +msgid "An account can be mapped only once per balance side." +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__any +msgid "Any balance" +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.fields,help:l10n_cssk_core.field_cssk_statement_account_map__balance_side +msgid "" +"Any balance: always reported under the code.\n" +"Debit / credit balance only: reported under the code only when the account's balance at the period end has that sign (an overdrawn bank account reported as a bank loan); otherwise it keeps its own code. Applies to balance-sheet balances only." +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__balance_side +msgid "Balance Side" +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__credit +msgid "Credit balance only" +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__debit +msgid "Debit balance only" +msgstr "" + +#. module: l10n_cssk_core +#: model_terms:ir.ui.view,arch_db:l10n_cssk_core.cssk_statement_account_map_view_search +msgid "Depends on balance side" +msgstr "" + +#. module: l10n_cssk_core +#: model_terms:ir.actions.act_window,help:l10n_cssk_core.cssk_statement_account_map_action +msgid "Report an account under another code" +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__reported_code +msgid "Reported Code" +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.actions.act_window,name:l10n_cssk_core.cssk_statement_account_map_action +#: model:ir.model,name:l10n_cssk_core.model_cssk_statement_account_map +#: model:ir.ui.menu,name:l10n_cssk_core.menu_cssk_statement_account_map +msgid "Statement account mapping" +msgstr "" + +#. module: l10n_cssk_core +#: model_terms:ir.actions.act_window,help:l10n_cssk_core.cssk_statement_account_map_action +msgid "" +"Statutory statements read balances by the account codes their\n" +" rows name. An account of your own chart that no row names —\n" +" an analytic carried over from another system — lands on no row.\n" +" Map it to the code the form expects, for any balance or only\n" +" for a debit or credit balance at the period end." +msgstr "" + +#. module: l10n_cssk_core +#: model:ir.model.fields,help:l10n_cssk_core.field_cssk_statement_account_map__reported_code +msgid "" +"The account code the statutory statements read this account as — a code the " +"form's rows name, e.g. 461200 for a short-term bank loan on the Slovak Úč " +"POD." +msgstr "" + +#. module: l10n_cssk_core +#. odoo-python +#: code:addons/l10n_cssk_core/models/cssk_statement_account_map.py:0 +msgid "" +"The reported code must be a plain account code, without spaces, commas, " +"signs or tag operators." +msgstr "" diff --git a/l10n_cssk_core/i18n/sk.po b/l10n_cssk_core/i18n/sk.po index 4f3116ca..cd395a05 100644 --- a/l10n_cssk_core/i18n/sk.po +++ b/l10n_cssk_core/i18n/sk.po @@ -278,6 +278,9 @@ msgstr "Spoločnosti" msgid "Companies that refers to partner" msgstr "Spoločnosti odkazujúce na partnera" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__company_id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__company_id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__company_id #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_tax_authority__company_id msgid "Company" @@ -357,6 +360,9 @@ msgstr "Krajina" msgid "Country code" msgstr "Kód krajiny" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__create_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__create_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__create_uid #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__create_uid #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__create_uid @@ -365,6 +371,9 @@ msgstr "Kód krajiny" msgid "Created by" msgstr "Vytvoril" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__create_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__create_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__create_date #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__create_date #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__create_date @@ -456,6 +465,9 @@ msgstr "Zobraziť formát EDI faktúry" msgid "Display Invoice Template Pdf Report" msgstr "Zobraziť šablónu PDF reportu faktúry" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__display_name +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__display_name +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__display_name #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move__display_name #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move_line__display_name @@ -614,6 +626,9 @@ msgstr "Globálne lokalizačné číslo (GLN)" msgid "Has Message" msgstr "Má správu" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__id +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__id #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move__id #: model:ir.model.fields,field_description:l10n_cssk_core.field_account_move_line__id @@ -809,6 +824,9 @@ msgstr "Položka denníka" msgid "Language" msgstr "Jazyk" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__write_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__write_uid +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__write_uid #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__write_uid #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__write_uid @@ -817,6 +835,9 @@ msgstr "Jazyk" msgid "Last Updated by" msgstr "Naposledy upravil" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_comparison__write_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__write_date +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__write_date #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_person_type__write_date #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statutory_footprint_line__write_date @@ -1896,6 +1917,7 @@ msgstr "" msgid "No longer differs" msgstr "Už sa nelíši" +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__note #. module: l10n_cssk_core #: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_filing_discrepancy__note msgid "Note" @@ -2599,3 +2621,111 @@ msgstr "" #: model:ir.ui.menu,name:l10n_cssk_core.menu_cssk_filing_discrepancy msgid "Rows to review" msgstr "Riadky na posúdenie" + +#. module: l10n_cssk_core +#. odoo-python +#: code:addons/l10n_cssk_core/models/cssk_statement_account_map.py:0 +msgid "" +"Account %s is mapped for any balance AND for one side. Use either one 'any " +"balance' mapping, or a debit and/or a credit mapping." +msgstr "" +"Účet %s je namapovaný pre akýkoľvek zostatok A ZÁROVEŇ pre jednu stranu. " +"Použite buď jedno mapovanie „akýkoľvek zostatok“, alebo debetné a/alebo " +"kreditné mapovanie." + +#. module: l10n_cssk_core +#: model:ir.model.constraint,message:l10n_cssk_core.constraint_cssk_statement_account_map_company_account_side_uniq +msgid "An account can be mapped only once per balance side." +msgstr "Účet možno namapovať len raz pre každú stranu zostatku." + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__any +msgid "Any balance" +msgstr "Akýkoľvek zostatok" + +#. module: l10n_cssk_core +#: model:ir.model.fields,help:l10n_cssk_core.field_cssk_statement_account_map__balance_side +msgid "" +"Any balance: always reported under the code.\n" +"Debit / credit balance only: reported under the code only when the account's balance at the period end has that sign (an overdrawn bank account reported as a bank loan); otherwise it keeps its own code. Applies to balance-sheet balances only." +msgstr "" +"Akýkoľvek zostatok: účet sa vždy vykazuje pod týmto kódom.\n" +"Len debetný / kreditný zostatok: pod týmto kódom sa vykazuje, len ak má zostatok účtu ku koncu obdobia toto znamienko (prečerpaný bankový účet vykázaný ako bankový úver); inak si ponecháva vlastný kód. Platí len pre zostatky v súvahe." + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__balance_side +msgid "Balance Side" +msgstr "Strana zostatku" + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__credit +msgid "Credit balance only" +msgstr "Len kreditný zostatok" + +#. module: l10n_cssk_core +#: model:ir.model.fields.selection,name:l10n_cssk_core.selection__cssk_statement_account_map__balance_side__debit +msgid "Debit balance only" +msgstr "Len debetný zostatok" + +#. module: l10n_cssk_core +#: model_terms:ir.ui.view,arch_db:l10n_cssk_core.cssk_statement_account_map_view_search +msgid "Depends on balance side" +msgstr "Závisí od strany zostatku" + +#. module: l10n_cssk_core +#: model_terms:ir.actions.act_window,help:l10n_cssk_core.cssk_statement_account_map_action +msgid "Report an account under another code" +msgstr "Vykázať účet pod iným kódom" + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__reported_code +msgid "Reported Code" +msgstr "Vykazovaný kód" + +#. module: l10n_cssk_core +#: model:ir.actions.act_window,name:l10n_cssk_core.cssk_statement_account_map_action +#: model:ir.model,name:l10n_cssk_core.model_cssk_statement_account_map +#: model:ir.ui.menu,name:l10n_cssk_core.menu_cssk_statement_account_map +msgid "Statement account mapping" +msgstr "Mapovanie účtov do výkazov" + +#. module: l10n_cssk_core +#: model_terms:ir.actions.act_window,help:l10n_cssk_core.cssk_statement_account_map_action +msgid "" +"Statutory statements read balances by the account codes their\n" +" rows name. An account of your own chart that no row names —\n" +" an analytic carried over from another system — lands on no row.\n" +" Map it to the code the form expects, for any balance or only\n" +" for a debit or credit balance at the period end." +msgstr "" +"Zákonné výkazy čítajú zostatky podľa kódov účtov, ktoré uvádzajú ich riadky." +" Účet vašej účtovej osnovy, ktorý žiadny riadok neuvádza — analytika " +"prenesená z iného systému — nepatrí do žiadneho riadku. Namapujte ho na kód," +" ktorý tlačivo očakáva, pre akýkoľvek zostatok alebo len pre debetný či " +"kreditný zostatok ku koncu obdobia." + +#. module: l10n_cssk_core +#: model:ir.model.fields,help:l10n_cssk_core.field_cssk_statement_account_map__reported_code +msgid "" +"The account code the statutory statements read this account as — a code the " +"form's rows name, e.g. 461200 for a short-term bank loan on the Slovak Úč " +"POD." +msgstr "" +"Kód účtu, pod ktorým zákonné výkazy tento účet čítajú — kód, ktorý uvádzajú " +"riadky tlačiva, napr. 461200 pre krátkodobý bankový úver na slovenskom Úč " +"POD." + +#. module: l10n_cssk_core +#. odoo-python +#: code:addons/l10n_cssk_core/models/cssk_statement_account_map.py:0 +msgid "" +"The reported code must be a plain account code, without spaces, commas, " +"signs or tag operators." +msgstr "" +"Vykazovaný kód musí byť samotný kód účtu, bez medzier, čiarok, znamienok a " +"operátorov značiek." + +#. module: l10n_cssk_core +#: model:ir.model.fields,field_description:l10n_cssk_core.field_cssk_statement_account_map__account_id +msgid "Account" +msgstr "Účet" diff --git a/l10n_cssk_core/models/__init__.py b/l10n_cssk_core/models/__init__.py index 2ffa433e..163afa52 100644 --- a/l10n_cssk_core/models/__init__.py +++ b/l10n_cssk_core/models/__init__.py @@ -1,4 +1,5 @@ from . import cssk_statutory_submission +from . import cssk_statement_account_map from . import cssk_filing_comparison from . import cssk_filing_discrepancy from . import cssk_tax_authority diff --git a/l10n_cssk_core/models/cssk_statement_account_map.py b/l10n_cssk_core/models/cssk_statement_account_map.py new file mode 100644 index 00000000..cb04857f --- /dev/null +++ b/l10n_cssk_core/models/cssk_statement_account_map.py @@ -0,0 +1,110 @@ +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class CsskStatementAccountMap(models.Model): + """Report an account of THIS company's chart under another account code. + + Every statutory statement reads ledger balances by account-code prefix, + and the forms name the standard chart's codes: Úč POD puts dlhodobé + bankové úvery on 461100 and bežné on 461200. A chart carried over from + another system keeps its own analytics — 461001 for the long-term loan, + 461002 for the short-term one — and those match no row at all. The money + is in the ledger and not on the form, and the sheet stops balancing. + + Renaming the accounts would rewrite the client's chart to suit a form. + This keeps the chart and says, per company, which code an account is + REPORTED under. The substitution happens where the balances are read + (``_cssk_balances_by_account_code``), so the preview, the filed XML, the + unmapped check and the drill-down all see the same thing. + + ``balance_side`` handles the account whose row depends on the sign of its + balance at the period end — the overdrawn bank account that must be shown + as a krátkodobý bankový úver. A debit- or credit-only mapping applies to + the cumulative as-of balance only (the balance sheet); a period movement + has no side in that sense, so those windows read the account under its + own code. + """ + + _name = "cssk.statement.account.map" + _description = "Statement account mapping" + _order = "company_id, reported_code, id" + + company_id = fields.Many2one( + "res.company", required=True, ondelete="cascade", index=True, + default=lambda self: self.env.company) + account_id = fields.Many2one( + "account.account", required=True, ondelete="cascade", + domain="[('company_ids', 'in', company_id)]") + reported_code = fields.Char( + required=True, + help="The account code the statutory statements read this account " + "as — a code the form's rows name, e.g. 461200 for a short-term bank " + "loan on the Slovak Úč POD.") + balance_side = fields.Selection( + [("any", "Any balance"), + ("debit", "Debit balance only"), + ("credit", "Credit balance only")], + required=True, default="any", + help="Any balance: always reported under the code.\n" + "Debit / credit balance only: reported under the code only when the " + "account's balance at the period end has that sign (an overdrawn " + "bank account reported as a bank loan); otherwise it keeps its own " + "code. Applies to balance-sheet balances only.") + note = fields.Char() + + _company_account_side_uniq = models.Constraint( + "UNIQUE (company_id, account_id, balance_side)", + "An account can be mapped only once per balance side.") + + @api.constrains("reported_code") + def _check_reported_code(self): + for rec in self: + code = (rec.reported_code or "").strip() + if not code or any(ch in code for ch in ", -&!"): + raise ValidationError(_( + "The reported code must be a plain account code, without " + "spaces, commas, signs or tag operators.")) + + @api.constrains("account_id", "company_id", "balance_side") + def _check_sides(self): + for rec in self: + sides = self.search([ + ("company_id", "=", rec.company_id.id), + ("account_id", "=", rec.account_id.id), + ]).mapped("balance_side") + if "any" in sides and len(sides) > 1: + raise ValidationError(_( + "Account %s is mapped for any balance AND for one side. " + "Use either one 'any balance' mapping, or a debit and/or " + "a credit mapping.", rec.account_id.display_name)) + + @api.model + def _cssk_map_for(self, company): + """``{account_id: {side: reported_code}}`` for ``company``.""" + if not company: + return {} + result = {} + # sudo: the mapping is configuration that decides the figures, so a + # user who may compute or read a statement of ``company`` must get + # the same figures as the manager who configured it — not fewer rows + # because they lack read access to the configuration. Only that one + # company's mappings are read, and only to produce its own figures. + for rec in self.sudo().search([("company_id", "=", company.id)]): + result.setdefault(rec.account_id.id, {})[rec.balance_side] = ( + rec.reported_code.strip()) + return result + + @api.model + def _cssk_reported_code(self, mapping, account_id, own_code, balance, + as_of): + """The code ``account_id`` is reported under, given its balance.""" + sides = mapping.get(account_id) + if not sides: + return own_code + if "any" in sides: + return sides["any"] + if as_of and balance: + side = "credit" if balance < 0 else "debit" + return sides.get(side, own_code) + return own_code diff --git a/l10n_cssk_core/models/cssk_statutory_submission.py b/l10n_cssk_core/models/cssk_statutory_submission.py index af953a31..7fc9e421 100644 --- a/l10n_cssk_core/models/cssk_statutory_submission.py +++ b/l10n_cssk_core/models/cssk_statutory_submission.py @@ -1264,19 +1264,65 @@ def _attach_xml(self, xml_bytes): # map per period window with a single grouped query and do the prefix # matching in Python. - def _cssk_balances_by_account_code(self, domain, company=None): - """``{account_code: summed balance}`` of the ``account.move.line`` - records matching ``domain`` — one ``_read_group`` (grouped by - account). ``company`` resolves the company-dependent account code.""" + def _cssk_balance_groups(self, domain, company=None, as_of=False): + """``{reported_code: [summed balance, [account ids]]}`` of the + ``account.move.line`` records matching ``domain`` — one + ``_read_group`` (grouped by account). ``company`` resolves the + company-dependent account code and its statement account mapping + (``cssk.statement.account.map``); ``as_of`` says the window is a + cumulative balance, the only one a debit- or credit-only mapping + applies to.""" groups = self.env["account.move.line"]._read_group( domain, groupby=["account_id"], aggregates=["balance:sum"]) - balances = {} + Map = self.env["cssk.statement.account.map"] + mapping = Map._cssk_map_for(company) if company else {} + result = {} for account, balance in groups: if company: account = account.with_company(company) - code = account.code or "" - balances[code] = balances.get(code, 0.0) + (balance or 0.0) - return balances + balance = balance or 0.0 + code = Map._cssk_reported_code( + mapping, account.id, account.code or "", balance, as_of) + entry = result.setdefault(code, [0.0, []]) + entry[0] += balance + entry[1].append(account.id) + return result + + def _cssk_balances_by_account_code(self, domain, company=None, + as_of=False): + """``{account_code: summed balance}`` — see ``_cssk_balance_groups``; + the code is the one the account is REPORTED under.""" + return { + code: entry[0] + for code, entry in self._cssk_balance_groups( + domain, company=company, as_of=as_of).items() + } + + def _cssk_account_balance_domain(self, date_from, date_to): + """The ``account.move.line`` domain behind + ``_cssk_account_balance_map`` — shared with the drill-down, so the + journal items a row opens are the ones its figure was summed from.""" + self.ensure_one() + domain = [ + ("parent_state", "=", "posted"), + ("company_id", "=", self.company_id.id), + ("date", "<=", date_to), + ] + if date_from: + domain.append(("date", ">=", date_from)) + closing = self.company_id.l10n_cssk_closing_journal_ids + if closing: + domain.append(("journal_id", "not in", closing.ids)) + return domain + + def _cssk_account_balance_groups(self, date_from, date_to): + """``_cssk_account_balance_map`` with the account ids kept.""" + self.ensure_one() + if not date_to: + return {} + return self._cssk_balance_groups( + self._cssk_account_balance_domain(date_from, date_to), + company=self.company_id, as_of=not date_from) def _cssk_account_balance_map(self, date_from, date_to): """The statement's posted balances per account code for a period @@ -1290,18 +1336,9 @@ def _cssk_account_balance_map(self, date_from, date_to): self.ensure_one() if not date_to: return {} - domain = [ - ("parent_state", "=", "posted"), - ("company_id", "=", self.company_id.id), - ("date", "<=", date_to), - ] - if date_from: - domain.append(("date", ">=", date_from)) - closing = self.company_id.l10n_cssk_closing_journal_ids - if closing: - domain.append(("journal_id", "not in", closing.ids)) return self._cssk_balances_by_account_code( - domain, company=self.company_id) + self._cssk_account_balance_domain(date_from, date_to), + company=self.company_id, as_of=not date_from) # ------------------------------------------------------------------ # Shared evaluator plumbing: ONE account-code matcher @@ -1389,7 +1426,7 @@ def _cssk_code_matches(code, prefix, claimed): @api.model def _cssk_eval_formula(self, formula, balances, conds=None, default_sign=1.0, claimed=None, tag_codes=None, - two_sided=None): + two_sided=None, contributors=None): """Signed sum of ``{account_code: balance}`` matching ``formula``. ``formula`` is a comma-separated list of account-code prefixes; a @@ -1428,7 +1465,11 @@ def _cssk_eval_formula(self, formula, balances, conds=None, account is gated on its OWN balance: a positive token takes only debit balances, a negative token only credit ones. Without the gate the same balance lands on both sides at once and the sheet foots to double the - truth. Left ``None`` the gate is off and every prefix sums as before.""" + truth. Left ``None`` the gate is off and every prefix sums as before. + + ``contributors``, when a set is passed, receives every code that added + to the total — what the row's drill-down must open, decided by this + matcher and not by a second reading of the formula.""" if two_sided and default_sign < 0: # The gate reads the token's own '-' so that it says which SIDE of # the sheet the row is claiming, independent of a caller's global @@ -1460,18 +1501,23 @@ def _cssk_eval_formula(self, formula, balances, conds=None, if negated != (balance < 0): continue total += sign * balance + if contributors is not None: + contributors.add(code) for low, high, mode, csign in (conds or []): - in_range = [balance for code, balance in balances.items() + in_range = [(code, balance) for code, balance in balances.items() if code and low <= code < high] if mode == "pos_each": - total += csign * sum(b for b in in_range if b > 0) + taken = [(c, b) for c, b in in_range if b > 0] elif mode == "neg_each": - total += csign * sum(b for b in in_range if b < 0) + taken = [(c, b) for c, b in in_range if b < 0] else: - net = sum(in_range) - if mode == "sum" or (mode == "pos" and net > 0) \ - or (mode == "neg" and net < 0): - total += csign * net + net = sum(b for _c, b in in_range) + taken = in_range if ( + mode == "sum" or (mode == "pos" and net > 0) + or (mode == "neg" and net < 0)) else [] + total += csign * sum(b for _c, b in taken) + if contributors is not None: + contributors.update(c for c, _b in taken) return total @api.model diff --git a/l10n_cssk_core/models/res_partner.py b/l10n_cssk_core/models/res_partner.py index f97b3ee5..17f1fe71 100644 --- a/l10n_cssk_core/models/res_partner.py +++ b/l10n_cssk_core/models/res_partner.py @@ -29,8 +29,8 @@ class ResPartner(models.Model): # defect in a statutory document, not untidy data. # # Storage is canonicalised and validity is enforced, both scoped to - # CZ/SK. Scoping is not optional: Hetzner's "HRB 6089", a US EIN - # "93-1564675" and a Latvian 11-digit registry are all legitimate, and an + # CZ/SK. Scoping is not optional: a German supplier's "HRB 12345", a US EIN + # "12-3456789" and a Latvian 11-digit registry are all legitimate, and an # unscoped check rejects every one of them. # ------------------------------------------------------------------ @@ -40,7 +40,7 @@ def _l10n_cssk_uses_ico(self): Keyed on the country and NOT on ``_deduce_country_code``, which also reads the VAT prefix. A foreign company VAT-registered in Czechia carries a ``CZ…`` VAT number while its company registry is still its - home register's — a German ``HRB 6089`` — and deducing CZ there would + home register's — a German ``HRB 12345`` — and deducing CZ there would reject a legitimate value. Core scopes the field's own uniqueness by country too. A partner whose country is not yet known is not checked; the constraint fires as soon as one is set. diff --git a/l10n_cssk_core/security/cssk_statement_account_map_rules.xml b/l10n_cssk_core/security/cssk_statement_account_map_rules.xml new file mode 100644 index 00000000..1e88abed --- /dev/null +++ b/l10n_cssk_core/security/cssk_statement_account_map_rules.xml @@ -0,0 +1,8 @@ + + + + Statement account mapping: allowed companies + + [('company_id', 'in', company_ids)] + + diff --git a/l10n_cssk_core/security/ir.model.access.csv b/l10n_cssk_core/security/ir.model.access.csv index 18030686..70235f06 100644 --- a/l10n_cssk_core/security/ir.model.access.csv +++ b/l10n_cssk_core/security/ir.model.access.csv @@ -11,3 +11,5 @@ access_cssk_filing_comparison_user,cssk.filing.comparison user,model_cssk_filing access_cssk_filing_comparison_manager,cssk.filing.comparison manager,model_cssk_filing_comparison,account.group_account_manager,1,1,1,1 access_cssk_footprint_wizard_manager,cssk.statutory.footprint.wizard manager,model_cssk_statutory_footprint_wizard,account.group_account_manager,1,1,1,1 access_cssk_footprint_line_manager,cssk.statutory.footprint.line manager,model_cssk_statutory_footprint_line,account.group_account_manager,1,1,1,1 +access_cssk_statement_account_map_user,cssk.statement.account.map user,model_cssk_statement_account_map,account.group_account_user,1,0,0,0 +access_cssk_statement_account_map_manager,cssk.statement.account.map manager,model_cssk_statement_account_map,account.group_account_manager,1,1,1,1 diff --git a/l10n_cssk_core/tests/test_company_registry.py b/l10n_cssk_core/tests/test_company_registry.py index 911ca9a1..4bbc82ab 100644 --- a/l10n_cssk_core/tests/test_company_registry.py +++ b/l10n_cssk_core/tests/test_company_registry.py @@ -4,8 +4,8 @@ The check itself is pinned in ``test_tools``. What is pinned here is where it applies: CZ/SK only, and only to a value the partner actually carries. Both -boundaries were found the expensive way. An unscoped check rejects Hetzner's -``HRB 6089`` and a US EIN, and a check that fires on an empty value makes every +boundaries were found the expensive way. An unscoped check rejects a German supplier's +``HRB 12345`` and a US EIN, and a check that fires on an empty value makes every private customer unsaveable at checkout. """ from odoo.exceptions import ValidationError @@ -28,7 +28,7 @@ def _partner(self, **vals): def test_valid_sk_and_cz(self): self.assertTrue(self._partner(country_id=self.sk.id, company_registry="54093431")) - self.assertTrue(self._partner(country_id=self.cz.id, company_registry="25380141")) + self.assertTrue(self._partner(country_id=self.cz.id, company_registry="25000063")) def test_no_registry_is_fine(self): """A private customer has no IČO and must still be able to check out.""" @@ -37,8 +37,8 @@ def test_no_registry_is_fine(self): def test_foreign_registry_is_not_checked(self): """The mod-11 scheme is Czechoslovak. Nowhere else uses it.""" - self.assertTrue(self._partner(country_id=self.de.id, company_registry="HRB 6089")) - self.assertTrue(self._partner(country_id=self.us.id, company_registry="93-1564675")) + self.assertTrue(self._partner(country_id=self.de.id, company_registry="HRB 12345")) + self.assertTrue(self._partner(country_id=self.us.id, company_registry="12-3456789")) def test_no_country_is_not_checked(self): """Signup captures an e-mail long before it captures a country.""" @@ -47,14 +47,14 @@ def test_no_country_is_not_checked(self): def test_a_foreign_vat_registration_does_not_pull_in_the_check(self): """A German company VAT-registered in Czechia carries a ``CZ…`` VAT number while its company registry is still its home register's. - Deducing the country from the VAT prefix would reject ``HRB 6089`` + Deducing the country from the VAT prefix would reject ``HRB 12345`` here, so the scope is keyed on the country instead.""" partner = self._partner( country_id=self.de.id, - vat="CZ25380141", - company_registry="HRB 6089", + vat="CZ25000063", + company_registry="HRB 12345", ) - self.assertEqual(partner.company_registry, "HRB 6089") + self.assertEqual(partner.company_registry, "HRB 12345") # -- rejects ------------------------------------------------------------ @@ -65,7 +65,7 @@ def test_rejects_bad_checksum(self): def test_rejects_the_company_name(self): """The failure this exists for: three of these reached production, two of them onto posted invoices.""" - for value in ("TRAIVA, s.r.o.", "JUSTICE.CZ", "Test", "12345", "-"): + for value in ("Vzorová, s.r.o.", "JUSTICE.CZ", "Test", "12345", "-"): with self.assertRaises(ValidationError, msg=f"{value!r} was accepted"): self._partner(country_id=self.cz.id, company_registry=value) @@ -83,30 +83,30 @@ def test_rejects_when_the_country_arrives_later(self): # -- canonicalisation --------------------------------------------------- def test_stores_the_padded_form(self): - partner = self._partner(country_id=self.sk.id, company_registry="00 585 441") - self.assertEqual(partner.company_registry, "00585441") + partner = self._partner(country_id=self.sk.id, company_registry="00 580 007") + self.assertEqual(partner.company_registry, "00580007") def test_pads_an_unpadded_number(self): - partner = self._partner(country_id=self.sk.id, company_registry="614556") - self.assertEqual(partner.company_registry, "00614556") + partner = self._partner(country_id=self.sk.id, company_registry="610003") + self.assertEqual(partner.company_registry, "00610003") def test_canonicalisation_makes_cores_duplicate_check_work(self): """``same_company_registry_partner_id`` is an exact string match in - core, so ``00 585 441`` and ``00585441`` would not see each other + core, so ``00 580 007`` and ``00580007`` would not see each other without this. Normalising at the storage layer is what makes core's own duplicate warning fire.""" - first = self._partner(country_id=self.sk.id, company_registry="00585441") - second = self._partner(country_id=self.sk.id, company_registry="00 585 441") + first = self._partner(country_id=self.sk.id, company_registry="00580007") + second = self._partner(country_id=self.sk.id, company_registry="00 580 007") self.assertEqual(second.same_company_registry_partner_id, first) def test_foreign_registry_is_left_alone(self): - partner = self._partner(country_id=self.de.id, company_registry="HRB 6089") - self.assertEqual(partner.company_registry, "HRB 6089") + partner = self._partner(country_id=self.de.id, company_registry="HRB 12345") + self.assertEqual(partner.company_registry, "HRB 12345") def test_canonicalisation_does_not_loop(self): - partner = self._partner(country_id=self.sk.id, company_registry="614556") - partner.write({"company_registry": "00 585 441"}) - self.assertEqual(partner.company_registry, "00585441") + partner = self._partner(country_id=self.sk.id, company_registry="610003") + partner.write({"company_registry": "00 580 007"}) + self.assertEqual(partner.company_registry, "00580007") def test_short_number_is_not_padded_into_validity(self): """``1`` zero-pads to ``00000001``, which satisfies the check digit. diff --git a/l10n_cssk_core/tests/test_tools.py b/l10n_cssk_core/tests/test_tools.py index 75dfb7ad..5d08ba3d 100644 --- a/l10n_cssk_core/tests/test_tools.py +++ b/l10n_cssk_core/tests/test_tools.py @@ -57,21 +57,23 @@ def test_half_up(self): self.assertEqual(statutory_whole(-9.7), -10) -# Real IČOs, taken from the Data Dance production partner base and confirmed -# against the live registers (ORSF for SK, ARES for CZ) on 2026-09-07. +# The shapes found on a production partner base and confirmed against the +# live registers (ORSF for SK, ARES for CZ) on 2026-09-07. Except Data +# Dance's own, the numbers are synthetic with the check digit computed, so +# that no business partner is named. VALID_ICOS = ( ("54093431", "Data Dance s.r.o. (SK)"), - ("36459488", "eezysmile spol. s r.o. (SK)"), - ("00614556", "Moore BDR s. r. o. (SK, leading zeros)"), - ("47068370", "Cloudfarms a.s. (SK)"), - ("56040890", "Táborčatá s. r. o. (SK, recent)"), - ("57152063", "AVENTIC s. r. o. (SK)"), - ("32112475", "Jana Havelková-HAVELKA (SK, sole trader)"), - ("25380141", "TRAIVA, s.r.o. (CZ)"), - ("14042576", "PEMAT CZECHIA s.r.o. (CZ)"), - ("41603141", "Bitt technology ČS (CZ)"), - ("28585658", "VITAMINÁTOR s.r.o. (CZ)"), - ("00216054", "Helian Group s.r.o. (CZ, leading zeros)"), + ("36000019", "SK company"), + ("00610003", "SK, leading zeros"), + ("47000023", "SK joint-stock company"), + ("56000031", "SK, recent"), + ("57000042", "SK company"), + ("32000057", "SK, sole trader"), + ("25000063", "CZ company"), + ("14000075", "CZ company"), + ("41000081", "CZ company"), + ("28000099", "CZ company"), + ("00210005", "CZ, leading zeros"), ) # Every value the checksum rejected on that same base. Three are the company @@ -85,20 +87,20 @@ def test_half_up(self): ("20239413", "8 digits, wrong check digit"), ("Test", "placeholder word"), ("-", "dash"), - ("cepatay111", "mailbox name"), - ("TRAIVA, s.r.o.", "company name in the number field"), + ("office111", "mailbox name"), + ("Vzorová, s.r.o.", "company name in the number field"), ("JUSTICE.CZ", "register's website"), - ("Bitt technology ČS, spol. s r.o.", "company name in the number field"), + ("Príklad technology ČS, spol. s r.o.", "company name in the number field"), ) # Legitimate registry numbers from countries that do not use the IČO scheme. # The check must never see these; if it does, it rejects real customers. FOREIGN_REGISTRIES = ( - "HRB 6089", # Hetzner Online GmbH (DE) - "93-1564675", # Runpod Inc (US EIN) - "40103265308", # Ascensio System SIA (LV) - "1-120979442", # LABA INTERNATIONAL LIMITED (PG) - "201871706", # VATIVAN Ltd. (BG) + "HRB 12345", # DE, Handelsregister + "12-3456789", # US EIN + "40000000000", # LV, 11 digits + "1-123456789", # PG + "200000000", # BG ) @@ -113,11 +115,11 @@ def test_invalid(self): def test_accepts_the_printed_forms(self): # The register prints in triplets and users paste that verbatim. - self.assertTrue(is_valid_ico("00 585 441")) # KOOPERATIVA (SK) + self.assertTrue(is_valid_ico("00 580 007")) # KOOPERATIVA (SK) self.assertTrue(is_valid_ico("097 25 539")) # One Glare s.r.o. (CZ) # An unpadded number is still the same number. - self.assertTrue(is_valid_ico("614556")) # == 00614556 - self.assertTrue(is_valid_ico("216054")) # == 00216054 + self.assertTrue(is_valid_ico("610003")) # == 00610003 + self.assertTrue(is_valid_ico("210005")) # == 00210005 def test_falsy_and_zero(self): for value in ("", None, False, "0", "00000000"): @@ -140,20 +142,20 @@ def test_rejects_short_numbers_that_satisfy_the_check_digit(self): def test_the_floor_does_not_reject_a_real_number(self): """Every genuine IČO is written with at least six digits, including the old ones that carry leading zeros.""" - for value in ("614556", "216054", "585441", "695599"): - self.assertTrue(is_valid_ico(value), f"{value} is a real IČO") + for value in ("610003", "210005", "580007", "690007"): + self.assertTrue(is_valid_ico(value), f"{value} is a valid IČO") class TestNormalizeRegistry(BaseCase): def test_pads_and_strips(self): - self.assertEqual(normalize_registry("00 585 441"), "00585441") + self.assertEqual(normalize_registry("00 580 007"), "00580007") self.assertEqual(normalize_registry("097 25 539"), "09725539") - self.assertEqual(normalize_registry("614556"), "00614556") + self.assertEqual(normalize_registry("610003"), "00610003") self.assertEqual(normalize_registry(" 54093431 "), "54093431") def test_keeps_significant_leading_zeros(self): # Dropping these corrupts the number on a Czech invoice. - self.assertEqual(normalize_registry("00216054"), "00216054") + self.assertEqual(normalize_registry("00210005"), "00210005") self.assertEqual(normalize_registry("02144298"), "02144298") def test_never_destroys_an_unrecognised_value(self): @@ -185,8 +187,8 @@ def test_does_not_pad_a_short_number_into_validity(self): class TestRegistryKey(BaseCase): def test_drops_padding_so_the_two_forms_match(self): - self.assertEqual(registry_key("00614556"), registry_key("614556")) - self.assertEqual(registry_key("00 585 441"), "585441") + self.assertEqual(registry_key("00610003"), registry_key("610003")) + self.assertEqual(registry_key("00 580 007"), "580007") def test_distinct_numbers_stay_distinct(self): self.assertNotEqual(registry_key("54093431"), registry_key("54093432")) diff --git a/l10n_cssk_core/tools.py b/l10n_cssk_core/tools.py index be657b3f..aa5b0710 100644 --- a/l10n_cssk_core/tools.py +++ b/l10n_cssk_core/tools.py @@ -36,7 +36,7 @@ def statutory_whole(value): #: it, so ``1``, ``19``, ``27`` … all satisfy the arithmetic once zero-padded, #: and **10 000** values of five digits or fewer would be accepted as IČOs. #: Every genuine IČO is written with at least six (the oldest carry leading -#: zeros -- ``00614556``, ``00216054`` -- and are printed as ``614556``), so +#: zeros -- ``00610003``, ``00210005`` -- and are printed as ``610003``), so #: this floor rejects all 10 000 and costs nothing real. ICO_MIN_DIGITS = 6 #: Weights applied to the first seven digits by the mod-11 check. @@ -47,8 +47,8 @@ def normalize_registry(value): """Canonical STORAGE form of a CZ/SK company registry (IČO). Whitespace goes and the number is zero-padded to eight digits, so the - register's ``00 585 441`` and a user's ``585441`` both become - ``00585441``. The leading zeros are significant and are KEPT — a Czech + register's ``00 580 007`` and a user's ``580007`` both become + ``00580007``. The leading zeros are significant and are KEPT — a Czech IČO is printed with them, and dropping them corrupts the number on an invoice. @@ -56,10 +56,10 @@ def normalize_registry(value): :data:`ICO_LENGTH` digits is returned with only its outer whitespace trimmed. This function never destroys an unrecognised value: the caller sees back what was typed, so a validation error can quote it, and a - foreign registry (``HRB 6089``, ``93-1564675``) passes through untouched. + foreign registry (``HRB 12345``, ``12-3456789``) passes through untouched. **The lower bound is load-bearing, not tidiness.** Padding is what makes - ``614556`` and ``00614556`` the same number — but pad with no floor and + ``610003`` and ``00610003`` the same number — but pad with no floor and ``1`` becomes ``00000001``, which satisfies the check digit. The floor in :func:`is_valid_ico` cannot catch that on its own, because by the time it looks there are eight digits. Both functions must refuse the same short @@ -99,7 +99,7 @@ def is_valid_ico(value): ``11 - remainder`` — except that a remainder of 0 gives 1 and a remainder of 1 gives 0. - An unpadded number is accepted (``614556`` is ``00614556``), but not one + An unpadded number is accepted (``610003`` is ``00610003``), but not one shorter than :data:`ICO_MIN_DIGITS` — see there for why the check digit alone is not enough. @@ -196,10 +196,10 @@ def company_name_similarity(left, right): one that was **calibrated against the register**. Measured on 45 real Slovak companies (stored name vs the name ORSF returned): 44 scored exactly 1.000, including every case where the raw strings differed — - ``DEMI Šport plus`` vs ``DEMI šport plus`` (case), ``spol. s r.o.`` vs - ``spol.s r.o.`` (spacing), ``Europe Express s. r. o.`` vs ``Europe + ``ALFA Šport plus`` vs ``ALFA šport plus`` (case), ``spol. s r.o.`` vs + ``spol.s r.o.`` (spacing), ``GAMA Express s. r. o.`` vs ``GAMA Express, s. r. o.`` (comma). The single outlier scored 0.716: - ``Nakladatelství FORUM s.r.o.`` against the register's ``…, organizačná + ``Nakladatelství EPSILON s.r.o.`` against the register's ``…, organizačná zložka``, which is a real difference and belongs in review. Swapping in a cleverer ratio would move those numbers and invalidate the diff --git a/l10n_cssk_core/views/cssk_core_menus.xml b/l10n_cssk_core/views/cssk_core_menus.xml index fb9c262b..18dcd975 100644 --- a/l10n_cssk_core/views/cssk_core_menus.xml +++ b/l10n_cssk_core/views/cssk_core_menus.xml @@ -28,6 +28,13 @@ action="cssk_person_type_action" sequence="20"/> + + + + +
+ + + +
+ + + + + + + diff --git a/l10n_cssk_fs_base/tests/test_fs_base.py b/l10n_cssk_fs_base/tests/test_fs_base.py index 8a2542d2..8c4f9a89 100644 --- a/l10n_cssk_fs_base/tests/test_fs_base.py +++ b/l10n_cssk_fs_base/tests/test_fs_base.py @@ -1,5 +1,7 @@ +from freezegun import freeze_time + from odoo.addons.account.tests.common import AccountTestInvoicingCommon -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError from odoo.tests import tagged from odoo.tests.common import TransactionCase @@ -124,6 +126,68 @@ def test_override_flows_into_aggregate(self): self.assertAlmostEqual(rows["total"].current_value, 500.0, places=2) self.assertTrue(rows["a022"].is_overridden) + def test_prior_override_survives_recompute_and_feeds_aggregate(self): + """The first year in Odoo has no prior-year ledger: the comparative + figure is typed in, kept on recompute and summed into the totals, + while the current column still comes from the books.""" + self._post_entry("2026-06-15", 1000.0) + st = self._make_statement() + st.action_compute_lines() + a022 = st.line_ids.filtered(lambda line: line.code == "a022") + self.assertAlmostEqual(a022.prior_value, 0.0, places=2) + a022.write({"is_prior_overridden": True, "prior_manual_value": 640.0}) + st.action_compute_lines() + rows = {line.code: line for line in st.line_ids} + self.assertTrue(rows["a022"].is_prior_overridden) + self.assertAlmostEqual(rows["a022"].prior_value, 640.0, places=2) + self.assertAlmostEqual(rows["total"].prior_value, 640.0, places=2) + self.assertAlmostEqual(rows["a022"].current_value, 1000.0, places=2) + self.assertFalse(rows["a022"].is_overridden) + + def test_override_edit_requires_compute_before_export(self): + """Totals over an edited row are stale until Compute, so the edit + sends the statement back to draft and export refuses it.""" + self._post_entry("2026-06-15", 1000.0) + st = self._make_statement() + st.action_compute_lines() + self.assertEqual(st.state, "preview") + a022 = st.line_ids.filtered(lambda line: line.code == "a022") + a022.write({"is_overridden": True, "manual_value": 500.0}) + self.assertEqual(st.state, "draft") + with self.assertRaises(UserError): + st.action_export_xml() + st.action_compute_lines() + self.assertEqual(st.state, "preview") + total = st.line_ids.filtered(lambda line: line.code == "total") + self.assertAlmostEqual(total.current_value, 500.0, places=2) + + def test_aggregate_cannot_be_overridden(self): + """A total typed over its own rows no longer adds them up.""" + self._post_entry("2026-06-15", 1000.0) + st = self._make_statement() + st.action_compute_lines() + total = st.line_ids.filtered(lambda line: line.code == "total") + with self.assertRaises(ValidationError): + total.write({"is_overridden": True, "manual_value": 1.0}) + with self.assertRaises(ValidationError): + total.write({"is_prior_overridden": True}) + + def test_stale_aggregate_override_is_not_carried_over(self): + """One ticked before the constraint existed is dropped on recompute + instead of failing it.""" + self._post_entry("2026-06-15", 1000.0) + st = self._make_statement() + st.action_compute_lines() + total = st.line_ids.filtered(lambda line: line.code == "total") + self.env.cr.execute( + "UPDATE cssk_fs_statement_line SET is_overridden = TRUE, " + "manual_value = 1 WHERE id = %s", [total.id]) + total.invalidate_recordset() + st.action_compute_lines() + total = st.line_ids.filtered(lambda line: line.code == "total") + self.assertFalse(total.is_overridden) + self.assertAlmostEqual(total.current_value, 1000.0, places=2) + def test_leaf_drilldown_is_on_demand(self): """Leaf rows carry a re-queryable domain (not stored ids) that ties to the postings; aggregates carry none; the statement health roll-up is OK.""" @@ -201,3 +265,359 @@ def test_reverse_drill_footprint(self): wizard = self.env["cssk.statutory.footprint.wizard"].browse( wizard_action["res_id"]) self.assertIn("a022", wizard.line_ids.mapped("code")) + + # ------------------------------------------------------------------ + # Income-statement coverage and the fiscal-year default + # ------------------------------------------------------------------ + def _combined_version(self): + """A balance sheet that also carries movement-basis P&L rows, the way + the Slovak Úč POD does.""" + return self.env["cssk.fs.statement.version"].create({ + "name": "TST Úč POD", "country_id": self.env.ref("base.sk").id, + "statement_kind": "balance_sheet", "valid_from": "2025-01-01", + "xml_template_ref_id": self.template.id, + "xml_root_element": "FS", "xml_schema_optional": True, + "line_def_ids": [ + (0, 0, {"code": "s022", "name": "DHM", "kind": "accounts", + "basis": "as_of", "account_formula": "022", + "sequence": 10}), + (0, 0, {"code": "r6", "name": "Výnosy", "kind": "accounts", + "basis": "movement", "account_formula": "6", + "sequence": 20}), + ], + }) + + def test_covers_profit_loss(self): + self.assertFalse(self.version.covers_profit_loss, + "a plain balance sheet is not an income statement") + self.assertTrue(self._combined_version().covers_profit_loss) + + def test_income_statement_menu_lists_combined_statement(self): + """On SK the Income statement menu was always empty: no profit_loss + version exists, the výkaz lives inside the Úč POD balance sheet.""" + combined = self._combined_version() + # Leave no profit_loss version in play for SK, as on a real SK database. + self.env["cssk.fs.statement.version"].search([ + ("statement_kind", "=", "profit_loss"), + ("country_id", "=", self.env.ref("base.sk").id), + ]).write({"country_id": self.env.ref("base.cz").id}) + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": combined.id, + "date_from": "2026-01-01", "date_to": "2026-12-31", + }) + action = self.env.ref("l10n_cssk_fs_base.cssk_fs_pl_action") + domain = eval(action.domain) # noqa: S307 - our own action literal + self.assertIn(st, self.env["cssk.fs.statement"].search(domain)) + plain = self._make_statement() + self.assertNotIn(plain, self.env["cssk.fs.statement"].search(domain)) + self.assertTrue(st.name.startswith("Financial statements"), st.name) + # New from that menu lands on the combined version. + defaults = self.env["cssk.fs.statement"].with_company( + self.company).with_context(fs_create_kind="profit_loss", + lang="en_US").default_get( + ["version_id", "date_from", "date_to"]) + self.assertEqual(defaults.get("version_id"), combined.id) + + def test_default_period_is_last_fiscal_year(self): + """A company whose year ends in March gets April-March, not Jan-Dec.""" + self.company.write({"fiscalyear_last_month": "3", + "fiscalyear_last_day": 31}) + Statement = self.env["cssk.fs.statement"].with_company(self.company) + with freeze_time("2026-10-01"): + defaults = Statement.default_get(["date_from", "date_to"]) + self.assertEqual(str(defaults["date_from"]), "2025-04-01") + self.assertEqual(str(defaults["date_to"]), "2026-03-31") + self.company.write({"fiscalyear_last_month": "12"}) + with freeze_time("2026-10-01"): + defaults = Statement.default_get(["date_from", "date_to"]) + self.assertEqual(str(defaults["date_from"]), "2025-01-01") + self.assertEqual(str(defaults["date_to"]), "2025-12-31") + + # ------------------------------------------------------------------ + # Statement account mapping and the contributor drill-down + # ------------------------------------------------------------------ + def _account(self, code, account_type): + return self.env["account.account"].create({ + "name": "Test %s" % code, "code": code, + "account_type": account_type, + "company_ids": [(6, 0, [self.company.id])], + }) + + def _post_pair(self, date, debit_acc, credit_acc, amount, journal=None): + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": (journal or self.journal).id, + "date": date, + "line_ids": [ + (0, 0, {"account_id": debit_acc.id, + "debit": amount, "credit": 0.0}), + (0, 0, {"account_id": credit_acc.id, + "debit": 0.0, "credit": amount}), + ], + }) + move.action_post() + return move + + def _loan_version(self): + return self.env["cssk.fs.statement.version"].create({ + "name": "TST loans", "country_id": self.env.ref("base.sk").id, + "statement_kind": "balance_sheet", "valid_from": "2025-01-01", + "xml_template_ref_id": self.template.id, + "xml_root_element": "FS", "xml_schema_optional": True, + "line_def_ids": [ + (0, 0, {"code": "bank", "name": "Banky", "kind": "accounts", + "account_formula": "221000", "sequence": 10}), + (0, 0, {"code": "loan_long", "name": "Dlhodobé úvery", + "kind": "accounts", "account_formula": "-461100", + "sequence": 20}), + (0, 0, {"code": "loan_short", "name": "Bežné úvery", + "kind": "accounts", + "account_formula": "-221900,-461200", + "sequence": 30}), + (0, 0, {"code": "eq", "name": "VI", "kind": "accounts", + "account_formula": "-411", "sequence": 40}), + # A residual token, as Úč POD has: it switches on the + # synthetic-absorb rule that lets 221000 take 221019. + (0, 0, {"code": "res02", "name": "Ostatný DHM", + "kind": "accounts", "account_formula": "02X", + "sequence": 50}), + ], + }) + + def _loan_statement(self, version): + return self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": version.id, + "date_from": "2026-01-01", "date_to": "2026-12-31", + }) + + def test_account_mapping_reports_analytic_under_form_code(self): + """A chart's own analytic (461002) reaches no row until mapped to the + code the form names (461200); then it lands there, leaves the + unmapped list, and its row drills into exactly its journal items.""" + version = self._loan_version() + loan = self._account("461002", "liability_non_current") + self._post_pair("2026-03-01", self.acc_asset, loan, 800.0) + st = self._loan_statement(version) + st.action_compute_lines() + rows = {line.code: line for line in st.line_ids} + self.assertAlmostEqual(rows["loan_short"].current_value, 0.0, places=2) + self.assertIn("461002", st.unmapped_note or "") + row = st.unmapped_line_ids.filtered(lambda r: r.code == "461002") + self.assertEqual(row.account_ids, loan) + self.assertEqual(row.account_name, loan.name) + self.assertAlmostEqual(row.balance, -800.0, places=2) + items = self.env["account.move.line"].search( + row.action_view_journal_items()["domain"]) + self.assertEqual(items.account_id, loan) + self.assertAlmostEqual(sum(items.mapped("balance")), -800.0, places=2) + action = st.action_view_unmapped() + self.assertEqual(action["target"], "new") + self.assertEqual(action["res_id"], st.id) + + self.env["cssk.statement.account.map"].create({ + "company_id": self.company.id, "account_id": loan.id, + "reported_code": "461200", + }) + st.action_compute_lines() + rows = {line.code: line for line in st.line_ids} + self.assertAlmostEqual(rows["loan_short"].current_value, 800.0, places=2) + self.assertNotIn("461002", st.unmapped_note or "") + self.assertTrue(rows["loan_short"].source_reconciles) + found = self.env["account.move.line"].search( + rows["loan_short"].action_view_source_lines()["domain"]) + self.assertEqual(found.account_id, loan) + + def test_account_mapping_credit_side_only(self): + """An overdrawn bank account is reported as a short-term bank loan; + in funds it stays a bank account. The side follows the balance at + the period end.""" + version = self._loan_version() + bank = self._account("221019", "asset_cash") + self.env["cssk.statement.account.map"].create({ + "company_id": self.company.id, "account_id": bank.id, + "balance_side": "credit", "reported_code": "221900", + }) + # in funds: +300 on the bank + self._post_pair("2026-02-01", bank, self.acc_eq, 300.0) + st = self._loan_statement(version) + st.action_compute_lines() + rows = {line.code: line for line in st.line_ids} + self.assertAlmostEqual(rows["bank"].current_value, 300.0, places=2) + self.assertAlmostEqual(rows["loan_short"].current_value, 0.0, places=2) + # overdrawn: −700 by the period end + self._post_pair("2026-11-01", self.acc_asset, bank, 1000.0) + st.action_compute_lines() + rows = {line.code: line for line in st.line_ids} + self.assertAlmostEqual(rows["bank"].current_value, 0.0, places=2) + self.assertAlmostEqual(rows["loan_short"].current_value, 700.0, places=2) + self.assertTrue(rows["loan_short"].source_reconciles) + # a movement window has no side: the account keeps its own code + movement = st._cssk_account_balance_map("2026-01-01", "2026-12-31") + self.assertIn("221019", movement) + self.assertNotIn("221900", movement) + + def test_account_mapping_constraints(self): + bank = self._account("221018", "asset_cash") + Map = self.env["cssk.statement.account.map"] + Map.create({"company_id": self.company.id, "account_id": bank.id, + "balance_side": "credit", "reported_code": "221900"}) + Map.create({"company_id": self.company.id, "account_id": bank.id, + "balance_side": "debit", "reported_code": "221000"}) + with self.assertRaises(ValidationError): + Map.create({"company_id": self.company.id, "account_id": bank.id, + "balance_side": "any", "reported_code": "221000"}) + other = self._account("461003", "liability_non_current") + with self.assertRaises(ValidationError): + Map.create({"company_id": self.company.id, "account_id": other.id, + "reported_code": "-461200"}) + + def test_drilldown_of_as_of_row_is_cumulative_and_skips_closing(self): + """A balance-sheet row reports the CUMULATIVE balance without the + year-end closing journals; its drill-down used to open only the + period movement, in every journal, and so never reconciled once a + prior year existed.""" + closing = self.env["account.journal"].create({ + "name": "Uzávierka", "code": "UZTS", "type": "general", + "company_id": self.company.id, + }) + self.company.l10n_cssk_closing_journal_ids = [(6, 0, closing.ids)] + self._post_entry("2025-06-15", 700.0) + self._post_entry("2026-06-15", 1000.0) + self._post_pair("2025-12-31", self.acc_eq, self.acc_asset, 50.0, + journal=closing) + st = self._make_statement() + st.action_compute_lines() + leaf = st.line_ids.filtered(lambda line: line.code == "a022") + self.assertAlmostEqual(leaf.current_value, 1700.0, places=2) + self.assertTrue(leaf.source_reconciles) + found = self.env["account.move.line"].search( + leaf.action_view_source_lines()["domain"]) + self.assertAlmostEqual(sum(found.mapped("balance")), 1700.0, places=2) + self.assertNotIn(closing, found.journal_id) + + def test_footprint_follows_account_mapping(self): + version = self._loan_version() + loan = self._account("461004", "liability_non_current") + self.env["cssk.statement.account.map"].create({ + "company_id": self.company.id, "account_id": loan.id, + "reported_code": "461100", + }) + move = self._post_pair("2026-03-01", self.acc_asset, loan, 100.0) + self._loan_statement(version).action_compute_lines() + line = move.line_ids.filtered(lambda ln: ln.account_id == loan) + codes = [fp["code"] for fp in line._cssk_statutory_footprint()] + self.assertIn("loan_long", codes) + self.assertNotIn("loan_short", codes) + + def test_every_row_files_columns_that_add_up(self): + """Totals and rows without a korekcia formula carry brutto and + korekcia too: r001 SPOLU MAJETOK used to file 0 / 0 / netto.""" + version = self.env["cssk.fs.statement.version"].create({ + "name": "TST columns", "country_id": self.env.ref("base.sk").id, + "statement_kind": "balance_sheet", "valid_from": "2025-01-01", + "xml_template_ref_id": self.template.id, + "xml_root_element": "FS", "xml_schema_optional": True, + "line_def_ids": [ + (0, 0, {"code": "total", "name": "Spolu", "kind": "aggregate", + "aggregate_formula": "dhm + cash", "sequence": 5}), + (0, 0, {"code": "dhm", "name": "DHM", "kind": "accounts", + "account_formula": "022", + "account_formula_correction": "082", + "sequence": 10}), + (0, 0, {"code": "cash", "name": "Peniaze", "kind": "accounts", + "account_formula": "211", "sequence": 20}), + ], + }) + depreciation = self._account("082999", "asset_non_current") + cash = self._account("211999", "asset_cash") + self._post_entry("2026-03-01", 1000.0) + self._post_pair("2026-06-01", self.acc_eq, depreciation, 200.0) + self._post_pair("2026-06-02", cash, self.acc_eq, 500.0) + st = self._loan_statement(version) + st.action_compute_lines() + rows = {line.code: line for line in st.line_ids} + expected = {"dhm": (1000.0, 200.0, 800.0), + "cash": (500.0, 0.0, 500.0), + "total": (1500.0, 200.0, 1300.0)} + for code, (brutto, korekcia, netto) in expected.items(): + row = rows[code] + self.assertAlmostEqual(row.gross_value, brutto, places=2, msg=code) + self.assertAlmostEqual( + row.correction_value, korekcia, places=2, msg=code) + self.assertAlmostEqual(row.current_value, netto, places=2, msg=code) + + def test_override_keeps_korekcia_and_derives_brutto(self): + """An overridden netto keeps the ledger's korekcia, and brutto + follows, so the three columns still satisfy netto = brutto - + korekcia.""" + version = self.env["cssk.fs.statement.version"].create({ + "name": "TST brutto", "country_id": self.env.ref("base.sk").id, + "statement_kind": "balance_sheet", "valid_from": "2025-01-01", + "xml_template_ref_id": self.template.id, + "xml_root_element": "FS", "xml_schema_optional": True, + "line_def_ids": [ + (0, 0, {"code": "dhm", "name": "DHM", "kind": "accounts", + "account_formula": "022", + "account_formula_correction": "082", + "sequence": 10}), + ], + }) + depreciation = self._account("082999", "asset_non_current") + self._post_entry("2026-03-01", 1000.0) + self._post_pair("2026-06-01", self.acc_eq, depreciation, 200.0) + st = self._loan_statement(version) + st.action_compute_lines() + row = st.line_ids.filtered(lambda line: line.code == "dhm") + self.assertAlmostEqual(row.gross_value, 1000.0, places=2) + self.assertAlmostEqual(row.correction_value, 200.0, places=2) + self.assertAlmostEqual(row.current_value, 800.0, places=2) + self.assertTrue(row.source_reconciles) + row.write({"is_overridden": True, "manual_value": 750.0}) + st.action_compute_lines() + row = st.line_ids.filtered(lambda line: line.code == "dhm") + self.assertAlmostEqual(row.current_value, 750.0, places=2) + self.assertAlmostEqual(row.correction_value, 200.0, places=2) + self.assertAlmostEqual(row.gross_value, 950.0, places=2) + self.assertAlmostEqual( + row.gross_value - row.correction_value, row.current_value, + places=2) + + def test_sections_split_combined_document(self): + """A combined document splits by the TOP of each row's tree: a + movement-basis row under an as-of total (A.VIII under SPOLU VLASTNÉ + IMANIE A ZÁVÄZKY) stays in the balance-sheet part.""" + version = self.env["cssk.fs.statement.version"].create({ + "name": "TST Úč POD parts", "country_id": self.env.ref("base.sk").id, + "statement_kind": "balance_sheet", "valid_from": "2025-01-01", + "xml_template_ref_id": self.template.id, + "xml_root_element": "FS", "xml_schema_optional": True, + "line_def_ids": [ + (0, 0, {"code": "s079", "name": "Spolu pasíva", + "kind": "aggregate", "basis": "as_of", + "aggregate_formula": "s080 + s100", "sequence": 10}), + (0, 0, {"code": "s080", "name": "VI", "kind": "accounts", + "basis": "as_of", "account_formula": "-411", + "sequence": 20}), + (0, 0, {"code": "s100", "name": "A.VIII VH", + "kind": "accounts", "basis": "movement", + "account_formula": "-5,-6", "sequence": 30}), + (0, 0, {"code": "r61", "name": "VH po zdanení", + "kind": "aggregate", "basis": "movement", + "aggregate_formula": "r01", "sequence": 40}), + (0, 0, {"code": "r01", "name": "Výnosy", "kind": "accounts", + "basis": "movement", "account_formula": "-6", + "sequence": 50}), + ], + }) + st = self._loan_statement(version) + st.action_compute_lines() + parts = {line.code: line.in_movement_section for line in st.line_ids} + self.assertEqual(parts, {"s079": False, "s080": False, "s100": False, + "r61": True, "r01": True}) + self.assertEqual(set(st.balance_line_ids.mapped("code")), + {"s079", "s080", "s100"}) + self.assertEqual(set(st.movement_line_ids.mapped("code")), + {"r61", "r01"}) + for xmlid, section in (("cssk_fs_balance_action", "balance"), + ("cssk_fs_pl_action", "movement")): + ctx = self.env.ref("l10n_cssk_fs_base.%s" % xmlid).context + self.assertIn("'fs_section': '%s'" % section, ctx) diff --git a/l10n_cssk_fs_base/views/cssk_fs_statement_views.xml b/l10n_cssk_fs_base/views/cssk_fs_statement_views.xml index 97c22238..cc531165 100644 --- a/l10n_cssk_fs_base/views/cssk_fs_statement_views.xml +++ b/l10n_cssk_fs_base/views/cssk_fs_statement_views.xml @@ -57,7 +57,7 @@ + + + + + diff --git a/l10n_cssk_receipt_capture_expense/views/hr_expense_views.xml b/l10n_cssk_receipt_capture_expense/views/hr_expense_views.xml new file mode 100644 index 00000000..e69c1560 --- /dev/null +++ b/l10n_cssk_receipt_capture_expense/views/hr_expense_views.xml @@ -0,0 +1,14 @@ + + + + hr.expense.form.cssk.receipt + hr.expense + + + + + + + + diff --git a/l10n_cssk_receipt_capture_expense/views/res_config_settings_views.xml b/l10n_cssk_receipt_capture_expense/views/res_config_settings_views.xml new file mode 100644 index 00000000..485a7246 --- /dev/null +++ b/l10n_cssk_receipt_capture_expense/views/res_config_settings_views.xml @@ -0,0 +1,20 @@ + + + + res.config.settings.form.receipt.expense + res.config.settings + + + + + + + + + + + + + diff --git a/l10n_cssk_registry_verify/models/res_partner.py b/l10n_cssk_registry_verify/models/res_partner.py index cdbeea9b..473a53c8 100644 --- a/l10n_cssk_registry_verify/models/res_partner.py +++ b/l10n_cssk_registry_verify/models/res_partner.py @@ -43,7 +43,7 @@ def _prefixed_vat(value): NAME_MATCH_ACCEPT = 0.90 #: Below this the names are not the same company and the caller should say so. #: Between the two, a human looks: the one real case in that band was -#: "Nakladatelství FORUM s.r.o." against the register's ", organizačná zložka" +#: "Nakladatelství EPSILON s.r.o." against the register's ", organizačná zložka" #: -- a genuine company whose registered name carries a branch suffix the user #: omitted, which is exactly what review is for. NAME_MATCH_REVIEW = 0.60 @@ -239,7 +239,7 @@ def _cssk_registry_parse_ares(self, body): a cosmetic error. ARES answers ``dic: null`` for a company that was never registered — - verified against IČO 21924660, which returns null alongside + verified against IČO 21900001, which returns null alongside ``stavZdrojeDph: NEEXISTUJICI``. The registration state is checked as well because "never registered" and "no longer registered" are different facts and only the first is visible in ``dic``. diff --git a/l10n_cssk_registry_verify/tests/test_verify.py b/l10n_cssk_registry_verify/tests/test_verify.py index 240b2603..c9e8f9d9 100644 --- a/l10n_cssk_registry_verify/tests/test_verify.py +++ b/l10n_cssk_registry_verify/tests/test_verify.py @@ -6,9 +6,9 @@ suite that depends on ORSF being up fails for reasons that have nothing to do with the code, and would not run in CI at all. -The payloads are real — captured from ``api.orsf.sk/v1/lookup`` on 2026-09-07 -for Data Dance s.r.o. (a company) and Jana Havelková-HAVELKA (a sole trader, -whose address the register locks for GDPR). +The payloads have the shape captured from ``api.orsf.sk/v1/lookup`` on +2026-09-07: Data Dance s.r.o.'s own record (a company), and a sole trader +whose address the register locks for GDPR — that person's data replaced. """ import unittest from unittest.mock import patch @@ -34,10 +34,10 @@ } SOLE_TRADER = { - "ico": "32112475", - "dic": "1020173099", + "ico": "32000057", + "dic": "1020000000", "icDph": None, - "name": "Jana Havelková-HAVELKA", + "name": "Jana Vzorová-VZOR", "status": "aktívna", "dissolvedOn": None, "address": {"street": None, "city": "Bratislava - mestská časť Ružinov", @@ -107,7 +107,7 @@ def test_a_bad_checksum_never_reaches_the_register(self): Asking the register would spend a request per keystroke to be told the same thing.""" with self._forbid_register(): - for value in ("12345", "Test", "TRAIVA, s.r.o.", "1"): + for value in ("12345", "Test", "Vzorová, s.r.o.", "1"): res = self.Partner._cssk_verify_registry(value, "SK") self.assertEqual(res["outcome"], "invalid", f"{value!r}") @@ -178,16 +178,16 @@ def test_a_sole_traders_locked_address_is_not_an_error(self): field still comes back, which is what a gate needs; the customer types the two missing lines.""" with self._probe("ok", SOLE_TRADER): - res = self.Partner._cssk_verify_registry("32112475", "SK") + res = self.Partner._cssk_verify_registry("32000057", "SK") self.assertEqual(res["outcome"], "verified") - self.assertEqual(res["name"], "Jana Havelková-HAVELKA") + self.assertEqual(res["name"], "Jana Vzorová-VZOR") self.assertTrue(res["active"]) self.assertEqual(res["city"], "Bratislava - mestská časť Ružinov") self.assertFalse(res["street"]) self.assertFalse(res["zip"]) # Not a VAT payer: no IČ DPH invented from the DIČ. self.assertFalse(res["vat"]) - self.assertEqual(res["tax_id"], "1020173099") + self.assertEqual(res["tax_id"], "1020000000") # -- caching ------------------------------------------------------------ @@ -269,10 +269,10 @@ def _verdict(self, typed, official): def test_formatting_differences_are_the_same_company(self): for typed, official in ( ("Data Dance s.r.o.", "Data Dance s.r.o."), - ("DEMI Šport plus, s.r.o.", "DEMI šport plus, s.r.o."), - ("AGEM COMPUTERS, spol. s r.o.", "AGEM COMPUTERS, spol.s r.o."), - ("Europe Express s. r. o.", "Europe Express, s. r. o."), - ("Vitaminátor s.r.o.", "VITAMINÁTOR s.r.o."), + ("ALFA Šport plus, s.r.o.", "ALFA šport plus, s.r.o."), + ("BETA COMPUTERS, spol. s r.o.", "BETA COMPUTERS, spol.s r.o."), + ("GAMA Express s. r. o.", "GAMA Express, s. r. o."), + ("Deltanátor s.r.o.", "DELTANÁTOR s.r.o."), ): verdict, score = self._verdict(typed, official) self.assertEqual(verdict, "match", f"{typed!r} vs {official!r}") @@ -281,8 +281,8 @@ def test_formatting_differences_are_the_same_company(self): def test_a_branch_suffix_goes_to_a_human(self): """The one real mid-band case on the calibration set.""" verdict, score = self._verdict( - "Nakladatelství FORUM s.r.o.", - "Nakladatelství FORUM s.r.o., organizačná zložka", + "Nakladatelství EPSILON s.r.o.", + "Nakladatelství EPSILON s.r.o., organizačná zložka", ) self.assertEqual(verdict, "review") self.assertGreater(score, 0.60) @@ -308,21 +308,22 @@ def test_nothing_to_compare_is_not_a_mismatch(self): self.assertEqual(score, 0.0) -#: Real ARES payloads, captured 2026-09-08. TRAIVA is VAT-registered; Romana -#: Nahorniaková is not, and ARES answers `dic: null` for her. +#: ARES payloads in the shape captured on 2026-09-08, with the companies' +#: and the person's data replaced. The first is VAT-registered; the second +#: is not, and ARES answers `dic: null` for it. ARES_VAT_PAYER = { - "ico": "25380141", - "obchodniJmeno": "TRAIVA, s.r.o.", - "dic": "CZ25380141", - "sidlo": {"textovaAdresa": "Pohraniční 2911/13b, Vítkovice, 70300 Ostrava", + "ico": "25000063", + "obchodniJmeno": "Vzorová, s.r.o.", + "dic": "CZ25000063", + "sidlo": {"textovaAdresa": "Vzorová 1/1, 70300 Ostrava", "nazevObce": "Ostrava", "psc": 70300}, "seznamRegistraci": {"stavZdrojeDph": "AKTIVNI"}, } ARES_NOT_REGISTERED = { - "ico": "21924660", - "obchodniJmeno": "Romana Nahorniaková", + "ico": "21900001", + "obchodniJmeno": "Jana Příkladová", "dic": None, - "sidlo": {"textovaAdresa": "Máchova 402/55, 74101 Nový Jičín", + "sidlo": {"textovaAdresa": "Příkladová 1/1, 74101 Nový Jičín", "nazevObce": "Nový Jičín", "psc": 74101}, "seznamRegistraci": {"stavZdrojeDph": "NEEXISTUJICI"}, } @@ -348,8 +349,8 @@ def setUp(self): def test_a_registered_payer_gets_a_prefixed_vat(self): parsed = self.Partner._cssk_registry_parse_ares(ARES_VAT_PAYER) - self.assertEqual(parsed["vat"], "CZ25380141") - self.assertEqual(parsed["tax_id"], "CZ25380141") + self.assertEqual(parsed["vat"], "CZ25000063") + self.assertEqual(parsed["tax_id"], "CZ25000063") def test_a_company_that_never_registered_gets_no_vat(self): """Empty is correct: it falls through to the consumer position and @@ -363,12 +364,12 @@ def test_a_deregistered_payer_gets_no_vat_even_though_dic_remains(self): parsed = self.Partner._cssk_registry_parse_ares(ARES_DEREGISTERED) self.assertFalse(parsed["vat"]) # Still their tax identifier, just not a VAT number. - self.assertEqual(parsed["tax_id"], "CZ25380141") + self.assertEqual(parsed["tax_id"], "CZ25000063") def test_an_unprefixed_number_is_never_written_as_vat(self): """The guard that would have caught the production incident: a bare IČO in `vat` is what waived VAT on order S00197.""" - bare = dict(ARES_VAT_PAYER, dic="25380141") + bare = dict(ARES_VAT_PAYER, dic="25000063") self.assertFalse(self.Partner._cssk_registry_parse_ares(bare)["vat"]) def test_the_slovak_side_holds_the_same_line(self): @@ -381,5 +382,5 @@ def test_a_slovak_dic_is_never_promoted_to_a_vat_number(self): """The SK trap: DIČ and IČ DPH are different identifiers, and a subject can hold a DIČ without being a VAT payer at all.""" parsed = self.Partner._cssk_registry_parse(SOLE_TRADER) - self.assertEqual(parsed["tax_id"], "1020173099") + self.assertEqual(parsed["tax_id"], "1020000000") self.assertFalse(parsed["vat"]) diff --git a/l10n_cz_dppo/CHANGELOG.rst b/l10n_cz_dppo/CHANGELOG.rst new file mode 100644 index 00000000..09dccc3b --- /dev/null +++ b/l10n_cz_dppo/CHANGELOG.rst @@ -0,0 +1,116 @@ +========= +Changelog +========= + +All notable changes to **l10n_cz_dppo** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +[19.0.1.3.0] — 2026-09-29 +------------------------- + +Fixed +~~~~~ + +- **DPPDP9** ``VetaD`` now carries ``c_nace``, the company's CZ-NACE code, which + EPO checks critically against the code list. + +[19.0.1.2.0] — 2026-09-28 +------------------------- + +Fixed +~~~~~ + +- **Every DPPDP9 declared itself a return on entering liquidation.** VetaD + carried ``typ_dapdpp="B"`` hard-coded, which the XSD defines as *daňové + přiznání při vstupu do likvidace*; the ordinary return for the tax period + is **A**. It is now a choice on the return (*Typ daňového přiznání*, the + XSD's full code list), defaulting to A. +- **``typ_zo`` was "1", which is no letter of § 21a ZDP.** It is now derived + from the period — a calendar year A, another twelve-month year B, anything + longer D — and can be set by hand (C for the period from a merger's decisive + day). ``typ_popldpp`` is a choice too, defaulting to 1 (ostatní). +- **"Dodatečné" exported E, which the form reads as "dodatečné-opravné".** D + now exists and E is labelled for what it is (migration 19.0.1.2.0); a + dodatečné return needs *Důvody zjištěny dne* (``d_zjist``) before export. + Returns already exported are not touched. + +TODO +~~~~ + +- CZ asset ř.50/150 wiring is still pending. The asset book-vs-tax difference is not yet fed + into the DPPDP9 spine (připočitatelné ř.50 / odčitatelné ř.150); the SK counterpart + (``l10n_sk_dppo``) is wired via the ``asset_diff`` line kind, CZ is not. + +[19.0.1.1.0] — 2026-09-28 +------------------------- + +Added +~~~~~ + +- **Rozvaha and Výkaz zisku a ztráty inside the return.** The účetní závěrka + is filed as the XSD's own výkaz records — VetaUA (aktiva: brutto / korekce / + netto / netto minulé), VetaUD (pasiva) and VetaUB (VZZ druhové členění), + zkrácený rozsah per vyhláška 500/2002 Sb., in thousands — with the VetaD + header (``uv_vyhl``, ``uv_rozsah``, ``uv_mena``, ``uz_rad``, ``d_uv``). The + figures are those of the ``l10n_cz_fs`` Rozvaha / VZZ linked to the return + (new dependency), matched row by statutory designation; there is no second + account mapping. Linking is explicit, and a half-linked pair, a statement of + another period or company, an uncomputed one, or one missing a row the + výkaz reads is refused by name. +- The EPO row číselník for the three tables (the XSD does not carry it) is + vendored as ``data/uv_radky_500.csv``. Every row of the zkrácený rozsah is + mapped or declared not applicable with a reason (C.II.1., C.II.3., pasiva + C.III., VZZ V.), and every other výkaz record of the XSD is declared not + applicable by table; a test enforces both, and checks each EPO total + against its statutory parts by signed weight per account of the l10n_cz + chart. +- ``data/SCHEMA_VERSION`` pins the XSD and the číselník (size + md5) with the + refresh procedure. + +Changed +~~~~~~~ + +- ``data/dppdp9_epo2.xsd`` replaced by the published file (it differed only in + the order of a pattern's alternatives), and ``verzePis`` raised from 05.01 + to the published structure version 05.01.01. +- A return exported with no statements linked says so in the chatter: the + závěrka must then go as an E-příloha. +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.1.0.1] — 2026-07-04 +------------------------- + +Changed +~~~~~~~ + +- Statutory rounding unified to HALF-UP (``statutory_round()``/``statutory_whole()``). (Wave 1) +- Multi-company ``ir.rule`` (``company_id in company_ids``) added. (Wave 1) + +Fixed +~~~~~ + +- Removed ``typ_ds="P"`` from the DPPDP9 VetaP element — that attribute belongs to the DPH forms, + not the income-tax return. (Wave 1) +- EPO Pisemnost/DPPDP9 XML: empty attributes now emitted as ``None`` (omitted) instead of ``""`` + (XSD-invalid). (Wave 1) + +[2026-06-30] — Baseline & i18n +------------------------------ + +Added +~~~~~ + +- Czech corporate income-tax return (DPPDP9) on ``l10n_cssk_income_tax_base``: II. oddíl tax + spine (ř.10 → ř.340) mapped to the EPO ``VetaO/@kc_ii*`` attributes. +- ř.10 (výsledek hospodaření před zdaněním) computed from the P&L (class 6 − class 5, excluding + income-tax accounts 591–599); připočitatelné/odčitatelné adjustments, §34 deductions and slevy + are accountant-entered and aggregate into the spine. +- EPO Pisemnost/DPPDP9 XML export validated against the bundled official XSD + (``data/dppdp9_epo2.xsd``). +- cs_CZ jsonb translations (i18n sweep). diff --git a/l10n_cz_dppo/LICENSE b/l10n_cz_dppo/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/l10n_cz_dppo/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/l10n_cz_dppo/README.rst b/l10n_cz_dppo/README.rst new file mode 100644 index 00000000..79d40d6b --- /dev/null +++ b/l10n_cz_dppo/README.rst @@ -0,0 +1,74 @@ +======================================= +CZ Corporate Income Tax Return (DPPDP9) +======================================= + +The Czech country layer for the income-tax framework +(``l10n_cssk_income_tax_base``). It builds the corporate income-tax return +(Přiznání k dani z příjmů právnických osob, DPPDP9): ``ř.10`` (výsledek +hospodaření před zdaněním) is computed from the P&L, the adjustments are entered +by the accountant, and the II. oddíl tax-calculation spine +(základ daně → daň 21 % → daň po slevách) is arithmetic. + +The export produces the official EPO ``Pisemnost``/``DPPDP9`` XML and validates +it against the bundled official DPPDP9 XSD (``data/dppdp9_epo2.xsd``). + +Features +======== + +* ``ř.10`` (výsledek hospodaření před zdaněním) is computed from the P&L: + class 6 − class 5, excluding the income-tax accounts 591–599. +* The připočitatelné / odčitatelné adjustments (ř.20–170), the loss / § 34 + deductions (ř.230 / ř.240) and the slevy na dani (ř.300) are manual, + accountant-entered, and aggregate into the spine. +* The II. oddíl tax-calculation spine (ř.10 → ř.340) is arithmetic: + základ daně → zaokrouhlení → daň 21 % (§ 21 odst. 1) → daň po slevách → + celková daňová povinnost. +* Each spine line maps to its official EPO ``VetaO/@kc_ii*`` attribute; only + non-zero spine lines are emitted. +* The export builds the EPO ``Pisemnost``/``DPPDP9`` XML and validates it + against the bundled official DPPDP9 XSD (``data/dppdp9_epo2.xsd``). +* Statement types Řádné (B), Opravné (O) and Dodatečné (E) are shipped as + version data. +* The účetní závěrka travels inside the return: link the computed + ``l10n_cz_fs`` Rozvaha and Výkaz zisku a ztráty of the same period, and the + export files them as ``VetaUA`` (aktiva — brutto / korekce / netto / netto + minulé), ``VetaUD`` (pasiva) and ``VetaUB`` (VZZ druhové členění), in + thousands, zkrácený rozsah per vyhláška 500/2002 Sb. Each EPO row takes the + ``l10n_cz_fs`` row of the same statutory designation — one account mapping, + not two. The row numbers come from the EPO číselník vendored as + ``data/uv_radky_500.csv``; ``models/dppdp9_vykazy.py`` maps every row of the + zkrácený rozsah or states why it is not applicable. XSD and číselník are + pinned in ``data/SCHEMA_VERSION``. + +**Not applicable, by design:** C.II.1. dlouhodobé pohledávky (the chart +convention files every receivable as krátkodobé), C.II.3. / pasiva C.III. (the +alternative placement of časové rozlišení, which is filed under D.) and VZZ +V. (účet 665 is not split, so it is all filed on IV.) — confirm these with the +accountant. The plný rozsah is not produced: an entity obliged to file it +(střední a velké účetní jednotky, or any with a statutory audit) attaches the +závěrka as an E-příloha and leaves the statements unlinked. + +**Out of scope:** the appendix tables (odpisy příl. 1B, § 23e výpůjční náklady +příl. 3, zápočet daně ze zahraničí, investiční fondy), the přehledy and the +příloha účetní závěrky (E-přílohy). The XSD makes every line but the VetaD header optional, so a +spine-only return validates. Confirm the VetaD header codes +(``typ_dapdpp`` / ``typ_zo`` / ``typ_popldpp``) and the full adjustment set with +a Czech accountant before live filing. + +Usage +===== + +Create a corporate income-tax return for the company and period, pick the +statement type (Řádné / Opravné / Dodatečné), and compute the lines: ``ř.10`` +is derived from the P&L and the tax spine is evaluated automatically. Enter the +připočitatelné / odčitatelné adjustments, the loss and § 34 deductions and the +slevy na dani by hand, link the Rozvaha and the Výkaz zisku a ztráty +(computed in *Financial statements*), recompute, then export the EPO ``Pisemnost``/``DPPDP9`` +XML — it is validated against the bundled DPPDP9 XSD before it is attached. + +Credits +======= + +* Data Dance s.r.o. + +License: AGPL-3.0 or later (see the LICENSE file). diff --git a/l10n_cz_dppo/__init__.py b/l10n_cz_dppo/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_cz_dppo/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_cz_dppo/__manifest__.py b/l10n_cz_dppo/__manifest__.py new file mode 100644 index 00000000..dc4d92fd --- /dev/null +++ b/l10n_cz_dppo/__manifest__.py @@ -0,0 +1,49 @@ +{ + "name": "CZ Corporate Income Tax Return (DPPDP9)", + "summary": "Czech corporate income-tax return on the income-tax framework: " + "ř.10 from the P&L, manual adjustments, the II. oddíl tax spine, " + "and EPO Pisemnost/DPPDP9 XML validated against the official XSD.", + "description": """ +CZ Corporate Income Tax Return (DPPDP9) +======================================= + +The Czech country layer for the income-tax framework +(``l10n_cssk_income_tax_base``). ``ř.10`` (výsledek hospodaření před zdaněním) is +computed from the P&L; the adjustments are entered by the accountant; the spine +(základ daně → daň 21 % → daň po slevách) is arithmetic. + +**Scope:** the II. oddíl tax-calculation spine (ř.10 → ř.340) mapped to the +official EPO ``VetaO/@kc_ii*`` attributes; the export validates against the +bundled official DPPDP9 XSD (``data/dppdp9_epo2.xsd``). ř.10 is computed from the +P&L (class 6 − class 5, excluding income-tax accounts 591–599); the +připočitatelné/odčitatelné adjustments, loss/§34 deductions and slevy are manual +(accountant-entered) and aggregate into the spine. + +**Účetní závěrka:** the Rozvaha and the Výkaz zisku a ztráty (druhové +členění) are filed inside the return as VetaUA / VetaUD / VetaUB, zkrácený +rozsah per vyhláška 500/2002 Sb., read from the ``l10n_cz_fs`` statements +linked to the return — the one account → row mapping, not a second one. Every +row of the zkrácený rozsah is mapped or declared not applicable +(``models/dppdp9_vykazy.py``). + +**Out of scope:** the appendix tables (odpisy příl. 1B, §23e výpůjční náklady +příl. 3, zápočet daně ze zahraničí, investiční fondy), the plný rozsah of the +výkazy, and the přehledy / příloha účetní závěrky (E-přílohy). The XSD makes every line but the VetaD header optional, so a +spine-only return validates. Confirm the VetaD header codes (typ_dapdpp / +typ_zo / typ_popldpp) and the full adjustment set with a CZ accountant before +live filing. +""", + "author": "Data Dance s.r.o.", + "website": "https://www.datadance.eu", + "version": "19.0.1.3.0", + "category": "Accounting/Localizations", + "license": "AGPL-3", + "depends": ["l10n_cssk_income_tax_base", "l10n_cz", "l10n_cz_statutory", + "l10n_cz_fs"], + "data": [ + "report/dppo_report.xml", + "data/cz_dppo_version_data.xml", + "views/cssk_income_tax_views.xml", + ], + "installable": True, +} diff --git a/l10n_cz_dppo/data/SCHEMA_VERSION b/l10n_cz_dppo/data/SCHEMA_VERSION new file mode 100644 index 00000000..bc10fac6 --- /dev/null +++ b/l10n_cz_dppo/data/SCHEMA_VERSION @@ -0,0 +1,46 @@ +DPPDP9 05.01.01 ze dne 21. 10. 2025 + +dppdp9_epo2.xsd 451406 2a1df2ce989d6fa7c4475d5132330f1a +uv_radky_500.csv 10180 2367b4054819acf261bf036d978cfb37 + +Structure version of the bundled dppdp9_epo2.xsd, and the row číselník of the +výkazy filed inside it, pinned by hand. + +WHY A FILE AND NOT A FIELD IN THE SCHEMA: there isn't one. `verzePis` is a +bare xs:string with no fixed value, and the XSD carries no version stamp of its +own; the Finanční správa overwrites the one file in place (one XSD for every +zdaňovací období it accepts — currently 2021-2025 and parts of 2026). A stale +copy validates exactly as cleanly as a current one. This file is the only +thing that makes a refresh visible; tests/test_cz_dppo.py checks the digests +and the template's `verzePis` against it. + +THE ČÍSELNÍK IS NOT IN THE XSD. VetaUA / VetaUB / VetaUD carry `c_radku` as a +3-digit decimal and nothing else; which numbers exist, what they designate and +which rozsah (P / Z / M) they belong to is an EPO application číselník, +published only as HTML. data/uv_radky_500.csv is that číselník for vyhláška +500/2002 Sb. (tables A = VetaUA, P = VetaUD, D = VetaUB), extracted from + https://adisspr.mfcr.cz/dpr/adis/idpr_pub/hlib/uv_info/info_detail.faces?tabulka=23810&platnost=2025 + https://adisspr.mfcr.cz/dpr/adis/idpr_pub/hlib/uv_info/info_detail.faces?tabulka=24810&platnost=2025 + https://adisspr.mfcr.cz/dpr/adis/idpr_pub/hlib/uv_info/info_detail.faces?tabulka=25810&platnost=2025 +(index: .../hlib/uv_info/uv_info.faces?rok=2025). Platnost 2024, 2025 and 2026 +are identical row for row, verified 2026-09-28. + +REFRESH PROCEDURE + 1. Fetch https://adisspr.mfcr.cz/adis/jepo/schema/dppdp9_epo2.xsd (plain + curl). A miss is HTTP 200 with ZERO bytes — test the size, not the code. + 2. Compare against data/dppdp9_epo2.xsd byte for byte. + 3. Read Verze / Ze dne from + https://adisspr.mfcr.cz/dpr/adis/idpr_pub/epo2_info/popis_struktury_detail.faces?zkratka=DPPDP9 + 4. Re-extract the three číselník tables for the new platnost and diff them + against data/uv_radky_500.csv. + 5. If anything moved: replace the files, update the lines at the top of this + file, the `verzePis` literal in report/dppo_report.xml, and the mapping in + models/dppdp9_vykazy.py. Nothing keeps them in step automatically; the + tests only make the drift loud. + +VERIFIED 2026-09-28: the copy bundled since 2026-07-04 differed from the +published file in one byte range only — the order of the alternatives in the +Prilohy/@kod pattern (PP_ZVKAP|PP_UZMUS|PP_PTOK|PP_OPISPUV), which accepts the +same values. Replaced by the published file anyway, so the digest is the +authority's. The template's `verzePis` said 05.01; the published structure is +05.01.01. diff --git a/l10n_cz_dppo/data/cz_dppo_version_data.xml b/l10n_cz_dppo/data/cz_dppo_version_data.xml new file mode 100644 index 00000000..2d127b0d --- /dev/null +++ b/l10n_cz_dppo/data/cz_dppo_version_data.xml @@ -0,0 +1,91 @@ + + + + + + + DPPDP9 2025 + + 2025-01-01 + + Pisemnost + dppdp9_epo2.xsd + + r340 + + + + + + BŘádné + B + + + + OOpravné + O + + + + + DDodatečné + D + + + + EDodatečné-opravné + E + + + + diff --git a/l10n_cz_dppo/data/dppdp9_epo2.xsd b/l10n_cz_dppo/data/dppdp9_epo2.xsd new file mode 100644 index 00000000..17a02314 --- /dev/null +++ b/l10n_cz_dppo/data/dppdp9_epo2.xsd @@ -0,0 +1,6316 @@ + + + + + + + + + + + + + + + + + Uvede se slovní označení předmětu činnosti vykonávané poplatníkem (hlavní činnost). Pokud bylo vykonáváno více činností, uvedou se maximálně dvě činnosti, z nichž dosažené brutto výnosy (příjmy) byly v daném zdaňovacím období nejvyšší (převažující činnost). Neuvádějí se činnosti vykonávané pro vlastní potřebu poplatníka, které podmiňují výkon hlavní (převažující) činnosti. Při vyplnění tohoto údaje se použije Klasifikace ekonomických činností (CZ-NACE), která je přílohou sdělení Českého statistického úřadu č. 400/2024 Sb. <br />Položka obsahuje kritickou kontrolu: musí být vyplněn existující kód činnosti.<br> +Pro popis číselníku Činnosti klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/okec">zde</a>. + + + + + + + + + + + Daňoví nerezidenti (§ 17 odst. 4 zákona), kteří <strong>nejsou</strong> rezidenty členského státu Evropské unie nebo dalších států tvořících Evropský hospodářský prostor, uvedou na tomto řádku celkovou částku zajištění daně, sraženého jim všemi plátci z příjmů podle § 38e odst. 2 zákona, které jsou součástí základu daně za zdaňovací období nebo za období, za které se podává daňové přiznání; její rozčlenění podle jednotlivých plátců se provede na zvláštní příloze. Pokud jsou tito poplatníci společníky veřejných obchodních společností nebo komplementáři komanditních společností, bude částka vykázaná na tomto řádku zahrnovat též zajištění daně sražené jim veřejnou obchodní společností nebo komanditní společností podle § 38e odst. 3 písm. b) zákona za zdaňovací období nebo období, za něž je podáváno daňové přiznání. + + + + + + + + + + + <TABLE border="0" class="InstrukceWiz"> <strong>- druhá položka označující typ daňového přiznání</strong> <TR><TD class="InstrukceWiz" valign="top">A</TD><TD class="InstrukceWiz">- daňové přiznání <STRONG>za zdaňovací období</STRONG> (§ 21a zákona) nebo daňové přiznání nespadající do typů uvedených pod symboly B až T</TD></TR> <TR><TD class="InstrukceWiz" valign="top">B</TD><TD class="InstrukceWiz">- daňové přiznání <STRONG>při vstupu do likvidace</STRONG></TD></TR> <TR><TD class="InstrukceWiz" valign="top">C</TD><TD class="InstrukceWiz">- daňové přiznání v <STRONG>průběhu likvidace</STRONG></TD></TR> <TR><TD class="InstrukceWiz" valign="top">D</TD><TD class="InstrukceWiz">- daňové přiznání za uplynulou část zdaňovacího období předcházející dni zániku subjektu <STRONG>při zrušení bez provedení likvidace</STRONG></TD></TR> <TR><TD class="InstrukceWiz" valign="top">G</TD><TD class="InstrukceWiz">- daňové přiznání <STRONG>při ukončení činnosti v rámci privatizace</STRONG> v případě stanoveném zvláštním zákonem</TD></TR> <TR><TD class="InstrukceWiz" valign="top">H</TD><TD class="InstrukceWiz">- daňové přiznání <STRONG>za období předcházející dni zpracování návrhu na použití likvidačního zůstatku</STRONG></TD></TR> <TR><TD class="InstrukceWiz" valign="top">J</TD><TD class="InstrukceWiz">- daňové přiznání za období <STRONG>předcházející rozhodnému dni fúze</STRONG> nebo <STRONG>převodu jmění na společníka</STRONG> anebo <STRONG>rozdělení obchodní korporace</STRONG>, zápisu přeměny obchodní společnosti do obchodního rejstříku přejímajícím společníkem, fyzickou osobou, za které nebylo dosud daňové přiznání podáno, není-li tento rozhodný den prvním dnem kalendářního roku nebo hospodářského roku</TD></TR> <TR><TD class="InstrukceWiz" valign="top">K</TD><TD class="InstrukceWiz">- daňové přiznání za období předcházející dni zápisu <STRONG>změny právní formy</STRONG> komanditní společnosti na jinou obchodní korporaci a změny právní formy akciové společnosti nebo společnosti s ručením omezeným či družstva na veřejnou obchodní společnost nebo na komanditní společnost, za které nebylo dosud daňové přiznání podáno</TD></TR> <TR><TD class="InstrukceWiz" valign="top">L</TD><TD class="InstrukceWiz">- daňové přiznání za období předcházející <STRONG>změně zdaňovacího období z kalendářního roku na hospodářský rok nebo naopak</STRONG>, za které nebylo dosud daňové přiznání podáno; tento typ označení daňového přiznání se použije i tehdy, bude-li uvedené období delší než dvanáct měsíců</TD></TR> <TR><TD class="InstrukceWiz" valign="top">M</TD><TD class="InstrukceWiz">- daňové přiznání za období počínající dnem <STRONG>vzniku poplatníka</STRONG>, bez ohledu na to, zda je toto období kratší nebo delší než dvanáct měsíců</TD></TR> <TR><TD class="InstrukceWiz" valign="top">O</TD><TD class="InstrukceWiz">- daňové přiznání za období předcházející dni, od kterého <STRONG>poplatník změnil daňové rezidenství z České republiky do zahraničí</STRONG>, nebylo-li dosud za toto období daňové přiznání podáno.</TD></TR> <TR><TD class="InstrukceWiz" valign="top">P</TD><TD class="InstrukceWiz">- daňové přiznání ke dni nabytí účinnosti <STRONG>rozhodnutí o úpadku</STRONG>, bez ohledu na způsob jeho řešení</TD></TR> <TR><TD class="InstrukceWiz" valign="top">R</TD><TD class="InstrukceWiz">- daňové přiznání <STRONG>v průběhu insolvenčního řízení</STRONG>, bez ohledu na způsob řešení úpadku</TD></TR> <TR><TD class="InstrukceWiz" valign="top">T</TD><TD class="InstrukceWiz">- daňové přiznání ke dni <STRONG>předložení konečné zprávy</STRONG></TD></TR> <TR><TD class="InstrukceWiz" valign="top">V</TD><TD class="InstrukceWiz">- daňové přiznání při <STRONG>ukončení svěřenského fondu</STRONG></TD></TR> <TR><TD class="InstrukceWiz" valign="top">Y</TD><TD class="InstrukceWiz">- daňové přiznání v průběhu <STRONG>vypořádání majetku svěřenského fondu</STRONG></TD></TR> <TR><TD class="InstrukceWiz" valign="top">Z</TD><TD class="InstrukceWiz">- daňové přiznání za období předcházející dni <STRONG>zániku svěřenského fondu</STRONG></TD></TR></TABLE><br /><STRONG>Poznámka:</STRONG> Typ A daňového přiznání se použije též pro období od rozhodného dne přeměny do dne zápisu přeměny do obchodního rejstříku za zanikající nebo rozdělovanou obchodní korporaci nebo za její část, u které je právním nástupcem poplatník daně z příjmů právnických osob, který je daňovým nerezidentem a který nemá ke dni zápisu přeměny do obchodního rejstříku stálou provozovnu na území České republiky.<br />Typy daňových přiznání vážící se k přeměnám obchodních korporací se použijí též pro poplatníky, kteří nejsou obchodními korporacemi (§ 37e odst. 1 zákona).<br />Typy R a T se použijí též pro označení daňových přiznání za období, která následují po prohlášení konkursu podle zákona č. 328/1991 Sb., o konkursu a vyrovnání, ve znění pozdějších předpisů.<br /><br /><STRONG>Upozornění:</STRONG> Pro označení daňových přiznání typů B, C, D, G, H, J, K, O, P, R, T, V, Y a Z se vyjmenovaná velká písmena abecedy použijí i tehdy, rozhodne-li se poplatník v postavení účetní jednotky pro možnost uplatnění postupu podle § 3 odst. 4 písm. c) a d) zákona o účetnictví, tzn. že bude podáváno daňové přiznání za účetní období delší než nepřetržitě po sobě jdoucích dvanáct měsíců.<br />V případě připojení „krátkého účetního období“ v trvání nejvýše tří měsíců k účetnímu období kalendářního nebo hospodářského roku, je vhodné zvážit důsledky tohoto postupu z pohledu vlivu na stanovený základ daně (daňovou ztrátu), m.j. v souvislosti s uplatňováním daňových odpisů hmotného a nehmotného majetku, popř. s omezeními při tvorbě daňových rezerv a opravných položek.<br />Položka obsahuje kritické kontroly: Kód rozlišení typu přiznání musí být vyplněn; Kód musí nabývat pouze hodnot uvedených v masce položky. + + + + + + + + + + + Rozsah údajů účetních výkazů.<br />P = plný / Z = zkrácený.<br />Pokud navíc některý výkaz vybrané vyhlášky podporuje více zkrácených tvarů podle velikosti účetní jednotky:<br />Z = Zkrácený pro malé ÚJ / M = Zkrácený pro mikro ÚJ. + + + + + + + + + + + Tento řádek bude vyplněn současně se ř. 6, přechází-li poslední známá částka daně do daňové ztráty (v tomto případě se na ř. 3 uvede částka ze ř. 1 se znaménkem minus (–)) nebo naopak z poslední známé daňové ztráty do částky daně (v tomto případě se na ř. 3 uvede částka ze ř. 2 se znaménkem plus (+)). + + + + + + + + + + + Uvede se poslední známá částka daně vztahující se k danému zdaňovacímu období, a to i tehdy, bude-li na ř. 4 vykázána poslední známá daňová ztráta z příjmů zahrnovaných do obecného základu daně. + + + + + + + + + + + Datum zjištění důvodů pro podání dodatečného daňového přiznání.<br />Položka obsahuje kritické kontroly: musí být vyplněna u dodatečného a dodatečného-opravného DAP (dapdpp_forma = D, E). + + + + + Poplatníci, kteří vedou účetnictví, přikládají k daňovému přiznání účetní závěrku v rozsahu vymezeném v § 18 odst. 1 a 2 zákona o účetnictví; poplatníci, účetní jednotky uvedené v § 19a zákona o účetnictví, účetní závěrku sestavenou podle Mezinárodních účetních standardů.<br />Při elektronickém podání daňového přiznání se účetní závěrkou poplatníků, účetních jednotek, které <strong>nejsou</strong> povinny používat pro účtování a sestavování účetní závěrky Mezinárodní účetní standardy, rozumí elektronické přílohy <strong>Rozvaha</strong> a <strong>Výkaz zisku a ztráty</strong>, popřípadě <strong>Přehled o změnách vlastního kapitálu</strong> a <strong>Přehled o peněžních tocích</strong>), vyplňované s využitím údajů z Rozvahy a Výkazu zisku a ztráty, popřípadě Přehledu o změnách vlastního kapitálu a Přehledu o peněžních tocích, a <strong>Příloha účetní závěrky</strong>, vkládaná jako samostatný soubor typu .doc, .docx, .xls, .xlsx, .pdf, .jpg, .txt nebo .rtf, s použitím tzv. <strong>E-přílohy</strong>. Poplatníci, účetní jednotky, jimž zatím nejsou v programovém vybavení aplikace Elektronická podání pro finanční správu k dispozici elektronické přílohy nahrazující účetní závěrku se závazně stanoveným uspořádáním údajů, mohou tyto elektronické přílohy účinně nahradit elektronickými kopiemi Rozvahy, Výkazu zisku a ztráty, popřípadě Přehledu o změnách vlastního kapitálu, Přehledu o peněžních tocích a Přílohy účetní závěrky, vkládanými prostřednictvím <strong>E-příloh</strong>.<br />Poplatníci, účetní jednotky, které <strong>jsou</strong> povinny používat pro účtování a sestavování účetní závěrky Mezinárodní účetní standardy, mohou při elektronickém podání daňového přiznání účinně nahradit účetní závěrku elektronickými kopiemi jejích jednotlivých složek, vkládanými s použitím <strong>E-příloh</strong>. Jako <strong>E-přílohu</strong> lze podat též zvláštní přílohu se specifikací a kvantifikací vlivů, z nichž vyplývá rozdíl mezi výsledkem hospodaření zjištěným podle Mezinárodních účetních standardů a výsledkem hospodaření zjištěným podle českých právních předpisů (§ 23 odst. 2 písm. a) zákona). Stejně mohou postupovat poplatníci, konsolidující účetní jednotky, jestliže využijí možnost podle § 23a odst. 2 zákona o účetnictví, a pro sestavení konsolidované účetní závěrky použijí Mezinárodní účetní standardy.<br />Poplatníci, kteří vedou jednoduché účetnictví, přikládají přehledy podle § 13b odst. 3 zákona o účetnictví, jejichž označení a obsahové vymezení je stanoveno přílohou č. 1 a přílohou č. 2 k vyhlášce č. 325/2015 Sb., kterou se provádějí některá ustanovení zákona o účetnictví, pro účetní jednotky, které vedou jednoduché účetnictví. Při elektronickém podání daňového přiznání se těmito přehledy rozumí elektronické přílohy Přehled o majetku a závazcích a Přehled o příjmech a výdajích.<br />Účetní jednotky, které jsou obchodními korporacemi a které nepředávají účetní závěrku do sbírky listin prostřednictvím České národní banky, mohou podle § 21b zákona o účetnictví předat účetní závěrku do sbírky listin veřejného rejstříku prostřednictvím podání u příslušného správce daně z příjmů, které musí obsahovat údaj o rozsahu, v jakém má být účetní závěrka předána rejstříkovému soudu. Podání se činí prostřednictvím tiskopisu Žádosti o předání účetní závěrky do sbírky listin veřejného rejstříku (25 5404/I MFin 5404/I – vzor č. 1), která je přílohou daňového přiznání nebo dodatečného daňového přiznání k dani z příjmů právnických osob, podaného v elektronické podobě a ve formátu, struktuře a za podmínek zveřejněných podle daňového řádu správcem daně z příjmů. Ustanovení § 21b zákona o účetnictví lze poprvé použít na předání účetní závěrky, která je přílohou daňového nebo dodatečného daňového přiznání a je sestavena za účetní období, které započalo nejdříve 1. ledna 2021 a skončilo nejdříve 31. prosince 2021 (čl. VI. Přechodné ustanovení zákona č. 609/2020 Sb.). + + + + + + + + + + + Počet samostatných příloh. Počet samostatných příloh se vyplňuje automaticky. + + + + + + + + + + + Tento řádek bude vyplněn současně se ř. 3, přechází-li poslední známá daňová ztráta do částky daně (v tomto případě se na ř. 6 uvede částka ze ř. 4 se znaménkem mínus (–)) nebo naopak z poslední známé částky daně do daňové ztráty (v tomto případě se na ř. 6 uvede částka ze ř. 5 se znaménkem plus (+)). + + + + + + + + + + + Uvede se slovní označení předmětu činnosti vykonávané poplatníkem (hlavní činnost). Pokud bylo vykonáváno více činností, uvedou se maximálně dvě činnosti, z nichž dosažené výnosy (příjmy) byly v daném zdaňovacím období nejvyšší (převažující činnost). Neuvádějí se činnosti vykonávané pro vlastní potřebu poplatníka, které podmiňují výkon hlavní (převažující) činnosti. Při vyplnění tohoto údaje se použije Klasifikace ekonomických činností (CZ-NACE), která je přílohou sdělení Českého statistického úřadu č. 244/2007 Sb.<br />Pro hodnotu této položky použijte číselník Činnosti (okec). Z číselníku se vkládá položka c_nace. <br />Položka obsahuje kritickou kontrolu: musí být vyplněn existující kód činnosti.<br> +Pro popis číselníku Činnosti klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/okec">zde</a>. + + + + + + + + + + + Daňoví nerezidenti (§ 17 odst. 4 zákona), kteří <strong>jsou</strong> rezidenty členského státu Evropské unie nebo dalších států tvořících Evropský hospodářský prostor, uvedou na tomto řádku celkovou částku daně, která jim byla vybrána srážkou všemi plátci z příjmů podle § 22 odst. 1 písm. b), c), d), f) a g) bodů 1, 2, 4, 5, 6, 12 až 14, h) a i) zákona, kterou uplatňují k zápočtu na celkovou daň vztahující se k příjmům ze zdrojů na území České republiky, za zdaňovací období nebo za období, za které se podává daňové přiznání. Rozčlenění celkové částky podle jednotlivých plátců se provede na zvláštní příloze. + + + + + + + + + + + Uvede se celková částka zaplacených záloh, splatných v průběhu zdaňovacího období nebo období, za které je podáváno daňové přiznání. Ze záloh splatných ve zdaňovacím období nebo v období, za které je podáváno daňové přiznání, u nichž došlo k prodlení s placením, lze do úhrnu na ř. 1 zahrnout pouze ty zálohy, které byly zaplaceny do posledního dne lhůty pro podání daňového přiznání. + + + + + + + + + + + Poplatník typu 4 uvede, zda je základním investičním fondem podle § 17b zákona.<br />A - ano<br />N - ne<br /><br>Položka obsahuje kritické kontroly: hodnota musí být vyplněna pro poplatníky typu (typ_popldpp) 4; naopak pro poplatníky ostatních typů hodnota vyplněna být nesmí. + + + + + + + + + + + Počet zvláštních příloh. Počet zvláštních příloh se vyplňuje automaticky. + + + + + + + + + + + Poplatníci podávající daňové přiznání typu A uvedou ve volném rámečku příslušné písmeno § 21a zákona. Poplatníci, kteří podávají daňová přiznání typu B až T, uvedou v tomto rámečku, s použitím odkazu na příslušné písmeno § 21a zákona, informaci o zdaňovacím období, do něhož spadal poslední den období, za které je podáváno daňové přiznání; bude-li některé z těchto období delší než dvanáct měsíců, uvede se odkaz na písm. d) § 21a zákona.<br />Položka obsahuje kritické kontroly: Pokud je hodnota A nebo B, nesmí být ZO delší než 1 rok. + + + + + + + + + + + Uvede se poslední známá daňová ztráta vztahující se k danému zdaňovacímu období, a to i tehdy, bude-li na ř. 1 vykázána poslední známá částka daně z příjmů zahrnovaných do samostatného základu daně. + + + + + + + + + + + Na tomto řádku se uvede částka daně ze ř. 340 II. oddílu, a to i tehdy, bude-li na ř. 5 vykázána daňová ztráta z příjmů zahrnovaných do obecného základu daně. + + + + + + + + + + + Vyplňte s použitím těchto symbolů:<br> <strong>- první položka označující typ poplatníka</strong> <TABLE border="0" class="InstrukceWiz"> <TR><TD class="InstrukceWiz" valign="top">0</TD><TD class="InstrukceWiz">- nositel příslibu investiční pobídky ve formě slevy na dani podle § 35b zákona</TD></TR> <TR><TD class="InstrukceWiz" valign="top">9</TD><TD class="InstrukceWiz">- nositel příslibu investiční pobídky ve formě slevy na dani podle § 35a zákona</TD></TR> <TR><TD class="InstrukceWiz" valign="top">8</TD><TD class="InstrukceWiz">- nositel příslibu investiční pobídky v daňové oblasti podle usnesení vlády</TD></TR> <TR><TD class="InstrukceWiz" valign="top">7</TD><TD class="InstrukceWiz">- poplatník, který byl po část zdaňovacího období základním investičním fondem (§ 20a zákona)</TD></TR> <TR><TD class="InstrukceWiz" valign="top">6</TD><TD class="InstrukceWiz">- instituce penzijního pojištění nebo penzijní společnost, vč. fondů penzijní společnosti (§ 17 odst. 1 písm. e) zákona)</TD></TR> <TR><TD class="InstrukceWiz" valign="top">5</TD><TD class="InstrukceWiz">- investiční společnost, vč. obhospodařovaných podílových fondů</TD></TR> <TR><TD class="InstrukceWiz" valign="top">4</TD><TD class="InstrukceWiz">- investiční fond podle zákona upravujícího investiční společnosti a investiční fondy vyjma podílových fondů</TD></TR> <TR><TD class="InstrukceWiz" valign="top">3</TD><TD class="InstrukceWiz">- veřejně prospěšný poplatník (§ 17a zákona)</TD></TR> <TR><TD class="InstrukceWiz" valign="top">2</TD><TD class="InstrukceWiz">- daňový nerezident (§ 17 odst. 4 zákona)</TD></TR> <TR><TD class="InstrukceWiz" valign="top">1</TD><TD class="InstrukceWiz">- ostatní</TD></TR> </TABLE><br />Položka obsahuje kritickou kontrolu: Kód typu poplatníka musí být vyplněn. Typ poplatníka musí nabývat hodnot 0, 1, 2, 3, 4, 5, 6, 7, 8 nebo 9. + + + + + + + + + + + Vyplňte číslo vyhlášky (první část), podle které byly účetní výkazy sestaveny. Poplatníci, kteří sestavují účetní závěrku podle Mezinárodních účetních standardů, uvedou číslo vyhlášky, kterou použijí ke zjištění výsledku hospodaření, a to podle působnosti vymezené v § 2 uvedených vyhlášek. Uveďte následující označení: <br />500 pro <br />Vyhlášku č. 500/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou podnikateli účtujícími v soustavě podvojného účetnictví, v platném znění.<br /><br />501 pro<br />Vyhlášku č.501/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou bankami a jinými finančními institucemi, v platném znění.<br /><br />502 pro<br />Vyhlášku č.502/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou pojišťovnami, v platném znění.<br /><br />503 pro<br />Vyhlášku č.503/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro zdravotní pojišťovny, v platném znění.<br /><br />504 pro<br />Vyhlášku č. 504/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, u kterých hlavním předmětem činnosti není podnikání, pokud účtují v soustavě podvojného účetnictví, v platném znění.<br /><br />325 pro<br />Vyhlášku č. 325/2015 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které vedou jednoduché účetnictví, v platném znění.<br /><br />410 pro<br />Vyhlášku č. 410/2009 Sb., pro vybrané účetní jednotky, v platném znění.<br /><br />Položka obsahuje kritické kontroly: nesmí být vyplněn současně druhový a účelový výkaz zisků a ztrát, pokud je zadána hodnota musí odpovídat masce položky, může být použito pouze číslo vyhlášky korespondující s danou písemností, může být použito pouze číslo vyhlášky korespondující s daným typem subjektu, může být použito pouze číslo vyhlášky platné pro dané ZO, pro zadané číslo vyhlášky musí být použity odpovídající výkazy, pro vyhlášky kromě 500 musí být vyplněny všechny výkazy, číslo vyhlášky musí být vyplněno je-li zadán alespoň 1 výkaz, pro vyhlášky jiné než č. 500 a 501 nelze žádat o předání účetní závěrky do sbírky listin. + + + + + + + + + + + Počet příloh II. oddílu Počet příloh II. oddílu se vyplňuje automaticky. + + + + + + + + + + + Doplňte zbývající část oficiálního názvu svého místně příslušného finančního úřadu (např. – pro hlavní město Prahu, - pro Jihočeský kraj, apod.). Bude-li poplatník vybraným subjektem podle § 11 odst. 2 zákona o Finanční správě České republiky, doplní údaj slovy Specializovanému finančnímu úřadu.<br />Pro hodnotu této položky použijte číselník Územní finanční orgány (ufo). Z číselníku se vkládá položka c_ufo.<br />Položka obsahuje kritickou kontrolu: musí být vyplněno číslo existujícího FÚ.<br> +Pro popis číselníku Územní finanční orgány klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/ufo">zde</a>. + + + + + + + + + + + vyplňte datum počátku zdaňovacího období.<br />Položka obsahuje kritické kontroly: u podání typu A (typ_dapdpp) musí být v rozmezí 1.1.2021 až 30.12.2025, u ostatních typů musí být v rozmezí 1.1.2021 až 31.12.2025. + + + + + + Poplatník uvede, zda uskutečnil či neuskutečnil transakce s osobou kapitálově spojenou (§ 23 odst. 7 písm a) zákona) či jinak spojenou (§ 23 odst. 7 písm. b) zákona), a to s použitím těchto kódů:<br>T - transakce uskutečněné s tuzemskou spojenou osobou<br>Z - transakce uskutečněné se zahraniční spojenou osobou<br>A - transakce uskutečněné s tuzemskou i zahraniční spojenou osobou<br>N - žádné uskutečněné transakce se spojenou osobou<br>Samostatná příloha k položce 12 I. oddílu se vyplňuje vždy, pokud byly se spojenou osobou uskutečněny transakce a současně, jsou splněny podmínky uvedené v dílčích pokynech pro její vyplnění.<br /> + + + + + + + + + + + Bude-li na ř. 340 II. oddílu vykázána částka <= 200 Kč (viz § 38b zákona), použije se pro výpočet částky na tomto řádku zkrácený algoritmus ve tvaru (ř. 1 + ř. 2 + ř. 3).<br />V případě vratitelného přeplatku může poplatník požádat správce daně o jeho vrácení, nebo o použití na úhradu nedoplatku jiného daňového subjektu u téhož nebo jiného správce daně, na úhradu zálohy, jejíž výše se předepíše na debetní stranu osobního daňového účtu na základě uhrazené částky, anebo o jeho převod na jiný osobní daňový účet u téhož nebo jiného správce daně (§ 155 odst. 1 DŘ). Pokud poplatník podá daňové přiznání současně s žádostí o vrácení vratitelného přeplatku před uplynutím lhůty stanovené pro podání daňového přiznání, hledí se na žádost jako na podanou v poslední den lhůty pro podání daňového přiznání (§ 38zf zákona). + + + + + + + + + + + Typ daňového přiznání.<BR /><STRONG>B</STRONG> - řádné<BR /><STRONG>O</STRONG> - řádné-opravné<BR /><STRONG>D</STRONG> - dodatečné<BR /><STRONG>E</STRONG> - dodatečné-opravné + + + + + + + + + + + musí obsahovat "DPP" - Daň z příjmů právnických osob + + + + + Uvede se, zda daňové přiznání zpracoval a podává poradce (§ 29 odst. 2 DŘ) na základě plné moci k zastupování, pokud ano, je povinen v I. oddílu vyplnit předepsané údaje. + + + + + + + + + + + Na tomto řádku se uvede daňová ztráta ze ř. 220 II. oddílu, a to i tehdy, bude-li na ř. 2 vykázána částka daně z příjmů zahrnovaných do samostatného základu daně. + + + + + + + + + + + vyplňte datum konce zdaňovacího období.<br />Položka obsahuje kritické kontroly: Datum konce ZO musí být vyplněn. Datum konce ZO nesmí být starší než 1.1.2021. Datum konce ZO nesmí být starší než datum počátku ZO. + + + + + Vyplňte datum, ke kterému se údaje tabulek vztahují.<br />Položka obsahuje kritickou kontrolu: hodnota musí být zadána, pokud je vyplněn alespoň jeden výkaz nebo pokud žádáte o předání účetní závěrky pomocí "Žádosti" (existence věty UZ s hodnotou 'A'). + + + + + Uveďte, zda máte zákonnou povinnost ověření účetní závěrky auditorem. + + + + + + + + + + + Rozsah údajů pro Rozvahu:<br />P = Plný / Z = Zkrácený - Malé ÚJ / M = Zkrácený - Mikro ÚJ.<br /><br />Položka obsahuje kritické kontroly: v případě vyplňování odlišných rozsahů účetních výkazů je nutné vyplnit zároveň položku pro rozsah Rozvahy a položku pro Výkaz zisku a ztráty. + + + + + + + + + + + Rozsah údajů pro Výkaz zisku a ztráty:<br />P = Plný / Z = Zkrácený.<br /><br />Položka obsahuje kritické kontroly: v případě vyplňování odlišných rozsahů účetních výkazů je nutné vyplnit zároveň položku pro rozsah Rozvahy a položku pro Výkaz zisku a ztráty. + + + + + + + + + + + Uvede se datum počátku účetního, potažmo zdaňovacího období hospodářského roku, za které nebo za jehož část je podáváno daňové přiznání.<br />Položka by neměla být vyplněna pokud je typ ZO jiný než "B" nebo typ daňového přiznání roven "A". + + + + + Poplatník uvede, do jaké kategorie účetní jednotky podle § 1b zákona o účetnictví je zařazen, a to s použitím uvedených kódů:<br>M - mikro účetní jednotka<br>L - malá účetní jednotka<br>S - střední účetní jednotka<br>V - velká účetní jednota.<br>Tento údaj není povinný pro poplatníky, kteří vedou jednoduché účetnictví. Pro zařazení do kategorie účetní jednotky podle § 1b zákona o účetnictví postupuje účetní jednotka podle § 1e zákona o účetnictví ve spojení s bodem 4 čl. II. přechodných ustanovení zákona č. 221/2015 Sb. Pokud ve dvou po sobě následujících rozvahových dnech řádných účetních závěrek účetní jednotka překročí nebo přestane překračovat dvě hraniční hodnoty podle § 1b zákona o účetnictví, změní od počátku bezprostředně následujícího účetního období kategorii účetní jednotky (§ 1e odst. 2 zákona o účetnictví).<br>&nbsp<br>Položka smí nabývat pouze hodnot uvedených v masce položky. Vyplnění položky není povinné pro poplatníky, kteří po 1. 1. 2018 vedli jednoduché účetnictví (uv_vyhl = 325). Účetní kritérium uvedené v zákoně o účetnictví v české měně se v případě, že měnou účetnictví účetní jednotky není česká měna, přepočte na měnu účetnictví kurzem devizového trhu vyhlášeným Českou národní bankou k měně účetnictví pro poslední den bezprostředně předcházejícího účetního období. + + + + + + + + + + + Uveďte, zda máte zákonnou povinnost sestavení účetní závěrky dle mezinárodních účetních standardů dle § 19a zákona o účetnictví. + + + + + + + + + + + Položka smí nabývat pouze hodnot uvedených v masce položky. + + + + + + + + + + + Položka smí nabývat pouze hodnot uvedených v masce položky. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + uveďte, zda je účetní zavěrka sestavena v celých tisících (T) nebo milionech (M). V základním stavu je přednastavena běžná hodnota 'T'.<br />Položka obsahuje kritickou kontrolu: hodnota musí být vyplněna za předpokladu vyplnění alespoň jednoho výkazu u vyhlášek č. 500 nebo č. 501. + + + + + + + + + + + Pro vyhlášky 500/2002 Sb., 501/2002 Sb., 502/2002 Sb., 504/2002 Sb.</br> Zvolte měnu účetnictví, kterou může být česká měna, nebo jiná měna, pokud je funkční měnou účetní jednotky a jedná se o euro, americký dolar, nebo britskou libru (§ 24a zákona o účetnictví). Funkční měnou se rozumí měna primárního ekonomického prostředí, ve kterém účetní jednotka působí.</br></br> Pro vyhlášku 503/2002 Sb.</br> Měnou účetnictví vybrané účetní jednotky, kterou je i zdravotní pojišťovna (§ 1odst. 3 zákona o účetnictví), je vždy česká měna (§ 24a odst. 3 zákona o účetnictví).</br></br> Pro vyhlášku 325/2015 Sb.</br> Měnou účetnictví účetní jednotky vedoucí jednoduché účetnictví je vždy česká měna (§ 24a odst. 3 zákona o účetnictví).</br></br>Položka obsahuje kritické kontroly: položka musí být vyplněna od roku 2024, pro vyhlášky 503/2002 Sb. a 325/2015 Sb. může položka nabývat pouze CZK. + + + + + + + + + + + + + + + místa pobytu nebo sídla daňového subjektu. PSČ v České republice musí mít délku 5 znaků bez mezer. + + + + + + + + + + + Uvede se obchodní firma právnické osoby zapsaná do veřejného rejstříku, včetně dodatku označujícího její právní formu, popřípadě též dovětku „v likvidaci“. U právnických osob, které se nezapisují do veřejného rejstříku, se uvede název, pod kterým byly založeny nebo zřízeny, a u ostatních subjektů název, pod nímž vystupují vůči třetím osobám. Pokud rozsah předtištěných políček položky 05 nestačí k zapsání celého názvu obchodní firmy nebo názvu poplatníka, uvede se pouze jejich zkrácený tvar s tím, že na zvláštní příloze se pak vyznačí celý název obchodní firmy, případně název poplatníka + + + + + + + + + + + Číselný kód podle níže uvedených typů podepisující osoby:<br>Fyzická osoba: <br>1 - zákonný zástupce nebo opatrovník<br>2 - ustanovený zástupce<br>3 - společný zástupce, společný zmocněnec<br>4a - obecný zmocněnec - fyzická osoba i právnická osoba<br>4b - fyzická osoba daňový poradce nebo advokát<br>5a - osoba spravující pozůstalost<br>5b - zástupce osoby spravující pozůstalost<br>6a - dědic po skončení řízení o pozůstalosti<br>6b - zástupce dědice po skončení řízení o pozůstalosti<br>7b - zástupce právního nástupce právnické osoby<br><br>Právnická osoba:<br>1 - zákonný zástupce nebo opatrovník<br>2 - ustanovený zástupce<br>3 - společný zástupce, společný zmocněnec<br>4a - obecný zmocněnec - fyzická osoba i právnická osoba<br>4c - právnická osoba vykonávající daňové poradenství<br>5a - osoba spravující pozůstalost<br>5b - zástupce osoby spravující pozůstalost<br>6a - dědic po skončení řízení o pozůstalosti<br>6b - zástupce dědice po skončení řízení o pozůstalosti<br>7a - právní nástupce právnické osoby<br>7b - zástupce právního nástupce právnické osoby + + + + + + + + + + + Za předtištěným kódem CZ se uvede kmenová část přiděleného DIČ (§ 130 odst. 1 DŘ). + + + + + + + + + + + + + + + + + + <br> +Pro popis číselníku Obce klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/obce">zde</a>. + + + + + + + + + + + F - fyzická osoba <BR> P - právnická osoba<BR> Pokud podává podepisující osoba je povinné. + + + + + + + + + + + podepisující osoba.<BR>Pokud podává právnická podepisující osoba je povinné. + + + + + + + + + + Vyplňte číslo telefonu, na kterém je možné vás kontaktovat. Vyplnění údaje je nepovinné. + + + + + + + + + + + podepisující osoba.<BR>Pokud podává právnická podepisující osoba je povinné. + + + + + + + + + + + + + + + + + + + + + + + + + + + <br> +Pro popis číselníku Země klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/zeme">zde</a>. + + + + + + + + + + + podepisující osoba.<BR>Pokud podává fyzická podepisující osoba je povinné. + + + + + + + + + + + fyzické osoby oprávněné k podpisu.<BR>Vyplňuje se, je-li typ daňového subjektu nebo typ jeho podepisující osoby právnická osoba. + + + + + + + + + + + územní pracoviště, kde je nebo bude umístěn spis daňového subjektu (podle § 13 zákona číslo 456/2011 Sb., o Finanční správě České republiky, ve znění pozdějších předpisů).<br> +Pro popis číselníku Pracoviště FÚ klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/pracufo">zde</a>. + + + + + + + + + + + podepisující osoba.<BR>Pokud podává fyzická podepisující osoba, buď datum narození nebo evidenční číslo je povinné. + + + + + fyzické osoby oprávněné k podpisu.<BR>Vyplňuje se, je-li typ daňového subjektu nebo typ jeho podepisující osoby právnická osoba. + + + + + + + + + + + Příjmení oprávněné osoby. + + + + + + + + + + + Bude uvedeno přidělené identifikační číslo (ve smyslu zákona č. 89/1995 Sb., o státní statistické službě, ve znění pozdějších předpisů). Podílové fondy, podfondy akciové společnosti s proměnným základním kapitálem a fondy penzijní společnosti tento údaj nevyplňují. + + + + + + + + + + Údaj d) vyplňují pouze zahraniční právnické osoby (§ 3024 zákona č. 89/2012 Sb., občanský zákoník), které pod písm. a), b), c) a d) uvedou svoji úplnou zahraniční adresu a na zvláštní příloze adresu (umístění) své organizační složky, popřípadě stálé provozovny, pokud není současně organizační složkou, a adresu svého zmocněnce pro doručování. Kód státu se vyplňuje podle Sdělení Českého statistického úřadu ze dne 18. prosince 2003 č. 489/2003 Sb. o vydání číselníku zemí (CZEM) v aktuálním znění (2015-2016: Kód státu se vyplňuje podle Sdělení Českého statistického úřadu ze dne 18. května 2012 o aktualizaci číselníku zemí (CZEM).<br />Pro hodnotu této položky použijte číselník Země (zeme). Z číselníku se vkládá položka naz_zeme_c25.<br> +Pro popis číselníku Země klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/zeme">zde</a>. + + + + + + + + + + + Pokud podává fyzická podepisující osoba, buď datum narození nebo evidenční číslo je povinné. + + + + + + + + + + + podepisující osoba.<BR>Pokud podává fyzická podepisující osoba je povinné. + + + + + + + + + + + V generovaném souboru nemusí být vyplněno.<br> +Pro popis číselníku Obce klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/obce">zde</a>. + + + + + + + + + + + + + + + Úhrnná částka uvedená na tomto řádku se na zvláštní příloze rozčlení podle jednotlivých bodů § 23 odst. 3 písm. b) zákona. + + + + + + + + + + + Tento řádek vyplňují pouze poplatníci vstupující do likvidace, u nichž § 23 zákona stanoví povinnost úpravy výsledku hospodaření za část zdaňovacího období do dne vstupu do likvidace. U poplatníků, kteří vedou účetnictví, bude částka na tomto řádku zahrnovat zůstatky vytvořených zákonných rezerv a opravných položek (tzn. rezerv a opravných položek, jejichž tvorba byla uznána za daňový výdaj), zůstatky výnosů příštích období a výdajů příštích období, které nebudou prokazatelně zúčtovány v období likvidace, a to za předpokladu, že tato úprava již není promítnuta ve výsledku hospodaření na ř. 10. Na zvláštní příloze je pak třeba uvést rozčlenění souhrnné částky z tohoto řádku na jednotlivé položky. + + + + + + + + + + + Zde se uvede úhrnná částka daní zaplacených v zahraničí z příjmů zahrnovaných do samostatného základu daně. Specifikace jednotlivých zaplacených daní s přiřazením ke konkrétním zdaněným příjmům bude součástí zvláštní přílohy k ř. 331. U zahrnovaného příjmu podle § 38fa odst. 6 písm. c) zákona, pokud není u ovládající společnosti osvobozen od daně z příjmů, se uplatní institut snížení daně dle § 38fa odst. 9 zákona. K zápočtu dle § 38f zákona lze uplatnit pouze takovou částku daně, která byla v zahraničí u jednotlivých konkrétních příjmů vybrána v souladu s právními předpisy státu, v němž byl zdroj příjmu, nejvýše však do částky tuzemské daně z příjmů připadající na daný příjem, a pokud se jedná o příjem ze státu, s nímž Česká republika uzavřela smlouvu o zamezení dvojího zdanění, nejvýše částku daně, která může být v tomto státě vybrána v souladu s uzavřenou smlouvou. Částky daně zaplacené v zahraničí musí být doloženy seznamem potvrzení zahraničních správců daně (§ 38f odst. 10 zákona) nebo, půjde-li o ojedinělé příjmy ze zdrojů v zahraničí, potvrzeními zahraničních správců daně (§ 38f odst. 5 zákona). + + + + + + + + + + + Název položky u řádku 301. Tento řádek se nevyplňuje. Jeho využití je možné jen tehdy, jestliže kromě slev na dani zákon vymezí nové skutečnosti, které budou důvodem k dalším úpravám daňové povinnosti. + + + + + + + + + + + Na tomto řádku se uvede odečet na podporu výzkumu a vývoje podle § 34 odst. 4 a § 34a až § 34e zákona. Částka odečtu se musí rovnat částce vykázané na ř. 5 ve sl. 4 v dílčí tabulce b) tabulky F přílohy č. 1 II. oddílu. Uplatněním odečtu na podporu výzkumu a vývoje podle § 34 odst. 4 zákona nelze vykázat daňovou ztrátu, proto částka na tomto řádku <strong>nesmí být vyšší</strong> než základ daně uvedený na ř. 220, snížený o případné částky odečtů uplatněné na ř. 230, 240 a 243.<br /> Pokud poplatníkem je společník veřejné obchodní společnosti nebo komplementář komanditní společnosti, bude částka uvedená na tomto řádku obsahovat i poměrnou část odečtu podle § 34 odst. 4 zákona zjištěného za veřejnou obchodní společnost nebo komanditní společnost, přičemž tato poměrná část se stanoví v souladu s § 34d odst. 1 a 2 zákona. Na zvláštní příloze se pak uvede rozčlenění souhrnné částky z tohoto řádku na část vztahující se k odečtu za veřejnou obchodní společnost nebo komanditní společnost a na část vztahující se k vlastní podnikatelské činnosti poplatníka. Obdobně se u těchto poplatníků postupuje i v případě položek uvedených na řádcích 243, 260 a 300. + + + + + + + + + + + Částka na tomto řádku je poslední známou daní pro stanovení periodicity a výše záloh podle § 38a odst. 1 zákona. Tato částka <strong>nesmí</strong> nabývat záporných hodnot. + + + + + + + + + + + Celková daňová povinnost na tomto řádku se vyměřuje a je základem pro stanovení pokuty za opožděné tvrzení daně (§ 250 DŘ).<br />Bude-li pro použití ř. 331 až 340 věcná náplň, vyplní se i tehdy, vykáže-li poplatník na ř. 220 daňovou ztrátu. + + + + + + + + + + + Uvede se úhrn skutečně uplatněných slev na dani podle § 35 odst. 1 a 4 a § 35a nebo § 35b zákona, nejvýše v částce na ř. 290. Jejich specifikace se provede v tabulce H přílohy č. 1 II. oddílu, přičemž nárok na slevy podle § 35 odst. 1 a 4 zákona se vyčíslí i tehdy, bude-li na ř. 220 vykázána daňová ztráta, nebo jestliže jej nebude možné s ohledem na částku vykázanou na ř. 290 plně využít. + + + + + + + + + + + + + + + + + + + Na tomto řádku se uvede odečet na podporu odborného vzdělávání podle § 34 odst. 4 a § 34f až § 34h zákona. Částka odečtu se musí rovnat částce vykázané na ř. 5 ve sl. 4 v dílčí tabulce c) tabulky F přílohy č. 1 II. oddílu. Uplatněním odečtu na podporu odborného vzdělávání podle § 34 odst. 4 zákona nelze vykázat daňovou ztrátu, proto částka na tomto řádku <strong>nesmí být vyšší</strong> než základ daně uvedený na ř. 220, snížený o případné částky odečtů uplatněné na ř. 230, 240 a 242. + + + + + + + + + + + + + + + + + + + Tento řádek vyplňují pouze veřejně prospěšní poplatníci (§ 17a zákona), kterým zákon umožňuje uplatnění snížení základu daně podle § 20 odst. 7 zákona. + + + + + + + + + + + Částka uvedená na tomto řádku nemůže nabývat záporných hodnot. To platí i pro řádky 270, 310, 330, 340 a 360. + + + + + + + + + + + Uvede se sazba daně podle § 21 odst. 1 nebo odst. 2 anebo odst. 3 zákona, platná k prvnímu dni zdaňovacího období nebo období, za něž je podáváno daňové přiznání. V roce 2026 činí sazba podle § 21 odst. 1 zákona 21 %, sazba podle § 21 odst. 2 zákona 5 % a sazba podle § 21 odst. 3 zákona 0 %.<br /><br />Položka obsahuje kritickou kontrolu: nesmí být vyplněna u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Uvede se celkový rozdíl, o který souhrn odpisů hmotného a nehmotného majetku (§ 26 zákona) uplatněných v účetnictví <strong>převyšuje</strong> souhrn odpisů tohoto majetku stanovených podle § 26 až 33 zákona. V opačném případě se použije ř. 150. + + + + + + + + + + + Tento řádek se nevyplňuje. Jeho využití je možné jen tehdy, jestliže kromě slev na dani zákon vymezí nové skutečnosti, které budou důvodem k dalším úpravám částky daně. + + + + + + + + + + + Uvede se souhrn příjmů ze zdrojů v zahraničí, u nichž se podle uzavřených mezinárodních smluv o zamezení dvojího zdanění uplatňuje metoda úplného vynětí ze základu daně (daňové ztráty), pokud jsou součástí celosvětového základu daně (daňové ztráty) na ř. 200. Rozčlenění částky vykázané na tomto řádku podle jednotlivých smluvních států, v nichž byl zdroj vyjímaných příjmů, musí být doloženo seznamem potvrzení zahraničních správců daně (§ 38f odst. 10 zákona) nebo, půjde-li o ojedinělý příjem ze zdrojů v zahraničí, potvrzením zahraničního správce daně (§ 38f odst. 5 zákona).<br />Pokud poplatníkem je společník veřejné obchodní společnosti nebo komplementář komanditní společnosti, bude částka uvedená na tomto řádku obsahovat i na něho připadající poměrnou část příjmů ze zdrojů v zahraničí plynoucí veřejné obchodní společnosti nebo komanditní společnosti, které se podle smluv o zamezení dvojího zdanění vyjímají ze zdanění, zahrnutou v základu daně uvedeném na ř. 200. Tento poměr u společníka veřejné obchodní společnosti odpovídá poměru, kterým se společník podílí na zisku veřejné obchodní společnosti u komplementáře komanditní společnosti odpovídá poměru, kterým se komplementář podílí na zisku komanditní společnosti. Na zvláštní příloze se pak uvede rozčlenění souhrnné částky z tohoto řádku na část vztahující se k veřejné obchodní společnosti nebo komanditní společnosti a na část vztahující se k vlastní podnikatelské činnosti poplatníka.<br /><strong>Upozornění:</strong><br />Pod pojmem <strong>„příjmy ze zdrojů v zahraničí“</strong> se pro účely vyloučení dvojího zdanění příjmů rozumí příjmy plynoucí ze zdrojů v zahraničí, které podléhají zdanění v zahraničí v souladu s uzavřenou mezinárodní smlouvou, snížené o související výdaje stanovené podle tuzemského zákona o daních z příjmů. Odčitatelné položky a položky snižující základ daně podle zahraničních právních předpisů nelze při stanovení základu daně použít. Při použití metody úplného vynětí se příjmy ze zdrojů v zahraničí vyjímají ze základu daně před uplatněním částek odčitatelných od základu daně (ř. 230, 240, 241, 242, 243, 251 a 260). Nelze-li u některých výdajů prokazatelně stanovit, zda souvisejí s příjmy plynoucími ze zdrojů v zahraničí, považuje se za související výdaje jejich část stanovená ve stejném poměru, v jakém příjmy plynoucí ze zdrojů v zahraničí nesnížené o výdaje připadají na celosvětové příjmy.<br />Částky jednotlivých příjmů ze zdrojů v zahraničí se pro účely jejich vynětí ze zdanění přepočtou na Kč s použitím kursů devizového trhu vyhlášených ČNB, uplatňovaných v účetnictví poplatníka (§ 38 odst. 1 zákona). Pro přepočet měn neuváděných v kursovním lístku ČNB se použije přepočet přes třetí měnu. Poplatníci, kteří vedou jednoduché účetnictví, mohou přepočítat částky jednotlivých příjmů ze zdrojů v zahraničí pro účely jejich vynětí ze zdanění na Kč s použitím jednotného kursu, vymezeného v § 38 odst. 1 zákona, nevyužijí-li kursy devizového trhu uplatňované podle zvláštních předpisů o účetnictví. + + + + + + + + + + + + + + + + + + + Zde se uvede úhrn příjmů zdaňovaných sazbou daně podle § 21 odst. 4 zákona, které nejsou osvobozeny od daně, zaúčtovaných ve prospěch výnosů. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tento řádek vyplňují pouze poplatníci vstupující do likvidace, u nichž § 23 zákona stanoví povinnost úpravy výsledku hospodaření za část zdaňovacího období do dne vstupu do likvidace. U poplatníků, kteří vedou účetnictví, bude částka na tomto řádku zahrnovat zůstatky příjmů příštích období a nákladů příštích období (včetně komplexních nákladů příštích období), které nebudou prokazatelně zúčtovány v období likvidace, a to za předpokladu, že tato úprava již není promítnuta ve výsledku hospodaření na ř. 10. Na zvláštní příloze je pak třeba uvést rozčlenění souhrnné částky z tohoto řádku na jednotlivé položky. + + + + + + + + + + + Uvede se daň zaplacená v zahraničí, o niž lze podle smluv o zamezení dvojího zdanění snížit daň, nejvýše však do částky uvedené na ř. 310 snížené o částku na ř. 319 a ř. 319a. Při výpočtu výše daně zaplacené v zahraničí, kterou lze započíst na daň za zdaňovací období nebo za období, za které se podává daňové přiznání, se postupuje podle tabulky I přílohy č. 1 II. oddílu. + + + + + + + + + + + + + + + + + + + Uvede se část základu daně nebo daňové ztráty ze ř. 200, připadající komplementářům. + + + + + + + + + + + Na tomto řádku se uvede souhrn rozdílů, o které náklady uplatněné v účetnictví (viz účtová třída – náklady) převyšují výdaje (náklady) na dosažení, zajištění a udržení příjmu (dále jen daňové výdaje) podle § 24 a 25 zákona, s výjimkou rozdílu, o který účetní odpisy převyšují daňové odpisy hmotného a nehmotného majetku, jenž se uvede samostatně na ř. 50.<br>Celková částka na ř. 40 musí být shodná s částkou na ř. 13 tabulky A přílohy č. 1 II. oddílu. + + + + + + + + + + + + + + + + + + + Tohoto řádku lze využít v ostatních případech, neuvedených na ř. 20 až 61, 63 a 65 kdy je nutné pro správné stanovení základu daně zvýšit výsledek hospodaření na ř. 10. Věcnou náplň částky vykázané na tomto řádku je třeba uvést buď přímo v nepředtištěném názvu položky tohoto řádku nebo, zahrnuje-li více položek, na zvláštní příloze. Např. v návaznosti na ustanovení § 20 odst. 5 a 6 zákona zde poplatník, jenž je společníkem veřejné obchodní společnosti nebo komplementářem komanditní společnosti, uvede rozdíl mezi jeho podílem na výsledku hospodaření a podílem na základu daně, popřípadě na daňové ztrátě, veřejné obchodní společnosti nebo komanditní společnosti, bude-li tento rozdíl kladný (částka zvyšující výsledek hospodaření). Jestliže bude tento rozdíl záporný, uvede se na ř. 162 (částka snižující výsledek hospodaření). Řádek se využije i v případě zvýšení výsledku hospodaření podle § 23 odst. 19 zákona.<br /><br />Je-li řádek vyplněn, je nutné ve smyslu dílčích pokynů pro jejich vyplnění rozvést na zvláštní příloze věcnou náplň částky vykázané na tomto řádku, popřípadě její propočet. + + + + + + + + + + + Využití tohoto řádku se týká případů neuvedených na ř. 109 až 161, 163 a 165, kdy zákon pro účely stanovení základu daně umožňuje snížit výsledek hospodaření uvedený na ř. 10. Na tomto řádku se uvede např. částka uplatněných paušálních výdajů na dopravu silničním motorovým vozidlem (§ 24 odst. 2 písm. zt) zákona). Řádek se využije i v případě snížení výsledku hospodaření podle § 23 odst. 19 zákona. Věcná náplň částky vykázané na tomto řádku se uvede buď přímo v nepředtištěném názvu položky tohoto řádku nebo, pokud zahrnuje více položek, na zvláštní příloze.<br /><br />Je-li řádek vyplněn, je nutné ve smyslu dílčích pokynů pro jejich vyplnění rozvést na zvláštní příloze věcnou náplň částky vykázané na tomto řádku, popřípadě její propočet. + + + + + + + + + + + Uvede se celkový rozdíl, o který souhrn odpisů hmotného a nehmotného majetku (§ 26 zákona) stanovených podle § 26 až 33 zákona <strong>převyšuje</strong> souhrn odpisů tohoto majetku uplatněných v účetnictví. V opačném případě se použije ř. 50. + + + + + + + + + + + Na tomto řádku se uvede úhrn příjmů před zdaněním (základů daně), které podle § 20b zákona vstupují do samostatného základu daně. Uvede se též zahrnovaný příjem podle § 38fa odst. 6 písm. c) zákona, pokud není u ovládající společnosti osvobozen od daně z příjmů.<br />Specifikace jednotlivých příjmů uplatněných na ř. 331 se uvede na zvláštní příloze. K příjmům z vypořádacích podílů, z podílů na likvidačním zůstatku nebo z obdobných plnění budou v této zvláštní příloze přiřazeny s nimi související nabývací ceny podílů na obchodní korporaci.<br />Částka na tomto řádku nesmí nabývat záporných hodnot. + + + + + + + + + + + + + + + + + + + Uvede se skutečně uplatněná částka odečtu hodnoty bezúplatných plnění, poskytnutých ve zdaňovacím období na zákonem vymezené účely (§ 20 odst. 8 zákona) z tabulky G Přílohy č. 1 II. oddílu, nejvýše však do 10 % z částky na ř. 250; to neplatí pro zdaňovací období, která skončila v období od 1. 3. 2020 do 28. 2. 2022 (zákon č. 39/2021 Sb.), nebo v období od 1. 3. 2022 do 28. 2. 2027 (zákon č. 128/2022 Sb.), za která lze v úhrnu odečíst nejvýše 30 % základu daně sníženého podle § 34 zákona. + + + + + + + + + + + Úhrnná částka uvedená na tomto řádku se na zvláštní příloze rozčlení podle jednotlivých bodů § 23 odst. 3 písm. c) zákona. + + + + + + + + + + + Příjmy osvobozené od daně podle § 19 zákona se na tomto řádku vyloučí pouze tehdy, jestliže jsou tyto příjmy zahrnuty ve výsledku hospodaření na ř. 10. Věcná náplň nebo propočet částek vykázaných na řádcích 109 a 110 se rozvede na zvláštní příloze.<br /><br />Je-li řádek vyplněn, je nutné ve smyslu dílčích pokynů pro jejich vyplnění rozvést na zvláštní příloze věcnou náplň částky vykázané na tomto řádku, popřípadě její propočet. + + + + + + + + + + + U poplatníka, jenž je společníkem veřejné obchodní společnosti nebo komplementářem komanditní společnosti, je součástí základu daně popřípadě daňové ztráty i na něho připadající poměrná část základu daně nebo daňové ztráty veřejné obchodní společnosti nebo komanditní společnosti. Tento poměr u společníka veřejné obchodní společnosti odpovídá poměru, kterým se společník podílí na zisku veřejné obchodní společnosti, u komplementáře komanditní společnosti odpovídá poměru, kterým se komplementář podílí na zisku komanditní společnosti. + + + + + + + + + + + Částka vykázaná na tomto řádku je součástí celkové daně (ř. 340).<br /><br />Položka obsahuje kritickou kontrolu: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. + + + + + + + + + + + Na tomto řádku se uvede úhrn částek neoprávněně zkracujících příjmy a hodnota nepeněžních příjmů, které je nutné zahrnout do základu daně, jestliže nejsou zahrnuty ve výsledku hospodaření nebo v rozdílu mezi příjmy a výdaji na ř. 10. Věcná náplň nebo propočet částky vykázané na tomto řádku se rozvede na zvláštní příloze.<br /><br />Je-li řádek vyplněn, je nutné ve smyslu dílčích pokynů pro jejich vyplnění rozvést na zvláštní příloze věcnou náplň částky vykázané na tomto řádku, popřípadě její propočet. + + + + + + + + + + + Uvede se sazba daně podle § 21 odst. 4 zákona, účinná k prvnímu dni zdaňovacího období nebo období, za něž je podáváno daňové přiznání. V roce 2025 činí sazba podle § 21 odst. 4 zákona 15 %. + + + + + + + + + + + Poplatník, který byl jen po část zdaňovacího období základním investičním fondem (§ 20a zákona) použije pro výpočet částky daně na ř. 290 II. oddílu tiskopis Příloha č. 2 II. oddílu (25 5404/B MFin 5404/B). + + + + + + + + + + + + + + + + + + + Částka odečtu daňové ztráty, uplatněná na tomto řádku podle § 34 odst. 1 zákona, s omezením podle § 38na zákona, nesmí být vyšší než částka základu daně upraveného o část základu daně (daňové ztráty) připadající na komplementáře a sníženého o příjmy podléhající zdanění v zahraničí, u nichž je uplatňováno vynětí, uvedená na ř. 220, a současně musí být shodná s částkou uvedenou na ř. Celkem ve sl. 4 tabulky E přílohy č. 1 II. oddílu. V daňovém přiznání nebo dodatečném daňovém přiznání uvádí poplatník zdaňovací období nebo období, za které se podává daňové přiznání, za která byla daňová ztráta, kterou uplatňuje, stanovena, a výši v jaké ji uplatňuje (§ 34 odst. 1 zákona).)<br />Za zdaňovací období nebo období, za které se podává daňové přiznání, započaté v roce 2025, lze jako položku odčitatelnou od základu v pěti následujících zdaňovacích obdobích daně naposledy uplatnit zbývající část daňové ztráty pravomocně stanovené za zdaňovací období nebo období, za které se podává daňové přiznání, započaté v roce 2020. Za zdaňovací období nebo období, za které se podává daňové přiznání, započaté v roce 2025 lze jako položku odčitatelnou od základu daně ve dvou bezprostředně předcházejících zdaňovacích obdobích naposledy formou dodatečného daňového přiznání uplatnit zbývající část daňové ztráty pravomocně stanovené za zdaňovací období nebo období, za které se podává daňové přiznání, započaté v roce 2027. + + + + + + + + + + + Na řádku 10 uvedou poplatníci daně z příjmů právnických osob <strong>výsledek hospodaření před zdaněním</strong> (zisk nebo ztráta), a to vždy bez vlivu Mezinárodních účetních standardů. Poplatníci, kteří sestavují účetní závěrku podle Mezinárodních účetních standardů, použijí ke zjištění výsledku hospodaření<br/> <ul style="margin:1em 0 1em 2em"> <li>vyhlášku č. 500/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>podnikateli</strong> účtujícími v soustavě podvojného účetnictví, v platném znění, nebo </li> <li>vyhlášku č. 501/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>bankami a jinými finančními institucemi</strong>, v platném znění, anebo</li> <li>vyhlášku č. 502/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>pojišťovnami</strong>, v platném znění,</li> </ul> a to podle působnosti vymezené v § 2 uvedených vyhlášek. Tito poplatníci uvedou na zvláštní příloze specifikaci a kvantifikaci vlivů, z nichž vyplývá rozdíl mezi výsledkem hospodaření zjištěným podle Mezinárodních účetních standardů a výsledkem hospodaření zjištěným podle příslušné, shora uvedené vyhlášky, a na ni navazujících Českých účetních standardů (§ 23 odst. 2 písm. a) zákona). Namísto této zvláštní přílohy lze předložit Výkaz zisku a ztráty a Rozvahu, popřípadě Přehled o změnách vlastního kapitálu, sestavené podle shora uvedených vyhlášek.<br/> Při stanovení základu daně se nepřihlíží k zápisům v knihách podrozvahových účtů, není-li v zákoně o daních z příjmů stanoveno jinak.<br/> Z výsledku hospodaření před zdaněním nebo z rozdílu mezi příjmy a výdaji se vychází pro zjištění základu daně (§ 23 odst. 2 zákona) za zdaňovací období nebo za období, za něž se podává daňové přiznání.<br/> Komanditní společnost uvede na tomto řádku výsledek hospodaření před úpravou o převod podílů na výsledku hospodaření komplementářům komanditní společnosti; tato úprava se provede na řádku 201.<br/> Jestliže poplatník vedl jednoduché účetnictví, uvede na tomto řádku <strong>rozdíl mezi příjmy a výdaji</strong> (kladný nebo záporný).<br/> Daňoví nerezidenti použijí řádek 10 též pro případ stanovení základu daně nebo jeho části jinými metodami podle § 23 odst. 11 zákona. V tomto případě bude na zvláštní příloze uveden propočet vykázané částky.<br/><br /><strong>Výsledkem hospodaření před zdaněním, z něhož se vychází pro zjištění základu daně (dále jen „výsledek hospodaření“), se rozumí:</strong><br/><br/> <strong>a)</strong> u poplatníků, účetních jednotek vymezených v § 2 vyhlášky č. 500/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>podnikateli</strong> účtujícími v soustavě podvojného účetnictví, v platném znění, částka vykázaná ve <strong>Výkazu zisku a ztráty - druhové členění</strong>, sestavovaného v plném nebo ve zkráceném rozsahu podle přílohy č. 2 k vyhlášce č. 500/2002 Sb., v platném znění, kterou se stanoví uspořádání a označování položek výkazu zisku a ztráty - druhové členění, popřípadě ve <strong>Výkazu zisku a ztráty - účelové členění</strong>, sestavovaného v plném nebo ve zkráceném rozsahu podle přílohy č. 3 k vyhlášce č. 500/2002 Sb., v platném znění, kterou se stanoví uspořádání a označování položek výkazu zisku a ztráty - druhové členění, ve výpočtové položce „<strong>**Výsledek hospodaření před zdaněním</strong>“, před jejím zaokrouhlením.<br /><strong>b)</strong> u poplatníků, účetních jednotek vymezených v § 2 vyhlášky č. 501/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>bankami a jinými finančními institucemi</strong>, v platném znění, součet částek vykázaných ve <strong>Výkazu zisku a ztráty</strong> v položce 19. <strong>„Zisk nebo ztráta za účetní období z běžné činnosti před zdaněním“</strong> a v položce 22. <strong>„Zisk nebo ztráta za účetní období z mimořádné činnosti před zdaněním“</strong> podle přílohy č. 2 k vyhlášce č. 501/2002 Sb., v platném znění, kterou se stanoví uspořádání položek výkazu zisku a ztráty a jejich označování, před jejich zaokrouhlením. <br/>V případech, kdy poplatník na základě právních předpisů upravujících účetnictví postupuje podle přímo použitelného předpisu Evropské unie upravujícího účetní výkaznictví leasingů, se pro zjištění základu daně vychází z výsledku hospodaření, který by byl zjištěn, kdyby poplatník v těchto případech postupoval podle právních předpisů upravujících účetnictví účinných před 1. lednem 2018. Specifikaci a kvantifikaci rozdílu mezi výsledkem hospodaření zjištěným podle právních předpisů upravujících účetnictví a výsledkem hospodaření zjištěným podle § 23 odst. 20 zákona uvede poplatník na zvláštní příloze k řádku 10.)<br/> <strong>c)</strong> u poplatníků, účetních jednotek vymezených v § 2 vyhlášky č. 502/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>pojišťovnami</strong>, v platném znění, částka vykázaná ve <strong>Výkazu zisku a ztráty</strong> v položce III. 16. <strong>Zisk nebo ztráta za účetní období</strong>, neupravená o částky v položkách III. 9. <strong>Daň z příjmů z běžné činnosti</strong> a III. 14. <strong>Daň z příjmů z mimořádné činnosti</strong>, podle přílohy č. 2 k vyhlášce č. 502/2002 Sb., v platném znění, kterou se stanoví uspořádání a označování položek výkazu zisku a ztráty, před jejím zaokrouhlením.<br/> <strong>d)</strong> u poplatníků, účetních jednotek vymezených v § 2 vyhlášky č. 503/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro <strong>zdravotní pojišťovny</strong>, v platném znění, částka vykázaná ve <strong>Výkazu zisku a ztráty</strong> v položce II. 12 <strong>Hospodářský výsledek za účetní období</strong>, neupravená o částku v položce II. 7. <strong>Daň z příjmů</strong>, podle přílohy č. 2 k vyhlášce č. 503/2002 Sb., v platném znění, kterou se stanoví uspořádání a označování položek výkazu zisku a ztráty, před jejím zaokrouhlením.<br/> <strong> e)</strong> u poplatníků, účetních jednotek vymezených v § 2 vyhlášky č. 504/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, u kterých <strong>hlavním předmětem činnosti není podnikání</strong>, pokud účtují v soustavě podvojného účetnictví, v platném znění, součet částek za hlavní a hospodářskou činnost, vykázaných ve <strong>Výkazu zisku a ztráty</strong> v položce C <strong>Výsledek hospodaření před zdaněním</strong>, podle přílohy č. 2 k vyhlášce č. 504/2002 Sb., v platném znění, kterou se stanoví uspořádání a označování položek výkazu zisku a ztráty, před jejich zaokrouhlením.<br/> <strong>f)</strong> u poplatníků, účetních jednotek vymezených v § 2 vyhlášky č. 410/2009 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro některé vybrané účetní jednotky, v platném znění, (t.j. <strong>územní samosprávné celky, dobrovolné svazky obcí, regionální rady regionů soudržnosti, příspěvkové organizace, státní fondy, Pozemkový fond České republiky a organizační složky státu</strong>), částka vykázaná ve <strong>Výkazu zisku a ztráty</strong> v položce C.1 <strong>Výsledek hospodaření před zdaněním</strong>, podle přílohy č. 2 k vyhlášce č. 410/2009 Sb., v platném znění, kterou se stanoví uspořádání a označování položek výkazu zisku a ztráty, před jejím zaokrouhlením.<br/><strong>g)</strong> daňoví nerezidenti uplatní podle druhu činnosti vykonávané na území České republiky postup týkající se tuzemských poplatníků uvedených pod písmeny a) až c), s týmž nebo obdobným předmětem činnosti. <br/> </p > <p class="Napoveda"> <br/><strong><u>Rozdílem mezi příjmy a výdaji</u></strong><br/> se u poplatníků, vedoucích jednoduché účetnictví podle § 13b zákona o účetnictví, rozumí částka vykázaná v <strong>Přehledu o příjmech a výdajích</strong> v položce 99 <strong>"Rozdíl příjmů a výdajů"</strong>, podle přílohy č. 1 k vyhlášce č. 325/2015 Sb., kterou se provádějí některá ustanovení zákona o účetnictví, pro účetní jednotky, které vedou jednoduché účetnictví, kterou se stanoví uspořádání a označování položek v přehledu o příjmech a výdajích, před jejím zaokrouhlením. + + + + + + + + + + + + + + + + + + + Na tomto řádku se v návaznosti na ustanovení § 24 zákona uvede souhrn rozdílů, o které částky daňových výdajů převyšují účetní náklady, např. při prodeji hmotného a nehmotného majetku rozdíl, o který daňová zůstatková cena (§ 29 zákona) převyšuje účetní zůstatkovou cenu.<br>Na zvláštní příloze se pak uvede rozdělení této souhrnné částky podle účtových skupin účtové třídy - náklady. + + + + + + + + + + + Na tomto řádku uvedou poplatníci se sídlem na území České republiky a stálé provozovny úhrn příjmů, z nichž je daň vybírána srážkou podle zvláštní sazby daně. + + + + + + + + + + + Tento řádek se nevyplňuje. Jeho využití je možné jen tehdy, jestliže zákon vymezí nové skutečnosti, které budou důvodem k dalším úpravám částky základu daně. + + + + + + + + + + + Uvede se souhrnná částka příjmů a částek nezahrnovaných do základu daně podle § 23 odst. 4 zákona, s výjimkou příjmů podle § 23 odst. 4 písm. a) a b) zákona, uvedených na ř. 120 a 130. Na zvláštní příloze se uvede rozčlenění souhrnné částky na tomto řádku podle zbývajících položek § 23 odst. 4 zákona. Částky podle § 23 odst. 4 písm. d) zákona může uplatnit též právní nástupce poplatníka. + + + + + + + + + + + Bude-li na tomto řádku vykázána daňová ztráta, následující řádky 230 až 330 se <strong>nevyplňují ani neproškrtávají</strong>. Daňovou ztrátu, která bude pravomocně stanovena za zdaňovací období nebo období, za které se podává daňové přiznání, započaté v roce 2025, lze podle § 34 odst. 1 zákona uplatňovat jako položku odčitatelnou od základu daně nejdéle v pěti bezprostředně následujících zdaňovacích obdobích. Pravomocně stanovenou daňovou ztrátu za zdaňovací období, které skončilo ode dne 30. června 2020, lze uplatnit i ve dvou zdaňovacích obdobích bezprostředně předcházejících, nejvýše však do výše 30 mil Kč v úhrnu za obě předcházející zdaňovací období. Poplatník se může vzdát práva na uplatnění daňové ztráty pro zdaňovací období následující po období, za které se tato daňová ztráta stanoví, a to oznámením správci daně ve lhůtě pro podání daňového přiznání za období, za které se daňová ztráta stanoví; lhůtu pro podání tohoto oznámení nelze navrátit v předešlý stav. Vzdání se práva má účinky pro všechna období následující po období, za které se tato daňová ztráta stanoví, přičemž vzdání se práva na uplatnění daňové ztráty nelze vzít zpět (§ 34 odst. 1 zákona).<br />Příklad výpočtu částky uvedené na tomto řádku při vykázání záporných čísel na ř. 200, 201 a 210:<br /> ř. 200 ......... –200 000 Kč<br /> ř. 201 ........... –50 000 Kč<br /> ř. 210 ........... –30 000 Kč<br /> ř. 220 = –200 000 – (–50 000) – (–30 000) = –120 000 Kč + + + + + + + + + + + Věcná náplň nebo propočet částek vykázaných na tomto řádku se rozvede na zvláštní příloze. Na tomto řádku se uvede uplatnění odečtu podle § 34 odst. 3 až 10 a 12 zákona, ve znění platném do 31. prosince 2004, uplatněním odečtu nelze vykázat daňovou ztrátu, pro jejíž stanovení platí ustanovení § 23 až 33, § 38n a § 38na zákona. Proto částka na tomto řádku <strong>nesmí být vyšší</strong> než základ daně uvedený na ř. 220, snížený o případné odečty daňové ztráty podle § 34 odst. 1 zákona (ř. 230) a odečty na podporu výzkumu a vývoje nebo odečty na podporu odborného vzdělávání (ř. 242 a 243).<br /><br />Je-li řádek vyplněn, je nutné ve smyslu dílčích pokynů pro jejich vyplnění rozvést na zvláštní příloze věcnou náplň částky vykázané na tomto řádku, popřípadě její propočet. + + + + + + + + + + + Úhrnná částka uvedená na tomto řádku se na zvláštní příloze rozčlení podle jednotlivých bodů § 23 odst. 3 písm. a) zákona, s výjimkou příjmů zahrnutých do částky na ř. 20 a výdajů (nákladů) zahrnutých do částky na řádku 40. Věcná náplň nebo propočet částky vykázané na tomto řádku se rozvede na zvláštní příloze.<br /><br />Je-li řádek vyplněn, je nutné ve smyslu dílčích pokynů pro jejich vyplnění rozvést na zvláštní příloze věcnou náplň částky vykázané na tomto řádku, popřípadě její propočet. + + + + + + + + + + + Výsledek hospodaření před zdaněním (zisk+, ztráta -) nebo rozdíl mezi příjmy a výdaji ke dni. + + + + + Příjmy osvobozené od daně podle § 19b zákona se na tomto řádku vyloučí pouze tehdy, jestliže jsou tyto příjmy zahrnuty ve výsledku hospodaření na ř. 10. + + + + + + + + + + + + + + + + + + + Na tomto řádku se uvede úhrn částek, o které se zvyšuje výsledek hospodaření nebo rozdíl mezi příjmy a výdaji podle ustanovení § 23e odst. 1 zákona, § 23g odst. 1 a 5 zákona, § 23h zákona a § 38fa odst. 1 nebo 2 a 8 zákona. Celková částka na ř. 63 musí být shodná s částkou na ř. 7 tabulky A Přílohy č. 3 II. oddílu. + + + + + + + + + + + Na tomto řádku se uvede úhrn částek, o které se snižuje výsledek hospodaření nebo rozdíl mezi příjmy a výdaji podle ustanovení § 23e odst. 7 zákona, § 23g odst. 1 a 5 zákona a § 38fa odst. 8 zákona. Celková částka na ř. 163 musí být shodná s částkou na ř. 6 tabulky B Přílohy č. 3 II. oddílu. + + + + + + + + + + + Uvede se daň zaplacená v zahraničí, o níž lze podle § 38fa odst. 9 zákona snížit daň, nejvýše však do částky uvedené na ř. 310. Při snížení daně se postupuje obdobně jako při použití metody prostého zápočtu podle § 38f zákona. Při výpočtu daně zaplacené v zahraničí, kterou lze započíst na daň za zdaňovací období nebo za období, za které se podává daňové přiznání, se vychází z údajů Samostatné přílohy k ř. 319 II. oddílu, která se vyplňuje samostatně za každý stát. Celková částka na ř. 319 nesmí být vyšší než součet ř. 7 všech Samostatných příloh k ř. 319 II. oddílu. + + + + + + + + + + + Uvede se daň, o kterou lze snížit daň ovládající společnosti podle § 38fa odst. 10 zákona, zaplacená z příjmů z činnosti a nakládání s majetkem, na které ovládající společnost použila ustanovení § 38fa odst. 1 nebo 2 zákona, základním investičním fondem, který je ovládající společností stejné ovládané zahraniční společnosti a prostřednictvím kterého ovládající společnost drží podíl na základním kapitálu této ovládané zahraniční společnosti, nejvýše však do částky uvedené na ř. 310 po případném snížení daně na ř. 319. + + + + + + + + + + + Tento řádek vyplňují poplatníci, kteří jsou v režimu vylučování kursových rozdílů a vznikne jim povinnost upravit základ daně, respektive výsledek hospodaření o kursové rozdíly podle § 23i odst. 1 a § 23i odst. 4 zákona. Na řádku se uvede úhrn nerealizovaných kursových ztrát, které se podle § 23i odst. 1 zákona nezahrnují do základu daně a kladné hodnoty vyplývající z výpočtu kumulovaných kursových rozdílů vypočtených podle § 23i odst. 4 zákona při realizaci kursového rozdílu. Pro uvedení úhrnu kursových zisků, které se podle § 23i odst. 1 zákona nezahrnují do základu daně a záporné hodnoty vyplývající z výpočtu kumulovaných kursových rozdílů vypočtených podle § 23i odst. 4 zákona při realizaci kursového rozdílu se použije ř. 165. Položka obsahuje kritickou kontrolu: nesmí být do 1.1.2024 vyplněna. + + + + + + + + + + + Tento řádek vyplňují poplatníci, kteří jsou v režimu vylučování kursových rozdílů a vznikne jim povinnost upravit základ daně, respektive výsledek hospodaření o kursové rozdíly podle § 23i odst. 1 a § 23i odst. 4 zákona. Na řádku se uvede úhrn nerealizovaných kursových zisků, které se podle § 23i odst. 1 zákona nezahrnují do základu daně a záporné hodnoty vyplývající z výpočtu kumulovaných kursových rozdílů vypočtených podle § 23i odst. 4 zákona při realizaci kursového rozdílu. Položka obsahuje kritickou kontrolu: nesmí být vyplněna do 1.1.2024 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Výsledná částka na řádku 13 tabulky musí být shodná s částkou na ř. 40 II. oddílu. + + + + + + + + + + + + + + + + + + + + + + + Na ř. 12 v části b) tabulky uvede poplatník účetní odpisy, s výjimkou uvedenou v § 25 odst. 1 písm. zg) zákona, u hmotného majetku, který není vymezen pro účely zákona jako hmotný majetek, a nehmotného majetku, který se neodpisuje podle tohoto zákona, uplatněné jako výdaj (náklad) k dosažení, zajištění a udržení zdanitelných příjmů, podle § 24 odst. 2 písm. v) zákona. Pro nehmotný majetek zaevidovaný do majetku poplatníka do 31. prosince 2000 a v době od 1. ledna 2001 do konce zdaňovacího období započatého v roce 2003, se použije zákon v příslušném platném znění, a to až do doby jeho vyřazení z majetku poplatníka. + + + + + + + + + + + + + + + + + + + + + + + + + + + Na ř. 9 budou uvedeny též uplatněné daňové odpisy hmotného majetku využívaného k výrobě elektřiny ze slunečního záření podle § 30b zákona. Vždy budou uvedeny daňové odpisy hmotného majetku, u kterého bylo odpisování podle § 30b zákona zahájeno před 1. 7. 2024 a daňové odpisy hmotného majetku, u kterého bylo odpisování podle § 30b zahájeno po 30. 6. 2024 a poplatník se rozhodl v souladu s přechodným ustanovením pokračovat v postupu odpisování podle § 30b zákona, ve znění před 1. 8. 2025. + + + + + + + + + + + + + + + + + + + Na ř. 10 uvede poplatník odpisy nehmotného majetku podle § 32a zákona, ve znění účinném do 31. prosince 2020. Na nehmotný majetek pořízený před 1. lednem 2021 a na technické zhodnocení tohoto majetku dokončené a uvedené do stavu způsobilého obvyklému užívání od 1. ledna 2021 se použije zákon č. 586/1992 Sb., ve znění do 31. prosince 2020 (bod 10 Čl. XII. přechodných ustanovení zákona č. 609/2020 Sb.). Na nehmotný majetek pořízený od 1. ledna 2020 lze od 1. ledna 2020 použít zákon č. 586/1992 Sb., ve znění účinném od 1. ledna 2021; využije-li poplatník takový postup, použije se na tento majetek a na technické zhodnocení tohoto majetku zákon č. 586/1992 Sb., ve znění účinném ode dne 1. ledna 2021, ve všech následujících zdaňovacích obdobích (bod 11 Čl. XII. přechodných ustanovení zákona č. 609/2020 Sb.). Na nehmotný majetek pořízený od 1. ledna 2021 se použije zákon č. 586/1992 Sb., ve znění účinném od 1. ledna 2021 (bod 12 Čl. XII. přechodných ustanovení zákona č. 609/2020 Sb.). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ve sl. 1 tabulky se v předepsaném tvaru uvede sestupně časové vymezení zdaňovacích období nebo období, za která se podává daňové přiznání, za které byla daňová ztráta, kterou poplatník uplatňuje, stanovena. + + + + + Ve sl. 2 se uvede celková výše daňové ztráty pravomocně stanovené za zdaňovací období nebo období, za které bylo podáno daňové přiznání, uvedené ve sl. 1. + + + + + + + + + + + Ve sl. 1 tabulky se v předepsaném tvaru uvede sestupně časové vymezení zdaňovacích období nebo období, za která se podává daňové přiznání, za které byla daňová ztráta, kterou poplatník uplatňuje, stanovena. + + + + + + + + + + + + + Ve sl. 6 se uvede identifikační číslo převádějící, zaniklé nebo rozdělené obchodní korporace, od které byla podle § 23a odst. 5 písm. b) a § 23c odst. 8 písm. b) zákona uplatňovaná daňová ztráta převzata. + + + + + + + + + + + + + + hodnota na ř. 9 (Celkem) + + + + + + + + + + + hodnota na ř. 9 (Celkem) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Na ř. 1 tabulky se uvede celková hodnota bezúplatných plnění poskytnutých poplatníkem na účely vymezené v § 20 odst. 8 zákona a v případě bezúplatného plnění poskytnutého v roce 2023 i na účely dle § 1 odst. 3 písm. a) zákona č. 128/2022 Sb., a to i tehdy, bude-li úhrnná částka těchto bezúplatných plnění vyšší než základ daně vykázaný na ř. 250, nejméně však 2 000 Kč, jako minimální zákonná hodnota poskytnutého bezúplatného plnění. Poplatníci k daňovému přiznání přikládají doklad, ze kterého musí být patrno, kdo je příjemcem bezúplatného plnění, hodnota, předmět bezúplatného plnění a účel, na který bylo bezúplatné plnění poskytnuto, a datum poskytnutí bezúplatného plnění.<br />Je-li poplatníkem komanditní společnost, může z celkové hodnoty jí poskytnutých bezúplatných plnění uplatnit pro účely odečtu nejvýše částku připadající na komanditisty (§ 20 odst. 10 zákona), vypočtenou na ř. 5 ve sloupci 3 tabulky J.<br />U poplatníka, který je společníkem veřejné obchodní společnosti nebo komplementářem komanditní společnosti je součástí hodnoty bezúplatných plnění, kterou lze odečíst od základu daně, i část hodnoty bezúplatných plnění poskytnutých veřejnou obchodní společností nebo komanditní společností na zákonem vymezené účely, stanovená podle § 20 odst. 9 nebo 10 zákona, jejíž výpočet se uvede na zvláštní příloze.<br />Odečet lze uplatnit též za část zdaňovacího období, za něž je podáváno daňové přiznání. + + + + + + + + + + + + + + + Řádek vyplňují pouze poplatníci, jimž byla poskytnuta investiční pobídka formou slevy na dani podle § 1a odst. 1 písm. a) zákona č. 72/2000 Sb., o investičních pobídkách a o změně některých zákonů (zákon o investičních pobídkách), ve znění pozdějších předpisů, kteří na tomto řádku uvedou částku této slevy, uplatněné za zdaňovací období.<br />Poplatník typu 9 přenese do tohoto řádku částku slevy vykázanou ve sl. 4 dílčí tabulky Uplatňování slev podle § 35a odst. 4 zákona, která je součástí Samostatné přílohy k řádku 5 tabulky H přílohy č. 1 II. oddílu.<br />Poplatník typu 0 přenese do tohoto řádku částku slevy vykázanou ve sl. 4 dílčí tabulky Uplatňování slev podle § 35b odst. 5 zákona, která je součástí Samostatné přílohy k řádku 5 tabulky H přílohy č. 1 II. oddílu. + + + + + + + + + + + Pro výpočet nároku na slevu podle § 35 odst. 1 písm. b) zákona je rozhodný průměrný přepočtený počet zaměstnanců s těžším zdravotním postižením ve zdaňovacím období, zaokrouhlený na dvě platná desetinná místa (§ 146 odst. 3 DŘ).<br /><br />Slevy na dani podle § 35 odst. 1 písm. a) a b) zákona lze uplatnit též za období, za něž je podáváno daňové přiznání; při výpočtu průměrného ročního přepočteného počtu zaměstnanců se zdravotním postižením podle § 35 odst. 2 zákona se jako dělitel, popřípadě jmenovatel u zlomku, použije plný roční fond pracovní doby připadající na jednoho zaměstnance pracujícího na plnou pracovní dobu stanovenou zvláštními předpisy, a to i tehdy, bude-li období, za něž je podáváno daňové přiznání kratší než nepřetržitě po sobě jdoucích dvanáct měsíců.<br />Bude-li daňové přiznání podáváno za zdaňovací období delší než dvanáct měsíců nebo za zdaňovací období hospodářského roku nebo za část zdaňovacího období, jejichž začátek a konec spadají do různých kalendářních roků, zjistí se celková částka slevy jako součet dílčích částek slev, vypočtených samostatně za každou z částí zdaňovacího období spadajících do různých kalendářních roků. Při výpočtu dílčích částek slev se postupuje podle § 35 odst. 2 zákona s tím, že za každý kalendářní rok nebo část kalendářního roku, které spadají do období, za něž se podává daňové přiznání, se jako dělitel, popřípadě jmenovatel u zlomku, použije plný roční fond pracovní doby připadající na jednoho zaměstnance pracujícího na plnou pracovní dobu stanovenou zvláštními předpisy příslušného kalendářního roku.<br />Poplatníci, kteří jsou společníky veřejné obchodní společnosti nebo komplementáři komanditní společnosti, popřípadě komanditních společností, uplatní příslušnou slevu pouze v částce, která odpovídá poměru, v jakém byl mezi ně rozdělen základ daně zjištěný za veřejnou obchodní společnost nebo za komanditní společnost. + + + + + + + + + + + Úhrn slev podle § 35 zákona (ř. 1 + 2 + 3). + + + + + + + + + + + Pro výpočet nároku na slevu podle § 35 odst. 1 písm. a) zákona je rozhodný průměrný přepočtený počet zaměstnanců se zdravotním postižením bez zaměstnanců s těžším zdravotním postižením, ve zdaňovacím období, zaokrouhlený na dvě platná desetinná místa (§ 146 odst. 3 DŘ).<br /><br />Slevy na dani podle § 35 odst. 1 písm. a) a b) zákona lze uplatnit též za období, za něž je podáváno daňové přiznání; při výpočtu průměrného ročního přepočteného počtu zaměstnanců se zdravotním postižením podle § 35 odst. 2 zákona se jako dělitel, popřípadě jmenovatel u zlomku, použije plný roční fond pracovní doby připadající na jednoho zaměstnance pracujícího na plnou pracovní dobu stanovenou zvláštními předpisy, a to i tehdy, bude-li období, za něž je podáváno daňové přiznání kratší než nepřetržitě po sobě jdoucích dvanáct měsíců.<br />Bude-li daňové přiznání podáváno za zdaňovací období delší než dvanáct měsíců nebo za zdaňovací období hospodářského roku nebo za část zdaňovacího období, jejichž začátek a konec spadají do různých kalendářních roků, zjistí se celková částka slevy jako součet dílčích částek slev, vypočtených samostatně za každou z částí zdaňovacího období spadajících do různých kalendářních roků. Při výpočtu dílčích částek slev se postupuje podle § 35 odst. 2 zákona s tím, že za každý kalendářní rok nebo část kalendářního roku, které spadají do období, za něž se podává daňové přiznání, se jako dělitel, popřípadě jmenovatel u zlomku, použije plný roční fond pracovní doby připadající na jednoho zaměstnance pracujícího na plnou pracovní dobu stanovenou zvláštními předpisy příslušného kalendářního roku.<br />Poplatníci, kteří jsou společníky veřejné obchodní společnosti nebo komplementáři komanditní společnosti, popřípadě komanditních společností, uplatní příslušnou slevu pouze v částce, která odpovídá poměru, v jakém byl mezi ně rozdělen základ daně zjištěný za veřejnou obchodní společnost nebo za komanditní společnost. + + + + + + + + + + + Uvede se sleva za zastavené exekuce, jejíž výše odpovídá výši náhrady, respektive náhrad, které přizná exekutor při zastavení exekuce (§ 35 odst. 4 zákona). O přiznání náhrady rozhodne exekutor v usnesení o zastavení exekuce (bod 21 Čl. IV přechodných ustanovení zákona č. 286/2021 Sb.) + + + + + + + + + + + + + + + Na tomto řádku se uvede úhrnná částka daní zaplacených v zahraničí, které lze v souladu s příslušnými ustanoveními smluv o zamezení dvojího zdanění, jimiž je Česká republika vázána, uplatnit k zápočtu na daň metodou <strong>úplného</strong> zápočtu. Její rozčlenění podle smluvních států musí být doloženo seznamem potvrzení zahraničních správců daně (§ 38f odst. 10 zákona) nebo, půjde-li o ojedinělý příjem ze zdrojů v zahraničí, potvrzením zahraničního správce daně (§ 38f odst. 5 zákona). + + + + + + + + + + + Částka z tohoto řádku se přenese do ř. 320 II. oddílu, maximálně však do výše částky vykázané na ř. 310 II. oddílu. + + + + + + + + + + + Uvede se úhrn částek vypočtených na ř. 7 všech samostatných příloh k tabulce I. + + + + + + + + + + + Počet samostatných příloh + + + + + + + + + + + Uvede se úhrn částek daní zaplacených v zahraničí, které lze v souladu s příslušnými ustanoveními smluv o zamezení dvojího zdanění, jimiž je Česká republika vázána, uplatnit k zápočtu na daň metodou <strong>prostého</strong> zápočtu, vykázaných na ř. 3 všech vyplněných samostatných příloh k tabulce I. + + + + + + + + + + + Částku vykázanou na tomto řádku lze za podmínek daných § 24 odst. 2 písm. ch) zákona uplatnit jako výdaj (náklad) vynaložený na dosažení, zajištění a udržení příjmů v následujícím zdaňovacím období nebo období, za něž bude podáváno daňové přiznání. To platí pouze za předpokladu, že z úhrnných částek uvedených na ř. 1 a 2 budou vyloučeny ty částky daně zaplacené v zahraničí, které byly vyměřeny a zaplaceny nad rámec daně stanovené podle mezinárodních smluv o zamezení dvojího zdanění či nad rámec vnitrostátních předpisů země zdroje příjmů. Jako výdaj (náklad) vynaložený na dosažení, zajištění a udržení příjmů podle § 24 odst. 2 písm. ch), s přihlédnutím k § 25 odst. 1 písm. s) zákona, lze uplatnit též daň zaplacenou v zahraničí z příjmů se zdrojem ve státě, s nímž dosud nemá Česká republika uzavřenu smlouvu o zamezení dvojího zdanění nebo jestliže již uzavřená smlouva dosud nenabyla účinnosti. + + + + + + + + + + + + + + + Řádek 9, sloupec 3<br />Musí se rovnat částce na ř. 4 tabulky I + + + + + + + + + + + Řádek 9, sloupec 4 + + + + + + + + + + + Řádek 2, sloupec 2 + + + + + + + + + + + Řádek 9, sloupec 2 + + + + + + + + + + + Řádek 5, sloupec 4 + + + + + + + + + + + Řádek 4, sloupec 4 + + + + + + + + + + + Řádek 2, sloupec 4 + + + + + + + + + + + Řádek 1, sloupec 4 + + + + + + + + + + + Řádek 3, sloupec 3<br />Musí se rovnat částce ve sl. 2 na řádku tabulky F/c, na němž je vykázán nárok na odečet za dané zdaňovací období, nebo období, za které je podáváno daňové přiznání + + + + + + + + + + + Řádek 3, sloupec 2 + + + + + + + + + + + Řádek 7, sloupec 4 + + + + + + + + + + + Řádek 5, sloupec 3<br />Musí se rovnat částce na ř. 1 tabulky G + + + + + + + + + + + Řádek 7, sloupec 2 + + + + + + + + + + + Řádek 1, sloupec 3 + + + + + + + + + + + Řádek 4, sloupec 3<br />Musí se rovnat částce ve sl. 2 na řádku tabulky F/b, na němž je vykázán nárok na odečet za dané zdaňovací období, nebo období, za které je podáváno daňové přiznání. + + + + + + + + + + + Řádek 1, sloupec 2 + + + + + + + + + + + Řádek 3, sloupec 4 + + + + + + + + + + + Řádek 2, sloupec 3 + + + + + + + + + + + Řádek 5, sloupec 2 + + + + + + + + + + + Řádek 7, sloupec 3<br />Musí se rovnat částce na ř. 4 tabulky H + + + + + + + + + + + Řádek 4, sloupec 2 + + + + + + + + + + + + + + + Na tomto řádku se uvede celkový průměrný přepočtený počet zaměstnanců, včetně případů pracovního vztahu člena k družstvu během zdaňovacího období nebo období, za které se podává daňové přiznání, který je u poplatníků v postavení účetních jednotek součástí údajů uváděných v příloze účetní závěrky podle<br />a) § 39 odst. 1 písm. i) vyhlášky č. 500/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>podnikateli</strong> účtujícími v soustavě podvojného účetnictví, v platném znění,<br />b) 54 odst. 5 písm. a) vyhlášky č. 501/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>bankami a jinými finančními institucemi</strong>, v platném znění,<br />c) § 22 odst. 2 písm. k) vyhlášky č. 502/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, které jsou <strong>pojišťovnami</strong>, v platném znění,<br />d) § 30 odst. 1 písm. p) vyhlášky č. 504/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro účetní jednotky, u kterých <strong>hlavním předmětem činnosti není podnikání</strong>, pokud účtují v soustavě podvojného účetnictví, v platném znění.<br />Poplatníci, účetní jednotky vymezené v § 2 vyhlášky č. 503/2002 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro <strong>zdravotní pojišťovny</strong>, v platném znění, a v § 2 vyhlášky č. 410/2009 Sb., kterou se provádějí některá ustanovení zákona č. 563/1991 Sb., o účetnictví, ve znění pozdějších předpisů, pro některé vybrané účetní jednotky, v platném znění, (t.j. <strong>územní samosprávné celky, dobrovolné svazky obcí, regionální rady regionů soudržnosti, příspěvkové organizace, státní fondy, Pozemkový fond České republiky a organizační složky státu</strong>), v platném znění, jimž zmíněné vyhlášky nestanoví povinnost uvádět celkový průměrný přepočtený počet zaměstnanců v příloze účetní závěrky, uvedou tento údaj za zdaňovací období nebo období, za které se podává daňové přiznání, jen pokud mají zpravodajskou povinnost ke statistickým výkazům podle vyhlášky č. 325/2024 Sb., o Programu statistických zjišťování na rok 2025, jejichž součástí je ukazatel o počtu zaměstnanců (např. P 3-04, Zdp 3-04, Práce 2-04).<br />Průměrný přepočtený počet zaměstnanců se zaokrouhluje na celá čísla. Pokud se průměrný přepočtený počet zaměstnanců zaokrouhlí na nulu, nebo poplatník neměl žádného zaměstnance uvede se na řádku hodnota nula (0)).<br />Tento údaj není povinný pro poplatníky, kteří vedou jednoduché účetnictví. + + + + + + + + + + + Údaj vyplňují všichni poplatníci podávající přiznání za zdaňovací období nebo za období, za které se podává daňové přiznání, kteří na tomto řádku uvedou roční úhrn čistého obratu, vypočtený v měně účetnictví podle § 1d odst. 2 věty druhé zákona o účetnictví.V položce Měrná jednotka se uvede příslušná měna účetnictví, kterou podle § 24a zákona o účetnictví může být česká koruna (CZK), euro (EUR), americký dolar (USD) nebo britská libra (GBP). Veřejně prospěšní poplatníci (§ 17a zákona), uvedou roční úhrn čistého obratu z celkové činnosti, tj. z hlavní i hospodářské činnosti. Poplatníci, kteří vedou jednoduché účetnictví, uvedou součet všech příjmů dosažených za zdaňovací období nebo za období, za které se podává daňové přiznání. + + + + + + + + + + + Uveďte příslušnou měnu účetnictví, kterou podle § 24a zákona o účetnictví může být česká koruna (CZK), euro (EUR), americký dolar (USD) nebo britská libra (GBP). Položka obsahuje kritické kontroly: nesmí být vyplněna do 1.1.2024. Musí být vyplněna od 1.1.2024 + + + + + + + + + + + + + + + Číslo řádku z formuláře, ke kterému příloha patří. + + + + + + + + + + + Jeden řádek textové přílohy, max. 72 znaků. + + + + + + + + + + + Historická položka, dále nepoužívat. Nahrazeno položkou t_prilohy. + + + + + + + + + + + Označení oddílu, ke kterému se příloha vztahuje.:<BR />2 - II. oddíl,<BR />5 - V.oddíl,<BR />C - příloha C,<BR />F - příloha F,<BR />H - příloha H,<BR />S - příloha H5 tabulka I,<BR />T - příloha H5 tabulka II,<BR />U - příloha H5 tabulka II b),<BR />I - příloha I,<BR />O - obecná příloha<BR />D - důvody pro podání dodatečného DAP + + + + + + + + + + + Číslo řádku přílohy. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Datum vydání rozhodnutí Ministerstva průmyslu a obchodu + + + + + + + + + + + + + + + + + + + + + Sleva je uplatňována na základě rozhodnutí Ministerstva průmyslu a obchodu (část před lomítkem). + + + + + + + + + + + + + + + + + + + + + + + + + + + Sleva je uplatňována na základě rozhodnutí Ministerstva průmyslu a obchodu (část za lomítkem). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uveďte, zda máte výskyt skutečností podle § 35a odst. 7 nebo 8 anebo 9 zákona (2015: ZDP) ve zdaňovacím období, za které je podáváno DAP + + + + + + + + + + + + + + + Část základu daně podle § 20a písm. b) zákona (ř. 270 II. oddílu - ř. 271).<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Sazba daně (v %) podle § 21 odst. 2 zákona.<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Část základu daně podle § 20a písm. a) zákona ze ř. 271, zaokrouhlená na celé tisíce Kč dolů.<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Daň (ř. 286 + 288).<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Počet kalendářních dnů v části zdaňovacího období ode dne vzniku investičního fondu.<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. U poplatníků typu 7 (typ_popldpp) musí být vyplněna a hodnota musí být větší než 0. U poplatníků ostatních typů nesmí být vyplněna. + + + + + + + + + + + Část daně připadající na zbývající část zdaňovacího období (ř. 274 x ř. 284)/100.<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Sazba daně (v %) podle § 21 odst. 1 zákona.<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Část základu daně podle § 20a písm. a) zákona (ř. 270 II. oddílu x počet dnů podle § 20a písm. a) zákona)/ počet dnů trvání zdaňovacího období.<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Část základu daně podle § 20a písm. b) zákona ze ř. 273, zaokrouhlená na celé tisíce Kč dolů.<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + Část daně připadající na dobu činnosti základního investičního fondu (ř. 272 x ř. 282) / 100.<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Smí být vyplněna pouze u poplatníků typu 7 (typ_popldpp). + + + + + + + + + + + + + + + Uvede se daň vztahující se k příjmům plynoucím ze zdrojů na území České republiky i ze zdrojů v zahraničí, která se převezme z řádku 310 II. oddílu přiznání. + + + + + + + + + + + Na tomto řádku bude uveden úhrn výdajů stanovených podle tuzemského zákona o daních z příjmů, souvisejících s celkovými hrubými příjmy na ř. 4. Při stanovení příjmu podléhajícího zdanění ve státě jejich zdroje, uváděného na ř. 6, nelze použít odčitatelné položky a položky snižující základ daně podle zahraničních právních předpisů.<br /> Při přepočtu souvisejících zahraničních výdajů na Kč se pro účely zápočtu používají kursy devizového trhu vyhlášené ČNB, uplatňované v účetnictví poplatníka (§ 38 odst. 1 zákona).<br /> Nelze-li u některých výdajů prokazatelně stanovit, zda souvisí s příjmy plynoucími ze zdrojů v daném státě, považuje se za související výdaje jejich část stanovená ve stejném poměru, v jakém příjmy plynoucí ze zdrojů v zahraničí nesnížené o výdaje připadají na celosvětové příjmy (§ 38f odst. 3 zákona). + + + + + + + + + + + Na tomto řádku bude uveden základ daně před uplatněním položek odčitatelných od základu daně a nezdanitelných částí základu daně, který se převezme z řádku 220 II. oddílu přiznání. + + + + + + + + + + + Uvede se příjem podléhající zdanění ve státě zdroje (§ 38f odst. 3 zákona). Bude-li na tomto řádku vykázáno záporné číslo (daňová ztráta) následující řádek 7 se nevyplňuje. + + + + + + + + + + + Částka vypočtená na tomto řádku se zahrne do celkové částky na ř. 3 tabulky I. Zápočet daně zaplacené v zahraničí. + + + + + + + + + + + Na tomto řádku se uvede částka daně zaplacená ve státě zdroje zdaněných příjmů, a to pouze do výše, která mohla být v tomto státě vybrána v souladu s příslušnými ustanoveními smlouvy o zamezení dvojího zdanění. Částka daně uplatňovaná k zápočtu musí být doložena seznamem potvrzení zahraničních správců daně (§ 38f odst. 10 zákona) nebo, půjde-li o ojedinělý příjem ze zdrojů v zahraničí, potvrzením zahraničního správce daně (§ 38f odst. 5 zákona). Částka vykázaná na tomto řádku se zahrne do celkové částky na ř. 2 tabulky I. Zápočet daně zaplacené v zahraničí. <br> Při přepočtu daně zaplacené v zahraničí na Kč se pro účely jejího zápočtu používají kursy devizového trhu vyhlášené ČNB, uplatňované v účetnictví poplatníka (§ 38 odst. 1 zákona). + + + + + + + + + + + Uvede se úhrn hrubých příjmů zdaněných ve státě zdroje. <br> Při přepočtu příjmů ze zdrojů v daném státě na Kč se pro účely zápočtu používají kursy devizového trhu vyhlášené ČNB, uplatňované v účetnictví poplatníka (§ 38 odst. 1 zákona). + + + + + + + + + + + Stát zdroje příjmů, z nichž je podle smlouvy o zamezení dvojího zdanění uplatňován zápočet daně zaplacené v tomto státě.<br />Pro hodnotu této položky použijte číselník Země (zeme). Z číselníku se vkládá položka kod2. <br />Položka obsahuje kritické kontroly: hodnota musí být vyplněna a musí obsahovat kód existujícího státu.<br> +Pro popis číselníku Země klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/zeme">zde</a>. + + + + + + + + + + + + + + + Hodnota ve sloupci minulé období netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci brutto (v tis. / mil.). <br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci korekce (v tis. / mil.). Záporné znaménko se neuvádí.<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci minulé účetní období (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci sledované účetní období (v tis. / mil.). + + + + + + + + + + + + + + + Hodnota ve sloupci sledované účetní období (v tis. / mil.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci minulé účetní období (v tis. / mil.). + + + + + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci sledované účetní období (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci minulé účetní období (v tis. / mil.). + + + + + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci minulé účetní období (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci sledované účetní období (v tis. / mil.). + + + + + + + + + + + + + + + Hodnota ve sloupci sledované účetní období (v tis. / mil.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci minulé účetní období (v tis. / mil.). + + + + + + + + + + + + + + + Hodnota ve sloupci sledované účetní období (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci minulé účetní období (v tis. / mil.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + + + + + Hodnota ve sloupci základní kapitál (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci vlastní akcie (v tis. / mil.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci rezervní fondy (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci kapitálové fondy (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci emisní ážio (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci oceňovací rozdíly (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci zisk (ztráta) (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci celkem (v tis. / mil.). + + + + + + + + + + + + + + + Hodnota ve sloupci sledované účetní období (v tis. / mil.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci minulé účetní období (v tis. / mil.). + + + + + + + + + + + + + + + Hodnota ve sloupci brutto (v tis. / mil.). <br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci korekce (v tis. / mil.). Záporné znaménko se neuvádí.<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci minulé období netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + + + + + Hodnota ve sloupci netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci korekce (v tis. / mil.). Záporné znaménko se neuvádí.<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci brutto (v tis. / mil.). <br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci minulé období netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + + + + + Hodnota ve sloupci běžné účetní období - výsledek (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci běžné účetní období - mezisoučet (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci běžné účetní období - základna (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci minulé účetní období - výsledek (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + + + + + Hodnota ve sloupci brutto (v tis. / mil.). <br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci korekce (v tis. / mil.). Záporné znaménko se neuvádí.<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci minulé období netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + + + + + Hodnota ve sloupci netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci minulé období netto (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci korekce (v tis. / mil.). Záporné znaménko se neuvádí.<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci brutto (v tis. / mil.). <br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + + + + + Hodnota ve sloupci minulé účetní období - výsledek (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci běžné účetní období - základna (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci běžné účetní období - výsledek (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Hodnota ve sloupci běžné účetní období - mezisoučet (v tis. / mil.).<br />Položka obsahuje kritickou kontrolu: hodnota nesmí být zadána u skrytého sloupce (dle definice v číselníku, u těch tabulek kde se skrývané sloupce aplikují - tedy v tabulce pasiv u rozvah pojišťoven a zdravotních pojišťoven). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci stav k prvnímu dni účetního období (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci stav k poslednímu dni účetního období (v tis. / mil.). + + + + + + + + + + + + + + + Hodnota ve sloupci stav k poslednímu dni účetního období (v tis. / mil.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci stav k prvnímu dni účetního období (v tis. / mil.). + + + + + + + + + + + + + + + Hodnota ve sloupci celkem (v tis. / mil.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci činnost hospodářská (v tis. / mil.). + + + + + + + + + + + Hodnota ve sloupci činnost hlavní (v tis. / mil.). + + + + + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci Účetní období (v tis. / mil. Kč). + + + + + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + Hodnota ve sloupci Snížení stavu (v tis. / mil. Kč). + + + + + + + + + + + Hodnota ve sloupci Zvýšení stavu (v tis. / mil. Kč). + + + + + + + + + + + Hodnota ve sloupci Minulé období (v tis. / mil. Kč). + + + + + + + + + + + Hodnota ve sloupci Běžné období (v tis. / mil. Kč). + + + + + + + + + + + + + + + Služby - Náklad. Vyplní se v celých tis. Kč + + + + + + + + + + + Celkový objem ostatních transakcí - Výnos. Vyplní se v celých tis. Kč + + + + + + + + + + + uvede se, zda byla uskutečněna se spojenou osobou transakce poskytnutí bezúplatného plnění. Za bezúplatné plnění se nepovažují reklamní a propagační předměty dle § 25 odst. 1 písm. t) zákona.<br />A - ano<br />N - ne + + + + + + + + + + + uvede se obchodní firma právnické osoby zapsaná do veřejných rejstříků, včetně dodatku označujícího její právní formu, popřípadě též dovětku „v likvidaci“. U právnických osob, které se nezapisují do veřejných rejstříků, se uvede název, pod kterým byly založeny nebo zřízeny, a u ostatních subjektů název, pod nímž vystupují vůči třetím osobám. Pokud rozsah předtištěných políček položky 01 nestačí k zapsání celého názvu obchodní firmy nebo názvu poplatníka, uvede se pouze jejich zkrácený tvar s tím, že na zvláštní příloze se pak vyznačí celý název obchodní firmy, případně název poplatníka. Je-li spojenou osobou fyzická osoba, uvede se současné jméno a příjmení včetně získaných vědeckých a akademických titulů.<br/>Položka obsahuje kritické kontroly: Název spojené osoby musí být vyplněn; V souboru nesmí existovat duplicitní listy přílohy, tj. věty A se stejnou hodnotou dvojice položek (naz_spojos, stat_spojos). + + + + + + + + + + + Úvěrové finanční nástroje - Vyplacené. Vyplní se v celých tis. Kč + + + + + + + + + + + Podíly na zisku - Přijaté. Vyplní se celých v tis. Kč + + + + + + + + + + + Dlouhodobý finanční majetek - Výnos (prodej). Vyplní se v celých tis. Kč + + + + + + + + + + + Úroky - Výnos. Vyplní se v celých tis. Kč + + + + + + + + + + + Krátkodobé závazky - Stav ke konci aktuálního období. Vyplní se v celých tis. Kč + + + + + + + + + + + Zásoby materiálu, výrobků a zboží - Pořizovací cena (nákup). Vyplní se v celých tis. Kč + + + + + + + + + + + Dlouhodobý hmotný majetek - Výnos (prodej). Vyplní se v celých tis. Kč + + + + + + + + + + + Úvěrové finanční nástroje - Přijaté. Vyplní se v celých tis. Kč + + + + + + + + + + + Krátkodobé závazky - Stav ke konci minulého období. Vyplní se v celých tis. Kč + + + + + + + + + + + Dlouhodobý nehmotný majetek - Výnos (prodej). Vyplní se v celých tis. Kč + + + + + + + + + + + Podíly na zisku - Vyplacené. Vyplní se v celých tis. Kč + + + + + + + + + + + Dlouhodobý finanční majetek - Pořizovací cena (nákup). Vyplní se v celých tis. Kč + + + + + + + + + + + Krátkodobé pohledávky - Stav ke konci minulého období. Vyplní se v celých tis. Kč + + + + + + + + + + + Dlouhodobé závazky - Stav ke konci minulého období. Vyplní se v celých tis. Kč + + + + + + + + + + + Zásoby materiálu, výrobků a zboží - Výnos (prodej). Vyplní se v celých tis. Kč + + + + + + + + + + + Dlouhodobé pohledávky - Stav ke konci minulého období. Vyplní se v celých tis. Kč + + + + + + + + + + + Licenční poplatek (vč. software) - Náklad. Vyplní se v celých tis. Kč + + + + + + + + + + + Dlouhodobé závazky - Stav ke konci aktuálního období. Vyplní se v celých tis. Kč + + + + + + + + + + + kód státu se vyplňuje podle Sdělení Českého statistického úřadu ze dne 18. prosince 2003 č. 489/2003 Sb., o vydání číselníku zemí (CZEM) v aktuální znění (viz též Seznam kódů států na webové adrese http://www.financnisprava.cz, použije se pouze dvoumístný kód vymezený velkými písmeny abecedy)(2015-2016: kód státu se vyplňuje podle Sdělení Českého statistického úřadu ze dne 18. května 2012 o aktualizaci číselníku zemí (CZEM).<br />Položka obsahuje kritické kontroly: položka musí být vyplněna a její hodnota musí být kód existujícího státu z číselníku Země (zeme - z číselníku se vkládá položka kod2).<br> +Pro popis číselníku Země klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/zeme">zde</a>. + + + + + + + + + + + uvede se, zda byla uskutečněna se spojenou osobou transakce přijetí bezúplatného plnění. Za bezúplatné plnění se nepovažují reklamní a propagační předměty dle § 25 odst. 1 písm. t) zákona.<br />A - ano<br />N - ne + + + + + + + + + + + Dlouhodobý hmotný majetek - Pořizovací cena (nákup). Vyplní se v celých tis. Kč + + + + + + + + + + + Úroky - Náklad. Vyplní se v celých tis. Kč + + + + + + + + + + + Licenční poplatek (vč. software) - Výnos. Vyplní se v celých tis. Kč + + + + + + + + + + + Služby - Výnos. Vyplní se v celých tis. Kč + + + + + + + + + + + Dlouhodobý nehmotný majetek - Pořizovací cena (nákup). Vyplní se v celých tis. Kč + + + + + + + + + + + (vnitroskupinové sdružování finančních prostředků) – uvede se, zda daňový subjekt využívá cash-pooling.<br />A - ano<br />N - ne. + + + + + + + + + + + Celkový objem ostatních transakcí - Náklad. Vyplní se v celých tis. Kč + + + + + + + + + + + Dlouhodobé pohledávky - Stav ke konci aktuálního období. Vyplní se v celých tis. Kč + + + + + + + + + + + uvede se identifikační číslo (identifikátor pro daňové účely) spojené osoby, pokud jí bylo přiděleno. + + + + + + + + + + + Ostatní složky vlastního kapitálu - Snížení. Vyplní se v celých tis. Kč + + + + + + + + + + + Ostatní složky vlastního kapitálu - Zvýšení. Vyplní se v celých tis. Kč + + + + + + + + + + + Krátkodobé pohledávky - Stav ke konci aktuálního období. Vyplní se v celých tis. Kč + + + + + + + + + + + Nájem - Výnos. Vyplní se v celých tis. Kč.<br />Položka se vyplňuje od ZO 2017. + + + + + + + + + + + Nájem - Náklad. Vyplní se v celých tis. Kč.<br />Položka se vyplňuje od ZO 2017. + + + + + + + + + + + uvede se, zda daňový subjekt přijal záruku nebo je v platnosti přijatá záruka od spojené osoby. Rozumí se jak finanční tak bankovní záruky podle ustanovení § 2029 až § 2039 zákona č. 89/2012 Sb., občanský zákoník, ve znění pozdějších předpisů.<br />Položka se vyplňuje od ZO 2017.<br />A - ano<br />N - ne + + + + + + + + + + + uvede se, zda daňový subjekt poskytl záruku nebo je v platnosti poskytnutá záruka spojené osobě. Rozumí se jak finanční tak bankovní záruky podle ustanovení § 2029 až § 2039 zákona č. 89/2012 Sb., občanský zákoník, ve znění pozdějších předpisů.<br />Položka se vyplňuje od ZO 2017.<br />A - ano<br />N - ne + + + + + + + + + + + + + + + Část zdaňovacího období, ve které byl poplatník daně z příjmu právnických osob základním investičním fondem § 20a písm. a) zákona).<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Musí být vyplněna u poplatníků typu 7 (typ_popldpp), je-li vyplněna hodnota (nezáporná) ř. 220. U ostatních typů poplatníků nesmí být vyplněna. Vyplněné datum musí být ve zdaň. období, za které je DAP podáváno. Datum konce ZIF musí být větší nebo rovno počátku ZIF. + + + + + Část zdaňovacího období, ve které byl poplatník daně z příjmu právnických osob základním investičním fondem (§ 20a písm. a) zákona).<br />Položka obsahuje kritické kontroly: nesmí být vyplněna, je-li na ř. 220 vykázána daňová ztráta. Musí být vyplněna u poplatníků typu 7 (typ_popldpp), je-li vyplněna hodnota (nezáporná) ř. 220. U ostatních typů poplatníků nesmí být vyplněna. Vyplněné datumy musí být ve zdaň. období, za které je DAP podáváno. Počátky intervalů ZIF nesmí být duplicitní. + + + + + + + + + Na řádku se uvede podle § 23e odst. 1 zákona částka kladného rozdílu mezi nadměrnými výpůjčními výdaji a limitem uznatelnosti nadměrných výpůjčních výdajů, kterým je vyšší hodnota z částky odpovídající 30 % daňového zisku před úroky, zdaněním a odpisy (§ 23e odst. 5 zákona), nebo částky 80 000 000 Kč. Limit pro omezení uznatelnosti nadměrných výpůjčních výdajů se použije pro zdaňovací období i pro období, za které se podává daňové přiznání. Částka zvýšení musí být shodná s částkou uvedenou ve sl. 4 tabulky I. Přílohy č. 3 II. oddílu. + + + + + + + + + + + Na řádku se uvede částka zvýšení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji v důsledku přemístění majetku bez změny vlastnictví z České republiky do zahraničí podle § 23g odst. 1 zákona, a to <u>do států</u> dle § 38zg odst. 1 zákona.<br />Přemístění majetku bez změny vlastnictví se pro účely daní z příjmů považuje za úplatný převod takového majetku sobě samému za cenu, která by byla sjednána mezi nespojenými osobami v běžných obchodních vztazích za stejných nebo obdobných podmínek. Uvedené platí shodně i pro ř. 3.<br />Částka zvýšení musí být shodná s kladnou částkou uvedenou na ř. 7 sl. 4 tabulky II. Přílohy č. 3 II. oddílu. + + + + + + + + + + + Na řádku se uvede částka zvýšení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji v důsledku přemístění majetku bez změny vlastnictví z České republiky do zahraničí podle § 23g odst. 1 zákona <u>mimo státy</u> dle § 38zg odst. 1 zákona.<br />Částka zvýšení musí být shodná s kladnou částkou uvedenou na ř. 7 sl. 4 tabulky III. Přílohy č. 3 II. oddílu. + + + + + + + + + + + Na řádku se uvede částka, o kterou je poplatník povinen zvýšit výsledek hospodaření nebo rozdíl mezi příjmy a výdaji podle § 23h zákona v důsledku rozdílné právní kvalifikace. + + + + + + + + + + + Na řádku se uvede částka zvyšující výsledek hospodaření nebo rozdíl mezi příjmy a výdaji podle § 38fa odst. 1 nebo 2 a 8 zákona v důsledku zdanění zahrnovaných příjmů z činnosti ovládané zahraniční společnosti (§ 38fa odst. 4 zákona). Na činnost ovládané zahraniční společnosti a nakládání s jejím majetkem, ze kterých plynou zahrnované příjmy (§ 38fa odst. 6 zákona), se pro účely daní z příjmů při naplnění podmínek podle § 38fa odst. 1 písm. a) a b) zákona hledí, jako by byly uskutečněny ovládající společností (§ 38fa odst. 3 zákona) na území České republiky. Na činnost ovládané zahraniční společnosti, která je k okamžiku skončení svého zdaňovacího období daňovým rezidentem státu uvedeného na unijním seznamu jurisdikcí nespolupracujících v daňové oblasti schváleném Radou Evropské unie nebo stálou provozovnou umístěnou v takovém státě, a nakládání s jejím majetkem, ze kterých plynou v daném zdaňovacím období příjmy, se podle § 38fa odst. 2 zákona pro účely daní z příjmů hledí, jako by byly uskutečněny ovládající společností na území České republiky. Státy, které jsou na tomto seznamu uvedeny, uveřejní Ministerstvo financí ve Finančním zpravodaji. Částka musí být shodná s hodnotou vykázanou na řádku Celkem v tabulce IV. Přílohy č. 3 II. oddílu za všechny ovládané zahraniční společnosti. + + + + + + + + + + + Hodnota celkem za řádky tabulky A přílohy č. 3 II. oddílu.<br />Výsledná částka na ř. 7 tabulky musí být shodná s částkou na ř. 63 II. oddílu. Částky v tabulce A nemohou nabývat záporných hodnot. + + + + + + + + + + + Na tomto řádku se podle § 23e odst. 7 zákona uvede částka snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji, o kterou byl za předcházející zdaňovací období nebo období, za které se podává daňové přiznání, podle § 23e odst. 1 zákona výsledek hospodaření nebo rozdíl mezi příjmy a výdaji zvýšen, nejvýše však do částky kladného rozdílu mezi limitem uznatelnosti nadměrných výpůjčních výdajů a nadměrnými výpůjčními výdaji. Částka snížení musí být shodná s částkou uvedenou ve sl. 3 tabulky I. Přílohy č. 3 II. oddílu a nemůže spolu s dříve využitým snížením převýšit částku uvedenou ve sl. 1 tabulky I. Přílohy č. 3 II. oddílu. + + + + + + + + + + + Na řádku se uvede částka snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji v důsledku přemístění majetku bez změny vlastnictví z České republiky do zahraničí podle § 23g odst. 1 zákona, a to <u>do států</u> dle § 38zg odst. 1 zákona.<br />Přemístění majetku bez změny vlastnictví se pro účely daní z příjmů považuje za úplatný převod takového majetku sobě samému za cenu, která by byla sjednána mezi nespojenými osobami v běžných obchodních vztazích za stejných nebo obdobných podmínek. Uvedené platí shodně i pro ř. 3.<br />Částka se musí rovnat záporné hodnotě vykázané na ř. 7 sl. 4 tabulky II. Přílohy č. 3 II. oddílu, přičemž částka snížení se uvádí bez záporného znaménka.<br />Příklad uvedení částky na tomto řádku:<br />ř. 7 sl. 4 tabulky II. Přílohy č. 3 II. oddílu …… - 30 000 Kč<br />ř. 2 tabulky B Přílohy č. 3 II. oddílu …………… 30 000 Kč + + + + + + + + + + + Na řádku se uvede částka snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji v důsledku přemístění majetku bez změny vlastnictví z České republiky do zahraničí podle § 23g odst. 1 zákona <u>mimo státy</u> dle § 38zg odst. 1 zákona. Částka se musí rovnat záporné hodnotě vykázané na ř. 7 sl. 4 tabulky III. Přílohy č. 3 II. oddílu, přičemž částka snížení se uvádí bez záporného znaménka. + + + + + + + + + + + Na řádku se uvede snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji podle § 23g odst. 5 zákona v důsledku přeřazení majetku bez změny vlastnictví z jiného členského státu Evropské unie do České republiky, kdy toto přeřazení podléhá v tomto státě zdanění při přemístění majetku bez změny vlastnictví. + + + + + + + + + + + Částka snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji na tomto řádku podle § 38fa odst. 8 zákona nesmí být vyšší než částka, o kterou byl zvýšen výsledek hospodaření nebo rozdíl mezi příjmy a výdaji za dané zdaňovací období na ř. 6 tabulky A Přílohy č. 3 II. oddílu a současně se musí shodovat s hodnotou uvedenou na ř. 1 tabulky V. Přílohy č. 3 II. oddílu. Zvýšení a snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji se posuzuje vždy jednotlivě za každou ovládanou zahraniční společnost. + + + + + + + + + + + Hodnota celkem za řádky tabulky B přílohy č. 3 II. oddílu.<br />Výsledná částka na ř. 6 tabulky musí být shodná s částkou na ř. 163 II. oddílu. Částky v tabulce B nemohou nabývat záporných hodnot. + + + + + + + + + + + Hodnota celkem za sloupec „Částka“ tabulky IV. Přílohy č. 3 II. oddílu.<br />Hodnota „Celkem“ se musí rovnat hodnotě na ř. 6 (do 2019: ř. 5) tabulky A Přílohy č. 3 II. oddílu a nesmí nabývat záporné hodnoty. + + + + + + + + + + + Na řádku se uvede zvýšení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji podle § 23g odst. 5 zákona v důsledku přeřazení majetku bez změny vlastnictví z jiného členského státu Evropské unie do České republiky, kdy toto přeřazení podléhá v tomto státě zdanění při přemístění majetku bez změny vlastnictví. + + + + + + + + + + + + + + + Ve sl. 1 se uvede součet všech částek, o které byl v předcházejících zdaňovacích obdobích nebo obdobích, za která se podává daňové přiznání, zvýšen výsledek hospodaření nebo rozdíl mezi příjmy a výdaji. + + + + + + + + + + + Ve sl. 2 se uvede součet všech částek, o které byl v předcházejících zdaňovacích obdobích nebo obdobích, za která se podává daňové přiznání, snížen výsledek hospodaření nebo rozdíl mezi příjmy a výdaji. Částka spolu s částkou ve sl. 3 nemůže být vyšší než částka ve sl. 1. + + + + + + + + + + + Ve sl. 3 a 4 se podle uvedeného algoritmu uvedou částky zvýšení nebo snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji za dané zdaňovací období nebo období, za které se podává daňové přiznání. Částka ve sl. 3 musí odpovídat částce uvedené na ř. 1 tabulky B Přílohy č. 3 II. oddílu a částka ve sl. 4 musí odpovídat částce uvedené na ř. 1 tabulky A Přílohy č. 3 II. oddílu. Nelze současně vyplnit sl. 3 a 4. + + + + + + + + + + + Ve sl. 3 a 4 se podle uvedeného algoritmu uvedou částky zvýšení nebo snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji za dané zdaňovací období nebo období, za které se podává daňové přiznání. Částka ve sl. 3 musí odpovídat částce uvedené na ř. 1 tabulky B Přílohy č. 3 II. oddílu a částka ve sl. 4 musí odpovídat částce uvedené na ř. 1 tabulky A Přílohy č. 3 II. oddílu. Nelze současně vyplnit sl. 3 a 4. + + + + + + + + + + + Ve sl. 5 se uvede souhrnná částka, o kterou lze snížit v následujících zdaňovacích obdobích nebo období, za která se podává daňové přiznání, výsledek hospodaření nebo rozdíl mezi příjmy a výdaji podle § 23e odst. 6 zákona. Tato částka odpovídá algoritmu výpočtu: sl. 1 – sl. 2 - sl. 3 + sl. 4. Uplatnění snížení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji není limitováno počtem zdaňovacích období nebo období, za která se podává daňové přiznání. + + + + + + + + + + + + + + + Hmotný majetek - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Hmotný majetek - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Hmotný majetek - Rozdíl (+/-). + + + + + + + + + + + Nehmotný majetek - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Nehmotný majetek - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Nehmotný majetek - Rozdíl (+/-). + + + + + + + + + + + Finanční majetek - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Finanční majetek - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Finanční majetek - Rozdíl (+/-). + + + + + + + + + + + Pohledávky - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Pohledávky - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Pohledávky - Rozdíl (+/-). + + + + + + + + + + + Zásoby - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Zásoby - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Zásoby - Rozdíl (+/-). + + + + + + + + + + + Ostatní - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Ostatní - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Ostatní - Rozdíl (+/-). + + + + + + + + + + + Celkem - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Celkem - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Celkem - Rozdíl (+/-). + + + + + + + + + + + + + + + Hmotný majetek - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Hmotný majetek - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Hmotný majetek - Rozdíl (+/-). + + + + + + + + + + + Nehmotný majetek - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Nehmotný majetek - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Nehmotný majetek - Rozdíl (+/-). + + + + + + + + + + + Finanční majetek - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Finanční majetek - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Finanční majetek - Rozdíl (+/-). + + + + + + + + + + + Pohledávky - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Pohledávky - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Pohledávky - Rozdíl (+/-). + + + + + + + + + + + Zásoby - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Zásoby - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Zásoby - Rozdíl (+/-). + + + + + + + + + + + Ostatní - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Ostatní - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Ostatní - Rozdíl (+/-). + + + + + + + + + + + Celkem - Cena podle § 23g odst. 1 zákona. + + + + + + + + + + + Celkem - Výdaj podle § 23g odst. 1 zákona. + + + + + + + + + + + Celkem - Rozdíl (+/-). + + + + + + + + + + + + + + + uvede se obchodní firma právnické osoby zapsaná do veřejných rejstříků, včetně dodatku označujícího její právní formu, popřípadě též dovětku „v likvidaci“. U právnických osob, které se nezapisují do veřejných rejstříků, se uvede název, pod kterým byly založeny nebo zřízeny, a u ostatních subjektů název, pod nímž vystupují vůči třetím osobám. Pokud rozsah řádku nestačí k zapsání celého názvu obchodní firmy nebo názvu poplatníka, uvede se pouze jejich zkrácený tvar s tím, že na zvláštní příloze se pak vyznačí celý název obchodní firmy, případně název poplatníka.<br />Položka obsahuje kritické kontroly: Název ovládané zahraniční společnosti musí být vyplněn; V souboru nesmí existovat duplicitní záznamy, tj. věty CD se stejnou hodnotou dvojice položek (naz_ovlspol, stat_ovlspol). + + + + + + + + + + + uvede se identifikační číslo (identifikátor pro daňové účely) ovládané zahraniční společnosti, pokud jí bylo přiděleno. + + + + + + + + + + + kód státu se vyplňuje podle Sdělení Českého statistického úřadu ze dne 18. prosince 2003 č. 489/2003 Sb., o vydání číselníku zemí (CZEM) v aktuální znění, (viz též Seznam kódů států na webové adrese http://www.financnisprava.cz, použije se pouze dvoumístný kód vymezený velkými písmeny abecedy).<br />Položka obsahuje kritické kontroly: položka musí být vyplněna a její hodnota musí být kód existujícího státu z číselníku Země (zeme - z číselníku se vkládá položka kod2).<br> +Pro popis číselníku Země klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/zeme">zde</a>. + + + + + + + + + + + Částkou se rozumí celkové zvýšení výsledku hospodaření nebo rozdílu mezi příjmy a výdaji za každou jednotlivou ovládanou zahraniční společnost v příslušném zdaňovacím období nebo období, za které se podává daňové přiznání, v souladu s ustanovením § 38fa odst. 1 nebo 2 a 8 zákona, a to s výjimkou zahrnovaného příjmu podle § 38fa odst. 6 písm. c) zákona, který podléhá u ovládající společnosti zdanění v samostatném základu daně podle § 20b zákona, pokud není od daně z příjmů osvobozen.<br />Hodnoty uvedené ve sloupci „Částka“ nemohou nabývat záporných hodnot. + + + + + + + + + + + + + + + + + + + + + + + Na tomto řádku se uvede úhrn částek, o které byl snížen výsledek hospodaření nebo rozdíl mezi příjmy a výdaji v daném zdaňovacím období nebo období, za které se podává daňové přiznání, uvedených ve sloupci 4 na řádku 8 jednotlivých samostatných příloh. + + + + + + + + + + + Uvede se úhrn částek, o které lze snížit výsledek hospodaření nebo rozdíl mezi příjmy a výdaji v následujících zdaňovacích obdobích nebo obdobích, za která se podává daňové přiznání, uvedených ve sloupci 5 na řádku 8 jednotlivých samostatných příloh. + + + + + + + + + + + + + + + uvede se obchodní firma právnické osoby zapsaná do veřejných rejstříků, včetně dodatku označujícího její právní formu, popřípadě též dovětku „v likvidaci“. U právnických osob, které se nezapisují do veřejných rejstříků, se uvede název, pod kterým byly založeny nebo zřízeny, a u ostatních subjektů název, pod nímž vystupují vůči třetím osobám. Pokud rozsah řádku nestačí k zapsání celého názvu obchodní firmy nebo názvu poplatníka, uvede se pouze jejich zkrácený tvar s tím, že na zvláštní příloze se pak vyznačí celý název obchodní firmy, případně název poplatníka.<br />Položka obsahuje kritické kontroly: Název ovládané zahraniční společnosti musí být vyplněn; V souboru nesmí existovat duplicitní listy přílohy, tj. věty CF se stejnou hodnotou dvojice položek (pr3v_naz_ovlspol, pr3v_stat_ovlspol). + + + + + + + + + + + uvede se identifikační číslo (identifikátor pro daňové účely) ovládané zahraniční společnosti, pokud jí bylo přiděleno. + + + + + + + + + + + kód státu se vyplňuje podle Sdělení Českého statistického úřadu ze dne 18. prosince 2003 č. 489/2003 Sb., o vydání číselníku zemí (CZEM) v aktuální znění, (viz též Seznam kódů států na webové adrese http://www.financnisprava.cz, použije se pouze dvoumístný kód vymezený velkými písmeny abecedy).<br />Položka obsahuje kritické kontroly: položka musí být vyplněna a její hodnota musí být kód existujícího státu z číselníku Země (zeme - z číselníku se vkládá položka kod2).<br> +Pro popis číselníku Země klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/zeme">zde</a>. + + + + + + + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 1 musí být datum vyplněno. + + + + + Položka obsahuje kritické kontroly: V případě plnění řádku 1 musí být datum vyplněno a nesmí být menší než datum počátku (pr3v_sl1_r1_od). + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 2 musí být datum vyplněno. + + + + + Položka obsahuje kritické kontroly: V případě plnění řádku 2 musí být datum vyplněno a nesmí být menší než datum počátku (pr3v_sl1_r2_od). + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 3 musí být datum vyplněno. + + + + + Položka obsahuje kritické kontroly: V případě plnění řádku 3 musí být datum vyplněno a nesmí být menší než datum počátku (pr3v_sl1_r3_od). + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 4 musí být datum vyplněno. + + + + + Položka obsahuje kritické kontroly: V případě plnění řádku 4 musí být datum vyplněno a nesmí být menší než datum počátku (pr3v_sl1_r4_od). + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 5 musí být datum vyplněno. + + + + + Položka obsahuje kritické kontroly: V případě plnění řádku 5 musí být datum vyplněno a nesmí být menší než datum počátku (pr3v_sl1_r5_od). + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 6 musí být datum vyplněno. + + + + + Položka obsahuje kritické kontroly: V případě plnění řádku 6 musí být datum vyplněno a nesmí být menší než datum počátku (pr3v_sl1_r6_od). + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 7 musí být datum vyplněno. + + + + + Položka obsahuje kritické kontroly: V případě plnění řádku 7 musí být datum vyplněno a nesmí být menší než datum počátku (pr3v_sl1_r7_od). + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 1 musí být hodnota vyplněna. + + + + + + + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 2 musí být hodnota vyplněna. + + + + + + + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 3 musí být hodnota vyplněna. + + + + + + + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 4 musí být hodnota vyplněna. + + + + + + + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 5 musí být hodnota vyplněna. + + + + + + + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 6 musí být hodnota vyplněna. + + + + + + + + + + + Položka obsahuje kritickou kontrolu: V případě plnění řádku 7 musí být hodnota vyplněna. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Celkem - Část nároku ze sl. 2 odečtená v daném zdaňovacím období. + + + + + + + + + + + Celkem - Část nároku ze sl. 2 kterou lze odečíst v následujících zdaňovacích obdobích. + + + + + + + + + + + + + + + Kód státu se vyplňuje podle Sdělení Českého statistického úřadu ze dne 18. prosince 2003 č. 489/2003 Sb., o vydání číselníku zemí (CZEM) v aktuální znění, (viz též Seznam kódů států na webové adrese http://www.financnisprava.cz, použije se pouze dvoumístný kód vymezený velkými písmeny abecedy).<br />Položka obsahuje kritické kontroly: položka musí být vyplněna a její hodnota musí být kód existujícího státu z číselníku Země (zeme - z číselníku se vkládá položka kod2).<br> +Pro popis číselníku Země klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/zeme">zde</a>. + + + + + + + + + + + Na řádek 1 se uvede hodnota z řádku 310 II. oddílu přiznání. + + + + + + + + + + + Na řádek 2 se uvede hodnota z řádku 220 II. oddílu přiznání. + + + + + + + + + + + Na řádku 3 se uvede částka daně obdobné dani z příjmů právnických osob zaplacená z příjmů z činnosti a nakládání s majetkem v zahraničí (ve státě sídla ovládané zahraniční společnosti), kterou lze započítat na daňovou povinnost ovládající společnosti. <strong>Započítat tuto daň lze pouze v rozsahu, ve kterém byla tato daň stanovena z příjmů z činnosti a nakládání s majetkem, které byly přičteny poplatníkovi (ovládající osobě).</strong> Příjmy z činnosti, na které se použije postup podle § 38fa odst. 1 nebo 2 zákona, se rozumí příjmy ovládající společnosti skutečně přičítané, tedy v rozsahu určeném podle § 38fa odst. 7 zákona, tj. v poměrné části vypočítané podle podílu ovládající společnosti na základním kapitálu ovládané zahraniční společnosti.<br>&nbsp<br>Při přepočtu daně zaplacené ze zahrnovaných příjmů v zahraničí na Kč se použije kurz devizového trhu vyhlášený ČNB uplatňovaný ovládající společností v účetnictví k poslednímu dni zdaňovacího období ovládané zahraniční společnosti. + + + + + + + + + + + Na řádku 4 se uvedou celkové zahrnované příjmy za daný stát, tj. součet zahrnovaných příjmů za všechny ovládané zahraniční společnosti se sídlem v daném státě, a to s výjimkou zahrnovaného příjmu podle § 38fa odst. 6 písm. c) zákona, který podléhá u ovládající společnosti zdanění v samostatném základu daně podle § 20b zákona, pokud není od daně z příjmů osvobozen. Při přepočtu příjmů zahrnovaných do základu daně ovládající společnosti na Kč se použije kurz devizového trhu vyhlášený ČNB uplatňovaný ovládající společností v účetnictví k poslednímu dni zdaňovacího období ovládané zahraniční společnosti. + + + + + + + + + + + Na řádku 5 se uvede úhrn částek daňových výdajů, stanovených podle tuzemského zákona o daních z příjmů, souvisejících se zahrnovanými příjmy uvedenými na řádku 4. Při přepočtu souvisejících zahraničních výdajů na Kč se použije kurz devizového trhu vyhlášený ČNB uplatňovaný ovládající společností v účetnictví k poslednímu dni zdaňovacího období ovládané zahraniční společnosti. + + + + + + + + + + + Na řádku 6 se vypočítá rozdíl údajů řádku 4 a řádku 5. V této tabulce se uvádí pouze kladné hodnoty výše uvedeného rozdílu.<br>&nbsp<br>Kumulovaný součet hodnot řádku 6 všech samostatných příloh k ř. 319 II. oddílu přiznání se rovná částce uvedené v řádku celkem tabulky IV. Přílohy č. 3 II. oddílu. + + + + + + + + + + + Na řádku 7 se vypočítá daň, kterou lze uznat k zápočtu postupem pro aplikaci zápočtu prostého dle § 38f odst. 2 zákona. Částka vypočtená na řádku 7 se přenese na ř. 319 II. oddílu přiznání. Pokud je více samostatných příloh k řádku 319 II. oddílu přiznání, přenese se kumulovaná hodnota z řádku 7 všech těchto příloh na řádek 319 II. oddílu přiznání, avšak nejvýše do hodnoty ř. 310 II. oddílu přiznání.<br>&nbsp<br>Obdobná možnost odečtení od daňové povinnosti poplatníka je upravena v § 38fa odst. 9 písm. c) zákona i pro daně zaplacené z těchto příjmů daňovým nerezidentem, jehož prostřednictvím drží český daňový rezident – poplatník daně z příjmů právnických osob – podíl na ovládané zahraniční společnosti, jejíž příjmy již byly zahrnuty do základu daně tohoto nerezidenta z důvodu, že ve státě jeho daňové rezidence jsou taktéž uplatňována pravidla obdobná § 38fa zákona. + + + + + + + + + + + + + + + Pro výběr, zda-li má být "Rozvaha" zahrnuta do žádosti o předání účetní závěrky do sbírky listin, vyplňte Ano (A) nebo Ne (N). + + + + + + + + + + + Pro výběr, zda-li má být "Výkaz zisku a ztráty" zahrnut do žádosti o předání účetní závěrky do sbírky listin, vyplňte Ano (A) nebo Ne (N). + + + + + + + + + + + Pro výběr, zda-li má být "Příloha účetní závěrky" zahrnuta do žádosti o předání účetní závěrky do sbírky listin, vyplňte Ano (A) nebo Ne (N). + + + + + + + + + + + Pro výběr, zda-li má být "Přehled o změnách vlastního kapitálu" zahrnut do žádosti o předání účetní závěrky do sbírky listin, vyplňte Ano (A) nebo Ne (N). + + + + + + + + + + + Pro výběr, zda-li má být "Přehled o peněžních tocích" zahrnut do žádosti o předání účetní závěrky do sbírky listin, vyplňte Ano (A) nebo Ne (N). + + + + + + + + + + + Pro výběr, zda-li má být "Účetní závěrka sestavená dle mezinárodních účetních standardů" zahrnuta do žádosti o předání účetní závěrky do sbírky listin, vyplňte Ano (A) nebo Ne (N). + + + + + + + + + + + Vyplňte e-mail, na který Vám má být zasláno potvrzení o předání účetní závěrky (neuvádějte e-mail rejstříkového soudu).<br /><br />Položka obsahuje kritické kontroly: e-mail musí mít správný formát a větu UZ (Příloha k položce 11) lze plnit pouze v případě účtování podle vyhlášky č. 500 nebo č. 501. + + + + + + + + + + + + + + + Hodnota ve sloupci hlavní činnost - běžné účetní období (v tis.). + + + + + + + + + + + Hodnota ve sloupci hospodářská činnost - běžné účetní období (v tis.). + + + + + + + + + + + Hodnota ve sloupci hlavní činnost - minulé účetní období (v tis.). + + + + + + + + + + + Hodnota ve sloupci hospodářská činnost - minulé účetní období (v tis.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + + + + + Hodnota ve sloupci stav k rozvahovému dni běžného účetního období (v tis.). + + + + + + + + + + + Hodnota ve sloupci stav k rozvahovému dni minulého účetního období (v tis.). + + + + + + + + + + + Číslo řádku účetního výkazu. Více informací viz stránka: <a href="idpr_pub/hlib/uv_info/uv_info.faces">Informace o číslech řádků pro věty účetních výkazů</a>.<br />Položka obsahuje kritické kontroly: nesmí být duplicitní, musí odpovídat hodnotě z číselníku (viz odkaz výše). + + + + + + + + + + + + + + + Uveďte číslo zahraničního účtu (nebo účtu v ČR vedeného v cizí měně) ve formátu IBAN.<br><br>Položka obsahuje kritickou kontrolu: při žádosti o vrácení přeplatku na zahraniční účet nebo účet vedený v cizí měně musí být vyplněno číslo zahraničního účtu. + + + + + + + + + + + Způsob vrácení přeplatku:<br><b>U</b> - na účet<br><b>Z</b> - na zahraniční účet.<br><br>Položka obsahuje kritické kontroly: pro způsob U musí být vyplněny všechny relevantní položky bankovního spojení. + + + + + + + + + + + + Uveďte kód měny, ve které je zahraniční účet (nebo účet v ČR vedený v cizí měně) veden.<br><br>Položka obsahuje kritickou kontrolu: hodnota musí být platnou hodnotou z číselníku měn.<br><br>Pro hodnotu této položky použijte číselník Měna (zeme). Z číselníku se vkládá položka k_meny.<br> +Pro popis číselníku Měna klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/mena">zde</a>. + + + + + + + + + + + + + + + + + + + Uveďte předčíslí bankovního účtu.<br><br>Položka obsahuje kritickou kontrolu: předčíslí bankovního účtu musí být ve správném formátu. + + + + + + + + + + + Uveďte číslo bankovního účtu.<br><br>Položka obsahuje kritickou kontrolu: číslo bankovního účtu musí být správné. + + + + + + + + + + + + + + + + + + Uveďte název finanční instituce, u které je zahraniční účet(nebo účet v ČR vedený v cizí měně) veden.<br><br>Položka obsahuje kritickou kontrolu: při žádosti o vrácení přeplatku na zahraniční účet (nebo účet v ČR vedený v cizí měně) musí být v žádosti uveden název bankovní instituce, u které je účet veden. + + + + + + + + + + + + + + + + + + + + + + + + + + + Uveďte název obce/města z adresy banky.<br><br>Položka obsahuje kritickou kontrolu: při žádosti o vrácení přeplatku na zahraniční účet musí být v adrese bankovní instituce uveden název města + + + + + + + + + + + Uveďte kód státu banky.<br><br>Položka obsahuje kritické kontroly: kód státu musí být uveden v číselníku zemí a v žádosti o vrácení přeplatku na zahraniční účet nelze žádat o vrácení přeplatku na účet vedený v českých korunách u banky sídlící v Česku a účet vedený ve slovenských korunách u banky sídlící na Slovensku.<br><br>Pro hodnotu této položky použijte číselník Země (zeme). Z číselníku se vkládá položka kod2.<br> +Pro popis číselníku Země klikněte <a href="https://mojedane.gov.cz/pmd/dokumentace/ciselniky/ukazka/zeme">zde</a>. + + + + + + + + + + + + + + + + + + + Uveďte ID banky (měl by být uveden BIC kód - swiftový kód banky).<br> +<br>Položka obsahuje kritickou kontrolu: při žádosti o vrácení přeplatku na zahraniční účet (nebo účet v ČR vedený v cizí měně) musí být vyplněna identifikace banky (BIC kód). + + + + + + + + + + + Uveďte název vlastníka účtu.<br><br>Položka obsahuje kritickou kontrolu: při žádosti o vrácení přeplatku na zahraniční účet musí být v žádosti uveden název vlastníka účtu. + + + + + + + + + + + + + + + + + + + + + + + + + + + Uveďte název obce/města z adresy banky.<br><br>Položka obsahuje kritickou kontrolu: při žádosti o vrácení přeplatku na zahraniční účet musí být v adrese bankovní instituce uveden název města. + + + + + + + + + + + Uveďte kód státu z adresy vlastníka účtu.<br>Pro hodnotu této položky použijte číselník Země (zeme). Z číselníku se vkládá položka kod2.<br><br>Položka obsahuje kritické kontroly: při žádosti o vrácení přeplatku na zahraniční účet musí být v adrese vlastníka účtu označen stát a kód státu musí být uveden v číselníku zemí. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Uveďte částku přeplatku. + + + + + + + + + + + + + + + + + + + + Pořadové číslo přílohy. Nesmí být v rámci jednoho podání duplicitní + + + + + + + + + + + Popis přiloženého souboru + + + + + + + + + + + <strong>Název přiloženého souboru.</strong><br/>Povolené typy souborů jsou: DOC, DOCX, RTF, XLS, XLSX, PDF, JPG, TXT a TXT/CSV. Dále je možné přiložit podepsané (formát PKCS#7) a komprimované (formát ZIP) soubory, vždy však jde o jeden podepsaný nebo jeden komprimovaný soubor některého z podporovaných formátů. Součet velikostí všech souborů přiložených v elektronické podobě (tzv. e-příloh) může být nejvýše 10 240 kilobajtů. + + + + + + + + + + + Kódování přiloženého souboru + + + + + + + + + + + + + + + + + + Pořadové číslo přílohy. Nesmí být v rámci jednoho podání duplicitní + + + + + + + + + + + Popis přiloženého souboru + + + + + + + + + + + <strong>Název přiloženého souboru.</strong><br/>Povolené typy souborů jsou: DOC, DOCX, RTF, XLS, XLSX, PDF, JPG, TXT a TXT/CSV. Dále je možné přiložit podepsané (formát PKCS#7) a komprimované (formát ZIP) soubory, vždy však jde o jeden podepsaný nebo jeden komprimovaný soubor některého z podporovaných formátů. Součet velikostí všech souborů přiložených v elektronické podobě (tzv. e-příloh) může být nejvýše 10 240 kilobajtů. + + + + + + + + + + + Kódování přiloženého souboru + + + + + + + + + + Kód přiloženého souboru + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_cz_dppo/data/uv_radky_500.csv b/l10n_cz_dppo/data/uv_radky_500.csv new file mode 100644 index 00000000..6f1d2aa5 --- /dev/null +++ b/l10n_cz_dppo/data/uv_radky_500.csv @@ -0,0 +1,206 @@ +veta,c_radku,oznaceni,rozsah,text +UA,1,,PZM,AKTIVA CELKEM +UA,2,A.,PZM,Pohledávky za upsaný základní kapitál +UA,3,B.,PZM,Stálá aktiva +UA,4,B.I.,PZ,Dlouhodobý nehmotný majetek +UA,5,B.I.1.,P,Nehmotné výsledky vývoje +UA,6,B.I.2.,P,Ocenitelná práva +UA,7,B.I.2.1.,P,Software +UA,8,B.I.2.2.,P,Ostatní ocenitelná práva +UA,9,B.I.3.,P,Goodwill +UA,10,B.I.4,P,Ostatní dlouhodobý nehmotný majetek +UA,11,B.I.5.,P,Poskytnuté zálohy na dlouhodobý nehmotný majetek a nedokončený dlouhodobý nehmotný majetek +UA,12,B.I.5.1.,P,Poskytnuté zálohy na dlouhodobý nehmotný majetek +UA,13,B.I.5.2.,P,Nedokončený dlouhodobý nehmotný majetek +UA,14,B.II.,PZ,Dlouhodobý hmotný majetek +UA,15,B.II.1.,P,Pozemky a stavby +UA,16,B.II.1.1.,P,Pozemky +UA,17,B.II.1.2.,P,Stavby +UA,18,B.II.2.,P,Hmotné movité věci a jejich soubory +UA,19,B.II.3.,P,Oceňovací rozdíl k nabytému majetku +UA,20,B.II.4.,P,Ostatní dlouhodobý hmotný majetek +UA,21,B.II.4.1.,P,Pěstitelské celky trvalých porostů +UA,22,B.II.4.2.,P,Dospělá zvířata a jejich skupiny +UA,23,B.II.4.3.,P,Jiný dlouhodobý hmotný majetek +UA,24,B.II.5.,P,Poskytnuté zálohy na dlouhodobý hmotný majetek a nedokončený dlouhodobý hmotný majetek +UA,25,B.II.5.1,P,Poskytnuté zálohy na dlouhodobý hmotný majetek +UA,26,B.II.5.2.,P,Nedokončený dlouhodobý hmotný majetek +UA,27,B.III.,PZ,Dlouhodobý finanční majetek +UA,28,B.III.1.,P,Podíly - ovládaná nebo ovládající osoba +UA,29,B.III.2.,P,Zápůjčky a úvěry - ovládaná nebo ovládající osoba +UA,30,B.III.3,P,Podíly - podstatný vliv +UA,31,B.III.4.,P,Zápůjčky a úvěry - podstatný vliv +UA,32,B.III.5.,P,Ostatní dlouhodobé cenné papíry a podíly +UA,33,B.III.6.,P,Zápůjčky a úvěry - ostatní +UA,34,B.III.7.,P,Ostatní dlouhodobý finanční majetek +UA,35,B.III.7.1.,P,Jiný dlouhodobý finanční majetek +UA,36,B.III.7.2.,P,Poskytnuté zálohy na dlouhodobý finanční majetek +UA,37,C.,PZM,Oběžná aktiva +UA,38,C.I.,PZ,Zásoby +UA,39,C.I.1.,P,Materiál +UA,40,C.I.2.,P,Nedokončená výroba a polotovary +UA,41,C.I.3,P,Výrobky a zboží +UA,42,C.I.3.1.,P,Výrobky +UA,43,C.I.3.2.,P,Zboží +UA,44,C.I.4.,P,Mladá a ostatní zvířata a jejich skupiny +UA,45,C.I.5.,P,Poskytnuté zálohy na zásoby +UA,46,C.II.,PZ,Pohledávky +UA,47,C.II.1.,PZ,Dlouhodobé pohledávky +UA,48,C.II.1.1.,P,Pohledávky z obchodních vztahů +UA,49,C.II.1.2.,P,Pohledávky - ovládaná nebo ovládající osoba +UA,50,C.II.1.3.,P,Pohledávky - podstatný vliv +UA,51,C.II.1.4.,P,Odložená daňová pohledávka +UA,52,C.II.1.5.,P,Pohledávky - ostatní +UA,53,C.II.1.5.1.,P,Pohledávky za společníky +UA,54,C.II.1.5.2.,P,Dlouhodobé poskytnuté zálohy +UA,55,C.II.1.5.3.,P,Dohadné účty aktivní +UA,56,C.II.1.5.4.,P,Jiné pohledávky +UA,57,C.II.2.,PZ,Krátkodobé pohledávky +UA,58,C.II.2.1.,P,Pohledávky z obchodních vztahů +UA,59,C.II.2.2.,P,Pohledávky - ovládaná nebo ovládající osoba +UA,60,C.II.2.3.,P,Pohledávky - podstatný vliv +UA,61,C.II.2.4.,P,Pohledávky - ostatní +UA,62,C.II.2.4.1.,P,Pohledávky za společníky +UA,63,C.II.2.4.2.,P,Sociální zabezpečení a zdravotní pojištění +UA,64,C.II.2.4.3.,P,Stát - daňové pohledávky +UA,65,C.II.2.4.4.,P,Krátkodobé poskytnuté zálohy +UA,66,C.II.2.4.5.,P,Dohadné účty aktivní +UA,67,C.II.2.4.6.,P,Jiné pohledávky +UA,78,C.II.3.,PZ,Časové rozlišení aktiv +UA,79,C.II.3.1.,P,Náklady příštích období +UA,80,C.II.3.2.,P,Komplexní náklady příštích období +UA,81,C.II.3.3.,P,Příjmy příštích období +UA,68,C.III.,PZ,Krátkodobý finanční majetek +UA,69,C.III.1.,P,Podíly - ovládaná nebo ovládající osoba +UA,70,C.III.2.,P,Ostatní krátkodobý finanční majetek +UA,71,C.IV.,PZ,Peněžní prostředky +UA,72,C.IV.1.,P,Peněžní prostředky v pokladně +UA,73,C.IV.2.,P,Peněžní prostředky na účtech +UA,74,D.,PZM,Časové rozlišení aktiv +UA,75,D.1.,P,Náklady příštích období +UA,76,D.2.,P,Komplexní náklady příštích období +UA,77,D.3.,P,Příjmy příštích období +UD,1,,PZM,PASIVA CELKEM +UD,2,A.,PZM,Vlastní kapitál +UD,3,A.I.,PZ,Základní kapitál +UD,4,A.I.1.,P,Základní kapitál +UD,5,A.I.2.,P,Vlastní podíly (-) +UD,6,A.I.3.,P,Změny základního kapitálu +UD,7,A.II.,PZ,Ážio a kapitálové fondy +UD,8,A.II.1.,P,Ážio +UD,9,A.II.2.,P,Kapitálové fondy +UD,10,A.II.2.1.,P,Ostatní kapitálové fondy +UD,11,A.II.2.2.,P,Oceňovací rozdíly z přecenění majetku a závazků (+/-) +UD,12,A.II.2.3.,P,Oceňovací rozdíly z přecenění při přeměnách obchodních korporací (+/-) +UD,13,A.II.2.4.,P,Rozdíly z přeměn obchodních korporací (+/-) +UD,14,A.II.2.5.,P,Rozdíly z ocenění při přeměnách obchodních korporací (+/-) +UD,15,A.III.,PZ,Fondy ze zisku +UD,16,A.III.1.,P,Ostatní rezervní fondy +UD,17,A.III.2.,P,Statutární a ostatní fondy +UD,18,A.IV.,PZ,Výsledek hospodaření minulých let (+/-) +UD,19,A.IV.1.,P,Nerozdělený zisk nebo neuhrazená ztráta minulých let (+/-) +UD,21,A.IV.2.,P,Jiný výsledek hospodaření minulých let (+/-) +UD,22,A.V,PZ,Výsledek hospodaření běžného účetního období (+/-) +UD,23,A.VI.,PZ,Rozhodnuto o zálohové výplatě podílu na zisku (-) +UD,24,B.+C.,PZM,Cizí zdroje +UD,25,B.,PZM,Rezervy +UD,26,B.1.,P,Rezerva na důchody a podobné závazky +UD,27,B.2.,P,Rezerva na daň z příjmů +UD,28,B.3.,P,Rezervy podle zvláštních právních předpisů +UD,29,B.4.,P,Ostatní rezervy +UD,30,C.,PZM,Závazky +UD,31,C.I.,PZ,Dlouhodobé závazky +UD,32,C.I.1.,P,Vydané dluhopisy +UD,33,C.I.1.1.,P,Vyměnitelné dluhopisy +UD,34,C.I.1.2.,P,Ostatní dluhopisy +UD,35,C.I.2.,P,Závazky k úvěrovým institucím +UD,36,C.I.3.,P,Dlouhodobé přijaté zálohy +UD,37,C.I.4.,P,Závazky z obchodních vztahů +UD,38,C.I.5.,P,Dlouhodobé směnky k úhradě +UD,39,C.I.6.,P,Závazky - ovládaná nebo ovládající osoba +UD,40,C.I.7.,P,Závazky - podstatný vliv +UD,41,C.I.8.,P,Odložený daňový závazek +UD,42,C.I.9.,P,Závazky - ostatní +UD,43,C.I.9.1.,P,Závazky ke společníkům +UD,44,C.I.9.2.,P,Dohadné účty pasivní +UD,45,C.I.9.3.,P,Jiné závazky +UD,46,C.II.,PZ,Krátkodobé závazky +UD,47,C.II.1.,P,Vydané dluhopisy +UD,48,C.II.1.1.,P,Vyměnitelné dluhopisy +UD,49,C.II.1.2.,P,Ostatní dluhopisy +UD,50,C.II.2.,P,Závazky k úvěrovým institucím +UD,51,C.II.3.,P,Krátkodobé přijaté zálohy +UD,52,C.II.4.,P,Závazky z obchodních vztahů +UD,53,C.II.5.,P,Krátkodobé směnky k úhradě +UD,54,C.II.6.,P,Závazky - ovládaná nebo ovládající osoba +UD,55,C.II.7.,P,Závazky - podstatný vliv +UD,56,C.II.8.,P,Závazky ostatní +UD,57,C.II.8.1.,P,Závazky ke společníkům +UD,58,C.II.8.2.,P,Krátkodobé finanční výpomoci +UD,59,C.II.8.3.,P,Závazky k zaměstnancům +UD,60,C.II.8.4.,P,Závazky ze sociálního zabezpečení a zdravotního pojištění +UD,61,C.II.8.5.,P,Stát - daňové závazky a dotace +UD,62,C.II.8.6.,P,Dohadné účty pasivní +UD,63,C.II.8.7.,P,Jiné závazky +UD,67,C.III.,PZ,Časové rozlišení pasiv +UD,68,C.III.1.,P,Výdaje příštích období +UD,69,C.III.2.,P,Výnosy příštích období +UD,64,D.,PZM,Časové rozlišení pasiv +UD,65,D.1.,P,Výdaje příštích období +UD,66,D.2.,P,Výnosy příštích období +UB,1,I.,PZ,Tržby z prodeje výrobků a služeb +UB,2,II.,PZ,Tržby za prodej zboží +UB,3,A.,PZ,Výkonová spotřeba +UB,4,A.1,P,Náklady vynaložené na prodané zboží +UB,5,A.2,P,Spotřeba materiálu a energie +UB,6,A.3,P,Služby +UB,7,B.,PZ,Změna stavu zásob vlastní činnosti (+/-) +UB,8,C.,PZ,Aktivace (-) +UB,9,D.,PZ,Osobní náklady +UB,10,D.1.,P,Mzdové náklady +UB,11,D.2,P,"Náklady na sociální zabezpečení, zdravotní pojištění a ostatní náklady" +UB,12,D.2.1.,P,Náklady na sociální zabezpečení a zdravotní pojištění +UB,13,D.2.2.,P,Ostatní náklady +UB,14,E.,PZ,Úpravy hodnot v provozní oblasti +UB,15,E.1.,P,Úpravy hodnot dlouhodobého nehmotného a hmotného majetku +UB,16,E.1.1.,P,Úpravy hodnot dlouhodobého nehmotného a hmotného majetku - trvalé +UB,17,E.1.2.,P,Úpravy hodnot dlouhodobého nehmotného a hmotného majetku - dočasné +UB,18,E.2.,P,Úpravy hodnot zásob +UB,19,E.3.,P,Úpravy hodnot pohledávek +UB,20,III.,PZ,Ostatní provozní výnosy +UB,21,III.1.,P,Tržby z prodaného dlouhodobého majetku +UB,22,III.2.,P,Tržby z prodaného materiálu +UB,23,III.3.,P,Jiné provozní výnosy +UB,24,F.,PZ,Ostatní provozní náklady +UB,25,F.1.,P,Zůstatková cena prodaného dlouhodobého majetku +UB,26,F.2.,P,Prodaný materiál +UB,27,F.3.,P,Daně a poplatky +UB,28,F.4.,P,Rezervy v provozní oblasti a komplexní náklady příštích období +UB,29,F.5.,P,Jiné provozní náklady +UB,30,*,PZ,Provozní výsledek hospodaření (+/-) +UB,31,IV.,PZ,Výnosy z dlouhodobého finančního majetku - podíly +UB,32,IV.1.,P,Výnosy z podílů - ovládaná nebo ovládající osoba +UB,33,IV.2.,P,Ostatní výnosy z podílů +UB,34,G.,PZ,Náklady vynaložené na prodané podíly +UB,35,V.,PZ,Výnosy z ostatního dlouhodobého finančního majetku +UB,36,V.1.,P,Výnosy z ostatního dlouhodobého finančního majetku - ovládaná nebo ovládající osoba +UB,37,V.2.,P,Ostatní výnosy z ostatního dlouhodobého finančního majetku +UB,38,H.,PZ,Náklady související s ostatním dlouhodobým finančním majetkem +UB,39,VI.,PZ,Výnosové úroky a podobné výnosy +UB,40,VI.1.,P,Výnosové úroky a podobné výnosy - ovládaná nebo ovládající osoba +UB,41,VI.2.,P,Ostatní výnosové úroky a podobné výnosy +UB,42,I.,PZ,Úpravy hodnot a rezervy ve finanční oblasti +UB,43,J.,PZ,Nákladové úroky a podobné náklady +UB,44,J.1.,P,Nákladové úroky a podobné náklady - ovládaná nebo ovládající osoba +UB,45,J.2.,P,Ostatní nákladové úroky a podobné náklady +UB,46,VII.,PZ,Ostatní finanční výnosy +UB,47,K.,PZ,Ostatní finanční náklady +UB,48,*,PZ,Finanční výsledek hospodaření (+/-) +UB,49,**,PZ,Výsledek hospodaření před zdaněním (+/-) +UB,50,L.,PZ,Daň z příjmů +UB,51,L.1.,P,Daň z příjmů splatná +UB,52,L.2.,P,Daň z příjmů odložená (+/-) +UB,53,**,PZ,Výsledek hospodaření po zdanění (+/-) +UB,54,M.,PZ,Převod podílu na výsledku hospodaření společníkům (+/-) +UB,55,***,PZ,Výsledek hospodaření za účetní období (+/-) +UB,56,*,PZ,Čistý obrat za účetní období diff --git a/l10n_cz_dppo/i18n/l10n_cz_dppo.pot b/l10n_cz_dppo/i18n/l10n_cz_dppo.pot new file mode 100644 index 00000000..70aae695 --- /dev/null +++ b/l10n_cz_dppo/i18n/l10n_cz_dppo.pot @@ -0,0 +1,157 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_cz_dppo +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-09-28 11:23+0000\n" +"PO-Revision-Date: 2026-09-28 11:23+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "%(statement)s belongs to another company." +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "" +"%(statement)s covers %(from)s – %(to)s, but the return covers %(rfrom)s – " +"%(rto)s. The výkazy filed with a return are those of its own zdaňovací " +"období." +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "" +"%(statement)s has no row %(codes)s, which the DPPDP9 výkaz is filled from. " +"It was computed on a version that is not the l10n_cz_fs Rozvaha / VZZ; " +"filing it would report those rows as zero." +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "" +"%(statement)s has not been computed. Compute it (and review it) before " +"filing it with the return." +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "%(statement)s is not a %(kind)s." +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "" +"%(statement)s was computed on a Rozvaha version whose rows state no korekce " +"(oprávky, opravné položky) separately, so the DPPDP9 aktiva cannot be filed " +"in brutto / korekce / netto. Move the correction accounts of B.I.–C.III. " +"into the correction formula of their rows and recompute." +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model.fields,field_description:l10n_cz_dppo.field_cssk_income_tax_return__l10n_cz_country_code +msgid "Country Code" +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model.fields,field_description:l10n_cz_dppo.field_cssk_income_tax_return__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "" +"Exported without the Rozvaha and Výkaz zisku a ztráty: no statements are " +"linked. Attach the účetní závěrka to the filing as an E-příloha, or link the" +" statements and export again." +msgstr "" + +#. module: l10n_cz_dppo +#: model_terms:ir.ui.view,arch_db:l10n_cz_dppo.cssk_income_tax_return_view_form_cz_vykazy +msgid "Financial statements filed with the return" +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model.fields,field_description:l10n_cz_dppo.field_cssk_income_tax_return__id +msgid "ID" +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model,name:l10n_cz_dppo.model_cssk_income_tax_return +msgid "Income Tax Return" +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "" +"Link both the Rozvaha and the Výkaz zisku a ztráty, or neither. The DPPDP9 " +"files the účetní závěrka as a whole (uv_rozsah covers both výkazy); one " +"without the other is an incomplete závěrka." +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model.fields,field_description:l10n_cz_dppo.field_cssk_income_tax_return__l10n_cz_fs_balance_sheet_id +msgid "Rozvaha" +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "" +"Rozvaha row %(row)s (%(designation)s) has a negative korekce of %(value)s " +"thousand: its oprávky / opravné položky carry a debit balance. Correct the " +"booking; DPPDP9 files korekce without a sign." +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model.fields,help:l10n_cz_dppo.field_cssk_income_tax_return__l10n_cz_country_code +msgid "" +"The ISO country code in two chars. \n" +"You can use this field for quick search." +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model.fields,help:l10n_cz_dppo.field_cssk_income_tax_return__l10n_cz_fs_balance_sheet_id +msgid "" +"The computed Rozvaha filed with this return as DPPDP9 VetaUA (aktiva) and " +"VetaUD (pasiva). Link it together with the Výkaz zisku a ztráty, or leave " +"both empty to file the return without the výkazy (the závěrka then goes as " +"an E-příloha)." +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model.fields,help:l10n_cz_dppo.field_cssk_income_tax_return__l10n_cz_fs_profit_loss_id +msgid "" +"The computed Výkaz zisku a ztráty (druhové členění) filed with this return " +"as DPPDP9 VetaUB." +msgstr "" + +#. module: l10n_cz_dppo +#. odoo-python +#: code:addons/l10n_cz_dppo/models/cssk_income_tax.py:0 +msgid "" +"The výkazy are kept in %(currency)s, but DPPDP9 accepts only CZK, EUR, USD " +"or GBP as the accounting currency (uv_mena, § 24a zákona o účetnictví)." +msgstr "" + +#. module: l10n_cz_dppo +#: model:ir.model.fields,field_description:l10n_cz_dppo.field_cssk_income_tax_return__l10n_cz_fs_profit_loss_id +msgid "Výkaz zisku a ztráty" +msgstr "" diff --git a/l10n_cz_dppo/migrations/19.0.1.2.0/post-migration.py b/l10n_cz_dppo/migrations/19.0.1.2.0/post-migration.py new file mode 100644 index 00000000..c704d76a --- /dev/null +++ b/l10n_cz_dppo/migrations/19.0.1.2.0/post-migration.py @@ -0,0 +1,20 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""E is "dodatečné-opravné", not "dodatečné". + +The DPPDP9 filing type exporting ``dapdpp_forma="E"`` was labelled plain +"Dodatečné", and D did not exist. The data file creates D; the existing E +record is ``noupdate``, so its label is corrected here. Returns already +exported are not touched. +""" + + +def migrate(cr, version): + cr.execute(""" + UPDATE cssk_income_tax_type t + SET name = 'Dodatečné-opravné' + FROM ir_model_data d + WHERE d.model = 'cssk.income.tax.type' + AND d.module = 'l10n_cz_dppo' AND d.name = 'cz_dppo_type_E' + AND d.res_id = t.id + """) diff --git a/l10n_cz_dppo/models/__init__.py b/l10n_cz_dppo/models/__init__.py new file mode 100644 index 00000000..2e6e3dd8 --- /dev/null +++ b/l10n_cz_dppo/models/__init__.py @@ -0,0 +1,2 @@ +from . import dppdp9_vykazy +from . import cssk_income_tax diff --git a/l10n_cz_dppo/models/cssk_income_tax.py b/l10n_cz_dppo/models/cssk_income_tax.py new file mode 100644 index 00000000..41307a06 --- /dev/null +++ b/l10n_cz_dppo/models/cssk_income_tax.py @@ -0,0 +1,337 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""DPPDP9: file the Rozvaha and the Výkaz zisku a ztráty inside the return. + +The return points at the two ``l10n_cz_fs`` statements the accountant has +already computed (and possibly corrected by override) for the same period, and +the export reads their rows. It does not compute a závěrka of its own: that +would be a second account → row mapping next to the one in ``l10n_cz_fs``, +and two mappings of one form drift apart without either looking wrong. + +Linking is explicit rather than a search for "the" statement of the period, +because a period can hold a draft, a filed one and an opravná závěrka at once, +and which of them goes to the finanční úřad is the accountant's decision. +""" + +from decimal import ROUND_HALF_UP, Decimal + +from dateutil.relativedelta import relativedelta + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.tools.safe_eval import safe_eval + +from . import dppdp9_vykazy as vykazy + +#: ``VetaD/@uv_mena`` accepts these and nothing else (DPPDP9 popis struktury). +_UV_CURRENCIES = ("CZK", "EUR", "USD", "GBP") + + +def _thousands(value): + """Whole thousands, half away from zero — the výkazy are filed "v tis." + (``VetaD/@uz_rad = T``). Decimal, because a float ``round`` of 2500.0 / + 1000 goes to the even neighbour and files 2 instead of 3.""" + return int((Decimal(repr(round(value or 0.0, 2))) / 1000).quantize( + Decimal(1), rounding=ROUND_HALF_UP)) + + +#: VetaD/typ_dapdpp — druh daňového přiznání, from the XSD's code list. +#: A is the ordinary return for the tax period; the others are the returns a +#: liquidation, insolvency, merger or change of period calls for. +DPPDP9_RETURN_KINDS = [ + ("A", "A – za zdaňovací období"), + ("B", "B – při vstupu do likvidace"), + ("C", "C – v průběhu likvidace"), + ("D", "D – před zánikem bez likvidace"), + ("G", "G – ukončení činnosti v rámci privatizace"), + ("H", "H – před návrhem na použití likvidačního zůstatku"), + ("J", "J – před rozhodným dnem fúze / rozdělení"), + ("K", "K – před změnou právní formy"), + ("L", "L – před změnou zdaňovacího období"), + ("M", "M – od vzniku poplatníka"), + ("O", "O – před změnou daňového rezidentství"), + ("P", "P – k účinnosti rozhodnutí o úpadku"), + ("R", "R – v průběhu insolvenčního řízení"), + ("T", "T – ke dni konečné zprávy"), + ("V", "V – při ukončení svěřenského fondu"), + ("Y", "Y – vypořádání majetku svěřenského fondu"), + ("Z", "Z – před zánikem svěřenského fondu"), +] + +#: VetaD/typ_zo — the letter of § 21a ZDP naming the tax period. +DPPDP9_PERIOD_KINDS = [ + ("A", "a) kalendářní rok"), + ("B", "b) hospodářský rok"), + ("C", "c) období od rozhodného dne fúze / rozdělení"), + ("D", "d) účetní období delší než 12 měsíců"), +] + +#: VetaD/typ_popldpp — typ poplatníka. +DPPDP9_TAXPAYER_KINDS = [ + ("1", "1 – ostatní"), + ("2", "2 – daňový nerezident (§ 17 odst. 4)"), + ("3", "3 – veřejně prospěšný poplatník (§ 17a)"), + ("4", "4 – investiční fond"), + ("5", "5 – investiční společnost"), + ("6", "6 – instituce penzijního pojištění / penzijní společnost"), + ("7", "7 – základní investiční fond po část období (§ 20a)"), + ("8", "8 – investiční pobídka dle usnesení vlády"), + ("9", "9 – investiční pobídka, sleva dle § 35a"), + ("0", "0 – investiční pobídka, sleva dle § 35b"), +] + + +class CSSKIncomeTaxReturn(models.Model): + _inherit = "cssk.income.tax.return" + + # VetaD of DPPDP9. typ_dapdpp was hard-coded "B" — a return ON ENTERING + # LIQUIDATION — and typ_zo "1", which is no letter of § 21a at all; both + # are now chosen, with the ordinary case as the default. + l10n_cz_return_kind = fields.Selection( + DPPDP9_RETURN_KINDS, string="Typ daňového přiznání", default="A", + help="VetaD/typ_dapdpp. A for the ordinary return for the tax period.") + l10n_cz_period_kind = fields.Selection( + DPPDP9_PERIOD_KINDS, string="Zdaňovací období (§ 21a)", + compute="_compute_l10n_cz_period_kind", store=True, readonly=False, + help="VetaD/typ_zo, derived from the period: a calendar year is a), " + "another twelve-month year b), anything longer d). Set c) by hand for " + "the period from a merger's decisive day.") + l10n_cz_taxpayer_kind = fields.Selection( + DPPDP9_TAXPAYER_KINDS, string="Typ poplatníka", default="1", + help="VetaD/typ_popldpp.") + l10n_cz_discovery_date = fields.Date( + string="Důvody zjištěny dne", + help="VetaD/d_zjist — required for a dodatečné přiznání.") + l10n_cz_filing_code = fields.Char(related="statement_type_id.fa_xml_value") + + @api.depends("date_from", "date_to") + def _compute_l10n_cz_period_kind(self): + for rec in self: + start, end = rec.date_from, rec.date_to + if not start or not end: + rec.l10n_cz_period_kind = "A" + continue + twelve = start + relativedelta(years=1, days=-1) + if (start.month, start.day) == (1, 1) and end == twelve: + rec.l10n_cz_period_kind = "A" + elif end == twelve: + rec.l10n_cz_period_kind = "B" + elif end > twelve: + rec.l10n_cz_period_kind = "D" + else: + # A shorter period (the first one, a liquidation) takes the + # letter of the period its last day falls in; a calendar year + # unless the company keeps a hospodářský rok. + rec.l10n_cz_period_kind = rec.l10n_cz_period_kind or "A" + + l10n_cz_fs_balance_sheet_id = fields.Many2one( + "cssk.fs.statement", string="Rozvaha", + domain="[('company_id', '=', company_id)," + " ('statement_kind', '=', 'balance_sheet')," + " ('date_to', '=', date_to)]", + help="The computed Rozvaha filed with this return as DPPDP9 VetaUA " + "(aktiva) and VetaUD (pasiva). Link it together with the Výkaz " + "zisku a ztráty, or leave both empty to file the return without " + "the výkazy (the závěrka then goes as an E-příloha).") + l10n_cz_fs_profit_loss_id = fields.Many2one( + "cssk.fs.statement", string="Výkaz zisku a ztráty", + domain="[('company_id', '=', company_id)," + " ('statement_kind', '=', 'profit_loss')," + " ('date_from', '=', date_from), ('date_to', '=', date_to)]", + help="The computed Výkaz zisku a ztráty (druhové členění) filed " + "with this return as DPPDP9 VetaUB.") + + #: For the form: the výkaz fields mean nothing on a Slovak DPPO. + l10n_cz_country_code = fields.Char(related="version_id.country_id.code") + + # ------------------------------------------------------------------ + def _l10n_cz_is_dppdp9(self): + self.ensure_one() + return (self.version_id.country_id.code or "").upper() == "CZ" + + def _l10n_cz_vykaz_statements(self): + """``{kind: statement}`` for the linked výkazy, or ``{}``.""" + self.ensure_one() + linked = { + "balance_sheet": self.l10n_cz_fs_balance_sheet_id, + "profit_loss": self.l10n_cz_fs_profit_loss_id, + } + return {k: st for k, st in linked.items() if st} + + def _cssk_preflight_export(self): + res = super()._cssk_preflight_export() + for rec in self: + if rec._l10n_cz_is_dppdp9(): + rec._l10n_cz_check_vykazy() + if rec.l10n_cz_filing_code in ("D", "E") \ + and not rec.l10n_cz_discovery_date: + raise UserError(_( + "A dodatečné přiznání needs the date its reasons were " + "found (Důvody zjištěny dne, d_zjist).")) + return res + + def _l10n_cz_check_vykazy(self): + """Refuse a výkaz that would be filed wrong rather than file it. + + Everything here fails as a named error because the XSD cannot catch + any of it: a Rozvaha of another date, a half-linked pair or a row + missing from the statement all validate, and EPO reads a missing row + as zero. + """ + self.ensure_one() + statements = self._l10n_cz_vykaz_statements() + if not statements: + return + if len(statements) != 2: + raise UserError(_( + "Link both the Rozvaha and the Výkaz zisku a ztráty, or " + "neither. The DPPDP9 files the účetní závěrka as a whole " + "(uv_rozsah covers both výkazy); one without the other is " + "an incomplete závěrka.")) + currency = self.company_id.currency_id.name + if currency not in _UV_CURRENCIES: + raise UserError(_( + "The výkazy are kept in %(currency)s, but DPPDP9 accepts only " + "CZK, EUR, USD or GBP as the accounting currency (uv_mena, " + "§ 24a zákona o účetnictví).", currency=currency)) + for kind, st in statements.items(): + label = st.display_name + if st.company_id != self.company_id: + raise UserError(_( + "%(statement)s belongs to another company.", + statement=label)) + if st.statement_kind != kind: + raise UserError(_( + "%(statement)s is not a %(kind)s.", + statement=label, kind=kind.replace("_", " "))) + if st.date_to != self.date_to or ( + kind == "profit_loss" and st.date_from != self.date_from): + raise UserError(_( + "%(statement)s covers %(from)s – %(to)s, but the return " + "covers %(rfrom)s – %(rto)s. The výkazy filed with a " + "return are those of its own zdaňovací období.", + statement=label, **{ + "from": st.date_from, "to": st.date_to, + "rfrom": self.date_from, "rto": self.date_to})) + if st.state in ("draft", "cancelled") or not st.line_ids: + raise UserError(_( + "%(statement)s has not been computed. Compute it (and " + "review it) before filing it with the return.", + statement=label)) + missing = vykazy.fs_codes(kind) - set(st.line_ids.mapped("code")) + if missing: + raise UserError(_( + "%(statement)s has no row %(codes)s, which the DPPDP9 " + "výkaz is filled from. It was computed on a version that " + "is not the l10n_cz_fs Rozvaha / VZZ; filing it would " + "report those rows as zero.", + statement=label, codes=", ".join(sorted(missing)))) + if kind == "balance_sheet" and not any( + st.version_id.line_def_ids.mapped( + "account_formula_correction")): + # The l10n_cz_fs 19.0.1.1.0 migration leaves a row an + # accountant has edited alone; if it left ALL of them, the + # sheet has no korekce column and VetaUA would file + # brutto = netto with every oprávka silently dropped. + raise UserError(_( + "%(statement)s was computed on a Rozvaha version whose " + "rows state no korekce (oprávky, opravné položky) " + "separately, so the DPPDP9 aktiva cannot be filed in " + "brutto / korekce / netto. Move the correction accounts " + "of B.I.–C.III. into the correction formula of their " + "rows and recompute.", statement=label)) + + # ------------------------------------------------------------------ + def _l10n_cz_vykaz_rows(self): + """``{věta: [attribute dict, …]}`` for the linked výkazy. + + Brutto is derived as netto + korekce AFTER rounding, so the filed + columns satisfy brutto − korekce = netto exactly, as EPO checks, + even on a row whose netto the accountant overrode. + """ + self.ensure_one() + statements = self._l10n_cz_vykaz_statements() + if len(statements) != 2: + return {} + values = {} + for kind, st in statements.items(): + current = {ln.code: ln.current_value for ln in st.line_ids} + prior = {ln.code: ln.prior_value for ln in st.line_ids} + values[kind] = (current, prior, self._l10n_cz_corrections(st)) + + out = {} + for (veta, row), (designation, codes) in sorted( + vykazy.ROWS.items(), key=lambda it: (it[0][0], it[0][1])): + current, prior, correction = values[vykazy.TABLES[veta]] + netto = _thousands(sum(current.get(c, 0.0) for c in codes)) + netto_min = _thousands(sum(prior.get(c, 0.0) for c in codes)) + if veta == "UA": + korekce = _thousands(sum(correction.get(c, 0.0) for c in codes)) + if korekce < 0: + # The form states korekce unsigned; a negative one is a + # debit balance on an oprávka / opravná položka, which is + # a booking to fix, not a sign to drop. + raise UserError(_( + "Rozvaha row %(row)s (%(designation)s) has a negative " + "korekce of %(value)s thousand: its oprávky / " + "opravné položky carry a debit balance. Correct the " + "booking; DPPDP9 files korekce without a sign.", + row=row, designation=designation, value=korekce)) + cells = {"kc_brutto": netto + korekce, "kc_korekce": korekce, + "kc_netto": netto, "kc_netto_min": netto_min} + else: + cells = {"kc_sled": netto, "kc_min": netto_min} + if not any(cells.values()): + continue + attrs = {"c_radku": str(row)} + attrs.update({k: str(v) for k, v in cells.items() if v}) + out.setdefault(veta, []).append(attrs) + return out + + def _l10n_cz_corrections(self, statement): + """``{code: korekce}`` for every row of ``statement``. + + A leaf reports the correction column it computed; an aggregate is + its own formula over its children's corrections — the formulas that + sum netto rows are linear, so they sum korekce rows too.""" + corrections = {} + stored = {ln.code: ln.correction_value for ln in statement.line_ids} + for ldef in statement._cssk_eval_order(): + if ldef.kind == "aggregate": + corrections[ldef.code] = float(safe_eval( + ldef.aggregate_formula or "0", dict(corrections)) or 0.0) + elif ldef.account_formula_correction: + corrections[ldef.code] = stored.get(ldef.code, 0.0) + else: + corrections[ldef.code] = 0.0 + return corrections + + def _cssk_render_context(self): + ctx = super()._cssk_render_context() + linked = (self._l10n_cz_is_dppdp9() + and len(self._l10n_cz_vykaz_statements()) == 2) + rows = self._l10n_cz_vykaz_rows() if linked else {} + ctx["vykazy"] = rows + # Keyed on the LINK, not on the rows: a linked závěrka that rounds to + # nothing is still a filed závěrka, and must not read as "none". + ctx["vykazy_header"] = { + "uv_vyhl": vykazy.DECREE, + "uv_rozsah": vykazy.RANGE, + "uv_mena": self.company_id.currency_id.name, + "uz_rad": "T", + "d_uv": self.date_to.strftime("%d.%m.%Y"), + } if linked else {} + return ctx + + def action_export_xml(self): + res = super().action_export_xml() + for rec in self: + if rec._l10n_cz_is_dppdp9() and not rec._l10n_cz_vykaz_statements(): + # Legal, and worth saying: a return without its výkazy is + # complete only if the závěrka goes as an E-příloha. + rec.message_post(body=_( + "Exported without the Rozvaha and Výkaz zisku a ztráty: " + "no statements are linked. Attach the účetní závěrka to " + "the filing as an E-příloha, or link the statements and " + "export again.")) + return res diff --git a/l10n_cz_dppo/models/dppdp9_vykazy.py b/l10n_cz_dppo/models/dppdp9_vykazy.py new file mode 100644 index 00000000..6cd3c6d5 --- /dev/null +++ b/l10n_cz_dppo/models/dppdp9_vykazy.py @@ -0,0 +1,182 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""DPPDP9 výkazy: which ``l10n_cz_fs`` row feeds which EPO row. + +The účetní závěrka travels INSIDE the DPPDP9 písemnost, as repeatable records +of ``c_radku`` + column attributes (see ``data/dppdp9_epo2.xsd``): + +========= ===================================================== ========================================== +Věta Výkaz (vyhláška 500/2002 Sb., podnikatelé) Columns +========= ===================================================== ========================================== +VetaUA Rozvaha — tabulka aktiv kc_brutto, kc_korekce, kc_netto, kc_netto_min +VetaUD Rozvaha — tabulka pasiv kc_sled, kc_min +VetaUB Výkaz zisku a ztráty — druhové členění kc_sled, kc_min +========= ===================================================== ========================================== + +The XSD says nothing about which ``c_radku`` exist: it is a bare 3-digit +decimal, checked by EPO against an application číselník that the Finanční +správa publishes only as a web page ("Informace o číslech řádků pro věty +účetních výkazů"). That číselník is vendored as ``data/uv_radky_500.csv`` — +row number, statutory designation, the rozsahy (P / Z / M) the row belongs +to, and the label — and pinned in ``data/SCHEMA_VERSION``. + +**The figures are not mapped here a second time.** Every EPO row names the +``l10n_cz_fs`` Rozvaha / VZZ row with the same statutory designation (B.I., +C.II., A.V, III., ** …), and the value is that row's computed figure. One +account → row mapping (in ``l10n_cz_fs``), one place to get it wrong. + +**Rozsah.** ``l10n_cz_fs`` carries the statement at the depth of the +ZKRÁCENÝ rozsah (vyhláška 500/2002 Sb., příloha č. 1 and 2, § 18 odst. 3 +zákona o účetnictví): the Rozvaha by its groups (B.I., C.II., …), not the +numbered lines below them. So the export files ``uv_rozsah = "Z"``, and every +number that the číselník marks for the zkrácený rozsah is below either MAPPED +or listed in :data:`NOT_APPLICABLE` with the reason — the test +``test_every_vykaz_row_is_mapped_or_declared`` enforces it, because an +unmapped row is not an error anywhere else: EPO reads a missing row as zero. +Rows of the plný rozsah only are not filed at all; an entity obliged to file +the plný rozsah (střední a velké ÚJ, or any ÚJ with a statutory audit) cannot +use this export for its závěrka and attaches it as an E-příloha instead. +""" + +#: The rozsah this export files (``VetaD/@uv_rozsah``). +RANGE = "Z" + +#: ``VetaD/@uv_vyhl`` — vyhláška 500/2002 Sb., podnikatelé. +DECREE = "500" + +#: The výkaz tables filed, and which ``cssk.fs.statement`` kind feeds each. +TABLES = { + "UA": "balance_sheet", + "UD": "balance_sheet", + "UB": "profit_loss", +} + +#: (věta, c_radku) -> (statutory designation, l10n_cz_fs row codes summed). +#: The designation repeats the číselník's own so the test can prove each +#: pairing, rather than trusting a row number typed from memory. +ROWS = { + # --- Rozvaha, aktiva (VetaUA) ------------------------------------------ + ("UA", 1): ("", ("AKTIVA",)), + ("UA", 2): ("A.", ("A_ZK",)), + ("UA", 3): ("B.", ("B",)), + ("UA", 4): ("B.I.", ("BI",)), + ("UA", 14): ("B.II.", ("BII",)), + ("UA", 27): ("B.III.", ("BIII",)), + ("UA", 37): ("C.", ("C",)), + ("UA", 38): ("C.I.", ("CI",)), + ("UA", 46): ("C.II.", ("CII",)), + # All of C.II. is krátkodobé: see NOT_APPLICABLE[("UA", 47)]. + ("UA", 57): ("C.II.2.", ("CII",)), + ("UA", 68): ("C.III.", ("CIII",)), + ("UA", 71): ("C.IV.", ("CIV",)), + ("UA", 74): ("D.", ("D_A",)), + # --- Rozvaha, pasiva (VetaUD) ------------------------------------------ + ("UD", 1): ("", ("PASIVA",)), + ("UD", 2): ("A.", ("VK",)), + ("UD", 3): ("A.I.", ("AI",)), + ("UD", 7): ("A.II.", ("AII",)), + ("UD", 15): ("A.III.", ("AIII",)), + ("UD", 18): ("A.IV.", ("AIV",)), + ("UD", 22): ("A.V", ("AV",)), + ("UD", 23): ("A.VI.", ("AVI",)), + ("UD", 24): ("B.+C.", ("BREZ", "CZAV")), + ("UD", 25): ("B.", ("BREZ",)), + ("UD", 30): ("C.", ("CZAV",)), + ("UD", 31): ("C.I.", ("CIP",)), + ("UD", 46): ("C.II.", ("CIIP",)), + ("UD", 64): ("D.", ("D_P",)), + # --- Výkaz zisku a ztráty, druhové členění (VetaUB) -------------------- + ("UB", 1): ("I.", ("I",)), + ("UB", 2): ("II.", ("II",)), + ("UB", 3): ("A.", ("A",)), + ("UB", 7): ("B.", ("Bzm",)), + ("UB", 8): ("C.", ("Cakt",)), + ("UB", 9): ("D.", ("D",)), + ("UB", 14): ("E.", ("E",)), + ("UB", 20): ("III.", ("III",)), + ("UB", 24): ("F.", ("F",)), + ("UB", 30): ("*", ("PROV",)), + ("UB", 31): ("IV.", ("IV",)), + ("UB", 34): ("G.", ("G",)), + ("UB", 38): ("H.", ("Hfin",)), + ("UB", 39): ("VI.", ("VI",)), + ("UB", 42): ("I.", ("Ifin",)), + ("UB", 43): ("J.", ("J",)), + ("UB", 46): ("VII.", ("VII",)), + ("UB", 47): ("K.", ("K",)), + ("UB", 48): ("*", ("FIN",)), + ("UB", 49): ("**", ("VHB",)), + ("UB", 50): ("L.", ("L",)), + ("UB", 53): ("**", ("VHA",)), + ("UB", 54): ("M.", ("M",)), + ("UB", 55): ("***", ("VHU",)), + ("UB", 56): ("*", ("COBR",)), +} + +#: Zkrácený-rozsah rows deliberately NOT filed, and why. Each one reads as zero +#: on the filed return, so each needs a reason a reviewer can check. +NOT_APPLICABLE = { + ("UA", 47): ( + "C.II.1. Dlouhodobé pohledávky: the směrná účtová osnova has no " + "long-term receivable account, and l10n_cz_fs files every class-3 " + "receivable under C.II. by the chart convention its README states, " + "so all of it is C.II.2. krátkodobé. An entity with receivables due " + "after more than a year must reclassify them — confirm with the " + "accountant."), + ("UA", 78): ( + "C.II.3. Časové rozlišení aktiv: the alternative placement of " + "prepayments inside C.II. l10n_cz_fs presents them as D. Časové " + "rozlišení aktiv (381, 382, 385), which is filed on row 74; filing " + "both would count them twice."), + ("UD", 67): ( + "C.III. Časové rozlišení pasiv: the alternative placement of accruals " + "inside C. Závazky. l10n_cz_fs presents them as D. Časové rozlišení " + "pasiv (383, 384), filed on row 64; filing both would count them " + "twice."), + ("UB", 35): ( + "V. Výnosy z ostatního dlouhodobého finančního majetku: both IV. and " + "V. are booked on účet 665 and l10n_cz_fs does not split it, so the " + "whole of 665 is filed on IV. (row 31). Row 49 and the result are " + "unaffected; an entity with income from debt instruments held to " + "maturity must move it to V. by hand."), +} + +#: The other výkaz records of the DPPDP9 XSD (every element carrying a +#: ``c_radku``), and why none of them is filed by this export. +NOT_APPLICABLE_TABLES = { + "UE": "Výkaz zisku a ztráty — účelové členění (500/2002): l10n_cz_fs " + "builds the druhové členění, and EPO refuses both in one return.", + "UF": "Rozvaha — aktiva, vyhláška 501/2002 Sb. (banky).", + "UG": "Rozvaha — podrozvahové položky, vyhláška 501/2002 Sb. (banky).", + "UH": "Rozvaha — pasiva, vyhláška 501/2002 Sb. (banky).", + "UI": "Přehled o změnách VK, vyhláška 501/2002 Sb. (banky).", + "UJ": "Výkaz zisku a ztráty, vyhláška 501/2002 Sb. (banky).", + "UK": "Rozvaha — aktiva, vyhláška 502/2002 Sb. (pojišťovny).", + "UL": "Rozvaha — pasiva, vyhláška 502/2002 Sb. (pojišťovny).", + "UN": "Výkaz zisku a ztráty, vyhláška 502/2002 Sb. (pojišťovny).", + "UO": "Rozvaha — aktiva, vyhláška 503/2002 Sb. (zdravotní pojišťovny).", + "UP": "Rozvaha — pasiva, vyhláška 503/2002 Sb. (zdravotní pojišťovny).", + "UQ": "Výkaz zisku a ztráty, vyhláška 503/2002 Sb. (zdravotní " + "pojišťovny).", + "UU": "Přehled o změnách VK, vyhláška 503/2002 Sb. (zdravotní " + "pojišťovny).", + "UV": "Přehled o peněžních tocích, vyhláška 503/2002 Sb. (zdravotní " + "pojišťovny).", + "UR": "Rozvaha — aktiva, vyhláška 504/2002 Sb. (nepodnikatelské ÚJ).", + "US": "Rozvaha — pasiva, vyhláška 504/2002 Sb. (nepodnikatelské ÚJ).", + "UT": "Výkaz zisku a ztráty, vyhláška 504/2002 Sb. (nepodnikatelské ÚJ).", + "U1": "Přehled o příjmech a výdajích, vyhláška 325/2015 Sb. (jednoduché " + "účetnictví).", + "U2": "Přehled o majetku a závazcích, vyhláška 325/2015 Sb. (jednoduché " + "účetnictví).", +} + + +def fs_codes(kind): + """Every l10n_cz_fs row code the export reads from a statement of ``kind``.""" + return { + code + for (veta, _row), (_designation, codes) in ROWS.items() + if TABLES[veta] == kind + for code in codes + } diff --git a/l10n_cz_dppo/report/dppo_report.xml b/l10n_cz_dppo/report/dppo_report.xml new file mode 100644 index 00000000..59bf26c7 --- /dev/null +++ b/l10n_cz_dppo/report/dppo_report.xml @@ -0,0 +1,60 @@ + + + + + diff --git a/l10n_cz_dppo/static/description/icon.png b/l10n_cz_dppo/static/description/icon.png new file mode 100644 index 00000000..a19d7d91 Binary files /dev/null and b/l10n_cz_dppo/static/description/icon.png differ diff --git a/l10n_cz_dppo/static/description/index.html b/l10n_cz_dppo/static/description/index.html new file mode 100644 index 00000000..5307095e --- /dev/null +++ b/l10n_cz_dppo/static/description/index.html @@ -0,0 +1,55 @@ +
+
+

🌐 EN · CZ · SK

+

CZ Corporate Income Tax Return (DPPDP9)

+

P&L-driven ř.10, manual adjustments and the II. oddíl tax spine, exported as validated EPO DPPDP9 XML

+
+

+ The Czech country layer for the income-tax framework (l10n_cssk_income_tax_base). + It builds the corporate income-tax return (Přiznání k dani z příjmů právnických osob, DPPDP9): + ř.10 (výsledek hospodaření před zdaněním) is computed from the profit & loss statement, + the adjustments are entered by the accountant, and the II. oddíl tax-calculation spine + (základ daně → daň 21 % → daň po slevách) is arithmetic. The export produces the official EPO + Pisemnost/DPPDP9 XML, validated against the bundled official DPPDP9 XSD. +

+
+
+
+ +
+

Key features

+
+
    +
  • ř.10 from the P&L — výsledek hospodaření před zdaněním is computed from class 6 − class 5, excluding the income-tax accounts 591–599.
  • +
  • Manual adjustments — připočitatelné / odčitatelné položky (ř.20–170), the loss and § 34 deductions (ř.230 / ř.240) and slevy na dani (ř.300) are accountant-entered and aggregate into the spine.
  • +
  • II. oddíl tax spine — the ř.10 → ř.340 calculation is arithmetic: základ daně → rounding → daň 21 % (§ 21 odst. 1) → daň po slevách → celková daňová povinnost.
  • +
  • Official EPO mapping — each spine line maps to its official VetaO/@kc_ii* attribute; only non-zero spine lines are emitted.
  • +
  • Validated export — the EPO Pisemnost/DPPDP9 XML is validated against the bundled official DPPDP9 XSD before it is attached.
  • +
  • Statement types — Řádné (B), Opravné (O) and Dodatečné (E) are shipped as version data.
  • +
+
+
+ +
+

Usage

+
+

+ Create a corporate income-tax return for the company and period, choose the statement type + (Řádné / Opravné / Dodatečné) and compute the lines: ř.10 is derived from the P&L and the tax + spine is evaluated automatically. Enter the připočitatelné / odčitatelné adjustments, the loss and + § 34 deductions and the slevy na dani by hand, recompute, then export the EPO + Pisemnost/DPPDP9 XML — it is validated against the bundled DPPDP9 XSD before + it is attached. The appendix tables (odpisy, § 23e výpůjční náklady, zápočet daně ze zahraničí, + investiční fondy) and the účetní-závěrka attachments are out of scope; confirm the VetaD header codes + (typ_dapdpp / typ_zo / typ_popldpp) and the full adjustment set + with a Czech accountant before live filing. +

+
+
+ +
+
+ Published by Data Dance s.r.o. — www.datadance.eu, + License: Data Dance Proprietary License v1.0. +
+
diff --git a/l10n_cz_dppo/static/description/index_cz.html b/l10n_cz_dppo/static/description/index_cz.html new file mode 100644 index 00000000..47a68f6c --- /dev/null +++ b/l10n_cz_dppo/static/description/index_cz.html @@ -0,0 +1,55 @@ +
+
+

🌐 EN · CZ · SK

+

Přiznání k dani z příjmů právnických osob (DPPDP9)

+

ř.10 z výkazu zisku a ztráty, ruční úpravy a výpočtová páteř II. oddílu, export jako validované EPO XML DPPDP9

+
+

+ Česká národní vrstva pro rámec daně z příjmů (l10n_cssk_income_tax_base). + Sestavuje přiznání k dani z příjmů právnických osob (DPPDP9): ř.10 + (výsledek hospodaření před zdaněním) se počítá z výkazu zisku a ztráty, úpravy zadává + účetní a výpočtová páteř II. oddílu (základ daně → daň 21 % → daň po slevách) je aritmetická. + Export vytváří oficiální EPO XML Pisemnost/DPPDP9, validované proti + přibalenému oficiálnímu XSD DPPDP9. +

+
+
+
+ +
+

Klíčové funkce

+
+
    +
  • ř.10 z výkazu zisku a ztráty — výsledek hospodaření před zdaněním se počítá jako třída 6 − třída 5, bez účtů daně z příjmů 591–599.
  • +
  • Ruční úpravy — připočitatelné / odčitatelné položky (ř.20–170), odečet daňové ztráty a odečty dle § 34 (ř.230 / ř.240) a slevy na dani (ř.300) zadává účetní a vstupují do páteře.
  • +
  • Výpočtová páteř II. oddílu — výpočet ř.10 → ř.340 je aritmetický: základ daně → zaokrouhlení → daň 21 % (§ 21 odst. 1) → daň po slevách → celková daňová povinnost.
  • +
  • Mapování na oficiální EPO — každý řádek páteře se mapuje na svůj oficiální atribut VetaO/@kc_ii*; emitují se pouze nenulové řádky páteře.
  • +
  • Validovaný export — EPO XML Pisemnost/DPPDP9 se před připojením validuje proti přibalenému oficiálnímu XSD DPPDP9.
  • +
  • Typy přiznání — Řádné (B), Opravné (O) a Dodatečné (E) jsou dodány jako verzová data.
  • +
+
+
+ +
+

Použití

+
+

+ Vytvořte přiznání k dani z příjmů za společnost a období, zvolte typ přiznání + (Řádné / Opravné / Dodatečné) a spočítejte řádky: ř.10 se odvodí z výkazu zisku a ztráty a daňová + páteř se vyhodnotí automaticky. Připočitatelné / odčitatelné položky, odečet daňové ztráty a odečty + dle § 34 a slevy na dani zadejte ručně, přepočtěte a poté exportujte EPO XML + Pisemnost/DPPDP9 — před připojením se validuje proti přibalenému XSD DPPDP9. + Přílohové tabulky (odpisy, § 23e výpůjční náklady, zápočet daně ze zahraničí, investiční fondy) a + přílohy účetní závěrky jsou mimo rozsah; před ostrým podáním ověřte kódy hlavičky VetaD + (typ_dapdpp / typ_zo / typ_popldpp) a úplnou sadu úprav + s českým účetním. +

+
+
+ +
+
+ Vydává Data Dance s.r.o. — www.datadance.eu, + Licence: Data Dance Proprietary License v1.0. +
+
diff --git a/l10n_cz_dppo/static/description/index_sk.html b/l10n_cz_dppo/static/description/index_sk.html new file mode 100644 index 00000000..12bbe5db --- /dev/null +++ b/l10n_cz_dppo/static/description/index_sk.html @@ -0,0 +1,55 @@ +
+
+

🌐 EN · CZ · SK

+

Daňové priznanie k dani z príjmov právnických osôb (DPPO)

+

ř.10 z výkazu ziskov a strát, ručné úpravy a výpočtová chrbtica II. oddielu, export ako validované EPO XML DPPDP9

+
+

+ Česká národná vrstva pre rámec dane z príjmov (l10n_cssk_income_tax_base). + Zostavuje daňové priznanie k dani z príjmov právnických osôb (DPPDP9): ř.10 + (výsledok hospodárenia pred zdanením) sa počíta z výkazu ziskov a strát, úpravy zadáva + účtovník a výpočtová chrbtica II. oddielu (základ dane → daň 21 % → daň po úľavách) je aritmetická. + Export vytvára oficiálne EPO XML Pisemnost/DPPDP9, validované voči + priloženému oficiálnemu XSD DPPDP9. +

+
+
+
+ +
+

Kľúčové funkcie

+
+
    +
  • ř.10 z výkazu ziskov a strát — výsledok hospodárenia pred zdanením sa počíta ako trieda 6 − trieda 5, bez účtov dane z príjmov 591–599.
  • +
  • Ručné úpravy — pripočítateľné / odpočítateľné položky (ř.20–170), odpočet daňovej straty a odpočty podľa § 34 (ř.230 / ř.240) a úľavy na dani (ř.300) zadáva účtovník a vstupujú do chrbtice.
  • +
  • Výpočtová chrbtica II. oddielu — výpočet ř.10 → ř.340 je aritmetický: základ dane → zaokrúhlenie → daň 21 % (§ 21 ods. 1) → daň po úľavách → celková daňová povinnosť.
  • +
  • Mapovanie na oficiálne EPO — každý riadok chrbtice sa mapuje na svoj oficiálny atribút VetaO/@kc_ii*; emitujú sa iba nenulové riadky chrbtice.
  • +
  • Validovaný export — EPO XML Pisemnost/DPPDP9 sa pred priložením validuje voči priloženému oficiálnemu XSD DPPDP9.
  • +
  • Typy priznania — Řádné (B), Opravné (O) a Dodatečné (E) sú dodané ako verzové dáta.
  • +
+
+
+ +
+

Použitie

+
+

+ Vytvorte daňové priznanie k dani z príjmov za spoločnosť a obdobie, zvoľte typ priznania + (Řádné / Opravné / Dodatečné) a vypočítajte riadky: ř.10 sa odvodí z výkazu ziskov a strát a daňová + chrbtica sa vyhodnotí automaticky. Pripočítateľné / odpočítateľné položky, odpočet daňovej straty a + odpočty podľa § 34 a úľavy na dani zadajte ručne, prepočítajte a potom exportujte EPO XML + Pisemnost/DPPDP9 — pred priložením sa validuje voči priloženému XSD DPPDP9. + Prílohové tabuľky (odpisy, § 23e výpožičné náklady, zápočet dane zo zahraničia, investičné fondy) a + prílohy účtovnej závierky sú mimo rozsahu; pred ostrým podaním overte kódy hlavičky VetaD + (typ_dapdpp / typ_zo / typ_popldpp) a úplnú sadu úprav + s českým účtovníkom. +

+
+
+ +
+
+ Vydáva Data Dance s.r.o. — www.datadance.eu, + Licencia: Data Dance Proprietary License v1.0. +
+
diff --git a/l10n_cz_dppo/tests/__init__.py b/l10n_cz_dppo/tests/__init__.py new file mode 100644 index 00000000..a323ed4c --- /dev/null +++ b/l10n_cz_dppo/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_cz_dppo +from . import test_cz_dppo_vykazy diff --git a/l10n_cz_dppo/tests/test_cz_dppo.py b/l10n_cz_dppo/tests/test_cz_dppo.py new file mode 100644 index 00000000..2681f2bb --- /dev/null +++ b/l10n_cz_dppo/tests/test_cz_dppo.py @@ -0,0 +1,106 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 + +from lxml import etree + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestCzDppo(AccountTestInvoicingCommon): + @classmethod + @AccountTestInvoicingCommon.setup_country("cz") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.vat = "CZ25663585" + # EPO requires the destination tax office (c_ufo_cil). + # FÚ pro hl. m. Prahu — seeded by l10n_cz_statutory's hook without an + # xmlid (the old l10n_cssk_core.cz_ufo_* ids were removed in 1.2.0); + # look it up by its stable submission code instead. + cls.company.l10n_cssk_tax_authority_id = cls.env["cssk.tax.authority"].search( + [("country_code", "=", "CZ"), ("submission_code", "=", "451")], limit=1) + cls.version = cls.env.ref("l10n_cz_dppo.cz_dppo_version_2025") + cls.type_b = cls.env.ref("l10n_cz_dppo.cz_dppo_type_B") + cls.misc = cls.company_data["default_journal_misc"] + + def _acc(self, like): + return self.env["account.account"].search( + [("code", "=like", like), ("company_ids", "in", self.company.id)], + limit=1) + + def _return(self): + ret = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "statement_type_id": self.type_b.id, + "date_from": "2025-01-01", "date_to": "2025-12-31"}) + ret.action_compute_lines() + return ret + + def _v(self, ret, code): + return ret.line_ids.filtered(lambda l: l.code == code).value + + def test_profit_spine_and_export(self): + income, expense, bank = self._acc("602%"), self._acc("501%"), self._acc("221%") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.misc.id, "date": "2025-06-30", + "line_ids": [ + Command.create({"account_id": income.id, "credit": 10000.0}), + Command.create({"account_id": expense.id, "debit": 6000.0}), + Command.create({"account_id": bank.id, "debit": 4000.0})]}) + move.action_post() + ret = self._return() + # profit 4000 -> base 4000 -> tax 21% = 840 + self.assertAlmostEqual(self._v(ret, "r10"), 4000.0, places=2) + self.assertAlmostEqual(self._v(ret, "r270"), 4000.0, places=2) + self.assertAlmostEqual(self._v(ret, "r290"), 840.0, places=2) + + # exporting validates against the official DPPDP9 EPO XSD + ret.action_export_xml() + self.assertEqual(ret.state, "exported") + root = etree.fromstring(base64.b64decode(ret.xml_attachment_id.datas)) + self.assertEqual(etree.QName(root).localname, "Pisemnost") + # II. oddíl values land on the official VetaO attributes + veta_o = root.find(".//VetaO") + self.assertIsNotNone(veta_o) + self.assertEqual(veta_o.get("kc_ii10_10"), "4000") # ř.10 + self.assertEqual(veta_o.get("kc_ii280_290"), "840") # ř.290 daň + self.assertEqual(veta_o.get("kc_ii_340"), "840") # celková daň + + # --- VetaD: druh přiznání ----------------------------------------- + # typ_dapdpp was hard-coded "B" (a return ON ENTERING LIQUIDATION) and + # typ_zo "1", which is no letter of § 21a. + + def _veta_d(self, ret): + ret.action_export_xml() + root = etree.fromstring(base64.b64decode(ret.xml_attachment_id.datas)) + return root.find(".//VetaD") + + def test_an_ordinary_return_is_type_a_for_a_calendar_year(self): + veta_d = self._veta_d(self._return()) + self.assertEqual(veta_d.get("typ_dapdpp"), "A") + self.assertEqual(veta_d.get("typ_zo"), "A") + self.assertEqual(veta_d.get("typ_popldpp"), "1") + + def test_a_hospodarsky_rok_is_period_b(self): + ret = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "statement_type_id": self.type_b.id, + "date_from": "2024-07-01", "date_to": "2025-06-30"}) + self.assertEqual(ret.l10n_cz_period_kind, "B") + + def test_a_dodatecne_priznani_needs_its_discovery_date(self): + from odoo.exceptions import UserError + + ret = self._return() + ret.statement_type_id = self.env.ref("l10n_cz_dppo.cz_dppo_type_D") + with self.assertRaisesRegex(UserError, "d_zjist"): + ret.action_export_xml() + ret.l10n_cz_discovery_date = "2026-03-02" + veta_d = self._veta_d(ret) + self.assertEqual(veta_d.get("dapdpp_forma"), "D") + self.assertEqual(veta_d.get("d_zjist"), "02.03.2026") diff --git a/l10n_cz_dppo/tests/test_cz_dppo_vykazy.py b/l10n_cz_dppo/tests/test_cz_dppo_vykazy.py new file mode 100644 index 00000000..18b7c05c --- /dev/null +++ b/l10n_cz_dppo/tests/test_cz_dppo_vykazy.py @@ -0,0 +1,455 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""DPPDP9 výkazy (VetaUA / VetaUB / VetaUD) from the l10n_cz_fs statements. + +Three questions, three tests, because each check is blind to what the others +catch (a balanced sheet proves none of them): + +* is every row of the zkrácený rozsah mapped or declared not applicable — + a silent unmapped row files as zero and nothing anywhere complains; +* does each EPO total equal the sum of its statutory parts by SIGNED WEIGHT + per account — the only comparison that sees an account claimed by two + sibling rows, or an aggregate missing a term, under a sheet that still foots; +* does a real ledger come out on the right rows, in the right columns, + XSD-valid. + +The matcher and the formula parser are duplicated here on purpose: a test that +validates row DATA must not run through the code whose behaviour it checks. +""" + +import base64 +import csv +import hashlib +import os +import re +from collections import Counter + +from lxml import etree + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.exceptions import UserError +from odoo.tests import tagged +from odoo.tools.misc import file_path + +from odoo.addons.l10n_cz_dppo.models import dppdp9_vykazy as vykazy + +MODULE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +XS = "{http://www.w3.org/2001/XMLSchema}" + +#: The statutory arithmetic of the výkazy (vyhláška 500/2002 Sb., přílohy 1 +#: and 2), written from the form, NOT from the l10n_cz_fs aggregate formulas — +#: those are what is being checked. {věta: {total row: [± part rows]}}. +IDENTITIES = { + "UA": {1: [2, 3, 37, 74], 3: [4, 14, 27], 37: [38, 46, 68, 71], + 46: [47, 57, 78]}, + "UD": {1: [2, 24, 64], 2: [3, 7, 15, 18, 22, 23], 24: [25, 30], + 30: [31, 46, 67]}, + "UB": {30: [1, 2, -3, -7, -8, -9, -14, 20, -24], + 48: [31, -34, 35, -38, 39, -42, -43, 46, -47], + 49: [30, 48], 53: [49, -50], 55: [53, -54], + 56: [1, 2, 20, 31, 35, 39, 46]}, +} + +#: Rows whose l10n_cz_fs designation legitimately differs from the číselník's. +#: C.II.2. is filed from C.II. because the chart convention makes every +#: receivable krátkodobé — NOT_APPLICABLE[("UA", 47)] states why. +DESIGNATION_CONVENTIONS = {("UA", 57)} + + +def _norm(designation): + return (designation or "").strip().rstrip(".") + + +def _load_ciselnik(): + with open(os.path.join(MODULE_DIR, "data", "uv_radky_500.csv"), + encoding="utf-8") as fh: + return {(r["veta"], int(r["c_radku"])): r for r in csv.DictReader(fh)} + + +@tagged("post_install", "-at_install") +class TestCzDppoVykazy(AccountTestInvoicingCommon): + @classmethod + @AccountTestInvoicingCommon.setup_country("cz") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.vat = "CZ25663585" + cls.company.l10n_cssk_tax_authority_id = cls.env[ + "cssk.tax.authority"].search( + [("country_code", "=", "CZ"), ("submission_code", "=", "451")], + limit=1) + cls.version = cls.env.ref("l10n_cz_dppo.cz_dppo_version_2025") + cls.type_b = cls.env.ref("l10n_cz_dppo.cz_dppo_type_B") + cls.bs_version = cls.env.ref("l10n_cz_fs.rozvaha_version_2025") + cls.pl_version = cls.env.ref("l10n_cz_fs.vysledovka_version_2025") + cls.misc = cls.company_data["default_journal_misc"] + cls.ciselnik = _load_ciselnik() + + # ------------------------------------------------------------------ + # helpers + # ------------------------------------------------------------------ + def _acc(self, like): + acc = self.env["account.account"].search( + [("code", "=like", like), ("company_ids", "in", self.company.id)], + limit=1) + self.assertTrue(acc, "the CZ chart has no %s account" % like) + return acc + + def _post(self, date, lines): + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.misc.id, "date": date, + "line_ids": [Command.create({"account_id": self._acc(a).id, + "debit": d, "credit": c}) + for a, d, c in lines]}) + move.action_post() + + def _statement(self, version): + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": version.id, + "date_from": "2025-01-01", "date_to": "2025-12-31"}) + st.action_compute_lines() + return st + + def _return(self, **vals): + ret = self.env["cssk.income.tax.return"].create(dict({ + "company_id": self.company.id, "version_id": self.version.id, + "statement_type_id": self.type_b.id, + "date_from": "2025-01-01", "date_to": "2025-12-31"}, **vals)) + ret.action_compute_lines() + return ret + + def _chart_codes(self): + """The l10n_cz chart as SHIPPED, off-balance accounts aside. + + Not the test company's accounts: the invoicing test fixture copies + accounts to make its own (343221 → 343222), and a mapping is judged + against the chart, not against a fixture's artefacts. Accounts a + customer adds are the runtime unmapped diagnostic's job.""" + with open(file_path("l10n_cz/data/template/account.account-cz.csv"), + encoding="utf-8") as fh: + return [r["code"] for r in csv.DictReader(fh) + if r["account_type"] != "off_balance"] + + def _fs_weights(self, version, codes): + """{fs row code: Counter(account code -> signed weight)}.""" + defs = {d.code: d for d in version.line_def_ids} + cache = {} + + def leaf(ldef): + w = Counter() + # netto = gross + the (credit) correction balances: both columns + # carry their tokens' own sign into the reported figure. + for formula in (ldef.account_formula, + ldef.account_formula_correction): + for tok in (formula or "").split(","): + tok = tok.strip() + if not tok: + continue + sign = -1 if tok.startswith("-") else 1 + prefix = tok.lstrip("-") + self.assertTrue(prefix.isdigit(), "token %r" % tok) + for code in codes: + if code.startswith(prefix): + w[code] += sign + return w + + def weight(code): + if code in cache: + return cache[code] + ldef = defs[code] + if ldef.kind == "aggregate": + formula = ldef.aggregate_formula or "" + self.assertRegex( + formula, + r"^\s*[A-Za-z_]\w*(\s*[+-]\s*[A-Za-z_]\w*)*\s*$", + "%s: only a signed sum can be compared by weight" % code) + w = Counter() + for sign, ref in re.findall(r"([+-]?)\s*([A-Za-z_]\w*)", + formula): + for acc, n in weight(ref).items(): + w[acc] += -n if sign == "-" else n + else: + w = leaf(ldef) + cache[code] = w + return w + + return {code: weight(code) for code in defs} + + def _row_weights(self): + codes = self._chart_codes() + fs = {"balance_sheet": self._fs_weights(self.bs_version, codes), + "profit_loss": self._fs_weights(self.pl_version, codes)} + rows = {} + for (veta, row), (_d, fs_codes) in vykazy.ROWS.items(): + w = Counter() + for code in fs_codes: + w.update(fs[vykazy.TABLES[veta]][code]) + rows[(veta, row)] = w + return rows + + @staticmethod + def _clean(counter): + return {k: v for k, v in counter.items() if v} + + # ------------------------------------------------------------------ + # 1. every row is accounted for + # ------------------------------------------------------------------ + def test_every_vykaz_row_is_mapped_or_declared(self): + # Every výkaz record the XSD defines (a VetaU* element with a + # c_radku) is either filed or declared not applicable, table by + # table. VetaR also has a c_radku, but it is the II. oddíl's own + # row-by-row příloha (t_prilohy / kod_sekce), not an účetní výkaz. + xsd = etree.parse(os.path.join(MODULE_DIR, "data", "dppdp9_epo2.xsd")) + tables = { + el.get("name")[len("Veta"):] + for el in xsd.iter(XS + "element") + if (el.get("name") or "").startswith("VetaU") + and el.find("%scomplexType/%sattribute[@name='c_radku']" + % (XS, XS)) is not None + } + self.assertTrue(tables, "premise: the XSD defines výkaz records") + self.assertEqual( + tables, set(vykazy.TABLES) | set(vykazy.NOT_APPLICABLE_TABLES), + "a výkaz record of the XSD is neither filed nor declared") + self.assertFalse(set(vykazy.TABLES) & set(vykazy.NOT_APPLICABLE_TABLES)) + + # Within the filed tables, every row of the filed rozsah is mapped or + # declared, never both, and nothing outside the rozsah is filed. + in_range = {key for key, r in self.ciselnik.items() + if vykazy.RANGE in r["rozsah"]} + self.assertEqual({veta for veta, _row in self.ciselnik}, + set(vykazy.TABLES)) + mapped, declared = set(vykazy.ROWS), set(vykazy.NOT_APPLICABLE) + self.assertFalse(mapped & declared, "a row is both mapped and n/a") + silent = in_range - mapped - declared + self.assertFalse(silent, "zkrácený-rozsah rows filed as a silent zero: " + "%s" % sorted(silent)) + self.assertFalse((mapped | declared) - in_range, + "rows outside the zkrácený rozsah (or outside the " + "číselník altogether) are mapped") + for key, reason in vykazy.NOT_APPLICABLE.items(): + self.assertGreater(len(reason), 40, "%s needs a real reason" % (key,)) + + # Mapping is by statutory designation: the číselník's and the + # l10n_cz_fs row's must agree, so a row number typed wrong shows. + names = { + kind: {d.code: d.name for d in version.line_def_ids} + for kind, version in (("balance_sheet", self.bs_version), + ("profit_loss", self.pl_version))} + for (veta, row), (designation, codes) in vykazy.ROWS.items(): + official = self.ciselnik[(veta, row)]["oznaceni"] + self.assertEqual(_norm(designation), _norm(official), + "%s/%s: designation" % (veta, row)) + for code in codes: + self.assertIn(code, names[vykazy.TABLES[veta]], + "%s/%s: l10n_cz_fs has no row %s" + % (veta, row, code)) + if len(codes) != 1 or (veta, row) in DESIGNATION_CONVENTIONS: + continue + first = names[vykazy.TABLES[veta]][codes[0]].split()[0] + fs_designation = first if ("." in first or set(first) == {"*"}) \ + else "" + self.assertEqual(_norm(fs_designation), _norm(official), + "%s/%s is filed from l10n_cz_fs %s (%s)" + % (veta, row, codes[0], first)) + + # ------------------------------------------------------------------ + # 2. the statute's arithmetic, by signed weight per account + # ------------------------------------------------------------------ + def test_statutory_totals_hold_by_signed_weight(self): + rows = self._row_weights() + + def w(veta, row): + return rows.get((veta, row), Counter()) # n/a rows weigh nothing + + for veta, identities in IDENTITIES.items(): + for total, parts in identities.items(): + expected = Counter() + for part in parts: + for acc, n in w(veta, abs(part)).items(): + expected[acc] += n if part > 0 else -n + self.assertEqual( + self._clean(w(veta, total)), self._clean(expected), + "%s row %s is not the sum of %s" % (veta, total, parts)) + + def test_each_account_lands_on_one_leaf_row(self): + rows = self._row_weights() + for veta, identities in IDENTITIES.items(): + leaves = [key for key in rows + if key[0] == veta and key[1] not in identities] + reach = Counter() + for key in leaves: + for acc, n in rows[key].items(): + reach[acc] += abs(n) + doubled = {acc: n for acc, n in reach.items() if n > 1} + self.assertFalse(doubled, "%s: accounts on two leaf rows: %s" + % (veta, doubled)) + + def test_every_account_reaches_the_zaverka(self): + rows = self._row_weights() + aktiva, pasiva = rows[("UA", 1)], rows[("UD", 1)] + missing = [code for code in self._chart_codes() + if code[:1] in "01234" + and abs(aktiva[code]) + abs(pasiva[code]) != 1] + self.assertFalse(missing, "balance-sheet accounts on neither or both " + "sides: %s" % missing) + # A.V of the Rozvaha and *** of the VZZ are one number: every class + # 5/6 account carries the same weight in both. + result_bs, result_pl = rows[("UD", 22)], rows[("UB", 55)] + drift = {code: (result_bs[code], result_pl[code]) + for code in self._chart_codes() + if code[:1] in "56" and result_bs[code] != result_pl[code]} + self.assertFalse(drift, "A.V and VH za účetní období disagree on " + "(A.V weight, *** weight): %s" % drift) + + # ------------------------------------------------------------------ + # 3. a real ledger, exported + # ------------------------------------------------------------------ + def _ledger(self): + self._post("2024-06-30", [("221%", 50000.0, 0.0), + ("411%", 0.0, 50000.0)]) + self._post("2025-01-10", [("221%", 1000000.0, 0.0), + ("411%", 0.0, 1000000.0)]) + self._post("2025-02-01", [("022%", 600000.0, 0.0), + ("221%", 0.0, 600000.0)]) + self._post("2025-12-31", [("551%", 120000.0, 0.0), + ("082%", 0.0, 120000.0)]) + self._post("2025-06-30", [("311%", 500000.0, 0.0), + ("602%", 0.0, 500000.0)]) + self._post("2025-12-31", [("558%", 20000.0, 0.0), + ("391%", 0.0, 20000.0)]) + self._post("2025-03-31", [("501%", 100000.0, 0.0), + ("321%", 0.0, 100000.0)]) + + def _rows_of(self, root, veta): + return {int(el.get("c_radku")): dict(el.attrib) + for el in root.iter("Veta" + veta)} + + def test_export_files_the_vykazy(self): + self._ledger() + self.company.partner_id.nace_code = "62101" + bs = self._statement(self.bs_version) + pl = self._statement(self.pl_version) + ret = self._return(l10n_cz_fs_balance_sheet_id=bs.id, + l10n_cz_fs_profit_loss_id=pl.id) + ret.action_export_xml() # validates against the bundled XSD + root = etree.fromstring(base64.b64decode(ret.xml_attachment_id.datas)) + + head = root.find(".//VetaD") + # c_nace: EPO's critical check wants an existing CZ-NACE code. + self.assertEqual(head.get("c_nace"), "62101") + self.assertEqual(head.get("uv_vyhl"), "500") + self.assertEqual(head.get("uv_rozsah"), "Z") + self.assertEqual(head.get("uv_mena"), "CZK") + self.assertEqual(head.get("uz_rad"), "T") + self.assertEqual(head.get("d_uv"), "31.12.2025") + + ua, ud, ub = (self._rows_of(root, v) for v in ("UA", "UD", "UB")) + # B.II.: 022 600 000 less oprávky 082 120 000, in thousands + self.assertEqual(ua[14], {"c_radku": "14", "kc_brutto": "600", + "kc_korekce": "120", "kc_netto": "480"}) + # C.II. and C.II.2.: 311 500 000 less OP 391 20 000 + for row in (46, 57): + self.assertEqual(ua[row]["kc_netto"], "480") + self.assertEqual(ua[row]["kc_korekce"], "20") + self.assertNotIn(47, ua, "C.II.1. is declared n/a, never filed") + # C.IV.: 50 000 (2024) + 1 000 000 − 600 000; prior year 50 000 + self.assertEqual(ua[71], {"c_radku": "71", "kc_brutto": "450", + "kc_netto": "450", "kc_netto_min": "50"}) + self.assertEqual(ua[1]["kc_brutto"], "1550") + self.assertEqual(ua[1]["kc_korekce"], "140") + self.assertEqual(ua[1]["kc_netto"], "1410") + # pasiva + self.assertEqual(ud[1], {"c_radku": "1", "kc_sled": "1410", + "kc_min": "50"}) + self.assertEqual(ud[3]["kc_sled"], "1050") + self.assertEqual(ud[22]["kc_sled"], "260") # A.V + self.assertEqual(ud[24]["kc_sled"], "100") # B.+C. + self.assertEqual(ud[46]["kc_sled"], "100") # C.II. (321) + # VZZ + self.assertEqual(ub[1]["kc_sled"], "500") + self.assertEqual(ub[3]["kc_sled"], "100") + self.assertEqual(ub[14]["kc_sled"], "140") # E. (551 + 558) + self.assertEqual(ub[30]["kc_sled"], "260") + self.assertEqual(ub[55]["kc_sled"], "260") # = A.V + self.assertEqual(ub[56]["kc_sled"], "500") + # The XSD's sequence puts the výkazy after VetaO, UA before UB before UD. + order = [etree.QName(el).localname for el in root.find("DPPDP9")] + self.assertEqual(order[:3], ["VetaD", "VetaP", "VetaO"]) + self.assertEqual(order.index("VetaUB"), order.index("VetaUA") + len(ua)) + + def test_export_without_statements_says_so(self): + ret = self._return() + ret.action_export_xml() + root = etree.fromstring(base64.b64decode(ret.xml_attachment_id.datas)) + self.assertIsNone(root.find(".//VetaUA")) + self.assertIsNone(root.find(".//VetaD").get("uv_rozsah")) + self.assertIn("E-příloha", ret.message_ids[:1].body) + + def test_a_wrong_link_is_refused_not_filed(self): + pl = self._statement(self.pl_version) + bs = self._statement(self.bs_version) + with self.assertRaisesRegex(UserError, "or neither"): + self._return(l10n_cz_fs_profit_loss_id=pl.id).action_export_xml() + other = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.pl_version.id, + "date_from": "2025-07-01", "date_to": "2025-12-31"}) + other.action_compute_lines() + with self.assertRaisesRegex(UserError, "zdaňovací období"): + self._return(l10n_cz_fs_balance_sheet_id=bs.id, + l10n_cz_fs_profit_loss_id=other.id).action_export_xml() + draft = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.bs_version.id, + "date_from": "2025-01-01", "date_to": "2025-12-31"}) + with self.assertRaisesRegex(UserError, "not been computed"): + self._return(l10n_cz_fs_balance_sheet_id=draft.id, + l10n_cz_fs_profit_loss_id=pl.id).action_export_xml() + # A statement of another version lacks the rows the výkaz reads. + bs.line_ids.filtered(lambda ln: ln.code == "CIV").unlink() + with self.assertRaisesRegex(UserError, "CIV"): + self._return(l10n_cz_fs_balance_sheet_id=bs.id, + l10n_cz_fs_profit_loss_id=pl.id).action_export_xml() + + def test_linked_but_empty_is_still_a_filed_zaverka(self): + """A linked závěrka whose every row rounds to nothing still carries + the výkaz header — it must not read as "no závěrka filed".""" + ret = self._return( + l10n_cz_fs_balance_sheet_id=self._statement(self.bs_version).id, + l10n_cz_fs_profit_loss_id=self._statement(self.pl_version).id) + ret.action_export_xml() + root = etree.fromstring(base64.b64decode(ret.xml_attachment_id.datas)) + self.assertEqual(root.find(".//VetaD").get("uv_rozsah"), "Z") + self.assertIsNone(root.find(".//VetaUA")) + + def test_negative_korekce_is_refused(self): + """kc_korekce is filed unsigned. A debit balance on an oprávka is a + booking error to fix, not a sign to drop silently.""" + self._post("2025-05-31", [("082%", 5000.0, 0.0), + ("221%", 0.0, 5000.0)]) + ret = self._return( + l10n_cz_fs_balance_sheet_id=self._statement(self.bs_version).id, + l10n_cz_fs_profit_loss_id=self._statement(self.pl_version).id) + with self.assertRaisesRegex(UserError, "negative korekce"): + ret.action_export_xml() + + # ------------------------------------------------------------------ + # 4. the pin + # ------------------------------------------------------------------ + def test_schema_and_ciselnik_are_pinned(self): + with open(os.path.join(MODULE_DIR, "data", "SCHEMA_VERSION"), + encoding="utf-8") as fh: + pin = fh.read() + verze = pin.split()[1] + for name in ("dppdp9_epo2.xsd", "uv_radky_500.csv"): + m = re.search(r"^%s\s+(\d+)\s+([0-9a-f]{32})$" % re.escape(name), + pin, re.M) + self.assertTrue(m, "%s is not pinned" % name) + with open(os.path.join(MODULE_DIR, "data", name), "rb") as fh: + data = fh.read() + self.assertEqual((len(data), hashlib.md5(data).hexdigest()), + (int(m.group(1)), m.group(2)), + "%s changed without its pin" % name) + with open(os.path.join(MODULE_DIR, "report", "dppo_report.xml"), + encoding="utf-8") as fh: + self.assertIn('verzePis="%s"' % verze, fh.read()) diff --git a/l10n_cz_dppo/views/cssk_income_tax_views.xml b/l10n_cz_dppo/views/cssk_income_tax_views.xml new file mode 100644 index 00000000..4effdc04 --- /dev/null +++ b/l10n_cz_dppo/views/cssk_income_tax_views.xml @@ -0,0 +1,34 @@ + + + + cssk.income.tax.return.form.cz.vykazy + cssk.income.tax.return + + + + + + + + + + + + + + + + + + + diff --git a/l10n_cz_fs/CHANGELOG.rst b/l10n_cz_fs/CHANGELOG.rst new file mode 100644 index 00000000..5f4af16d --- /dev/null +++ b/l10n_cz_fs/CHANGELOG.rst @@ -0,0 +1,92 @@ +========= +Changelog +========= + +All notable changes to **l10n_cz_fs** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +[19.0.1.1.0] — 2026-09-28 +------------------------- + +Changed +~~~~~~~ + +- **Rozvaha aktiva in brutto / korekce / netto.** B.I., B.II., B.III., C.I., + C.II. and C.III. now name their oprávky and opravné položky (07x, 08x, 09x, + 19x, 291, 391) in the correction formula instead of netting them into the + row. Netto is unchanged. The DPPDP9 return files the Rozvaha as VetaUA with + ``kc_brutto`` / ``kc_korekce`` / ``kc_netto`` per row, and a netted figure + cannot be split back into those columns. Migration included: the version + lives in a ``noupdate="1"`` file, and each row is rewritten only where it + still holds the formula this module shipped. + +Fixed +~~~~~ + +- **094 (opravná položka k nedokončenému DHM) sat in B.I.** It belongs to + B.II. Dlouhodobý hmotný majetek; B.I. and B.II. were each misstated by its + balance, while B. Stálá aktiva was right. + +[19.0.1.0.2] — 2026-09-06 +------------------------- + +Fixed +~~~~~ + +- Every Rozvaha / VZZ / Přehled export raised: no XSD is published for any of + them (they are filed inside the DPPO envelope), and the core mixin had been + tightened to refuse what it could not validate. The four versions now declare + ``xml_schema_optional``, with a migration because they live in a + ``noupdate="1"`` file — an upgrade would otherwise take the fix and leave the + database broken, showing up only when somebody tries to file. +- Test fixture sets a finanční úřad. ``l10n_cz_statutory`` adds a preflight + requiring the c_ufo and this module does not depend on it, so the suite was + green standalone and red on every full-bundle install. + +Changed +~~~~~~~ + +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.1.0.1] — 2026-07-04 +------------------------- + +Changed +~~~~~~~ + +- Evaluator rewritten to use ``_read_group`` balance helpers instead of per-prefix search loops — + the Rozvaha compute drops from ~600 queries to 2. (Wave 3) + +[2026-07-01] — Wave 1 (correctness) +----------------------------------- + +Changed +~~~~~~~ + +- Statutory rounding unified to HALF-UP (``statutory_round()``/``statutory_whole()``). +- Multi-company ``ir.rule`` (``company_id in company_ids``) added so statements no longer leak + across companies. + +[2026-06-30] — Baseline & i18n +------------------------------ + +Added +~~~~~ + +- Czech financial statements on ``l10n_cssk_fs_base``, CE-clean — line values summed from + ``account.move.line`` by account-code prefix (balance sheet cumulative as-of date, P&L period + movement). +- Rozvaha (plný rozsah) + Výkaz zisku a ztráty v druhovém členění per vyhláška 500/2002 Sb.; + every l10n_cz account maps to exactly one leaf line, so AKTIVA = PASIVA and výsledek + hospodaření = výnosy − náklady reconcile (partition verified in tests). +- Přehled o peněžních tocích (cash flow, nepřímá metoda) and Přehled o změnách vlastního + kapitálu (changes in equity) as příloha components, reconciling by construction + (A + B + C = Δcash; počátek + Σ změny = konec). +- Structured XML export as a stand-in (the full závěrka has no standalone EPO XSD). +- cs_CZ jsonb translations (i18n sweep). diff --git a/l10n_cz_fs/LICENSE b/l10n_cz_fs/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/l10n_cz_fs/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/l10n_cz_fs/README.rst b/l10n_cz_fs/README.rst new file mode 100644 index 00000000..7a170618 --- /dev/null +++ b/l10n_cz_fs/README.rst @@ -0,0 +1,52 @@ +============================================== +CZ Financial Statements (Rozvaha / Výsledovka) +============================================== + +The Czech country layer for the shared financial-statement framework +(``l10n_cssk_fs_base``). It is CE-clean: line values are summed from +``account.move.line`` by account-code prefix — the balance sheet (Rozvaha) is +cumulative as-of the report date, while the P&L (Výkaz zisku a ztráty) reports +the period movement. The module ships the full statutory line structure +required by vyhláška 500/2002 Sb. and the structured XML exports that feed the +DPPDP9 appendices and the commercial-register filing (sbírka listin). + +Features +======== + +* Full statutory line structure per vyhláška 500/2002 Sb.: Rozvaha (plný-rozsah + groups plus the key numbered lines) and Výkaz zisku a ztráty v druhovém + členění. +* Every account of the l10n_cz směrná účtová osnova maps to exactly one leaf + line (the partition is verified in tests), so the aggregates reconcile: + AKTIVA = PASIVA and výsledek hospodaření = výnosy − náklady. +* The current-year result (A.V) is computed from class 5/6, so the balance + sheet ties out even before the result is closed to účet 431. +* Přehled o peněžních tocích (cash flow, nepřímá metoda) and Přehled o změnách + vlastního kapitálu (changes in equity) — příloha components built as a + complete account partition into provozní/investiční/finanční činnost + (resp. equity components), reconciling by construction (A + B + C = Δcash; + počátek + Σ změny = konec; G = 0 asserted in tests). +* Structured XML export for every statement. There is no standalone EPO XSD for + the full závěrka, so the XML is a structured stand-in alongside the PDF / + commercial-register submission. + +Usage +===== + +Create a financial-statement record for the relevant statutory version +(Rozvaha, Výkaz zisku a ztráty, cash flow or changes in equity), set the +reporting period and compute the lines. The values are summed directly from the +posted journal items, the aggregates reconcile by construction, and the result +can be exported as structured XML for the DPPDP9 appendices and the +commercial-register filing. + +Accountant note: the dlouhodobé/krátkodobé receivable & payable splits and the +opravné-položka allocations follow the standard chart convention — confirm with +a CZ accountant for entities using non-standard account assignments. + +Credits +======= + +* Data Dance s.r.o. + +License: AGPL-3.0 or later (see the LICENSE file). diff --git a/l10n_cz_fs/__init__.py b/l10n_cz_fs/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_cz_fs/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_cz_fs/__manifest__.py b/l10n_cz_fs/__manifest__.py new file mode 100644 index 00000000..944344fa --- /dev/null +++ b/l10n_cz_fs/__manifest__.py @@ -0,0 +1,50 @@ +{ + "name": "CZ Financial Statements (Rozvaha / Výsledovka)", + "summary": "Czech balance sheet (Rozvaha), P&L (Výkaz zisku a ztráty), cash " + "flow and changes in equity on the shared FS framework — " + "account-code line mappings + XML.", + "description": """ +CZ Financial Statements (Rozvaha / Výsledovka) +============================================== + +The Czech country layer for the shared financial-statement framework +(``l10n_cssk_fs_base``). CE-clean — line values are summed from +``account.move.line`` by account-code prefix (balance sheet = cumulative as-of +date, P&L = period movement). + +**Scope:** the full statutory line structure per vyhláška 500/2002 Sb. — +Rozvaha (plný-rozsah groups + key numbered lines) and Výkaz zisku a ztráty +v druhovém členění. Every account of the l10n_cz směrná osnova is mapped to +exactly one leaf line (partition verified in tests), so the aggregates +reconcile: **AKTIVA = PASIVA** and výsledek hospodaření = výnosy − náklady. The +current-year result (A.V) is computed from class 5/6 so the balance sheet ties +out even before the result is closed to účet 431. + +The **Přehled o peněžních tocích** (cash flow, nepřímá metoda) and **Přehled o +změnách vlastního kapitálu** (changes in equity) are the příloha components, +built as a complete account partition into provozní/investiční/finanční činnost +(resp. equity components) so they reconcile by construction — A + B + C = Δcash +and počátek + Σ změny = konec (G = 0, asserted in tests). They are filed as PDF / +within the commercial-register submission; there is no standalone EPO XSD for +them, so the XML export is a structured stand-in. + +**Accountant note:** the dlouhodobé/krátkodobé receivable & payable splits and +the opravné-položka allocations follow the standard chart convention — confirm +with a CZ accountant for entities using non-standard account assignments. The +statements feed the DPPDP9 appendices / commercial-register filing; there is no +single standalone EPO XSD for the full závěrka. +""", + "author": "Data Dance s.r.o.", + "website": "https://www.datadance.eu", + "version": "19.0.1.1.0", + "category": "Accounting/Localizations", + "license": "AGPL-3", + "depends": ["l10n_cssk_fs_base", "l10n_cz"], + "data": [ + "report/l10n_cz_fs_templates.xml", + "data/cz_fs_version_data.xml", + "data/cz_cashflow_version_data.xml", + "data/cz_equity_changes_version_data.xml", + ], + "installable": True, +} diff --git a/l10n_cz_fs/data/cz_cashflow_version_data.xml b/l10n_cz_fs/data/cz_cashflow_version_data.xml new file mode 100644 index 00000000..a7c16b1d --- /dev/null +++ b/l10n_cz_fs/data/cz_cashflow_version_data.xml @@ -0,0 +1,94 @@ + + + + + + Přehled o peněžních tocích 2025 + + cash_flow + 2025-01-01 + + PrehledPT + + + + + + diff --git a/l10n_cz_fs/data/cz_equity_changes_version_data.xml b/l10n_cz_fs/data/cz_equity_changes_version_data.xml new file mode 100644 index 00000000..593e1dd6 --- /dev/null +++ b/l10n_cz_fs/data/cz_equity_changes_version_data.xml @@ -0,0 +1,68 @@ + + + + + + Přehled o změnách vlastního kapitálu 2025 + + equity_changes + 2025-01-01 + + PrehledVK + + + + + + diff --git a/l10n_cz_fs/data/cz_fs_version_data.xml b/l10n_cz_fs/data/cz_fs_version_data.xml new file mode 100644 index 00000000..9c69e360 --- /dev/null +++ b/l10n_cz_fs/data/cz_fs_version_data.xml @@ -0,0 +1,190 @@ + + + + + + Rozvaha 2025 + + balance_sheet + 2025-01-01 + + Rozvaha + + + + + + + Výkaz zisku a ztráty 2025 + + profit_loss + 2025-01-01 + + Vysledovka + + + + + + diff --git a/l10n_cz_fs/i18n/l10n_cz_fs.pot b/l10n_cz_fs/i18n/l10n_cz_fs.pot new file mode 100644 index 00000000..e69de29b diff --git a/l10n_cz_fs/migrations/19.0.1.0.2/post-migrate.py b/l10n_cz_fs/migrations/19.0.1.0.2/post-migrate.py new file mode 100644 index 00000000..f411d781 --- /dev/null +++ b/l10n_cz_fs/migrations/19.0.1.0.2/post-migrate.py @@ -0,0 +1,40 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Declare, on existing databases, that these forms have no published XSD. + +``_validate_against_schema`` was tightened to refuse an export it could not +validate — right, because a KV DPH once exported unvalidated and looked as +though it had passed. It broke every financial-statement export where no +schema exists to load, which is a published fact about the form and not a +missing file: the Czech statements are filed as attachments inside the DPPO +envelope and the Finanční správa publishes no standalone schema for any +of them. + +The version record now carries ``xml_schema_optional`` to say so, but those +records live in a ``noupdate="1"`` data file, so a module upgrade will NOT +rewrite them — an existing database would take the fix and stay broken, with +the failure appearing only when somebody tries to file. Hence this. + +Deliberately does not touch a version that HAS a schema, and does not create +the flag anywhere it was not already true. +""" + +XMLIDS = ['rozvaha_version_2025', 'vysledovka_version_2025', 'cashflow_version_2025', 'equity_changes_version_2025'] + + +def migrate(cr, version): + if not version: + return + cr.execute( + """ + UPDATE cssk_fs_statement_version v + SET xml_schema_optional = TRUE + FROM ir_model_data d + WHERE d.model = 'cssk.fs.statement.version' + AND d.module = %s + AND d.name = ANY(%s) + AND d.res_id = v.id + AND COALESCE(v.xml_schema_optional, FALSE) = FALSE + """, + ('l10n_cz_fs', list(XMLIDS)), + ) diff --git a/l10n_cz_fs/migrations/19.0.1.1.0/post-migrate.py b/l10n_cz_fs/migrations/19.0.1.1.0/post-migrate.py new file mode 100644 index 00000000..ed61417c --- /dev/null +++ b/l10n_cz_fs/migrations/19.0.1.1.0/post-migrate.py @@ -0,0 +1,70 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Split the Rozvaha aktiva rows into brutto and korekce on existing databases. + +The DPPDP9 return files the Rozvaha as VetaUA with ``kc_brutto`` / +``kc_korekce`` / ``kc_netto`` per row, and a row whose oprávky and opravné +položky are netted into one formula cannot be split back into those columns. +The line definitions now carry the correction accounts in +``account_formula_correction``; netto is unchanged by that. + +Those definitions live in a ``noupdate="1"`` file, so an upgrade would not +rewrite them: an existing database would keep the old, netted rows and the +DPPDP9 export would file brutto = netto and korekce = 0 without a word. + +Each row is rewritten only where it still holds EXACTLY the formula this module +shipped and no correction formula, so an accountant's own edit is left alone. +The one figure that moves is 094 (OP k nedokončenému DHM), from B.I. to B.II. +""" + +# code: (formula as shipped before, gross formula, correction formula) +SPLITS = { + "BI": ( + "012,013,014,015,016,017,019,040,041,050,051,072,073,074,075,079,091,093,094", + "012,013,014,015,016,017,019,040,041,050,051", + "072,073,074,075,079,091,093"), + "BII": ( + "021,022,025,026,027,029,031,032,042,052,081,082,085,086,089,092,095,097,098", + "021,022,025,026,027,029,031,032,042,052,097", + "081,082,085,086,089,092,094,095,098"), + "BIII": ( + "061,062,063,065,066,067,068,069,043,053,096", + "061,062,063,065,066,067,068,069,043,053", + "096"), + "CI": ( + "1", + "10,11,12,13,14,15,16,17,18", + "19"), + "CII": ( + "311,312,313,314,315,335,343001,343112,343115,343121,351,352,354,355," + "358,371,373,374,375,376,378,388,391,395", + "311,312,313,314,315,335,343001,343112,343115,343121,351,352,354,355," + "358,371,373,374,375,376,378,388,395", + "391"), + "CIII": ( + "251,253,254,256,257,258,259,291", + "251,253,254,256,257,258,259", + "291"), +} + + +def migrate(cr, version): + if not version: + return + for code, (old, gross, correction) in SPLITS.items(): + cr.execute( + """ + UPDATE cssk_fs_statement_line_def l + SET account_formula = %s, + account_formula_correction = %s + FROM ir_model_data d + WHERE d.model = 'cssk.fs.statement.version' + AND d.module = 'l10n_cz_fs' + AND d.name = 'rozvaha_version_2025' + AND l.version_id = d.res_id + AND l.code = %s + AND l.account_formula = %s + AND COALESCE(l.account_formula_correction, '') = '' + """, + (gross, correction, code, old), + ) diff --git a/l10n_cz_fs/models/__init__.py b/l10n_cz_fs/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_cz_fs/report/l10n_cz_fs_templates.xml b/l10n_cz_fs/report/l10n_cz_fs_templates.xml new file mode 100644 index 00000000..6101f2b9 --- /dev/null +++ b/l10n_cz_fs/report/l10n_cz_fs_templates.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + diff --git a/l10n_cz_fs/static/description/icon.png b/l10n_cz_fs/static/description/icon.png new file mode 100644 index 00000000..9c07b5a7 Binary files /dev/null and b/l10n_cz_fs/static/description/icon.png differ diff --git a/l10n_cz_fs/static/description/index.html b/l10n_cz_fs/static/description/index.html new file mode 100644 index 00000000..b5f73ef8 --- /dev/null +++ b/l10n_cz_fs/static/description/index.html @@ -0,0 +1,57 @@ +
+
+

🌐 EN · CZ · SK

+

CZ Financial Statements (Rozvaha / Výsledovka)

+

Czech statutory financial statements on the shared FS framework — account-code line mappings plus structured XML.

+
+

+ The Czech country layer for the shared financial-statement framework + (l10n_cssk_fs_base). It is CE-clean: line values are summed from + account.move.line by account-code prefix — the balance sheet (Rozvaha) + is cumulative as-of the report date, while the P&L (Výkaz zisku a ztráty) + reports the period movement. The module ships the full statutory line structure + required by vyhláška 500/2002 Sb. and the structured XML exports that feed the + DPPDP9 appendices and the commercial-register filing (sbírka listin). +

+
+
+
+
+
+

Key features

+
+
    +
  • Full statutory line structure per vyhláška 500/2002 Sb.: Rozvaha (plný-rozsah groups plus the key numbered lines) and Výkaz zisku a ztráty v druhovém členění.
  • +
  • Every account of the l10n_cz směrná účtová osnova maps to exactly one leaf line (the partition is verified in tests), so the aggregates reconcile: AKTIVA = PASIVA and výsledek hospodaření = výnosy − náklady.
  • +
  • The current-year result (A.V) is computed from class 5/6, so the balance sheet ties out even before the result is closed to účet 431.
  • +
  • Přehled o peněžních tocích (cash flow, nepřímá metoda) and Přehled o změnách vlastního kapitálu (changes in equity) — příloha components built as a complete account partition into provozní/investiční/finanční činnost (resp. equity components), reconciling by construction (A + B + C = Δcash; počátek + Σ změny = konec; G = 0 asserted in tests).
  • +
  • Structured XML export for every statement. There is no standalone EPO XSD for the full závěrka, so the XML is a structured stand-in alongside the PDF / commercial-register submission.
  • +
+
+
+
+
+
+

Usage

+
+

+ Create a financial-statement record for the relevant statutory version (Rozvaha, + Výkaz zisku a ztráty, cash flow or changes in equity), set the reporting period and + compute the lines. The values are summed directly from the posted journal items, the + aggregates reconcile by construction, and the result can be exported as structured XML + for the DPPDP9 appendices and the commercial-register filing. Accountant note: the + dlouhodobé/krátkodobé receivable & payable splits and the opravné-položka allocations + follow the standard chart convention — confirm with a CZ accountant for entities using + non-standard account assignments. +

+
+
+
+
+
+
+

Published by Data Dance s.r.o. — www.datadance.eu

+

License: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_cz_fs/static/description/index_cz.html b/l10n_cz_fs/static/description/index_cz.html new file mode 100644 index 00000000..d53603dd --- /dev/null +++ b/l10n_cz_fs/static/description/index_cz.html @@ -0,0 +1,57 @@ +
+
+

🌐 EN · CZ · SK

+

CZ Účetní závěrka (Rozvaha / Výsledovka)

+

Česká účetní závěrka nad sdíleným frameworkem FS — mapování řádků podle účtových předčíslí a strukturované XML.

+
+

+ Česká národní vrstva sdíleného frameworku pro účetní závěrku + (l10n_cssk_fs_base). Je CE-čistá: hodnoty řádků se sčítají z modelu + account.move.line podle předčíslí účtu — Rozvaha je kumulativní k datu + sestavení, zatímco Výkaz zisku a ztráty (VZZ) vykazuje pohyb za období. Modul + obsahuje úplnou zákonnou strukturu řádků podle vyhlášky 500/2002 Sb. a strukturované + XML exporty, které slouží jako podklad pro přílohy DPPDP9 a podání do obchodního + rejstříku (sbírka listin). +

+
+
+
+
+
+

Klíčové funkce

+
+
    +
  • Úplná zákonná struktura řádků podle vyhlášky 500/2002 Sb.: Rozvaha (skupiny v plném rozsahu a klíčové číslované řádky) a Výkaz zisku a ztráty v druhovém členění.
  • +
  • Každý účet l10n_cz směrné účtové osnovy je namapován právě na jeden listový řádek (rozklad je ověřen v testech), takže agregáty sedí: AKTIVA = PASIVA a výsledek hospodaření = výnosy − náklady.
  • +
  • Výsledek běžného období (A.V) se počítá z tříd 5/6, takže rozvaha sedí ještě dříve, než je výsledek převeden na účet 431.
  • +
  • Přehled o peněžních tocích (nepřímá metoda) a Přehled o změnách vlastního kapitálu — komponenty přílohy sestavené jako úplný rozklad účtů do provozní/investiční/finanční činnosti (resp. složek vlastního kapitálu), které sedí ze své konstrukce (A + B + C = Δpeníze; počátek + Σ změny = konec; G = 0 ověřeno v testech).
  • +
  • Strukturovaný XML export pro každý výkaz. Pro úplnou závěrku neexistuje samostatné EPO XSD, proto je XML strukturovanou náhradou vedle PDF / podání do obchodního rejstříku.
  • +
+
+
+
+
+
+

Použití

+
+

+ Vytvořte záznam účetní závěrky pro příslušnou zákonnou verzi (Rozvaha, Výkaz zisku + a ztráty, peněžní toky nebo změny vlastního kapitálu), nastavte vykazované období + a spočítejte řádky. Hodnoty se sčítají přímo ze zaúčtovaných účetních položek, + agregáty sedí ze své konstrukce a výsledek lze exportovat jako strukturované XML pro + přílohy DPPDP9 a podání do obchodního rejstříku. Poznámka pro účetní: rozdělení + dlouhodobých/krátkodobých pohledávek a závazků a alokace opravných položek vycházejí + ze standardní konvence osnovy — u subjektů s nestandardním přiřazením účtů ověřte + s českým účetním. +

+
+
+
+
+
+
+

Vydává Data Dance s.r.o. — www.datadance.eu

+

Licence: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_cz_fs/static/description/index_sk.html b/l10n_cz_fs/static/description/index_sk.html new file mode 100644 index 00000000..8be31a56 --- /dev/null +++ b/l10n_cz_fs/static/description/index_sk.html @@ -0,0 +1,58 @@ +
+
+

🌐 EN · CZ · SK

+

CZ Účtovná závierka (Rozvaha / Výsledovka)

+

Česká účtovná závierka nad zdieľaným frameworkom FS — mapovanie riadkov podľa predčíslí účtov a štruktúrované XML.

+
+

+ Česká národná vrstva zdieľaného frameworku pre účtovnú závierku + (l10n_cssk_fs_base). Je CE-čistá: hodnoty riadkov sa sčítavajú z modelu + account.move.line podľa predčíslia účtu — českú súvahu (Rozvaha) sa + vykazuje kumulatívne k dátumu zostavenia, zatiaľ čo český výkaz ziskov a strát + (Výkaz zisku a ztráty) vykazuje pohyb za obdobie. Modul obsahuje úplnú zákonnú + štruktúru riadkov podľa vyhlášky 500/2002 Sb. a štruktúrované XML exporty, ktoré + slúžia ako podklad pre prílohy DPPDP9 a podanie do obchodného registra + (sbírka listin). +

+
+
+
+
+
+

Kľúčové funkcie

+
+
    +
  • Úplná zákonná štruktúra riadkov podľa vyhlášky 500/2002 Sb.: Rozvaha (skupiny v plnom rozsahu a kľúčové číslované riadky) a Výkaz zisku a ztráty v druhovom členení.
  • +
  • Každý účet českej smernej účtovej osnovy (l10n_cz směrná účtová osnova) je namapovaný práve na jeden listový riadok (rozklad je overený v testoch), takže agregáty sedia: AKTIVA = PASIVA a výsledok hospodárenia = výnosy − náklady.
  • +
  • Výsledok bežného obdobia (A.V) sa počíta z tried 5/6, takže súvaha sedí ešte predtým, než sa výsledok prevedie na účet 431.
  • +
  • Přehled o peněžních tocích (prehľad peňažných tokov, nepriama metóda) a Přehled o změnách vlastního kapitálu (prehľad zmien vlastného imania) — komponenty prílohy zostavené ako úplný rozklad účtov do prevádzkovej/investičnej/finančnej činnosti (resp. zložiek vlastného imania), ktoré sedia zo svojej konštrukcie (A + B + C = Δpeniaze; počiatok + Σ zmeny = koniec; G = 0 overené v testoch).
  • +
  • Štruktúrovaný XML export pre každý výkaz. Pre úplnú závierku neexistuje samostatné EPO XSD, preto je XML štruktúrovanou náhradou popri PDF / podaní do obchodného registra.
  • +
+
+
+
+
+
+

Použitie

+
+

+ Vytvorte záznam účtovnej závierky pre príslušnú zákonnú verziu (Rozvaha, Výkaz zisku + a ztráty, peňažné toky alebo zmeny vlastného imania), nastavte vykazované obdobie + a vypočítajte riadky. Hodnoty sa sčítavajú priamo zo zaúčtovaných účtovných položiek, + agregáty sedia zo svojej konštrukcie a výsledok možno exportovať ako štruktúrované XML + pre prílohy DPPDP9 a podanie do obchodného registra. Poznámka pre účtovníka: rozdelenie + dlhodobých/krátkodobých pohľadávok a záväzkov a alokácie opravných položiek vychádzajú + zo štandardnej konvencie osnovy — pri subjektoch s neštandardným priradením účtov overte + s českým účtovníkom. +

+
+
+
+
+
+
+

Vydáva Data Dance s.r.o. — www.datadance.eu

+

Licencia: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_cz_fs/tests/__init__.py b/l10n_cz_fs/tests/__init__.py new file mode 100644 index 00000000..90942415 --- /dev/null +++ b/l10n_cz_fs/tests/__init__.py @@ -0,0 +1 @@ +from . import test_cz_fs diff --git a/l10n_cz_fs/tests/test_cz_fs.py b/l10n_cz_fs/tests/test_cz_fs.py new file mode 100644 index 00000000..ede59af8 --- /dev/null +++ b/l10n_cz_fs/tests/test_cz_fs.py @@ -0,0 +1,263 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 + +from lxml import etree + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.exceptions import UserError +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestCzFs(AccountTestInvoicingCommon): + @classmethod + @AccountTestInvoicingCommon.setup_country("cz") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.vat = "CZ25663585" + # l10n_cz_statutory, where it is installed alongside, adds a preflight + # that every Czech submission carry its finanční úřad code (c_ufo). + # This module does not depend on it, so the suite was green standalone + # and red on any full-bundle install — which is every customer, since + # the l10n_cz_datadance umbrella brings both. Production is right to + # demand the office; the fixture simply never provided one. + if not cls.company.l10n_cssk_tax_authority_id: + authority = cls.env["cssk.tax.authority"].search( + [("country_code", "=", "CZ")], limit=1) + if not authority: + authority = cls.env["cssk.tax.authority"].create({ + "name": "FÚ pro hlavní město Prahu", + "code": "CZ-TEST-1", + "submission_code": "451", + "country_id": cls.env.ref("base.cz").id, + }) + cls.company.l10n_cssk_tax_authority_id = authority + cls.pl_version = cls.env.ref("l10n_cz_fs.vysledovka_version_2025") + cls.bs_version = cls.env.ref("l10n_cz_fs.rozvaha_version_2025") + cls.misc = cls.company_data["default_journal_misc"] + # A single posted entry: revenue 5000 (602), cost 3000 (501), + # bank +2000 (221). YTD result = 2000. + income = cls._acc(cls, "602%") + expense = cls._acc(cls, "501%") + bank = cls._acc(cls, "221%") + move = cls.env["account.move"].create({ + "move_type": "entry", "journal_id": cls.misc.id, "date": "2025-06-30", + "line_ids": [ + Command.create({"account_id": income.id, "credit": 5000.0}), + Command.create({"account_id": expense.id, "debit": 3000.0}), + Command.create({"account_id": bank.id, "debit": 2000.0}), + ], + }) + move.action_post() + + def _acc(self, like): + return self.env["account.account"].search( + [("code", "=like", like), ("company_ids", "in", self.company.id)], + limit=1, + ) + + def _statement(self, version): + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": version.id, + "date_from": "2025-01-01", "date_to": "2025-12-31", + }) + st.action_compute_lines() + return st + + def _v(self, st, code): + return st.line_ids.filtered(lambda l: l.code == code).current_value + + def test_profit_loss_full_lines_and_export(self): + st = self._statement(self.pl_version) + # I = revenue (601,602); A2 = material/energy (501-503); result lines. + self.assertAlmostEqual(self._v(st, "I"), 5000.0, places=2) + self.assertAlmostEqual(self._v(st, "A2"), 3000.0, places=2) + self.assertAlmostEqual(self._v(st, "PROV"), 2000.0, places=2) + self.assertAlmostEqual(self._v(st, "VHU"), 2000.0, places=2) + self.assertAlmostEqual(self._v(st, "COBR"), 5000.0, places=2) + st.action_export_xml() + self.assertEqual(st.state, "exported") + root = etree.fromstring(base64.b64decode(st.xml_attachment_id.datas)) + self.assertEqual(etree.QName(root).localname, "Vysledovka") + + def test_export_without_an_xsd_is_a_declared_state_not_a_hole(self): + """The Czech statements have no published XSD, and that is the form. + + Rozvaha, VZZ and the two přehledy are filed as attachments inside the + DPPO envelope; the Finanční správa publishes no standalone schema for + any of them, while the Slovak Úč POD ships ``uzpod-2014.xsd``. So + "no schema" is a property of the VERSION. + + The mixin was tightened to refuse an export it could not validate — + right, and it broke every Czech FS export until the version could say + the absence is deliberate. Both halves are asserted here: the flag + permits the export, and clearing it brings the refusal straight back, + so the narrowing cannot quietly widen into "never validate". + """ + self.assertFalse( + self.bs_version.xml_schema_data, + "premise: no XSD is shipped for the Rozvaha") + self.assertTrue( + self.bs_version.xml_schema_optional, + "the version must declare that absence") + + st = self._statement(self.bs_version) + st.action_export_xml() + self.assertEqual(st.state, "exported") + + self.bs_version.xml_schema_optional = False + blocked = self._statement(self.bs_version) + with self.assertRaises(UserError) as cm: + blocked.action_export_xml() + self.assertIn("no XML schema", str(cm.exception)) + + def test_balance_sheet_reconciles(self): + """AKTIVA must equal PASIVA — the proof that the account partition is + complete and the current-year result (A.V) ties the sheet out.""" + st = self._statement(self.bs_version) + aktiva = self._v(st, "AKTIVA") + pasiva = self._v(st, "PASIVA") + self.assertAlmostEqual(aktiva, 2000.0, places=2) # bank +2000 + self.assertAlmostEqual(self._v(st, "AV"), 2000.0, places=2) # YTD result + self.assertAlmostEqual(aktiva, pasiva, places=2) # ties out + st.action_export_xml() + root = etree.fromstring(base64.b64decode(st.xml_attachment_id.datas)) + self.assertEqual(etree.QName(root).localname, "Rozvaha") + + def test_aktiva_carry_brutto_and_korekce(self): + """The Rozvaha is filed in brutto / korekce / netto columns (DPPDP9 + VetaUA), so the oprávky and opravné položky must stay a column of the + row rather than be netted into it — with netto what it always was, + and the row's documents still adding up to it. 094 (OP k + nedokončenému DHM) is B.II., not B.I.""" + def entry(lines): + self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.misc.id, + "date": "2025-09-30", + "line_ids": [Command.create({"account_id": self._acc(a).id, + "debit": d, "credit": c}) + for a, d, c in lines]}).action_post() + + entry([("022%", 1000.0, 0.0), ("082%", 0.0, 300.0), + ("094%", 0.0, 50.0), ("221%", 0.0, 650.0)]) + st = self._statement(self.bs_version) + bii = st.line_ids.filtered(lambda l: l.code == "BII") + self.assertAlmostEqual(bii.gross_value, 1000.0, places=2) + self.assertAlmostEqual(bii.correction_value, 350.0, places=2) + self.assertAlmostEqual(bii.current_value, 650.0, places=2) + self.assertTrue(bii.source_reconciles, + "the row's documents include its oprávky") + self.assertAlmostEqual(self._v(st, "BI"), 0.0, places=2) + self.assertAlmostEqual(self._v(st, "AKTIVA"), + self._v(st, "PASIVA"), places=2) + + def test_cash_flow_reconciles(self): + """Přehled o peněžních tocích (nepřímá metoda): A+B+C = F = konec − + začátek; the partition reconciles by construction (G = 0). The setUpClass + entry (revenue 5000 / cost 3000 / bank +2000) sits in provozní činnost.""" + bank = self._acc("221%") + revenue = self._acc("602%") + asset = self.env["account.account"].create({ + "name": "Stroj", "code": "022900", + "account_type": "asset_non_current", + "company_ids": [(6, 0, [self.company.id])]}) + loan = self.env["account.account"].create({ + "name": "Bankovní úvěr", "code": "461900", + "account_type": "liability_non_current", + "company_ids": [(6, 0, [self.company.id])]}) + + def entry(date, lines): + self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.misc.id, "date": date, + "line_ids": [Command.create(ln) for ln in lines]}).action_post() + + # sale collected to bank (+1000 provozní) + entry("2025-03-10", [{"account_id": bank.id, "debit": 1000.0}, + {"account_id": revenue.id, "credit": 1000.0}]) + # buy a machine (−600 investiční) + entry("2025-04-10", [{"account_id": asset.id, "debit": 600.0}, + {"account_id": bank.id, "credit": 600.0}]) + # draw a loan (+300 finanční) + entry("2025-05-10", [{"account_id": bank.id, "debit": 300.0}, + {"account_id": loan.id, "credit": 300.0}]) + + cf = self._statement(self.env.ref("l10n_cz_fs.cashflow_version_2025")) + v = {l.code: l.current_value for l in cf.line_ids} + self.assertAlmostEqual(v["A"], 3000.0, 2) # provozní (2000 setUp + 1000) + self.assertAlmostEqual(v["B"], -600.0, 2) # investiční + self.assertAlmostEqual(v["C"], 300.0, 2) # finanční + self.assertAlmostEqual(v["F"], 2700.0, 2) # čistá změna PP + self.assertAlmostEqual(v["P"], 0.0, 2) # stav na začátku + self.assertAlmostEqual(v["R"], 2700.0, 2) # stav na konci + self.assertAlmostEqual(v["G"], 0.0, 2) # kontrola P+F−R=0 + cf.action_export_xml() # renders, no official XSD + self.assertEqual(cf.state, "exported") + root = etree.fromstring(base64.b64decode(cf.xml_attachment_id.datas)) + self.assertEqual(etree.QName(root).localname, "PrehledPT") + + def test_changes_in_equity_reconciles(self): + """Přehled o změnách vlastního kapitálu: počátek + Σ změny = konec + (incl. the current-period result); G = 0 by construction.""" + bank = self._acc("221%") + zk = self._acc("411%") + + def entry(date, lines): + self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.misc.id, "date": date, + "line_ids": [Command.create(ln) for ln in lines]}).action_post() + + # capital increase (+10000 základní kapitál) + entry("2025-01-15", [{"account_id": bank.id, "debit": 10000.0}, + {"account_id": zk.id, "credit": 10000.0}]) + + eq = self._statement(self.env.ref("l10n_cz_fs.equity_changes_version_2025")) + v = {l.code: l.current_value for l in eq.line_ids} + self.assertAlmostEqual(v["ZK"], 10000.0, 2) # základní kapitál + self.assertAlmostEqual(v["VHB"], 2000.0, 2) # VH běžného období (setUp) + self.assertAlmostEqual(v["ZMENY"], 12000.0, 2) + self.assertAlmostEqual(v["POC"], 0.0, 2) # počátek + self.assertAlmostEqual(v["KON"], 12000.0, 2) # konec (incl. result) + self.assertAlmostEqual(v["G"], 0.0, 2) # kontrola A+B−C=0 + eq.action_export_xml() + root = etree.fromstring(base64.b64decode(eq.xml_attachment_id.datas)) + self.assertEqual(etree.QName(root).localname, "PrehledVK") + + def test_opravna_zaverka_amendment(self): + """Opravná účetní závěrka: submit a Rozvaha, raise an amendment — it + links back to the original, is flagged Opravná, keeps the filed copy, + and recomputes + exports with the corrective marker in the header.""" + st = self._statement(self.bs_version) + self.assertEqual(st.submission_type, "radna") + st.action_export_xml() + st.submission_reference = "FS-CZ-2025-001" + st.action_submit() + self.assertEqual(st.state, "submitted") + filed = st.submitted_attachment_id + + action = st.action_create_amendment() + amend = self.env["cssk.fs.statement"].browse(action["res_id"]) + # linked, flagged, fresh draft without a carried-over filed XML + self.assertEqual(amend.original_return_id, st) + self.assertEqual(amend.submission_type, "opravna") + self.assertEqual(amend.state, "draft") + self.assertFalse(amend.xml_attachment_id) + # the suffix's source msgid is English "(corrective)" — asserting the + # Czech rendering only works with cs_CZ loaded + active + self.assertIn("corrective", amend.name) + self.assertEqual(amend.amendment_ids, self.env["cssk.fs.statement"]) + self.assertIn(amend, st.amendment_ids) + # original is untouched: still submitted, filed copy preserved + self.assertEqual(st.state, "submitted") + self.assertEqual(st.submitted_attachment_id, filed) + + # the amendment is a full restatement that still reconciles + exports + amend.action_compute_lines() + self.assertAlmostEqual(self._v(amend, "AKTIVA"), + self._v(amend, "PASIVA"), places=2) + amend.action_export_xml() + root = etree.fromstring(base64.b64decode(amend.xml_attachment_id.datas)) + self.assertEqual(root.findtext(".//TypZaverky"), "opravna") diff --git a/l10n_cz_kh/tests/test_cz_kh.py b/l10n_cz_kh/tests/test_cz_kh.py index 34fb462d..9a768087 100644 --- a/l10n_cz_kh/tests/test_cz_kh.py +++ b/l10n_cz_kh/tests/test_cz_kh.py @@ -729,3 +729,13 @@ def test_mixed_direction_entry_is_left_unclassified(self): move.action_post() mixed = move.line_ids.filtered(lambda line: len(line.tax_ids) > 1) self.assertFalse(mixed.cssk_control_section_code) + + + def test_a_czech_row_keeps_whitespace_in_its_identity(self): + """The Czech KH writes ``c_evid_dd`` as stored, so whitespace can tell + two documents apart there. Only the Slovak form, which cannot carry + it, identifies its rows without it.""" + Row = self.env["l10n.cz.kh.a4"] + self.assertNotEqual( + Row.new({"partner_vat": "CZ25856294", "entry_ref": "AB 12"})._kv_identity(), + Row.new({"partner_vat": "CZ25856294", "entry_ref": "AB12"})._kv_identity()) diff --git a/l10n_sk_account_asset_profile/CHANGELOG.rst b/l10n_sk_account_asset_profile/CHANGELOG.rst index 457888e5..3a8ba7bc 100644 --- a/l10n_sk_account_asset_profile/CHANGELOG.rst +++ b/l10n_sk_account_asset_profile/CHANGELOG.rst @@ -1,6 +1,16 @@ Changelog ========= +19.0.1.1.0 (2026-10-04) +----------------------- + +* A Slovak company whose chart was loaded BEFORE this module was installed + now gets the profiles too, on install (``post_init_hook``) and on upgrade + (migration 19.0.1.1.0). The template only applies when the chart is loaded, + so such a company used to get none. Only missing profiles are loaded, so an + edited useful life is never reverted; a company missing an account the + template names is skipped with a warning rather than failing the upgrade. + 19.0.1.0.0 (2026-09-21) ----------------------- diff --git a/l10n_sk_account_asset_profile/__init__.py b/l10n_sk_account_asset_profile/__init__.py index 0650744f..cc6b6354 100644 --- a/l10n_sk_account_asset_profile/__init__.py +++ b/l10n_sk_account_asset_profile/__init__.py @@ -1 +1,2 @@ from . import models +from .hooks import post_init_hook diff --git a/l10n_sk_account_asset_profile/__manifest__.py b/l10n_sk_account_asset_profile/__manifest__.py index 804fc573..777fcd08 100644 --- a/l10n_sk_account_asset_profile/__manifest__.py +++ b/l10n_sk_account_asset_profile/__manifest__.py @@ -53,9 +53,10 @@ "author": "Data Dance s.r.o.", "website": "https://www.datadance.eu", "category": "Accounting/Localizations/Account Charts", - "version": "19.0.1.0.0", + "version": "19.0.1.1.0", "license": "AGPL-3", "depends": ["account_asset_tax_oca", "l10n_sk"], + "post_init_hook": "post_init_hook", "installable": True, "auto_install": False, } diff --git a/l10n_sk_account_asset_profile/hooks.py b/l10n_sk_account_asset_profile/hooks.py new file mode 100644 index 00000000..97352084 --- /dev/null +++ b/l10n_sk_account_asset_profile/hooks.py @@ -0,0 +1,56 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import logging + +_logger = logging.getLogger(__name__) + + +def post_init_hook(env): + """Give the starter profiles to SK companies that already had the chart. + + The profiles are chart-template data (``@template("sk", + "account.asset.profile")``), and a template is only applied when the chart + is LOADED. Installed afterwards — the ordinary case for a company that + exists before anyone needs fixed assets — the module created nothing at + all, and every asset class an import tried to map had no profile to land + on. Measured on an i6 migration: 0 profiles, 8 asset classes blocking the + load until the template was loaded by hand. + + Same shape as ``l10n_sk_account_move_template``'s hook, for the same + reasons: the chart-template loader keeps xmlids, company prefixing and + account dereferencing identical to a fresh load, and only the profiles a + company lacks are loaded — ``_load_data`` rewrites an existing record, so + reloading would revert the useful lives an accountant has already set. + + Runs sudo: a post-init hook must work whatever companies the installing + user happens to belong to. + """ + companies = env["res.company"].search([("chart_template", "=", "sk")]) + chart_template = env["account.chart.template"].sudo() + for company in companies: + loader = chart_template.with_company(company) + missing = { + xmlid: vals + for xmlid, vals in loader._get_sk_account_asset_profile().items() + if not loader.ref(xmlid, raise_if_not_found=False) + } + if not missing: + continue + try: + # A company that deleted or renumbered an account the template + # names (012000, 072000, 551000 …) cannot take these profiles as + # shipped. That is no reason to abort an upgrade, which is where + # this also runs: skip it and say so. + with env.cr.savepoint(): + loader._load_data({"account.asset.profile": missing}) + except Exception as exc: # noqa: BLE001 - reported, not swallowed + _logger.warning( + "SK asset profiles NOT loaded for company %s: %s", + company.name, exc) + continue + _logger.info( + "SK asset profiles: %s loaded for company %s", + len(missing), + company.name, + ) diff --git a/l10n_sk_account_asset_profile/migrations/19.0.1.1.0/post-migration.py b/l10n_sk_account_asset_profile/migrations/19.0.1.1.0/post-migration.py new file mode 100644 index 00000000..a65abdd2 --- /dev/null +++ b/l10n_sk_account_asset_profile/migrations/19.0.1.1.0/post-migration.py @@ -0,0 +1,12 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo import SUPERUSER_ID, api + +from odoo.addons.l10n_sk_account_asset_profile.hooks import post_init_hook + + +def migrate(cr, version): + """Installed before 19.0.1.1.0 on a company whose chart was already + loaded, the module created no profiles; give them now. Idempotent.""" + post_init_hook(api.Environment(cr, SUPERUSER_ID, {})) diff --git a/l10n_sk_account_asset_profile/tests/test_sk_asset_profile.py b/l10n_sk_account_asset_profile/tests/test_sk_asset_profile.py index 918198fa..d170c864 100644 --- a/l10n_sk_account_asset_profile/tests/test_sk_asset_profile.py +++ b/l10n_sk_account_asset_profile/tests/test_sk_asset_profile.py @@ -126,3 +126,28 @@ def test_an_asset_can_actually_be_created_from_one(self): self.assertEqual(asset.method_number, 6, "the profile's duration must reach the asset") self.assertEqual(profile.account_depreciation_id.code, "082000") + + def test_a_company_that_had_its_chart_first_gets_them_once(self): + """Installed after the chart, the module used to create nothing: the + template only applies when the chart is LOADED. The hook gives them + to the company, and running it again changes nothing.""" + from odoo.addons.l10n_sk_account_asset_profile.hooks import post_init_hook + + Profile = self.env["account.asset.profile"].with_company(self.company) + data = self.env["ir.model.data"].search([ + ("model", "=", "account.asset.profile"), + ("res_id", "in", self.profiles.ids)]) + self.profiles.unlink() + data.unlink() + self.assertFalse(Profile.search([("company_id", "=", self.company.id)])) + + post_init_hook(self.env) + restored = Profile.search([("company_id", "=", self.company.id)]) + self.assertEqual(len(restored), len(SK_ASSET_PROFILES)) + + restored[:1].method_number = 9 + post_init_hook(self.env) + again = Profile.search([("company_id", "=", self.company.id)]) + self.assertEqual(again, restored, "idempotent") + self.assertEqual(restored[:1].method_number, 9, + "an accountant's edit is not reverted") diff --git a/l10n_sk_base/hooks.py b/l10n_sk_base/hooks.py index 62adbc79..cf689b96 100644 --- a/l10n_sk_base/hooks.py +++ b/l10n_sk_base/hooks.py @@ -11,7 +11,7 @@ ``res_company.l10n_sk_dic`` A **plain stored Char** declared independently by - ``l10n_sk_hr_payroll_prehlad`` and ``l10n_sk_hr_payroll_hlasenie``, each to + ``l10n_sk_hr_payroll_monthly_tax_overview`` and ``l10n_sk_hr_payroll_annual_tax_report``, each to be self-contained. Install one of those without this module and an accountant fills a real column; install this module afterwards and the merged field definition acquires ``related=``, the ORM stops reading that diff --git a/l10n_sk_base/tests/test_sk_base.py b/l10n_sk_base/tests/test_sk_base.py index 7b04a0a5..1dda39f8 100644 --- a/l10n_sk_base/tests/test_sk_base.py +++ b/l10n_sk_base/tests/test_sk_base.py @@ -56,7 +56,7 @@ def test_a_contact_inherits_the_company_dic(self): self.assertEqual(child.l10n_sk_dic, "2020317068") def test_the_payroll_declarations_read_one_field(self): - """l10n_sk_hr_payroll_prehlad and _hlasenie used to declare + """l10n_sk_hr_payroll_monthly_tax_overview and _hlasenie used to declare res.company.l10n_sk_dic themselves, as a plain stored Char. Three definitions of one field is how a value ends up in a column the ORM has stopped reading. Assert the field is related, i.e. not stored.""" diff --git a/l10n_sk_cash_journal/CHANGELOG.rst b/l10n_sk_cash_journal/CHANGELOG.rst index 53fa6e3a..e0630395 100644 --- a/l10n_sk_cash_journal/CHANGELOG.rst +++ b/l10n_sk_cash_journal/CHANGELOG.rst @@ -8,6 +8,18 @@ Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Cha [Unreleased] ------------ +[19.0.1.1.0] — 2026-10-04 +------------------------- + +Added +~~~~~ + +- **The spreadsheet opens with the peňažný denník in its statutory layout**: + pokladnica, banka, priebežné položky and DPH, each with príjem and výdaj, then + the breakdown columns and the running balances. It uses the same figures as + the PDF, read from the same computation. The flat sheet for pivoting + follows. Asked for by a customer's accountant. + [19.0.1.0.4] — 2026-09-28 ------------------------- diff --git a/l10n_sk_cash_journal/__manifest__.py b/l10n_sk_cash_journal/__manifest__.py index 7e8c4a28..a528087c 100644 --- a/l10n_sk_cash_journal/__manifest__.py +++ b/l10n_sk_cash_journal/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Slovakia — Peňažný denník (jednoduché účtovníctvo / daňová evidencia)", - "version": "19.0.1.0.4", + "version": "19.0.1.1.0", "summary": "Slovak cash journal: the statutory členenie of opatrenie " "MF/27076/2007-74, the peňažný denník in its official column " "layout, and the DPFO typ B tabuľka 1 / 1a figures.", diff --git a/l10n_sk_cash_journal/models/__init__.py b/l10n_sk_cash_journal/models/__init__.py index 822a4489..6979c1c3 100644 --- a/l10n_sk_cash_journal/models/__init__.py +++ b/l10n_sk_cash_journal/models/__init__.py @@ -1,2 +1,3 @@ from . import l10n_sk_cash_dennik from . import res_company +from . import l10n_sk_cash_dennik_xlsx diff --git a/l10n_sk_cash_journal/models/l10n_sk_cash_dennik_xlsx.py b/l10n_sk_cash_journal/models/l10n_sk_cash_dennik_xlsx.py new file mode 100644 index 00000000..7cbef47a --- /dev/null +++ b/l10n_sk_cash_journal/models/l10n_sk_cash_dennik_xlsx.py @@ -0,0 +1,96 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import models + +#: The money columns of the statutory grid, in the PDF's order: +#: (heading, príjem key, výdaj key). +_MONEY_GROUPS = ( + ("Pokladnica", "pokladnica_prijem", "pokladnica_vydaj"), + ("Banka", "banka_prijem", "banka_vydaj"), + ("Priebežné položky", "priebezne_prijem", "priebezne_vydaj"), + ("DPH", "dph_prijem", "dph_vydaj"), +) + + +class CsskCashJournalXlsx(models.AbstractModel): + """The peňažný denník as the first sheet of the spreadsheet. + + The same grid and the same figures as the PDF — read from + ``_sk_dennik_values``, which the PDF renders, so the two cannot disagree: + pokladnica, banka, priebežné položky and DPH each split into príjem and + výdaj, the income and expense breakdown, and the running balances. + + Asked for by a customer's accountant after reviewing the PDF ("Zvlášť stĺpce + pre príjem a výdaj v pokladni a zvlášť príjem a výdaj v banke. Tak ako je + to v tom pdf."). The flat sheet stays after it: she pivots that one. + Headings are the statute's own terms, in Slovak, as on the PDF. + """ + + _inherit = "report.l10n_cssk_cash_journal_base.dennik_xlsx" + + def _cssk_write_national(self, workbook, wizard, formats): + super()._cssk_write_national(workbook, wizard, formats) + if wizard.company_id.country_id.code != "SK": + return + values = self.env["report.l10n_sk_cash_journal.report_penazny_dennik"] \ + ._sk_dennik_values(wizard.company_id, wizard.date_from, wizard.date_to) + bold, money, date_fmt = formats["bold"], formats["money"], formats["date"] + bold_money = workbook.add_format({"bold": True, "num_format": "#,##0.00"}) + head = workbook.add_format({"bold": True, "align": "center", + "valign": "vcenter", "text_wrap": True}) + sheet = workbook.add_worksheet("Peňažný denník") + + # Two header rows: a group over its príjem / výdaj pair, every other + # heading spanning both rows. + col = 0 + for heading in ("Dátum", "Číslo", "Doklad", "Text"): + sheet.merge_range(0, col, 1, col, heading, head) + col += 1 + money_cols = [] + for heading, key_in, key_out in _MONEY_GROUPS: + sheet.merge_range(0, col, 0, col + 1, heading, head) + sheet.write(1, col, "príjem", head) + sheet.write(1, col + 1, "výdaj", head) + money_cols += [(col, key_in), (col + 1, key_out)] + col += 2 + breakdown_cols = [] + for key, heading, _codes in values["columns"]: + sheet.merge_range(0, col, 1, col, heading, head) + breakdown_cols.append((col, key)) + col += 1 + cash_col, bank_col = col, col + 1 + sheet.merge_range(0, cash_col, 1, cash_col, "Zostatok pokladnica", head) + sheet.merge_range(0, bank_col, 1, bank_col, "Zostatok banka", head) + sheet.set_row(0, 30) + sheet.set_column(0, 0, 11) + sheet.set_column(1, 2, 14) + sheet.set_column(3, 3, 30) + sheet.set_column(4, bank_col, 13) + sheet.freeze_panes(2, 4) + + row = 2 + sheet.write(row, 3, "Počiatočný stav", bold) + sheet.write_number(row, cash_col, values["opening_cash"], bold_money) + sheet.write_number(row, bank_col, values["opening_bank"], bold_money) + + for line in values["lines"]: + row += 1 + record = line["row"] + if record.date: + sheet.write_datetime(row, 0, record.date, date_fmt) + sheet.write(row, 1, record.number or "") + sheet.write(row, 2, record.ref or "") + sheet.write(row, 3, record.label or "") + for column, key in money_cols + breakdown_cols: + if line["cells"][key]: + sheet.write_number(row, column, line["cells"][key], money) + sheet.write_number(row, cash_col, line["cash"], money) + sheet.write_number(row, bank_col, line["bank"], money) + + row += 1 + sheet.write(row, 3, "Spolu", bold) + for column, key in money_cols + breakdown_cols: + sheet.write_number(row, column, values["totals"][key], bold_money) + sheet.write_number(row, cash_col, values["closing_cash"], bold_money) + sheet.write_number(row, bank_col, values["closing_bank"], bold_money) diff --git a/l10n_sk_cash_journal/tests/test_sk_cash_journal.py b/l10n_sk_cash_journal/tests/test_sk_cash_journal.py index dc0415ca..ecfa1d61 100644 --- a/l10n_sk_cash_journal/tests/test_sk_cash_journal.py +++ b/l10n_sk_cash_journal/tests/test_sk_cash_journal.py @@ -427,3 +427,45 @@ def test_a_flat_rate_payer_gets_tabulka_1b(self): self.assertNotIn("t1a", tables) codes = {line.code for line in wizard.line_ids if line.table == "t1b"} self.assertEqual(codes, {"r3", "r4"}) + + # -- the statutory sheet of the spreadsheet ------------------------ + def test_the_spreadsheet_opens_with_the_statutory_grid(self): + """"Zvlášť stĺpce pre príjem a výdaj v pokladni a zvlášť príjem a + výdaj v banke. Tak ako je to v tom pdf." Same figures as the PDF.""" + import io + + from openpyxl import load_workbook + + self._pay(self._invoice(amount=1000.0)) + self._pay(self._invoice(move_type="in_invoice", amount=200.0, + account=self.expense_account), + journal=self.cash_journal) + self._generate() + wizard = self.env["cssk.cash.journal.print"].create({ + "company_id": self.company.id, "output": "xlsx", + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + content, _ext = self.env["ir.actions.report"]._render_xlsx( + "l10n_cssk_cash_journal_base.dennik_xlsx", wizard.ids, data=None) + book = load_workbook(io.BytesIO(content), data_only=True) + self.assertEqual(book.sheetnames[0], "Peňažný denník") + self.assertIn("Cash journal", " ".join(book.sheetnames[1:]), + "the flat sheet for pivoting stays") + sheet = book.worksheets[0] + header = [c.value for c in sheet[1]] + sub = [c.value for c in sheet[2]] + for group in ("Pokladnica", "Banka", "Priebežné položky", "DPH"): + col = header.index(group) + self.assertEqual(sub[col:col + 2], ["príjem", "výdaj"], group) + values = self.env["report.l10n_sk_cash_journal.report_penazny_dennik"] \ + ._sk_dennik_values(self.company, fields.Date.to_date("2026-01-01"), + fields.Date.to_date("2026-12-31")) + total_row = [c.value for c in list(sheet.rows)[-1]] + self.assertEqual(total_row[3], "Spolu") + self.assertAlmostEqual(total_row[header.index("Banka")], + values["totals"]["banka_prijem"], 2) + self.assertAlmostEqual(total_row[header.index("Pokladnica") + 1], + values["totals"]["pokladnica_vydaj"], 2) + self.assertAlmostEqual(values["totals"]["banka_prijem"], 1000.0, 2) + self.assertAlmostEqual(values["totals"]["pokladnica_vydaj"], 200.0, 2) + self.assertAlmostEqual(total_row[-1], values["closing_bank"], 2) + diff --git a/l10n_sk_dppo/CHANGELOG.rst b/l10n_sk_dppo/CHANGELOG.rst new file mode 100644 index 00000000..05c508d7 --- /dev/null +++ b/l10n_sk_dppo/CHANGELOG.rst @@ -0,0 +1,346 @@ +========= +Changelog +========= + +All notable changes to **l10n_sk_dppo** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +[19.0.1.4.3] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- **Code translations that Odoo was never loading.** An entry whose references + are ``code:addons/...`` is treated as a Python translation only if it carries + the extracted comment ``#. odoo-python`` — ``_load_python_translations`` + filters on exactly that and never on the reference. Without it an entry can + name the right ``.py``, carry a correct msgstr, pass ``msgfmt --check``, and + be silently ignored for ever. This module's hand-added entries were in that + state. Repaired by ``tools/fix_po_code_comments.py``, which is also the CI + check; the offline exporter now emits the comment itself. + +[19.0.1.4.2] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- **Terms the translation catalogue never carried.** Strings that reach the + user were absent from the template — a field with no ``string=``, a mixin + field addressed to the abstract model's ``ir.model.fields`` row rather + than each concrete one, or a whole category the offline exporter never + emitted. An absent term cannot be translated and shows English in an + otherwise Slovak screen. The template and the catalogues now carry them, + and the Slovak is written. + +[19.0.1.4.1] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- Slovak catalogue completed. Terms already stated in Slovak in the source are + deliberately left untranslated — the source IS the translation, per the + hybrid convention for these modules — so an empty ``msgstr`` there is correct + rather than missing. + +[19.0.1.4.0] — 2026-09-10 +------------------------- + +Fixed +~~~~~ + +- **The kladný rozdiel was filed on the wrong row for 2017–2019.** The spine + wrote ``r820 = r810`` on every vintage. That is right for the minimálna daň + vzory and wrong for the daňová licencia ones, because the form **reused the + row number**: verified against the pinned FS SR schemas, 2017/2018/2019 + define r800/r810/r820 and **no r830**, while 2013–2015 and 2024+ define r830 + as well. Finančná správa's own usmernenie (20. 12. 2018) prints the + licencia vzor's worked example — r800 600, r810 960, **r820 360** — i.e. + ``r810 − r800``, not ``r810``. + + The spine now routes on whether the vzor defines r830. 2013–2015 keep their + existing behaviour: they do define r830, their block is four rows, and + there is no source here for how it differs. + +Added +~~~~~ + +- **§ 46b daňová licencia bands on 2014–2019.** ``min_tax_bands`` was + populated only on 2024, so r810 computed 0,00 for every licencia year. + 480 € / 960 € / 2 880 €, verified against the usmernenie quoting § 46b + ods. 2 verbatim. + +- **Bands may carry a condition.** 480 and 960 sit at the *same* turnover + bound and are separated by whether the taxpayer was a platiteľ DPH on the + last day of the period — which a table keyed on turnover alone cannot say. + A band is now ``[upper, amount]`` or ``[upper, amount, condition]``; + unknown conditions never match, so a band nobody can evaluate is skipped + rather than silently applied. Backward compatible: the 2024/2025 tables + carry no conditions and are untouched. + + Platiteľ status is read from ``l10n_sk_vat_registration`` when installed + (it records the registration *and* its effective date, which is what the + statute asks about) and otherwise falls back to whether the company carries + a VAT number — an approximation that errs downwards, into the 480 band. + +Known limitations +~~~~~~~~~~~~~~~~~ + +- **The licencia vzory have no r560**, so there is no turnover row on the form + to key the band on — that row arrived with the 2020 vzor for the § 15 + reduced-rate test. The band falls back to **class 6 revenue**, which is + literally r560's own ``account_formula`` on the later form. That is a + reading rather than a citation, and it is documented at the call site. + Override r810 where the taxpayer's ročný obrat differs. + +- **§ 46b ods. 3** (half the licencia where disabled employees are ≥ 20 % of + the average headcount) and **ods. 7** (exemption) remain accountant + overrides; the module models neither. + +[19.0.1.3.0] — 2026-09-10 +------------------------- + +Added +~~~~~ + +- **``_cssk_derived_codes()`` for the SK tax spine.** The nineteen rows + ``_compute_tax_spine`` writes — r200, r300, r301, r310, r400, r500, r510, + r550, r600, r700, r800, r810, r820, r830, r900, r1050, r1080, r1100, r1101 — + intersected with what each vzor actually defines, because r830 exists only + on 2013–2015 and 2024+, and r810/r820 vanish across 2020–2022 with the + daňová licencia. + + The set is declared once as ``_SPINE_ROWS`` and **``put()`` raises on a code + that is not in it**, so the declaration cannot fall behind the spine. A + raise rather than an assert: ``python -O`` strips asserts, and a guard that + disappears under a flag is not a guard. + + This exists so a consumer does not copy the set. Until now it lived only + inside ``_compute_tax_spine``, and the only way for a legacy-import bridge + to have it was to duplicate it — which is the same defect + ``l10n_sk_vat_return`` 19.0.1.11.0 had to repair in its own migration. + + A test asserts that the derived rows are all ``kind='manual'``: the fact that + makes the hook necessary. If ``kind`` ever learns to express "derived", that + test fails and the hook can be retired instead of lingering as a second way + to ask one question. + +[19.0.1.2.1] — 2026-09-10 +------------------------- + +Fixed +~~~~~ + +- **Two 1.2.0 labels in the 800/900 block were wrong.** r820 was labelled + "Daňová licencia na úhradu" and r900 "Suma na účely určenia výšky + preddavkov" on the licencia-era vzory. Both were extrapolated from the + DPPOv24/v25 computation spine onto vzory it does not describe. Finančná + správa SR's own *Usmernenie k započítaniu daňovej licencie v súlade s § 46b + ods. 5 a § 52zk* (20. 12. 2018) prints those captions:: + + r. 800 – Daň po úľavách a po zápočte dane + r. 810 – Daňová licencia + r. 820 – Kladný rozdiel medzi daňovou licenciou a daňou určený na zápočet + r. 900 – Daňová licencia na úhradu + + **The form reused those row numbers between the two eras** — r820 is the + kladný rozdiel under the licencia and r900 is the amount payable, where the + minimálna daň vzory put the rozdiel on r830 and use r900 for the preddavky + base. So an extrapolation that is safe everywhere else on this form is not + safe here. + + r800/r810/r820/r830/r900 are now labelled **only where a source covers that + vzor** — 2017–2019 from the usmernenie, 2024–2025 from the spine's DPPOv25 + citations — and are bare on 2013–2015 and 2020–2022. Net effect: 142 + labelled line definitions rather than 1.2.0's 155, three of which were + wrong. + + 1.2.0's migration cannot repair this: it only fills a name still equal to + its code, by design, and a wrong label is not a bare one. All eleven vintage + data files are ``noupdate="1"``, so a reload does not reach them either. + A ``post-migration`` therefore **forces** the 800/900 block to match the data + files, bare included, and touches nothing outside that block. + + The test changed with it: "labelled on one vzor, labelled on all" now holds + only for rows outside the reused block, and a new test refuses an era's + wording on the other era's vzor. + +[19.0.1.2.0] — 2026-09-10 +------------------------- + +Added +~~~~~ + +- **Labels on the load-bearing DPPO rows.** Every one of the 941 line + definitions across the eleven vzory carried ``name`` equal to ``code``, so a + mapping onto a DPPO row could be checked *structurally* and never + *semantically* — a row that could not be corroborated from a second source + was unreviewable by anyone, including us. + + 17 codes now carry the statutory row name (155 line definitions in all): + r100, r200, r300, r301, r310, r400, r550, r560, r810, r820, r830, r900, + r1040, r1050, r1080, r1100, r1101. + + **§ 46b is named for its era** — *daňová licencia* on the 2013–2019 vzory, + *minimálna daň* from 2024. Same paragraph, different levy, and r810/r820/r830 + exist in exactly those years while being absent from 2020, 2021 and 2022: + the abolition visible in the form itself. + + Every label is **sourced, not inferred** — from the computation spine and its + comments in ``models/cssk_income_tax.py`` (which cite the DPPOv24/v25 eForm + routines and the DPPOv25 poučenie by row), from ``provision_line_code``'s + help, and from this changelog for r100. The remaining rows stay unlabelled on + purpose: there is no source for their names here, and an invented statutory + row name reads as authoritative, which is worse than a blank. + + The test is the invariant — a label on one vzor is a label on all of them, so + a new vintage generated from its XSD (which arrives with ``name`` equal to + ``code`` on every row) fails rather than quietly losing what the others have. + + A ``post-migration`` carries the labels to existing databases, reading them + out of the data files rather than repeating them. Needed: the 2025 file is + ``noupdate="1"``, and on the validation DB the data reload covered 121 of the + 155 — the migration supplied the other 34. + +Carry-over to 18.0 +~~~~~~~~~~~~~~~~~~ + +- **The nine pre-2024 DPPO vzory (19.0.1.1.0) are owed to 18.0**, together + with the switch of 2025 to ``dppo2025_v2.xsd``. The generators + (``tools/gen_dppo_template.py``, ``tools/gen_dppo_version.py``) read only the + official XSDs, so the same nine templates and version records regenerate on + 18.0 unchanged; ``values_int`` in the render context goes with them + (l10n_cssk_income_tax_base). Carry the empty ``rate_bands`` for 2015-2019 + and the reason with it — do not fill them on 18.0 either. + +[19.0.1.1.0] — 2026-09-09 +------------------------- + +Added +~~~~~ + +- **The nine DPPO vzory before 2024**, so a period earlier than 2024 resolves + to a version at all. It previously resolved to none — not a wrong figure but + NO figure — which matters most for a dodatočné priznanie, the case where an + old period comes back. + + :: + + dppo2013.xsd 2013 dppo2019.xsd 2019 + dppo2014.xsd 2014 dppo2020.xsd 2020 + dppo2015.xsd 2015-2016 dppo2021.xsd 2021 + dppo2017.xsd 2017 dppo2022.xsd 2022-2023 + dppo2018.xsd 2018 + + **2016 and 2023 have no vzor of their own** and are filed on the preceding + one — confirmed by the 2015 and 2022 poučenia, each of which ships separate + instructions for both years in one package. The eleven records now tile + every year from 2013 with no gap and no overlap. + +- Templates and version records are GENERATED from each vzor's own schema + (``tools/gen_dppo_template.py``, ``tools/gen_dppo_version.py``). Element + order, nesting, the row set and each field's type come from the XSD, and an + export test validates the result against the same file. The row NUMBERING is + stable across DPPO vintages — r100, r400 and r500 say the same thing in the + 2013 and 2022 poučenia — so the four computed rows carry forward unchanged + and the rest are manual, as they are on the current form. + +- ``values_int`` in the render context. Not every row is money: the older + vzory carry counts and years typed as an integer union, and ``"0.00"`` is + not a valid value of one. + +Changed +~~~~~~~ + +- **2025 now files against ``dppo2025_v2.xsd``.** FS SR published a revision + under a NEW NAME beside an unchanged ``dppo2025.xsd``: v2 adds a repeatable + ``dalsiaTransakcia`` and is otherwise the same file. A digest check on the + pinned name could never have found it, so the refresh procedure now probes + for a ``_v2`` suffix too. Being a superset, v2 accepts every document the + original did. A migration moves an existing database. + +Rate bands +~~~~~~~~~~ + +- **The § 15 písm. b) rate is now on every vintage that has a rate row**: + 22 % for 2015-2016, 21 % from 1. 1. 2017 and unchanged through 2019, after + which the 15 % reduced band arrives on 1. 1. 2020. 2013 and 2014 need none — + those vzory have no rate row at all. + + For 2020 onwards the figure comes from the vzor's OWN poučenie, which states + it at riadok 550. For 2015-2019 the vzor does not state it, and that is + deliberate on FS SR's part: r550 "Sadzba dane (v %)" is an ENTRY field and + the poučenie says only "sadzba dane podľa § 15 písm. b) prvého bodu zákona", + because a hospodársky rok can straddle a rate change and no single rate is + right for a vzor — only for a period. Those four figures are therefore + sourced separately and the source is named in + ``tools/gen_dppo_version.py``; it was cross-checked on 2020, the year where + it overlaps the official poučenie, and agrees word for word. + + A trade publication is not the law. These are good enough to compute and + compare with; have an accountant confirm the rate for the year before a + period from it is FILED. + +Superseded limitation +~~~~~~~~~~~~~~~~~~~~~ + +- **``rate_bands`` was deliberately empty for 2015-2019.** Those poučenia say + only "sadzba dane podľa § 15 písm. b)" — the rate was flat and the taxpayer + entered it — so nothing written down states it. The module already refuses + to compute a tax spine without bands, naming the version and saying what to + fill, and that is the right answer: a rate recited from memory computes + somebody's tax silently and wrongly. Fill it from § 15 for the year before + computing such a period. 2013 and 2014 need nothing: those vzory have no + rate row at all. 2020-2022 are filled from their own poučenia, which state + the bands at riadok 550. + +Changed +~~~~~~~ + +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.1.0.2] — 2026-07-04 +------------------------- + +Added +~~~~~ + +- r150 (pripočítateľná položka, book > tax) and r250 (odpočítateľná položka, tax > book) auto-fed from the asset board via the ``asset_diff`` line kind (2026-07-02). + +Changed +~~~~~~~ + +- §15 rate bands and §46b minimum-tax bands moved to version data (``rate_bands`` / ``min_tax_bands``), so a new DPPO vintage is pure data (Wave 3, 2026-07-03). +- Line spine guarded so custom/edited versions are not clobbered on upgrade. + +[2026-07-01] — Wave 1 (P0 correctness) +-------------------------------------- + +Fixed +~~~~~ + +- Multi-company leak closed with an ``ir.rule [('company_id','in',company_ids)]``. + +Changed +~~~~~~~ + +- Statutory rounding unified on ``statutory_round()`` (HALF-UP). + +[2026-06-30] — Initial baseline +------------------------------- + +Added +~~~~~ + +- Slovak DPPO return (**DPPOv25**, with DPPOv24 also shipped) on ``l10n_cssk_income_tax_base``: full body line set r100 … r1192 as version data; r100 (výsledok hospodárenia pred zdanením) computed from the P&L, the rest via the override UI. +- Header mapping from the company (DIČ, IČO, obchodné meno, sídlo, zdaňovacie obdobie, typ priznania). +- Official ``dppo2025.xsd`` vendored; XML export validated against it. +- sk_SK translations (Odoo 19 jsonb) as part of the localization i18n sweep. diff --git a/l10n_sk_dppo/LICENSE b/l10n_sk_dppo/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/l10n_sk_dppo/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/l10n_sk_dppo/README.rst b/l10n_sk_dppo/README.rst new file mode 100644 index 00000000..cb45d366 --- /dev/null +++ b/l10n_sk_dppo/README.rst @@ -0,0 +1,44 @@ +===================================== +SK Corporate Income Tax Return (DPPO) +===================================== + +The Slovak country layer for the income-tax framework +(``l10n_cssk_income_tax_base``). It provides the **DPPOv25** form — daňové +priznanie k dani z príjmov právnických osôb (DPPO): + +* The full body line set (r100 … r1192) as version data. ``r100`` (výsledok + hospodárenia pred zdanením) is computed from the P&L; the remaining lines are + entered by the accountant via the override UI. +* Header mapping from the company (DIČ, IČO, obchodné meno, sídlo, zdaňovacie + obdobie, typ priznania). +* The official **dppo2025.xsd** is vendored and the XML export is validated + against it. + +Features +======== + +* DPPOv25 body line set (r100 … r1192) as version data. +* ``r100`` auto-computed from the accounting result; the rest entered by the + accountant. +* Company header mapping (DIČ, IČO, obchodné meno, sídlo, obdobie, typ priznania). +* XSD-validated XML export against the official ``dppo2025`` schema. + +Usage +===== + +*Accounting ▸ Reporting ▸ Slovakia ▸ DPPO.* Create a return for the tax period, +review/enter the body lines (r100 is filled from the P&L), then **Export XML** — +the export is validated against ``dppo2025.xsd`` before download. + +**Scope note (v1):** the module auto-computes the accounting result and produces +schema-valid XML. The accounting→tax transformation (adjustment lines, loss +deduction, tax credits, minimum tax) is the accountant's domain and is entered +manually; the inter-line computation formulas need accountant validation before +being automated. + +Credits +======= + +* Data Dance s.r.o. + +License: AGPL-3.0 or later (see the LICENSE file). diff --git a/l10n_sk_dppo/__init__.py b/l10n_sk_dppo/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_sk_dppo/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_sk_dppo/__manifest__.py b/l10n_sk_dppo/__manifest__.py new file mode 100644 index 00000000..cc981dea --- /dev/null +++ b/l10n_sk_dppo/__manifest__.py @@ -0,0 +1,58 @@ +{ + "name": "SK Corporate Income Tax Return (DPPO)", + "summary": "Slovak DPPO return on the income-tax framework: full r-line set " + "+ header mapping + XSD-validated XML export against the official " + "dppo2025 schema.", + "description": """ +SK Corporate Income Tax Return (DPPO) +===================================== + +The Slovak country layer for the income-tax framework +(``l10n_cssk_income_tax_base``). Provides the **DPPOv25** form: + +* The full body line set (r100 … r1192) as version data; ``r100`` (výsledok + hospodárenia pred zdanením) is computed from the P&L, the rest are entered by + the accountant (override UI). +* Header mapping from the company (DIČ, IČO, obchodné meno, sídlo, zdaňovacie + obdobie, typ priznania). +* The official **dppo2025.xsd** is vendored and the XML export is validated + against it. + +**Scope note:** v1 auto-computes the accounting result and produces a +schema-valid XML; the accounting→tax transformation (the adjustment lines, loss +deduction, tax credits, minimum tax) is the accountant's domain and is entered +manually. The inter-line computation formulas need accountant validation before +being automated. +""", + "author": "Data Dance s.r.o.", + "website": "https://www.datadance.eu", + "version": "19.0.1.4.3", + "category": "Accounting/Localizations", + "license": "AGPL-3", + "depends": ["l10n_cssk_income_tax_base", "l10n_sk", "l10n_sk_base"], + "data": [ + "report/dppo2013_report.xml", + "report/dppo2014_report.xml", + "report/dppo2015_report.xml", + "report/dppo2017_report.xml", + "report/dppo2018_report.xml", + "report/dppo2019_report.xml", + "report/dppo2020_report.xml", + "report/dppo2021_report.xml", + "report/dppo2022_report.xml", + "report/dppo2024_report.xml", + "report/dppo_report.xml", + "data/dppo_version_2013_data.xml", + "data/dppo_version_2014_data.xml", + "data/dppo_version_2015_data.xml", + "data/dppo_version_2017_data.xml", + "data/dppo_version_2018_data.xml", + "data/dppo_version_2019_data.xml", + "data/dppo_version_2020_data.xml", + "data/dppo_version_2021_data.xml", + "data/dppo_version_2022_data.xml", + "data/dppo_version_2024_data.xml", + "data/dppo_version_data.xml", + ], + "installable": True, +} diff --git a/l10n_sk_dppo/data/SCHEMA_VERSION b/l10n_sk_dppo/data/SCHEMA_VERSION new file mode 100644 index 00000000..e564a31a --- /dev/null +++ b/l10n_sk_dppo/data/SCHEMA_VERSION @@ -0,0 +1,54 @@ +dppo2013.xsd a9b6b732243fbcd608446fbbebded507 18595 +dppo2014.xsd 84464edde668674c06860303f26426a7 21874 +dppo2015.xsd 086ae2e34e947fb2e8be74b8f2c53b52 21925 +dppo2017.xsd 9afaf7d8e9c9a879f64677c2756ee776 25716 +dppo2018.xsd 1373d19e27111188225f6bac5551aea4 26198 +dppo2019.xsd 51c1de07921dadf4373c48fa370993fe 26784 +dppo2020.xsd 98c921d0df29a3ff26657b951c555a86 26829 +dppo2021.xsd 6b63a96bf02b0400b9e0697cf42dd99b 26306 +dppo2022.xsd 261d72919b0a15ef212bc3f357382c1e 25022 +dppo2024.xsd 2a78c0900e9aa994e2dc922582b78682 32135 +dppo2025.xsd 393c1c9aedface4e1540734edcf71d26 36884 +dppo2025_v2.xsd d0aa82a94a134fdfd227977b8eac5823 36238 + +Every file verified byte-identical to the published copy on 2026-09-09, +fetched from https://ekr.financnasprava.sk/Formulare/XSD/ + +TWELVE VZORY, AND TWO YEARS HAVE NONE OF THEIR OWN. Probed across +dppo2011..dppo2026 on 2026-09-09: the names above are all Finančná správa +publishes, and there is NO dppo2016 and NO dppo2023. That is not a gap in the +fetch — those years are filed on the preceding vzor, which the 2022 poučenie +confirms by shipping separate instructions for rok 2022 and rok 2023 in one +package. The version records say the same: + + dppo2013.xsd 2013 + dppo2014.xsd 2014 + dppo2015.xsd 2015 - 2016 + dppo2017.xsd 2017 + dppo2018.xsd 2018 + dppo2019.xsd 2019 + dppo2020.xsd 2020 + dppo2021.xsd 2021 + dppo2022.xsd 2022 - 2023 + dppo2024.xsd 2024 + dppo2025_v2.xsd 2025 + +A REVISION CAN ARRIVE UNDER A NEW NAME. dppo2025_v2.xsd sits beside an +UNCHANGED dppo2025.xsd and differs by one element: a repeatable +`dalsiaTransakcia` (further controlled transactions). A digest check on the +pinned name would report "no change" forever while the revision sat one URL +away. Both are kept here — v2 is what the 2025 version record files against, +because a superset accepts every document the original did — and the refresh +procedure now probes for the suffix. + +The 2018 and 2019 schemas are NOT interchangeable despite defining the same +row set: 2019 adds the `optInt2` type. Nothing in a finished document says +which vzor it was written for, which is why the validity windows are recorded +here and enforced in the version records rather than inferred from a file. + +REFRESH PROCEDURE + 1. Fetch each name above and compare the md5 with the line here. + 2. Check the STATUS CODE, not the size: a miss returns 404 with a + ~1 245-byte HTML error page, a perfectly plausible file size. + 3. Probe the NEXT year AND a `_v2` suffix on every name already pinned. + dppo2025_v2 is why; a year alone is not enough to find a revision. diff --git a/l10n_sk_dppo/data/dppo2013.xsd b/l10n_sk_dppo/data/dppo2013.xsd new file mode 100644 index 00000000..e835d11f --- /dev/null +++ b/l10n_sk_dppo/data/dppo2013.xsd @@ -0,0 +1,477 @@ + + + + + DPPO 2013 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2014.xsd b/l10n_sk_dppo/data/dppo2014.xsd new file mode 100644 index 00000000..a9c820e4 --- /dev/null +++ b/l10n_sk_dppo/data/dppo2014.xsd @@ -0,0 +1,561 @@ + + + + + DPPO 2014 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2015.xsd b/l10n_sk_dppo/data/dppo2015.xsd new file mode 100644 index 00000000..88b0786e --- /dev/null +++ b/l10n_sk_dppo/data/dppo2015.xsd @@ -0,0 +1,553 @@ + + + + + DPPO 2015 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2017.xsd b/l10n_sk_dppo/data/dppo2017.xsd new file mode 100644 index 00000000..ccf9012a --- /dev/null +++ b/l10n_sk_dppo/data/dppo2017.xsd @@ -0,0 +1,645 @@ + + + + + DPPO 2017 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2018.xsd b/l10n_sk_dppo/data/dppo2018.xsd new file mode 100644 index 00000000..dfc301c8 --- /dev/null +++ b/l10n_sk_dppo/data/dppo2018.xsd @@ -0,0 +1,656 @@ + + + + + DPPO 2018 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2019.xsd b/l10n_sk_dppo/data/dppo2019.xsd new file mode 100644 index 00000000..54d62d5b --- /dev/null +++ b/l10n_sk_dppo/data/dppo2019.xsd @@ -0,0 +1,671 @@ + + + + + DPPO 2019 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2020.xsd b/l10n_sk_dppo/data/dppo2020.xsd new file mode 100644 index 00000000..b1e1e7b0 --- /dev/null +++ b/l10n_sk_dppo/data/dppo2020.xsd @@ -0,0 +1,672 @@ + + + + + DPPOv20 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2021.xsd b/l10n_sk_dppo/data/dppo2021.xsd new file mode 100644 index 00000000..6ba05481 --- /dev/null +++ b/l10n_sk_dppo/data/dppo2021.xsd @@ -0,0 +1,662 @@ + + + + + DPPOv21 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2022.xsd b/l10n_sk_dppo/data/dppo2022.xsd new file mode 100644 index 00000000..cfc8db27 --- /dev/null +++ b/l10n_sk_dppo/data/dppo2022.xsd @@ -0,0 +1,626 @@ + + + + + DPPOv22 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2024.xsd b/l10n_sk_dppo/data/dppo2024.xsd new file mode 100644 index 00000000..83837527 --- /dev/null +++ b/l10n_sk_dppo/data/dppo2024.xsd @@ -0,0 +1,694 @@ + + + + + DPPOv24 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2025.xsd b/l10n_sk_dppo/data/dppo2025.xsd new file mode 100644 index 00000000..b7252a84 --- /dev/null +++ b/l10n_sk_dppo/data/dppo2025.xsd @@ -0,0 +1,771 @@ + + + + + DPPOv25 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo2025_v2.xsd b/l10n_sk_dppo/data/dppo2025_v2.xsd new file mode 100644 index 00000000..f453b18e --- /dev/null +++ b/l10n_sk_dppo/data/dppo2025_v2.xsd @@ -0,0 +1,773 @@ + + + + + DPPOv25 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_dppo/data/dppo_version_2013_data.xml b/l10n_sk_dppo/data/dppo_version_2013_data.xml new file mode 100644 index 00000000..0627f8f6 --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2013_data.xml @@ -0,0 +1,319 @@ + + + + + + DPPO 2013 + + 2013-01-01 + 2013-12-31 + + dokument + dppo2013.xsd + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r120 + r12030 + manual + + r130 + r13040 + manual + + r140 + r14050 + manual + + r150 + r15060 + asset_diffaddback + + r160 + r16070 + manual + + r170 + r17080 + manual + + r180 + r18090 + manual + + r200 + Pripočítateľné položky100 + manual + + r210 + r210110 + manual + + r220 + r220120 + manual + + r230 + r230130 + manual + + r240 + r240140 + manual + + r250 + r250150 + asset_diffdeduction + + r260 + r260160 + manual + + r270 + r270170 + manual + + r280 + r280180 + manual + + r290 + r290190 + manual + + r300 + Odpočítateľné položky200 + manual + + r310 + Základ dane210 + manual + + r320 + r320220 + manual + + r330 + r330230 + manual + + r400 + Základ dane po úpravách240 + manual + + r410 + r410250 + manual + + r500 + r500260 + manual + + r510 + r510270 + manual + + r600 + r600280 + manual + + r610 + r610290 + manual + + r700 + r700300 + manual + + r710 + r710310 + manual + + r800 + r800320 + manual + + r810 + r810330 + manual + + r820 + r820340 + manual + + r830 + r830350 + manual + + r840 + r840360 + manual + + r850 + r850370 + manual + + r860 + r860380 + manual + + r900 + r900390 + manual + + r901 + r901400 + manual + + r910 + r910410 + manual + + r920 + r920420 + manual + + r930 + r930430 + manual + + r940 + r940440 + manual + + r950 + r950450 + manual + + r960 + r960460 + manual + + r970 + r970470 + manual + + r01 + r01480 + manual + + r02 + r02490 + manual + + r03 + r03500 + manual + + r04 + r04510 + manual + + r05 + r05520 + manual + + r06 + r06530 + manual + + r07 + r07540 + manual + + r08 + r08550 + manual + + r09 + r09560 + manual + + r10 + r10570 + manual + + r11 + r11580 + manual + + r12 + r12590 + manual + + r13 + r13600 + manual + + r14 + r14610 + manual + + r15 + r15620 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2014_data.xml b/l10n_sk_dppo/data/dppo_version_2014_data.xml new file mode 100644 index 00000000..b8bf8ff1 --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2014_data.xml @@ -0,0 +1,349 @@ + + + + + + DPPO 2014 + + 2014-01-01 + 2014-12-31 + + dokument + dppo2014.xsd + + + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r120 + r12030 + manual + + r130 + r13040 + manual + + r140 + r14050 + manual + + r150 + r15060 + asset_diffaddback + + r160 + r16070 + manual + + r170 + r17080 + manual + + r180 + r18090 + manual + + r200 + Pripočítateľné položky100 + manual + + r210 + r210110 + manual + + r220 + r220120 + manual + + r230 + r230130 + manual + + r240 + r240140 + manual + + r250 + r250150 + asset_diffdeduction + + r260 + r260160 + manual + + r270 + r270170 + manual + + r280 + r280180 + manual + + r290 + r290190 + manual + + r300 + Odpočítateľné položky200 + manual + + r310 + Základ dane210 + manual + + r320 + r320220 + manual + + r330 + r330230 + manual + + r400 + Základ dane po úpravách240 + manual + + r410 + r410250 + manual + + r500 + r500260 + manual + + r510 + r510270 + manual + + r600 + r600280 + manual + + r610 + r610290 + manual + + r700 + r700300 + manual + + r710 + r710310 + manual + + r800 + r800320 + manual + + r810 + r810330 + manual + + r820 + r820340 + manual + + r830 + r830350 + manual + + r840 + r840360 + manual + + r900 + r900370 + manual + + r910 + r910380 + manual + + r920 + r920390 + manual + + r930 + r930400 + manual + + r940 + r940410 + manual + + r950 + r950420 + manual + + r960 + r960430 + manual + + r1000 + r1000440 + manual + + r1001 + r1001450 + manual + + r1010 + r1010460 + manual + + r1020 + r1020470 + manual + + r1030 + r1030480 + manual + + r1040 + Preddavky na daň490 + manual + + r1050 + Daň pred úpravou o preddavky na daň500 + manual + + r1060 + r1060510 + manual + + r1070 + r1070520 + manual + + r01 + r01530 + manual + + r02 + r02540 + manual + + r03 + r03550 + manual + + r04 + r04560 + manual + + r05 + r05570 + manual + + r06 + r06580 + manual + + r07 + r07590 + manual + + r08 + r08600 + manual + + r09 + r09610 + manual + + r10 + r10620 + manual + + r11 + r11630 + manual + + r12 + r12640 + manual + + r13 + r13650 + manual + + r14 + r14660 + manual + + r15 + r15670 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2015_data.xml b/l10n_sk_dppo/data/dppo_version_2015_data.xml new file mode 100644 index 00000000..f65ec860 --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2015_data.xml @@ -0,0 +1,384 @@ + + + + + + DPPO 2015 + + 2015-01-01 + 2016-12-31 + + dokument + dppo2015.xsd + + + + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r120 + r12030 + manual + + r130 + r13040 + manual + + r140 + r14050 + manual + + r150 + r15060 + asset_diffaddback + + r160 + r16070 + manual + + r170 + r17080 + manual + + r180 + r18090 + manual + + r200 + Pripočítateľné položky100 + manual + + r210 + r210110 + manual + + r220 + r220120 + manual + + r230 + r230130 + manual + + r240 + r240140 + manual + + r250 + r250150 + asset_diffdeduction + + r260 + r260160 + manual + + r270 + r270170 + manual + + r280 + r280180 + manual + + r290 + r290190 + manual + + r300 + Odpočítateľné položky200 + manual + + r301 + Všeobecný základ dane210 + manual + + r302 + r302220 + manual + + r303 + r303230 + manual + + r304 + r304240 + manual + + r305 + r305250 + manual + + r310 + Základ dane260 + manual + + r320 + r320270 + manual + + r330 + r330280 + manual + + r400 + Základ dane po úpravách290 + manual + + r410 + r410300 + manual + + r500 + r500310 + manual + + r501 + r501320 + manual + + r510 + r510330 + manual + + r550 + Sadzba dane (%)340 + manual + + r600 + r600350 + manual + + r610 + r610360 + manual + + r700 + r700370 + manual + + r710 + r710380 + manual + + r800 + r800390 + manual + + r810 + r810400 + manual + + r820 + r820410 + manual + + r830 + r830420 + manual + + r900 + r900430 + manual + + r910 + r910440 + manual + + r920 + r920450 + manual + + r1000 + r1000460 + manual + + r1010 + r1010470 + manual + + r1020 + r1020480 + manual + + r1030 + r1030490 + manual + + r1040 + Preddavky na daň500 + manual + + r1050 + Daň pred úpravou o preddavky na daň510 + manual + + r1100 + Daň na úhradu, nedoplatok (+)520 + manual + + r1101 + Daňový preplatok (−)530 + manual + + r1110 + r1110540 + manual + + r1120 + r1120550 + manual + + r1130 + r1130560 + manual + + r1140 + r1140570 + manual + + r1150 + r1150580 + manual + + r01 + r01590 + manual + + r02 + r02600 + manual + + r03 + r03610 + manual + + r04 + r04620 + manual + + r05 + r05630 + manual + + r06 + r06640 + manual + + r07 + r07650 + manual + + r08 + r08660 + manual + + r09 + r09670 + manual + + r10 + r10680 + manual + + r11 + r11690 + manual + + r12 + r12700 + manual + + r13 + r13710 + manual + + r14 + r14720 + manual + + r15 + r15730 + manual + + r16 + r16740 + manual + + r17 + r17750 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2017_data.xml b/l10n_sk_dppo/data/dppo_version_2017_data.xml new file mode 100644 index 00000000..84e80e1c --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2017_data.xml @@ -0,0 +1,484 @@ + + + + + + DPPO 2017 + + 2017-01-01 + 2017-12-31 + + dokument + dppo2017.xsd + + + + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r120 + r12030 + manual + + r130 + r13040 + manual + + r140 + r14050 + manual + + r150 + r15060 + asset_diffaddback + + r160 + r16070 + manual + + r170 + r17080 + manual + + r180 + r18090 + manual + + r200 + Pripočítateľné položky100 + manual + + r210 + r210110 + manual + + r220 + r220120 + manual + + r230 + r230130 + manual + + r240 + r240140 + manual + + r250 + r250150 + asset_diffdeduction + + r260 + r260160 + manual + + r270 + r270170 + manual + + r280 + r280180 + manual + + r290 + r290190 + manual + + r300 + Odpočítateľné položky200 + manual + + r301 + Všeobecný základ dane210 + manual + + r302 + r302220 + manual + + r303 + r303230 + manual + + r304 + r304240 + manual + + r305 + r305250 + manual + + r306 + r306260 + manual + + r307 + r307270 + manual + + r310 + Základ dane280 + manual + + r320 + r320290 + manual + + r330 + r330300 + manual + + r400 + Základ dane po úpravách310 + manual + + r410 + r410320 + manual + + r500 + r500330 + manual + + r501 + r501340 + manual + + r510 + r510350 + manual + + r550 + Sadzba dane (%)360 + manual + + r600 + r600370 + manual + + r610 + r610380 + manual + + r700 + r700390 + manual + + r710 + r710400 + manual + + r800 + Daň po úľavách a po zápočte dane410 + manual + + r810 + Daňová licencia420 + manual + + r820 + Kladný rozdiel medzi daňovou licenciou a daňou určený na zápočet430 + manual + + r900 + Daňová licencia na úhradu440 + manual + + r910 + r910450 + manual + + r920 + r920460 + manual + + r1000 + r1000470 + manual + + r1010 + r1010480 + manual + + r1020 + r1020490 + manual + + r1030 + r1030500 + manual + + r1040 + Preddavky na daň510 + manual + + r1050 + Daň pred úpravou o preddavky na daň520 + manual + + r1060 + r1060530 + manual + + r1061 + r1061540 + manual + + r1062 + r1062550 + manual + + r1070 + r1070560 + manual + + r1080 + Celková daň570 + manual + + r1090 + r1090580 + manual + + r1100 + Daň na úhradu, nedoplatok (+)590 + manual + + r1101 + Daňový preplatok (−)600 + manual + + r1110 + r1110610 + manual + + r1120 + r1120620 + manual + + r1130 + r1130630 + manual + + r1140 + r1140640 + manual + + r1150 + r1150650 + manual + + r1160 + r1160660 + manual + + r1170 + r1170670 + manual + + r1180 + r1180680 + manual + + r1190 + r1190690 + manual + + r01 + r01700 + manual + + r02 + r02710 + manual + + r03 + r03720 + manual + + r04 + r04730 + manual + + r05 + r05740 + manual + + r06 + r06750 + manual + + r07 + r07760 + manual + + r08 + r08770 + manual + + r09 + r09780 + manual + + r10 + r10790 + manual + + r11 + r11800 + manual + + r12 + r12810 + manual + + r13 + r13820 + manual + + r14 + r14830 + manual + + r15 + r15840 + manual + + r16 + r16850 + manual + + r17 + r17860 + manual + + r18 + r18870 + manual + + r19 + r19880 + manual + + r20 + r20890 + manual + + r21 + r21900 + manual + + r22 + r22910 + manual + + r23 + r23920 + manual + + r24 + r24930 + manual + + r25 + r25940 + manual + + r26 + r26950 + manual + + r27 + r27960 + manual + + r28 + r28970 + manual + + r29 + r29980 + manual + + r30 + r30990 + manual + + r31 + r311000 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2018_data.xml b/l10n_sk_dppo/data/dppo_version_2018_data.xml new file mode 100644 index 00000000..ad702d7e --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2018_data.xml @@ -0,0 +1,480 @@ + + + + + + DPPO 2018 + + 2018-01-01 + 2018-12-31 + + dokument + dppo2018.xsd + + + + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r130 + r13030 + manual + + r140 + r14040 + manual + + r150 + r15050 + asset_diffaddback + + r170 + r17060 + manual + + r180 + r18070 + manual + + r200 + Pripočítateľné položky80 + manual + + r210 + r21090 + manual + + r220 + r220100 + manual + + r230 + r230110 + manual + + r240 + r240120 + manual + + r250 + r250130 + asset_diffdeduction + + r260 + r260140 + manual + + r270 + r270150 + manual + + r280 + r280160 + manual + + r290 + r290170 + manual + + r300 + Odpočítateľné položky180 + manual + + r301 + Všeobecný základ dane190 + manual + + r302 + r302200 + manual + + r303 + r303210 + manual + + r304 + r304220 + manual + + r305 + r305230 + manual + + r306 + r306240 + manual + + r307 + r307250 + manual + + r308 + r308260 + manual + + r310 + Základ dane270 + manual + + r320 + r320280 + manual + + r330 + r330290 + manual + + r400 + Základ dane po úpravách300 + manual + + r410 + r410310 + manual + + r500 + r500320 + manual + + r501 + r501330 + manual + + r510 + r510340 + manual + + r550 + Sadzba dane (%)350 + manual + + r600 + r600360 + manual + + r610 + r610370 + manual + + r700 + r700380 + manual + + r710 + r710390 + manual + + r800 + Daň po úľavách a po zápočte dane400 + manual + + r810 + Daňová licencia410 + manual + + r820 + Kladný rozdiel medzi daňovou licenciou a daňou určený na zápočet420 + manual + + r900 + Daňová licencia na úhradu430 + manual + + r910 + r910440 + manual + + r920 + r920450 + manual + + r1000 + r1000460 + manual + + r1010 + r1010470 + manual + + r1020 + r1020480 + manual + + r1030 + r1030490 + manual + + r1040 + Preddavky na daň500 + manual + + r1050 + Daň pred úpravou o preddavky na daň510 + manual + + r1060 + r1060520 + manual + + r1061 + r1061530 + manual + + r1062 + r1062540 + manual + + r1070 + r1070550 + manual + + r1080 + Celková daň560 + manual + + r1090 + r1090570 + manual + + r1100 + Daň na úhradu, nedoplatok (+)580 + manual + + r1101 + Daňový preplatok (−)590 + manual + + r1110 + r1110600 + manual + + r1120 + r1120610 + manual + + r1130 + r1130620 + manual + + r1140 + r1140630 + manual + + r1150 + r1150640 + manual + + r1160 + r1160650 + manual + + r1170 + r1170660 + manual + + r1180 + r1180670 + manual + + r1190 + r1190680 + manual + + r01 + r01690 + manual + + r02 + r02700 + manual + + r03 + r03710 + manual + + r04 + r04720 + manual + + r05 + r05730 + manual + + r06 + r06740 + manual + + r07 + r07750 + manual + + r08 + r08760 + manual + + r09 + r09770 + manual + + r10 + r10780 + manual + + r11 + r11790 + manual + + r12 + r12800 + manual + + r13 + r13810 + manual + + r14 + r14820 + manual + + r15 + r15830 + manual + + r16 + r16840 + manual + + r17 + r17850 + manual + + r18 + r18860 + manual + + r19 + r19870 + manual + + r20 + r20880 + manual + + r21 + r21890 + manual + + r22 + r22900 + manual + + r23 + r23910 + manual + + r24 + r24920 + manual + + r25 + r25930 + manual + + r26 + r26940 + manual + + r27 + r27950 + manual + + r28 + r28960 + manual + + r29 + r29970 + manual + + r30 + r30980 + manual + + r31 + r31990 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2019_data.xml b/l10n_sk_dppo/data/dppo_version_2019_data.xml new file mode 100644 index 00000000..a4ed00fe --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2019_data.xml @@ -0,0 +1,480 @@ + + + + + + DPPO 2019 + + 2019-01-01 + 2019-12-31 + + dokument + dppo2019.xsd + + + + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r130 + r13030 + manual + + r140 + r14040 + manual + + r150 + r15050 + asset_diffaddback + + r170 + r17060 + manual + + r180 + r18070 + manual + + r200 + Pripočítateľné položky80 + manual + + r210 + r21090 + manual + + r220 + r220100 + manual + + r230 + r230110 + manual + + r240 + r240120 + manual + + r250 + r250130 + asset_diffdeduction + + r260 + r260140 + manual + + r270 + r270150 + manual + + r280 + r280160 + manual + + r290 + r290170 + manual + + r300 + Odpočítateľné položky180 + manual + + r301 + Všeobecný základ dane190 + manual + + r302 + r302200 + manual + + r303 + r303210 + manual + + r304 + r304220 + manual + + r305 + r305230 + manual + + r306 + r306240 + manual + + r307 + r307250 + manual + + r308 + r308260 + manual + + r310 + Základ dane270 + manual + + r320 + r320280 + manual + + r330 + r330290 + manual + + r400 + Základ dane po úpravách300 + manual + + r410 + r410310 + manual + + r500 + r500320 + manual + + r501 + r501330 + manual + + r510 + r510340 + manual + + r550 + Sadzba dane (%)350 + manual + + r600 + r600360 + manual + + r610 + r610370 + manual + + r700 + r700380 + manual + + r710 + r710390 + manual + + r800 + Daň po úľavách a po zápočte dane400 + manual + + r810 + Daňová licencia410 + manual + + r820 + Kladný rozdiel medzi daňovou licenciou a daňou určený na zápočet420 + manual + + r900 + Daňová licencia na úhradu430 + manual + + r910 + r910440 + manual + + r920 + r920450 + manual + + r1000 + r1000460 + manual + + r1010 + r1010470 + manual + + r1020 + r1020480 + manual + + r1030 + r1030490 + manual + + r1040 + Preddavky na daň500 + manual + + r1050 + Daň pred úpravou o preddavky na daň510 + manual + + r1060 + r1060520 + manual + + r1061 + r1061530 + manual + + r1062 + r1062540 + manual + + r1070 + r1070550 + manual + + r1080 + Celková daň560 + manual + + r1090 + r1090570 + manual + + r1100 + Daň na úhradu, nedoplatok (+)580 + manual + + r1101 + Daňový preplatok (−)590 + manual + + r1110 + r1110600 + manual + + r1120 + r1120610 + manual + + r1130 + r1130620 + manual + + r1140 + r1140630 + manual + + r1150 + r1150640 + manual + + r1160 + r1160650 + manual + + r1170 + r1170660 + manual + + r1180 + r1180670 + manual + + r1190 + r1190680 + manual + + r01 + r01690 + manual + + r02 + r02700 + manual + + r03 + r03710 + manual + + r04 + r04720 + manual + + r05 + r05730 + manual + + r06 + r06740 + manual + + r07 + r07750 + manual + + r08 + r08760 + manual + + r09 + r09770 + manual + + r10 + r10780 + manual + + r11 + r11790 + manual + + r12 + r12800 + manual + + r13 + r13810 + manual + + r14 + r14820 + manual + + r15 + r15830 + manual + + r16 + r16840 + manual + + r17 + r17850 + manual + + r18 + r18860 + manual + + r19 + r19870 + manual + + r20 + r20880 + manual + + r21 + r21890 + manual + + r22 + r22900 + manual + + r23 + r23910 + manual + + r24 + r24920 + manual + + r25 + r25930 + manual + + r26 + r26940 + manual + + r27 + r27950 + manual + + r28 + r28960 + manual + + r29 + r29970 + manual + + r30 + r30980 + manual + + r31 + r31990 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2020_data.xml b/l10n_sk_dppo/data/dppo_version_2020_data.xml new file mode 100644 index 00000000..54281501 --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2020_data.xml @@ -0,0 +1,466 @@ + + + + + + DPPO 2020 + + 2020-01-01 + 2020-12-31 + + dokument + dppo2020.xsd + + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r130 + r13030 + manual + + r140 + r14040 + manual + + r150 + r15050 + asset_diffaddback + + r170 + r17060 + manual + + r180 + r18070 + manual + + r200 + Pripočítateľné položky80 + manual + + r210 + r21090 + manual + + r220 + r220100 + manual + + r230 + r230110 + manual + + r240 + r240120 + manual + + r250 + r250130 + asset_diffdeduction + + r260 + r260140 + manual + + r270 + r270150 + manual + + r280 + r280160 + manual + + r290 + r290170 + manual + + r300 + Odpočítateľné položky180 + manual + + r301 + Všeobecný základ dane190 + manual + + r302 + r302200 + manual + + r303 + r303210 + manual + + r304 + r304220 + manual + + r305 + r305230 + manual + + r306 + r306240 + manual + + r307 + r307250 + manual + + r308 + r308260 + manual + + r310 + Základ dane270 + manual + + r320 + r320280 + manual + + r330 + r330290 + manual + + r400 + Základ dane po úpravách300 + manual + + r410 + r410310 + manual + + r500 + r500320 + manual + + r501 + r501330 + manual + + r510 + r510340 + manual + + r550 + Sadzba dane (%)350 + manual + + r560 + Zdaniteľné príjmy (výnosy)360 + account6 + + r600 + r600370 + manual + + r610 + r610380 + manual + + r700 + r700390 + manual + + r710 + r710400 + manual + + r800 + r800410 + manual + + r900 + r900420 + manual + + r910 + r910430 + manual + + r920 + r920440 + manual + + r1000 + r1000450 + manual + + r1010 + r1010460 + manual + + r1020 + r1020470 + manual + + r1030 + r1030480 + manual + + r1040 + Preddavky na daň490 + manual + + r1050 + Daň pred úpravou o preddavky na daň500 + manual + + r1060 + r1060510 + manual + + r1061 + r1061520 + manual + + r1062 + r1062530 + manual + + r1070 + r1070540 + manual + + r1080 + Celková daň550 + manual + + r1090 + r1090560 + manual + + r1100 + Daň na úhradu, nedoplatok (+)570 + manual + + r1101 + Daňový preplatok (−)580 + manual + + r1110 + r1110590 + manual + + r1120 + r1120600 + manual + + r1130 + r1130610 + manual + + r1140 + r1140620 + manual + + r1150 + r1150630 + manual + + r1160 + r1160640 + manual + + r1170 + r1170650 + manual + + r1180 + r1180660 + manual + + r1190 + r1190670 + manual + + r01 + r01680 + manual + + r02 + r02690 + manual + + r03 + r03700 + manual + + r04 + r04710 + manual + + r05 + r05720 + manual + + r06 + r06730 + manual + + r07 + r07740 + manual + + r08 + r08750 + manual + + r09 + r09760 + manual + + r10 + r10770 + manual + + r11 + r11780 + manual + + r12 + r12790 + manual + + r13 + r13800 + manual + + r14 + r14810 + manual + + r15 + r15820 + manual + + r16 + r16830 + manual + + r17 + r17840 + manual + + r18 + r18850 + manual + + r19 + r19860 + manual + + r20 + r20870 + manual + + r21 + r21880 + manual + + r22 + r22890 + manual + + r23 + r23900 + manual + + r24 + r24910 + manual + + r25 + r25920 + manual + + r26 + r26930 + manual + + r27 + r27940 + manual + + r28 + r28950 + manual + + r29 + r29960 + manual + + r30 + r30970 + manual + + r31 + r31980 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2021_data.xml b/l10n_sk_dppo/data/dppo_version_2021_data.xml new file mode 100644 index 00000000..3b81ab4e --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2021_data.xml @@ -0,0 +1,466 @@ + + + + + + DPPO 2021 + + 2021-01-01 + 2021-12-31 + + dokument + dppo2021.xsd + + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r130 + r13030 + manual + + r140 + r14040 + manual + + r150 + r15050 + asset_diffaddback + + r170 + r17060 + manual + + r180 + r18070 + manual + + r200 + Pripočítateľné položky80 + manual + + r210 + r21090 + manual + + r220 + r220100 + manual + + r230 + r230110 + manual + + r240 + r240120 + manual + + r250 + r250130 + asset_diffdeduction + + r260 + r260140 + manual + + r270 + r270150 + manual + + r280 + r280160 + manual + + r290 + r290170 + manual + + r300 + Odpočítateľné položky180 + manual + + r301 + Všeobecný základ dane190 + manual + + r302 + r302200 + manual + + r303 + r303210 + manual + + r304 + r304220 + manual + + r305 + r305230 + manual + + r306 + r306240 + manual + + r307 + r307250 + manual + + r308 + r308260 + manual + + r310 + Základ dane270 + manual + + r320 + r320280 + manual + + r330 + r330290 + manual + + r400 + Základ dane po úpravách300 + manual + + r410 + r410310 + manual + + r500 + r500320 + manual + + r501 + r501330 + manual + + r510 + r510340 + manual + + r550 + Sadzba dane (%)350 + manual + + r560 + Zdaniteľné príjmy (výnosy)360 + account6 + + r600 + r600370 + manual + + r610 + r610380 + manual + + r700 + r700390 + manual + + r710 + r710400 + manual + + r800 + r800410 + manual + + r900 + r900420 + manual + + r910 + r910430 + manual + + r920 + r920440 + manual + + r1000 + r1000450 + manual + + r1010 + r1010460 + manual + + r1020 + r1020470 + manual + + r1030 + r1030480 + manual + + r1040 + Preddavky na daň490 + manual + + r1050 + Daň pred úpravou o preddavky na daň500 + manual + + r1060 + r1060510 + manual + + r1061 + r1061520 + manual + + r1062 + r1062530 + manual + + r1070 + r1070540 + manual + + r1080 + Celková daň550 + manual + + r1090 + r1090560 + manual + + r1100 + Daň na úhradu, nedoplatok (+)570 + manual + + r1101 + Daňový preplatok (−)580 + manual + + r1110 + r1110590 + manual + + r1120 + r1120600 + manual + + r1130 + r1130610 + manual + + r1140 + r1140620 + manual + + r1150 + r1150630 + manual + + r1160 + r1160640 + manual + + r1170 + r1170650 + manual + + r1180 + r1180660 + manual + + r1190 + r1190670 + manual + + r01 + r01680 + manual + + r02 + r02690 + manual + + r03 + r03700 + manual + + r04 + r04710 + manual + + r05 + r05720 + manual + + r06 + r06730 + manual + + r07 + r07740 + manual + + r08 + r08750 + manual + + r09 + r09760 + manual + + r10 + r10770 + manual + + r11 + r11780 + manual + + r12 + r12790 + manual + + r13 + r13800 + manual + + r14 + r14810 + manual + + r15 + r15820 + manual + + r16 + r16830 + manual + + r17 + r17840 + manual + + r18 + r18850 + manual + + r19 + r19860 + manual + + r20 + r20870 + manual + + r21 + r21880 + manual + + r22 + r22890 + manual + + r23 + r23900 + manual + + r24 + r24910 + manual + + r25 + r25920 + manual + + r26 + r26930 + manual + + r27 + r27940 + manual + + r28 + r28950 + manual + + r29 + r29960 + manual + + r30 + r30970 + manual + + r31 + r31980 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2022_data.xml b/l10n_sk_dppo/data/dppo_version_2022_data.xml new file mode 100644 index 00000000..927d8a6f --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2022_data.xml @@ -0,0 +1,454 @@ + + + + + + DPPO 2022 + + 2022-01-01 + 2023-12-31 + + dokument + dppo2022.xsd + + + + + + + RRiadne + R + + + + OOpravné + O + + + + DDodatočné + D + + + r100 + Výsledok hospodárenia pred zdanením10 + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r130 + r13030 + manual + + r140 + r14040 + manual + + r150 + r15050 + asset_diffaddback + + r170 + r17060 + manual + + r180 + r18070 + manual + + r200 + Pripočítateľné položky80 + manual + + r210 + r21090 + manual + + r220 + r220100 + manual + + r230 + r230110 + manual + + r240 + r240120 + manual + + r250 + r250130 + asset_diffdeduction + + r260 + r260140 + manual + + r270 + r270150 + manual + + r280 + r280160 + manual + + r290 + r290170 + manual + + r300 + Odpočítateľné položky180 + manual + + r301 + Všeobecný základ dane190 + manual + + r302 + r302200 + manual + + r303 + r303210 + manual + + r304 + r304220 + manual + + r305 + r305230 + manual + + r306 + r306240 + manual + + r307 + r307250 + manual + + r308 + r308260 + manual + + r310 + Základ dane270 + manual + + r320 + r320280 + manual + + r330 + r330290 + manual + + r400 + Základ dane po úpravách300 + manual + + r410 + r410310 + manual + + r500 + r500320 + manual + + r501 + r501330 + manual + + r502 + r502340 + manual + + r510 + r510350 + manual + + r550 + Sadzba dane (%)360 + manual + + r560 + Zdaniteľné príjmy (výnosy)370 + account6 + + r600 + r600380 + manual + + r610 + r610390 + manual + + r700 + r700400 + manual + + r710 + r710410 + manual + + r800 + r800420 + manual + + r1010 + r1010430 + manual + + r1020 + r1020440 + manual + + r1030 + r1030450 + manual + + r1040 + Preddavky na daň460 + manual + + r1050 + Daň pred úpravou o preddavky na daň470 + manual + + r1060 + r1060480 + manual + + r1061 + r1061490 + manual + + r1062 + r1062500 + manual + + r1070 + r1070510 + manual + + r1080 + Celková daň520 + manual + + r1090 + r1090530 + manual + + r1100 + Daň na úhradu, nedoplatok (+)540 + manual + + r1101 + Daňový preplatok (−)550 + manual + + r1110 + r1110560 + manual + + r1120 + r1120570 + manual + + r1130 + r1130580 + manual + + r1140 + r1140590 + manual + + r1150 + r1150600 + manual + + r1160 + r1160610 + manual + + r1170 + r1170620 + manual + + r1180 + r1180630 + manual + + r1190 + r1190640 + manual + + r01 + r01650 + manual + + r02 + r02660 + manual + + r03 + r03670 + manual + + r04 + r04680 + manual + + r05 + r05690 + manual + + r06 + r06700 + manual + + r07 + r07710 + manual + + r08 + r08720 + manual + + r09 + r09730 + manual + + r10 + r10740 + manual + + r11 + r11750 + manual + + r12 + r12760 + manual + + r13 + r13770 + manual + + r14 + r14780 + manual + + r15 + r15790 + manual + + r16 + r16800 + manual + + r17 + r17810 + manual + + r18 + r18820 + manual + + r19 + r19830 + manual + + r20 + r20840 + manual + + r21 + r21850 + manual + + r22 + r22860 + manual + + r23 + r23870 + manual + + r24 + r24880 + manual + + r25 + r25890 + manual + + r26 + r26900 + manual + + r27 + r27910 + manual + + r28 + r28920 + manual + + r29 + r29930 + manual + + r30 + r30940 + manual + + r31 + r31950 + manual + diff --git a/l10n_sk_dppo/data/dppo_version_2024_data.xml b/l10n_sk_dppo/data/dppo_version_2024_data.xml new file mode 100644 index 00000000..77ee014c --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_2024_data.xml @@ -0,0 +1,381 @@ + + + + + + + DPPO 2024 + + 2024-01-01 + 2024-12-31 + + dokument + dppo2024.xsd + + r1050 + + + + + + + + RRiadne + R + + + OOpravné + O + + + DDodatočné + D + + r100 + Výsledok hospodárenia pred zdanením10 + + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r130 + r13030 + manual + + r140 + r14040 + manual + + r150 + r15050 + + asset_diffaddback + + r170 + r17060 + manual + + r180 + r18070 + manual + + r200 + Pripočítateľné položky80 + manual + + r210 + r21090 + manual + + r220 + r220100 + manual + + r230 + r230110 + manual + + r240 + r240120 + manual + + r250 + r250130 + + asset_diffdeduction + + r260 + r260140 + manual + + r270 + r270150 + manual + + r280 + r280160 + manual + + r290 + r290170 + manual + + r300 + Odpočítateľné položky180 + manual + + r301 + Všeobecný základ dane190 + manual + + r302 + r302200 + manual + + r303 + r303210 + manual + + r304 + r304220 + manual + + r305 + r305230 + manual + + r306 + r306240 + manual + + r307 + r307250 + manual + + r308 + r308260 + manual + + r310 + Základ dane270 + manual + + r320 + r320280 + manual + + r330 + r330290 + manual + + r400 + Základ dane po úpravách300 + manual + + r410 + r410310 + manual + + r500 + r500320 + manual + + r501 + r501330 + manual + + r502 + r502340 + manual + + r510 + r510360 + manual + + r550 + Sadzba dane (%)370 + manual + + r560 + Zdaniteľné príjmy (výnosy)380 + + account6 + + r600 + r600390 + manual + + r610 + r610400 + manual + + r700 + r700410 + manual + + r710 + r710420 + manual + + r800 + r800430 + manual + + r810 + Minimálna daň (§ 46b)440 + manual + + r820 + r820450 + manual + + r830 + Kladný rozdiel medzi minimálnou daňou a daňou (§ 46b ods. 5)460 + manual + + r840 + r840470 + manual + + r900 + Suma na účely určenia výšky preddavkov480 + manual + + r910 + r910490 + manual + + r920 + r920500 + manual + + r1000 + r1000510 + manual + + r1010 + r1010520 + manual + + r1020 + r1020530 + manual + + r1030 + r1030540 + manual + + r1040 + Preddavky na daň550 + manual + + r1050 + Daň pred úpravou o preddavky na daň560 + manual + + r1060 + r1060570 + manual + + r1061 + r1061580 + manual + + r1062 + r1062590 + manual + + r1070 + r1070600 + manual + + r1080 + Celková daň620 + manual + + r1090 + r1090630 + manual + + r1100 + Daň na úhradu, nedoplatok (+)640 + manual + + r1101 + Daňový preplatok (−)650 + manual + + r1110 + r1110660 + manual + + r1120 + r1120670 + manual + + r1130 + r1130680 + manual + + r1140 + r1140690 + manual + + r1150 + r1150700 + manual + + r1160 + r1160710 + manual + + r1170 + r1170720 + manual + + r1180 + r1180730 + manual + + r1190 + r1190740 + manual + + diff --git a/l10n_sk_dppo/data/dppo_version_data.xml b/l10n_sk_dppo/data/dppo_version_data.xml new file mode 100644 index 00000000..a465e62b --- /dev/null +++ b/l10n_sk_dppo/data/dppo_version_data.xml @@ -0,0 +1,389 @@ + + + + + + DPPO 2025 + + 2025-01-01 + 2025-12-31 + + dokument + + dppo2025_v2.xsd + + r1050 + + + + + + + + RRiadne + R + + + OOpravné + O + + + DDodatočné + D + + r100 + Výsledok hospodárenia pred zdanením10 + + account5,6,-591,-592,-595,-596 + + r110 + r11020 + manual + + r130 + r13030 + manual + + r140 + r14040 + manual + + r150 + r15050 + + asset_diffaddback + + r170 + r17060 + manual + + r180 + r18070 + manual + + r200 + Pripočítateľné položky80 + manual + + r210 + r21090 + manual + + r220 + r220100 + manual + + r230 + r230110 + manual + + r240 + r240120 + manual + + r250 + r250130 + + asset_diffdeduction + + r260 + r260140 + manual + + r270 + r270150 + manual + + r280 + r280160 + manual + + r290 + r290170 + manual + + r300 + Odpočítateľné položky180 + manual + + r301 + Všeobecný základ dane190 + manual + + r302 + r302200 + manual + + r303 + r303210 + manual + + r304 + r304220 + manual + + r305 + r305230 + manual + + r306 + r306240 + manual + + r307 + r307250 + manual + + r308 + r308260 + manual + + r310 + Základ dane270 + manual + + r320 + r320280 + manual + + r330 + r330290 + manual + + r400 + Základ dane po úpravách300 + manual + + r410 + r410310 + manual + + r500 + r500320 + manual + + r501 + r501330 + manual + + r502 + r502340 + manual + + r503 + r503350 + manual + + r510 + r510360 + manual + + r550 + Sadzba dane (%)370 + manual + + r560 + Zdaniteľné príjmy (výnosy)380 + + account6 + + r600 + r600390 + manual + + r610 + r610400 + manual + + r700 + r700410 + manual + + r710 + r710420 + manual + + r800 + r800430 + manual + + r810 + Minimálna daň (§ 46b)440 + manual + + r820 + r820450 + manual + + r830 + Kladný rozdiel medzi minimálnou daňou a daňou (§ 46b ods. 5)460 + manual + + r840 + r840470 + manual + + r900 + Suma na účely určenia výšky preddavkov480 + manual + + r910 + r910490 + manual + + r920 + r920500 + manual + + r1000 + r1000510 + manual + + r1010 + r1010520 + manual + + r1020 + r1020530 + manual + + r1030 + r1030540 + manual + + r1040 + Preddavky na daň550 + manual + + r1050 + Daň pred úpravou o preddavky na daň560 + manual + + r1060 + r1060570 + manual + + r1061 + r1061580 + manual + + r1062 + r1062590 + manual + + r1070 + r1070600 + manual + + r1071 + r1071610 + manual + + r1080 + Celková daň620 + manual + + r1090 + r1090630 + manual + + r1100 + Daň na úhradu, nedoplatok (+)640 + manual + + r1101 + Daňový preplatok (−)650 + manual + + r1110 + r1110660 + manual + + r1120 + r1120670 + manual + + r1130 + r1130680 + manual + + r1140 + r1140690 + manual + + r1150 + r1150700 + manual + + r1160 + r1160710 + manual + + r1170 + r1170720 + manual + + r1180 + r1180730 + manual + + r1190 + r1190740 + manual + + r1191 + r1191750 + manual + + r1192 + r1192760 + manual + + diff --git a/l10n_sk_dppo/i18n/l10n_sk_dppo.pot b/l10n_sk_dppo/i18n/l10n_sk_dppo.pot new file mode 100644 index 00000000..d0783078 --- /dev/null +++ b/l10n_sk_dppo/i18n/l10n_sk_dppo.pot @@ -0,0 +1,66 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_dppo +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-30 06:14+0000\n" +"PO-Revision-Date: 2026-06-30 06:14+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_sk_dppo +#: model:ir.model.fields,field_description:l10n_sk_dppo.field_cssk_income_tax_return__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_sk_dppo +#: model:ir.model.fields,field_description:l10n_sk_dppo.field_cssk_income_tax_return__id +msgid "ID" +msgstr "" + +#. module: l10n_sk_dppo +#: model:ir.model,name:l10n_sk_dppo.model_cssk_income_tax_return +msgid "Income Tax Return" +msgstr "" + +#. module: l10n_sk_dppo +#. odoo-python +#: code:addons/l10n_sk_dppo/models/cssk_income_tax.py:0 +msgid "" +"DPPO version '%(version)s' carries no usable %(label)s (field '%(field)s'): " +"expected an ordered list of [upper_bound, value] pairs whose last band has a" +" null upper bound. Fill the bands on the version record (they are noupdate " +"data — on an existing database reload the l10n_sk_dppo version data). " +"Refusing to guess a statutory rate." +msgstr "" + +#. module: l10n_sk_dppo +#: model:ir.module.module,shortdesc:base.module_l10n_sk_dppo +msgid "SK Corporate Income Tax Return (DPPO)" +msgstr "" + +#. module: l10n_sk_dppo +#: model:ir.module.module,summary:base.module_l10n_sk_dppo +msgid "" +"Slovak DPPO return on the income-tax framework: full r-line set + header " +"mapping + XSD-validated XML export against the official dppo2025 schema." +msgstr "" + +#. module: l10n_sk_dppo +#. odoo-python +#: code:addons/l10n_sk_dppo/models/cssk_income_tax.py:0 +msgid "§ 15 rate bands" +msgstr "" + +#. module: l10n_sk_dppo +#. odoo-python +#: code:addons/l10n_sk_dppo/models/cssk_income_tax.py:0 +msgid "§ 46b minimum-tax bands" +msgstr "" diff --git a/l10n_sk_dppo/i18n/sk.po b/l10n_sk_dppo/i18n/sk.po new file mode 100644 index 00000000..f18bb1eb --- /dev/null +++ b/l10n_sk_dppo/i18n/sk.po @@ -0,0 +1,76 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_dppo +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-30 06:14+0000\n" +"PO-Revision-Date: 2026-06-30 06:14+0000\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Language: sk\n" + +#. module: l10n_sk_dppo +#: model:ir.model.fields,field_description:l10n_sk_dppo.field_cssk_income_tax_return__display_name +msgid "Display Name" +msgstr "Zobrazovaný názov" + +#. module: l10n_sk_dppo +#: model:ir.model.fields,field_description:l10n_sk_dppo.field_cssk_income_tax_return__id +msgid "ID" +msgstr "ID" + +#. module: l10n_sk_dppo +#: model:ir.model,name:l10n_sk_dppo.model_cssk_income_tax_return +msgid "Income Tax Return" +msgstr "Daňové priznanie k dani z príjmov právnických osôb" + +#. module: l10n_sk_dppo +#. odoo-python +#: code:addons/l10n_sk_dppo/models/cssk_income_tax.py:0 +msgid "" +"DPPO version '%(version)s' carries no usable %(label)s (field '%(field)s'): " +"expected an ordered list of [upper_bound, value] pairs whose last band has a" +" null upper bound. Fill the bands on the version record (they are noupdate " +"data — on an existing database reload the l10n_sk_dppo version data). " +"Refusing to guess a statutory rate." +msgstr "" +"Verzia DPPO „%(version)s“ neobsahuje použiteľné %(label)s (pole " +"„%(field)s“): očakáva sa usporiadaný zoznam dvojíc [horná hranica, " +"hodnota], ktorého posledné pásmo má hornú hranicu prázdnu. Doplňte " +"pásma na zázname verzie (sú to noupdate dáta — na existujúcej " +"databáze znova načítajte dáta verzií l10n_sk_dppo). Zákonná sadzba " +"sa hádať nebude." + +#. module: l10n_sk_dppo +#: model:ir.module.module,shortdesc:base.module_l10n_sk_dppo +msgid "SK Corporate Income Tax Return (DPPO)" +msgstr "SK priznanie k dani z príjmov právnických osôb (DPPO)" + +#. module: l10n_sk_dppo +#: model:ir.module.module,summary:base.module_l10n_sk_dppo +msgid "" +"Slovak DPPO return on the income-tax framework: full r-line set + header " +"mapping + XSD-validated XML export against the official dppo2025 schema." +msgstr "" +"Slovenské priznanie DPPO na rámci dane z príjmov: úplná sada riadkov " +"r + mapovanie hlavičky + export XML validovaný voči oficiálnej " +"schéme dppo2025." + +#. module: l10n_sk_dppo +#. odoo-python +#: code:addons/l10n_sk_dppo/models/cssk_income_tax.py:0 +msgid "§ 15 rate bands" +msgstr "Pásma sadzieb podľa § 15" + +#. module: l10n_sk_dppo +#. odoo-python +#: code:addons/l10n_sk_dppo/models/cssk_income_tax.py:0 +msgid "§ 46b minimum-tax bands" +msgstr "Pásma minimálnej dane podľa § 46b" \ No newline at end of file diff --git a/l10n_sk_dppo/migrations/19.0.1.1.0/post-migrate.py b/l10n_sk_dppo/migrations/19.0.1.1.0/post-migrate.py new file mode 100644 index 00000000..5aabed4a --- /dev/null +++ b/l10n_sk_dppo/migrations/19.0.1.1.0/post-migrate.py @@ -0,0 +1,51 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Point the 2025 DPPO at the revision FS SR published beside it. + +`dppo2025_v2.xsd` sits next to an UNCHANGED `dppo2025.xsd` and differs by one +element: a repeatable `dalsiaTransakcia` (further controlled transactions). +Being a superset it accepts every document the original did, so filing against +it is strictly safer — and a digest check on the pinned name could never have +found it, which is why the refresh procedure now probes for a `_v2` suffix too. + +The version record lives in a ``noupdate="1"`` file, so an upgrade alone would +leave an installed database filing against the superseded schema. The NINE new +vintages need no help: noupdate blocks updates, not creations. +""" + +import logging +import os + +from odoo import SUPERUSER_ID, api + +_logger = logging.getLogger(__name__) + +SCHEMA = "dppo2025_v2.xsd" +PATH = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + "data", SCHEMA, +) + + +def migrate(cr, version): + if not version: + return + if not os.path.exists(PATH): + _logger.warning("l10n_sk_dppo: %s is missing; 2025 left as it is", PATH) + return + + env = api.Environment(cr, SUPERUSER_ID, {}) + record = env.ref("l10n_sk_dppo.dppo_version_2025", raise_if_not_found=False) + if not record or record.xml_schema_filename == SCHEMA: + return + + # Written through the ORM, not SQL: xml_schema_data is attachment=True, + # so its bytes live in the filestore and there is no column to update. + import base64 + + with open(PATH, "rb") as handle: + record.write({ + "xml_schema_filename": SCHEMA, + "xml_schema_data": base64.b64encode(handle.read()), + }) + _logger.info("l10n_sk_dppo: 2025 now files against %s", SCHEMA) diff --git a/l10n_sk_dppo/migrations/19.0.1.2.0/post-migration.py b/l10n_sk_dppo/migrations/19.0.1.2.0/post-migration.py new file mode 100644 index 00000000..a4e84a5a --- /dev/null +++ b/l10n_sk_dppo/migrations/19.0.1.2.0/post-migration.py @@ -0,0 +1,86 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Carry the new row labels onto existing databases. + +New in 1.2.0. Until now every DPPO line definition carried ``name`` equal to +``code`` — 941 of them, across eleven vzory — so a mapping onto a DPPO row +could be checked structurally and never semantically. The load-bearing rows +now have sourced labels; this puts them on databases that already hold the +records. + +``data/dppo_version_data.xml`` is ``noupdate="1"``, so the 2025 vzor would +never see them otherwise, and a line definition already loaded is not reliably +rewritten for the others either (measured on l10n_sk_vat_return 1.11.0, where +five of five version records still lacked a newly added line after an +upgrade). + +Reads the labels from the data files themselves rather than repeating them — +a table here would be the same duplication that l10n_sk_vat_return 1.11.0's +first draft carried, and it would drift the moment a label is corrected. +Only rows whose stored name is still the bare code are touched, so a label +someone edited by hand survives. Idempotent. +""" +import logging +import os +import re + +_logger = logging.getLogger(__name__) + +_RECORD = re.compile( + r']*model="cssk\.income\.tax\.line\.def"[^>]*>(.*?)', + re.S) +_VERSION_REF = re.compile(r'name="version_id"\s+ref="([^"]+)"') +_CODE = re.compile(r'name="code">([^<]*)<') +_NAME = re.compile(r'name="name">([^<]*)<') + + +def _labels_from_data_files(): + """{(version xmlid, code): label} for every row the data files name.""" + module = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) + data = os.path.join(module, "data") + out = {} + for fname in sorted(os.listdir(data)): + if not (fname.startswith("dppo_version_") and fname.endswith(".xml")): + continue + with open(os.path.join(data, fname), encoding="utf-8") as fh: + source = fh.read() + for match in _RECORD.finditer(source): + block = match.group(1) + ref = _VERSION_REF.search(block) + code = _CODE.search(block) + name = _NAME.search(block) + if not (ref and code and name): + continue + if name.group(1) == code.group(1): + continue # deliberately unlabelled — no source for it + out[(ref.group(1), code.group(1))] = name.group(1) + return out + + +def migrate(cr, version): + from odoo import SUPERUSER_ID, api + + env = api.Environment(cr, SUPERUSER_ID, {}) + labels = _labels_from_data_files() + if not labels: + _logger.warning("l10n_sk_dppo 1.2.0: no labels found in the data files") + return + + by_version = {} + for (ref, code), label in labels.items(): + by_version.setdefault(ref, {})[code] = label + + renamed = 0 + for ref, per_code in by_version.items(): + xmlid = ref if "." in ref else "l10n_sk_dppo.%s" % ref + ver = env.ref(xmlid, raise_if_not_found=False) + if not ver: + continue + for ldef in ver.line_def_ids: + label = per_code.get(ldef.code) + # Only a name still equal to its code — never overwrite an edit. + if label and ldef.name == ldef.code: + ldef.name = label + renamed += 1 + _logger.info("l10n_sk_dppo 1.2.0: labelled %s DPPO row(s)", renamed) diff --git a/l10n_sk_dppo/migrations/19.0.1.2.1/post-migration.py b/l10n_sk_dppo/migrations/19.0.1.2.1/post-migration.py new file mode 100644 index 00000000..b63c664a --- /dev/null +++ b/l10n_sk_dppo/migrations/19.0.1.2.1/post-migration.py @@ -0,0 +1,94 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Correct the 800/900-block labels that 1.2.0 shipped wrong. + +1.2.0 labelled r820 "Daňová licencia na úhradu" and r900 "Suma na účely +určenia výšky preddavkov" on the licencia-era vzory. Both were extrapolated +from the DPPOv24/v25 computation spine onto vzory it does not describe, and +Finančná správa's own usmernenie (20. 12. 2018) prints those captions: + + r. 800 – Daň po úľavách a po zápočte dane + r. 810 – Daňová licencia + r. 820 – Kladný rozdiel medzi daňovou licenciou a daňou určený na zápočet + r. 900 – Daňová licencia na úhradu + +The form REUSED those numbers between the two eras, which is why an +extrapolation that is safe everywhere else on this form is not safe here. + +1.2.0's own migration cannot repair it: that one only fills a name still +equal to its code, deliberately, so it never overwrites an edit — and a wrong +label is not a bare one. Several vintage data files are ``noupdate="1"`` as +well, so a reload does not reach them either. + +So this one FORCES the 800/900 block to whatever the data files now say, +including back to bare where the label was withdrawn for want of a source. +Scoped to that block: a label anywhere else is left exactly as it is. +""" +import logging +import os +import re + +_logger = logging.getLogger(__name__) + +# The rows whose number the form reused between the daňová licencia and the +# minimálna daň eras. Only these are forced. +_VOLATILE = ("r800", "r810", "r820", "r830", "r900") + +_RECORD = re.compile( + r']*model="cssk\.income\.tax\.line\.def"[^>]*>(.*?)', + re.S) +_VERSION_REF = re.compile(r'name="version_id"\s+ref="([^"]+)"') +_CODE = re.compile(r'name="code">([^<]*)<') +_NAME = re.compile(r'name="name">([^<]*)<') + + +def _volatile_names_from_data_files(): + """{(version xmlid, code): name} for the 800/900 block, bare included.""" + module = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) + data = os.path.join(module, "data") + out = {} + for fname in sorted(os.listdir(data)): + if not (fname.startswith("dppo_version_") and fname.endswith(".xml")): + continue + with open(os.path.join(data, fname), encoding="utf-8") as fh: + source = fh.read() + for match in _RECORD.finditer(source): + block = match.group(1) + ref = _VERSION_REF.search(block) + code = _CODE.search(block) + name = _NAME.search(block) + if not (ref and code and name): + continue + if code.group(1) in _VOLATILE: + out[(ref.group(1), code.group(1))] = name.group(1) + return out + + +def migrate(cr, version): + from odoo import SUPERUSER_ID, api + + env = api.Environment(cr, SUPERUSER_ID, {}) + wanted = _volatile_names_from_data_files() + if not wanted: + _logger.warning("l10n_sk_dppo 1.2.1: no 800/900 rows found in the data") + return + + by_version = {} + for (ref, code), name in wanted.items(): + by_version.setdefault(ref, {})[code] = name + + fixed = 0 + for ref, per_code in by_version.items(): + xmlid = ref if "." in ref else "l10n_sk_dppo.%s" % ref + ver = env.ref(xmlid, raise_if_not_found=False) + if not ver: + continue + for ldef in ver.line_def_ids: + name = per_code.get(ldef.code) + if name is not None and ldef.name != name: + ldef.name = name + fixed += 1 + _logger.info( + "l10n_sk_dppo 1.2.1: corrected %s row name(s) in the 800/900 block", + fixed) diff --git a/l10n_sk_dppo/migrations/19.0.1.4.0/post-migration.py b/l10n_sk_dppo/migrations/19.0.1.4.0/post-migration.py new file mode 100644 index 00000000..43a3b15f --- /dev/null +++ b/l10n_sk_dppo/migrations/19.0.1.4.0/post-migration.py @@ -0,0 +1,62 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Seed the § 46b daňová licencia bands on the licencia-era vintages. + +New in 1.4.0. `min_tax_bands` was populated only on the 2024 vzor, so r810 +computed 0,00 for 2014-2019 while a taxpayer in those years owed 480, 960 or +2 880 €. The version records are `noupdate="1"` and a data-file edit does not +reach an existing database (measured twice already in this repo), so the bands +are written here. + +Reads them from the data files rather than repeating them — a table in a +migration is the duplication `l10n_sk_vat_return` 19.0.1.11.0 had to repair in +its own. Only a version whose bands are still empty is touched, so a value +someone entered by hand survives. Idempotent. +""" +import ast +import logging +import os +import re + +_logger = logging.getLogger(__name__) + +_RECORD = re.compile( + r']*model="cssk\.income\.tax\.version"' + r'[^>]*>(.*?)', re.S) +_BANDS = re.compile(r' daň), authoritatively from the DPPOv25 eForm + # ------------------------------------------------------------------ + # Exact arithmetic from form.622.html (vypocet_r* / inliner*): + # r310 = r301+r302+r303+r304+r305+r306 - r307 + r308 + # r400 = r310 - r320 - r330 + # r500 = max(0, r400 - r410) (0 if r400 <= 0) + # r510 = r500 - r501 - r502 - r503 (odpočet § 30c výskum a vývoj) + # r550 = sadzba dane (§ 15) by úhrn zdaniteľných príjmov r560 + # r600 = r510 × r550/100 (daň; 0 if r510 <= 0) + # r700 = max(0, r600 - r610) (úľava na dani) + # r800 = r700 - r710 (zápočet zahraničnej dane) + # r810 = minimálna daň (§ 46b) by r560, prorated by months + # r820 = r810 if r810 > r800 else 0 + # The judgment items (r301–r308 pripočítateľné/odpočítateľné, r320/r330, + # r410 odpočet straty, r501–r503, r560 úhrn príjmov, r610/r710) stay manual. + # The min-tax zápočet carry-forward (r830–r1000, cross-year table) stays + # manual; r1050 defaults to max(daň, minimálna daň) for the common case. + # Any row the accountant overrides (is_overridden) is left untouched. + + # The § 15 rate schedule and the § 46b minimum-tax schedule are STATUTORY + # DATA that changes with the law, not with the code — they live on the + # version record (cssk.income.tax.version.rate_bands / min_tax_bands, + # populated by data/dppo_version*_data.xml per form year) as ordered + # [upper_bound_inclusive, value] pairs keyed on r560, the last band with a + # null upper bound. A version without bands raises loudly: silently + # applying a wrong rate is worse than stopping the computation. + + def _dppo_version_bands(self, field_name, label): + self.ensure_one() + bands = self.version_id[field_name] or [] + if not bands or not bands[-1] or bands[-1][0] is not None: + raise UserError(_( + "DPPO version '%(version)s' carries no usable %(label)s " + "(field '%(field)s'): expected an ordered list of " + "[upper_bound, value] pairs whose last band has a null upper " + "bound. Fill the bands on the version record (they are " + "noupdate data — on an existing database reload the " + "l10n_sk_dppo version data). Refusing to guess a statutory " + "rate.", + version=self.version_id.display_name, label=label, + field=field_name)) + return bands + + @staticmethod + def _dppo_band_value(bands, r560, conditions=None): + """Value of the first band that covers r560 and whose condition holds. + + A band is ``[upper_bound, value]`` — inclusive bound, null last band + catches everything above — or ``[upper_bound, value, condition]``, + where the condition names a fact about the taxpayer that turnover + cannot express. § 46b's daňová licencia needs it: 480 € and 960 € sit + at the SAME turnover bound and are told apart by whether the taxpayer + was a platiteľ DPH on the last day of the period. + + Unknown conditions never match, so a band nobody can evaluate is + skipped rather than silently applied. + """ + conditions = conditions or {} + for band in bands: + upper, value = band[0], band[1] + if upper is not None and r560 > upper: + continue + if len(band) > 2 and not conditions.get(band[2]): + continue + return value + + def _dppo_rate(self, r560): + """Sadzba dane % (§ 15) by úhrn zdaniteľných príjmov r560 — statutory + schedule read from the version record (rate_bands).""" + return self._dppo_band_value( + self._dppo_version_bands("rate_bands", _("§ 15 rate bands")), + r560) + + def _period_months(self): + df, dt = self.date_from, self.date_to + if not dt or dt < df: + return 12 + return (dt.year - df.year) * 12 + (dt.month - df.month) + 1 + + def _dppo_is_vat_payer(self): + """Platiteľ DPH k poslednému dňu zdaňovacieho obdobia (§ 46b ods. 2). + + Read from ``l10n_sk_vat_registration`` when it is installed, because + that module records the registration and the date it took effect — + which is what the statute asks about, a status on one specific day. + + Without it, falls back to whether the company carries a VAT number at + all. That is an APPROXIMATION and it errs downwards: a registered + company with no ``vat`` on its record lands in the 480 band instead of + 960. Install ``l10n_sk_vat_registration`` on any agenda where the + licencia is actually computed. + """ + self.ensure_one() + partner = self.company_id.partner_id + if "l10n_sk_vat_payer_since" in partner._fields: + since = partner.l10n_sk_vat_payer_since + if since: + return since <= self.date_to + return bool(partner.l10n_sk_is_full_vat_payer) + return bool(self.company_id.vat) + + def _dppo_min_tax(self, r560): + """Minimálna daň / daňová licencia (§ 46b) — the band amount from the + version record (min_tax_bands), prorated by months. Polovičná sadzba + (ods. 3), neplatenie and oslobodenie (ods. 7) are accountant overrides. + + ⚠️ The licencia vzory (2014-2019) have **no r560**. That row arrived + with the 2020 form, for the § 15 reduced-rate test, so the older + tlačivo carries no turnover figure at all and the band has nothing on + the form to key on. We fall back to the same basis the later form uses + for r560 — class 6 revenue, which is literally r560's own + ``account_formula`` — rather than to nothing, because a silent 0,00 + here means "no licencia owed", which is the wrong answer for almost + every taxpayer. + + That is a reading, not a citation: the older poučenie does not define + ročný obrat by reference to a row, because it has none to point at. + Override r810 where the taxpayer's obrat differs from class 6. + """ + self.ensure_one() + if "r560" not in set(self.version_id.line_def_ids.mapped("code")): + r560 = self._eval_accounts("6") + if r560 <= 0: + return 0.0 + payer = self._dppo_is_vat_payer() + base = self._dppo_band_value( + self._dppo_version_bands( + "min_tax_bands", _("§ 46b minimum-tax bands")), + r560, + {"vat_payer": payer, "not_vat_payer": not payer}, + ) + months = self._period_months() + return base if months == 12 else statutory_round( + base / 12.0 * months, 2) + + def _compute_tax_spine(self): + self.ensure_one() + by = {line.code: line for line in self.line_ids} + + def g(code): + line = by.get(code) + return line.value if line else 0.0 + + def put(code, value): + # The guard is what keeps _SPINE_ROWS honest: a row the spine + # learns to write, and nobody adds here, fails on the next + # compute rather than silently dropping out of + # ``_cssk_derived_codes`` and out of every comparison built on it. + # A raise, not an assert: `python -O` strips asserts, and a guard + # that disappears under a flag is not a guard. + if code not in _SPINE_ROWS: + raise ValueError( + "the SK tax spine writes %s but _SPINE_ROWS does not list " + "it — add it there, or _cssk_derived_codes() will hide the " + "row from every comparator built on it" % code) + line = by.get(code) + if line and not line.is_overridden: + line.value = value + + # Base cascade (DPPOv24/v25 eForm vypocet_r200/_r300/_r301): + # r200 = pripočítateľné položky = r110+r130+r140+r150+r170+r180 + # r300 = odpočítateľné položky = r210+r220+…+r290 + # r301 = všeobecný základ dane = r100 + r200 − r300 + # i.e. the accounting result (r100) flows into the base — the user enters + # the individual judgment rows (r110…r290), not r301 itself. (The eForm + # adds ± r.11 tabuľky H transfer-pricing úprava; model that as an r301 + # override when it applies.) + r200 = statutory_round(g("r110") + g("r130") + g("r140") + g("r150") + + g("r170") + g("r180"), 2) + put("r200", r200) + r300 = statutory_round(g("r210") + g("r220") + g("r230") + g("r240") + + g("r250") + g("r260") + g("r270") + g("r280") + + g("r290"), 2) + put("r300", r300) + r301 = statutory_round(g("r100") + r200 - r300, 2) + put("r301", r301) + + r310 = statutory_round(g("r301") + g("r302") + g("r303") + g("r304") + + g("r305") + g("r306") - g("r307") + + g("r308"), 2) + put("r310", r310) + r400 = statutory_round(r310 - g("r320") - g("r330"), 2) + put("r400", r400) + r500 = (max(0.0, statutory_round(r400 - g("r410"), 2)) + if r400 > 0 else 0.0) + put("r500", r500) + r510 = statutory_round(r500 - g("r501") - g("r502") - g("r503"), 2) + put("r510", r510) + rate = self._dppo_rate(g("r560")) + put("r550", rate) + r600 = statutory_round(r510 * rate / 100.0, 2) if r510 > 0 else 0.0 + put("r600", r600) + r700 = (max(0.0, statutory_round(r600 - g("r610"), 2)) + if r600 > 0 else 0.0) + put("r700", r700) + r800 = statutory_round(r700 - g("r710"), 2) + put("r800", r800) + r810 = self._dppo_min_tax(g("r560")) + put("r810", r810) + # KLADNÝ ROZDIEL — and WHICH ROW carries it depends on the vzor, + # because the form reused the number. Verified against the pinned FS SR + # schemas: 2017/2018/2019 define r800/r810/r820 and NO r830, while + # 2013-2015 and 2024+ define r830 as well. + # + # licencia era, no r830 -> r820 is the rozdiel + # r830 present -> r830 is the rozdiel, r820 is other + # + # Finančná správa's own usmernenie (20. 12. 2018) prints the licencia + # vzor's captions and its worked example: r800 600, r810 960, + # r820 360 — i.e. r810 − r800, not r810. This branch used to write + # r810 into r820 on every vintage, which was right for the minimálna + # daň forms and wrong for the three licencia ones. + rozdiel = max(0.0, statutory_round(r810 - r800, 2)) + # Daň-na-úhradu cascade (DPPOv25 poučenie r830/r900/r1050/r1080/r1100/r1101). + # r830 = kladný rozdiel medzi minimálnou daňou (r810) a daňou (r800) — the + # carry-forwardable min-tax surplus (§ 46b ods. 5). + if "r830" in {d.code for d in self.version_id.line_def_ids}: + put("r820", r810 if r810 > r800 else 0.0) + put("r830", rozdiel) + else: + put("r820", rozdiel) + # r900 = suma na účely určenia výšky preddavkov: the minimum tax when the + # payer actually pays it (override for § 46b ods. 3/7 halving/ + # exemption, or to add r840, in those cases). + if r810 > r800: + put("r900", r810) + # r1050 = daň pred úpravou o preddavky na daň alebo minimálna daň. + r1050 = max(r800, r810) + put("r1050", r1050) + # r1080 = celková daň = súčet riadkov 1050 + 1060 + 1070 + 1071. + r1080 = statutory_round(r1050 + g("r1060") + g("r1070") + g("r1071"), 2) + put("r1080", r1080) + # r1100 = daň (min. daň) na úhradu, nedoplatok (+) = r1080 − preddavky + # (r1040); nula ak ≤ 5 €. r1101 = daňový preplatok (−). + settle = statutory_round(r1080 - g("r1040"), 2) + put("r1100", settle if settle > 5.0 else 0.0) + put("r1101", settle if settle < 0.0 else 0.0) + + def _fill_dodatocne_differences(self): + """Fill the 'posledná známa' rows from the previous return and the + 'rozdiel' rows as (this − previous). Manual overrides are respected.""" + self.ensure_one() + prev = {line.code: line.value for line in self.original_return_id.line_ids} + by_code = {line.code: line for line in self.line_ids} + for known, diff, src in _DODATOCNE_DIFF: + prev_val = prev.get(src, 0.0) + cur_val = by_code[src].value if src in by_code else 0.0 + if known in by_code and not by_code[known].is_overridden: + by_code[known].value = prev_val + if diff in by_code and not by_code[diff].is_overridden: + by_code[diff].value = cur_val - prev_val diff --git a/l10n_sk_dppo/report/dppo2013_report.xml b/l10n_sk_dppo/report/dppo2013_report.xml new file mode 100644 index 00000000..a664c5b4 --- /dev/null +++ b/l10n_sk_dppo/report/dppo2013_report.xml @@ -0,0 +1,358 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2014_report.xml b/l10n_sk_dppo/report/dppo2014_report.xml new file mode 100644 index 00000000..9286b0eb --- /dev/null +++ b/l10n_sk_dppo/report/dppo2014_report.xml @@ -0,0 +1,400 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2015_report.xml b/l10n_sk_dppo/report/dppo2015_report.xml new file mode 100644 index 00000000..eee6c0c8 --- /dev/null +++ b/l10n_sk_dppo/report/dppo2015_report.xml @@ -0,0 +1,380 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2017_report.xml b/l10n_sk_dppo/report/dppo2017_report.xml new file mode 100644 index 00000000..1ead9317 --- /dev/null +++ b/l10n_sk_dppo/report/dppo2017_report.xml @@ -0,0 +1,514 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2018_report.xml b/l10n_sk_dppo/report/dppo2018_report.xml new file mode 100644 index 00000000..559f9b08 --- /dev/null +++ b/l10n_sk_dppo/report/dppo2018_report.xml @@ -0,0 +1,516 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2019_report.xml b/l10n_sk_dppo/report/dppo2019_report.xml new file mode 100644 index 00000000..165da881 --- /dev/null +++ b/l10n_sk_dppo/report/dppo2019_report.xml @@ -0,0 +1,519 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2020_report.xml b/l10n_sk_dppo/report/dppo2020_report.xml new file mode 100644 index 00000000..feebb1eb --- /dev/null +++ b/l10n_sk_dppo/report/dppo2020_report.xml @@ -0,0 +1,529 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2021_report.xml b/l10n_sk_dppo/report/dppo2021_report.xml new file mode 100644 index 00000000..525d914f --- /dev/null +++ b/l10n_sk_dppo/report/dppo2021_report.xml @@ -0,0 +1,525 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2022_report.xml b/l10n_sk_dppo/report/dppo2022_report.xml new file mode 100644 index 00000000..3b55f53a --- /dev/null +++ b/l10n_sk_dppo/report/dppo2022_report.xml @@ -0,0 +1,554 @@ + + + + + diff --git a/l10n_sk_dppo/report/dppo2024_report.xml b/l10n_sk_dppo/report/dppo2024_report.xml new file mode 100644 index 00000000..8b84f387 --- /dev/null +++ b/l10n_sk_dppo/report/dppo2024_report.xml @@ -0,0 +1,739 @@ + + + + diff --git a/l10n_sk_dppo/report/dppo_report.xml b/l10n_sk_dppo/report/dppo_report.xml new file mode 100644 index 00000000..3910af78 --- /dev/null +++ b/l10n_sk_dppo/report/dppo_report.xml @@ -0,0 +1,672 @@ + + + + diff --git a/l10n_sk_dppo/static/description/icon.png b/l10n_sk_dppo/static/description/icon.png new file mode 100644 index 00000000..550cb40e Binary files /dev/null and b/l10n_sk_dppo/static/description/icon.png differ diff --git a/l10n_sk_dppo/static/description/index.html b/l10n_sk_dppo/static/description/index.html new file mode 100644 index 00000000..d8d9cf4d --- /dev/null +++ b/l10n_sk_dppo/static/description/index.html @@ -0,0 +1,61 @@ +
+
+

+ 🌐 EN · + CZ · + SK +

+

Slovakia — Corporate Income Tax Return (DPPO)

+

The Slovak DPPO return: full r-line set, header mapping and XSD-validated dppo2025 XML export

+
+

This app is the Slovak country layer for the income-tax framework + (l10n_cssk_income_tax_base). It provides the DPPOv25 form — daňové + priznanie k dani z príjmov právnických osôb (DPPO) — as version data: the full body line + set (r100 … r1192), with r100 (výsledok hospodárenia pred zdanením) computed + from the P&L and the remaining lines entered by the accountant via the override UI.

+

The header is mapped from the company (DIČ, IČO, obchodné meno, sídlo, zdaňovacie obdobie, + typ priznania). The official dppo2025.xsd is vendored and the XML export is + validated against it.

+
+
+
+ +
+
+

Key features

+
+
    +
  • DPPOv25 body line set (r100 … r1192) as version data.
  • +
  • r100 auto-computed from the accounting result; the rest entered by the + accountant.
  • +
  • Company header mapping (DIČ, IČO, obchodné meno, sídlo, obdobie, typ priznania).
  • +
  • XSD-validated XML export against the official dppo2025 schema.
  • +
+
+
+
+ +
+
+

Usage

+
+

Accounting ▸ Reporting ▸ Slovakia ▸ DPPO. Create a return for the tax period, + review/enter the body lines (r100 is filled from the P&L), then Export XML — + the export is validated against dppo2025.xsd before download.

+

Scope note (v1): the module auto-computes the accounting result and produces + schema-valid XML. The accounting→tax transformation (adjustment lines, loss deduction, + tax credits, minimum tax) is the accountant's domain and is entered manually; the + inter-line computation formulas need accountant validation before being automated.

+
+
+
+ +
+
+
+

Published by Data Dance s.r.o. — + www.datadance.eu

+

License: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_sk_dppo/static/description/index_cz.html b/l10n_sk_dppo/static/description/index_cz.html new file mode 100644 index 00000000..d4042fcc --- /dev/null +++ b/l10n_sk_dppo/static/description/index_cz.html @@ -0,0 +1,61 @@ +
+
+

+ 🌐 EN · + CZ · + SK +

+

Slovensko — přiznání k dani z příjmů právnických osob (DPPO)

+

Slovenské přiznání DPPO: kompletní sada řádků, mapování hlavičky a export XML validovaný proti XSD dppo2025

+
+

Tato aplikace je slovenskou zemskou vrstvou rámce pro daň z příjmů + (l10n_cssk_income_tax_base). Poskytuje formulář DPPOv25 — slovenské + daňové přiznání k dani z příjmů právnických osob — jako verzovaná data: kompletní sadu + řádků těla (r100 … r1192), kde r100 (výsledek hospodaření před zdaněním) se + počítá z výsledovky a ostatní řádky zadává účetní přes UI pro přepis.

+

Hlavička se mapuje ze společnosti (DIČ, IČO, obchodní jméno, sídlo, zdaňovací období, typ + přiznání). Oficiální dppo2025.xsd je přiloženo a export XML je proti němu + validován.

+
+
+
+ +
+
+

Klíčové funkce

+
+
    +
  • Sada řádků těla DPPOv25 (r100 … r1192) jako verzovaná data.
  • +
  • r100 se počítá automaticky z účetního výsledku; zbytek zadává účetní.
  • +
  • Mapování hlavičky ze společnosti (DIČ, IČO, obchodní jméno, sídlo, období, typ + přiznání).
  • +
  • Export XML validovaný proti oficiálnímu schématu dppo2025 (XSD).
  • +
+
+
+
+ +
+
+

Použití

+
+

Účetnictví ▸ Výkazy ▸ Slovensko ▸ DPPO. Vytvořte přiznání za zdaňovací období, + zkontrolujte/zadejte řádky těla (r100 se naplní z výsledovky) a poté Exportovat XML + — export je před stažením validován proti dppo2025.xsd.

+

Poznámka k rozsahu (v1): modul automaticky počítá účetní výsledek a vytváří + schématu odpovídající XML. Transformaci účetnictví→daň (úpravové řádky, odpočet ztráty, + slevy na dani, minimální daň) provádí účetní a zadává ji ručně; meziřádkové výpočetní + vzorce vyžadují validaci účetním, než budou automatizovány.

+
+
+
+ +
+
+
+

Vydává Data Dance s.r.o. — + www.datadance.eu

+

Licence: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_sk_dppo/static/description/index_sk.html b/l10n_sk_dppo/static/description/index_sk.html new file mode 100644 index 00000000..02127778 --- /dev/null +++ b/l10n_sk_dppo/static/description/index_sk.html @@ -0,0 +1,61 @@ +
+
+

+ 🌐 EN · + CZ · + SK +

+

Slovensko — daňové priznanie k dani z príjmov právnických osôb (DPPO)

+

Slovenské priznanie DPPO: kompletná množina riadkov, mapovanie hlavičky a export XML validovaný voči XSD dppo2025

+
+

Táto aplikácia je slovenskou krajinovou vrstvou rámca pre daň z príjmov + (l10n_cssk_income_tax_base). Poskytuje formulár DPPOv25 — slovenské + daňové priznanie k dani z príjmov právnických osôb — ako verzované dáta: kompletnú množinu + riadkov tela (r100 … r1192), kde r100 (výsledok hospodárenia pred zdanením) sa + počíta z výkazu ziskov a strát a ostatné riadky zadáva účtovník cez UI na prepis.

+

Hlavička sa mapuje zo spoločnosti (DIČ, IČO, obchodné meno, sídlo, zdaňovacie obdobie, typ + priznania). Oficiálne dppo2025.xsd je priložené a export XML je voči nemu + validovaný.

+
+
+
+ +
+
+

Kľúčové funkcie

+
+
    +
  • Množina riadkov tela DPPOv25 (r100 … r1192) ako verzované dáta.
  • +
  • r100 sa počíta automaticky z účtovného výsledku; zvyšok zadáva účtovník.
  • +
  • Mapovanie hlavičky zo spoločnosti (DIČ, IČO, obchodné meno, sídlo, obdobie, typ + priznania).
  • +
  • Export XML validovaný voči oficiálnej schéme dppo2025 (XSD).
  • +
+
+
+
+ +
+
+

Použitie

+
+

Účtovníctvo ▸ Výkazy ▸ Slovensko ▸ DPPO. Vytvorte priznanie za zdaňovacie obdobie, + skontrolujte/zadajte riadky tela (r100 sa naplní z výkazu ziskov a strát) a potom + Exportovať XML — export je pred stiahnutím validovaný voči dppo2025.xsd.

+

Poznámka k rozsahu (v1): modul automaticky počíta účtovný výsledok a vytvára XML + zodpovedajúce schéme. Transformáciu účtovníctvo→daň (úpravové riadky, odpočet straty, úľavy + na dani, minimálna daň) vykonáva účtovník a zadáva ju ručne; medziriadkové výpočtové vzorce + vyžadujú validáciu účtovníkom, kým budú automatizované.

+
+
+
+ +
+
+
+

Vydáva Data Dance s.r.o. — + www.datadance.eu

+

Licencia: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_sk_dppo/tests/__init__.py b/l10n_sk_dppo/tests/__init__.py new file mode 100644 index 00000000..ecba2754 --- /dev/null +++ b/l10n_sk_dppo/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sk_dppo diff --git a/l10n_sk_dppo/tests/test_sk_dppo.py b/l10n_sk_dppo/tests/test_sk_dppo.py new file mode 100644 index 00000000..33218b94 --- /dev/null +++ b/l10n_sk_dppo/tests/test_sk_dppo.py @@ -0,0 +1,1127 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.exceptions import UserError +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import TransactionCase, tagged + + +@tagged("post_install", "-at_install") +class TestSkDppo(AccountTestInvoicingCommon): + @classmethod + @AccountTestInvoicingCommon.setup_country("sk") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.l10n_sk_dic = "2020317068" + cls.company.company_registry = "36000001" + cls.version = cls.env.ref("l10n_sk_dppo.dppo_version_2025") + cls.type_r = cls.env.ref("l10n_sk_dppo.dppo_type_R_2025") + + def _make(self): + ret = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, + "version_id": self.version.id, + "statement_type_id": self.type_r.id, + "date_from": "2025-01-01", "date_to": "2025-12-31", + }) + ret.action_compute_lines() + return ret + + def test_tax_spine(self): + """The tax spine computes r100→daň: r310→r400→r500→r510, the § 15 rate + (r550), r600=base×rate, r800, and the § 46b minimálna daň (r810).""" + ret = self._make() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + setv("r560", 40000.0) # úhrn príjmov → 10 % rate, min tax 340 + setv("r301", 50000.0) # adjusted base + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertEqual(v["r550"], 10.0) # 40k ≤ 100k → 10 % + self.assertAlmostEqual(v["r310"], 50000.0, 2) + self.assertAlmostEqual(v["r400"], 50000.0, 2) + self.assertAlmostEqual(v["r500"], 50000.0, 2) + self.assertAlmostEqual(v["r510"], 50000.0, 2) + self.assertAlmostEqual(v["r600"], 5000.0, 2) # 50000 × 10 % + self.assertAlmostEqual(v["r800"], 5000.0, 2) + self.assertAlmostEqual(v["r810"], 340.0, 2) # § 46b minimálna daň + self.assertAlmostEqual(v["r820"], 0.0, 2) # daň > min tax + self.assertAlmostEqual(v["r1050"], 5000.0, 2) # daň na úhradu + + # Small base → minimálna daň dominates. + setv("r301", 1000.0) + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertAlmostEqual(v["r600"], 100.0, 2) # 1000 × 10 % + self.assertAlmostEqual(v["r820"], 340.0, 2) # min tax kicks in + self.assertAlmostEqual(v["r1050"], 340.0, 2) + + def test_tax_spine_half_up_rounding(self): + """Statutory arithmetic rounds HALF-UP, not Python banker's rounding: + r510 = 26.75 at the 10 % rate gives 2.675, which must round to r600 = + 2.68 (Python round(26.75 × 10 / 100, 2) yields 2.67).""" + ret = self._make() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + setv("r560", 40000.0) # úhrn príjmov ≤ 100k → 10 % rate + setv("r301", 26.75) + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertEqual(v["r550"], 10.0) + self.assertAlmostEqual(v["r510"], 26.75, 2) + # 26.75 × 10 % = 2.675 → HALF-UP 2.68 (banker's rounding gives 2.67) + self.assertAlmostEqual(v["r600"], 2.68, 2) + + def test_dan_na_uhradu_cascade(self): + """The r830/r900/r1050/r1080 tail, in both regimes. + + Normal tax dominates (r800 > r810): r820/r830 = 0, r900 stays 0, and + r1050 = daň. Min tax dominates (r810 > r800): r820 = r810, r830 = + r810 − r800 (§ 46b ods. 5 carry-forwardable surplus), r900 = r810, + r1050 = minimálna daň. With no r1060/r1070/r1071, r1080 = r1050.""" + ret = self._make() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + # --- Regime 1: normal tax dominates (r800 = 5000 > r810 = 340) ------- + setv("r560", 40000.0) # → 10 % rate, § 46b min tax 340 + setv("r301", 50000.0) # base → r600 = r800 = 5000 + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertAlmostEqual(v["r800"], 5000.0, 2) + self.assertAlmostEqual(v["r810"], 340.0, 2) + self.assertAlmostEqual(v["r820"], 0.0, 2) # daň > min tax + self.assertAlmostEqual(v["r830"], 0.0, 2) # no min-tax surplus + self.assertAlmostEqual(v["r900"], 0.0, 2) # not set when r810≤r800 + self.assertAlmostEqual(v["r1050"], 5000.0, 2) + self.assertAlmostEqual(v["r1080"], 5000.0, 2) # = r1050 (no additions) + + # --- Regime 2: minimálna daň dominates (r810 = 340 > r800 = 100) ----- + setv("r301", 1000.0) # base → r600 = r800 = 100 + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertAlmostEqual(v["r800"], 100.0, 2) + self.assertAlmostEqual(v["r810"], 340.0, 2) + self.assertAlmostEqual(v["r820"], 340.0, 2) # min tax kicks in + self.assertAlmostEqual(v["r830"], 240.0, 2) # 340 − 100 surplus + self.assertAlmostEqual(v["r900"], 340.0, 2) # = min tax + self.assertAlmostEqual(v["r1050"], 340.0, 2) + self.assertAlmostEqual(v["r1080"], 340.0, 2) + + def test_dan_na_uhradu_settlement_threshold(self): + """r1100/r1101 settlement = r1080 − preddavky (r1040). + + A tax-to-pay (nedoplatok) ≤ 5 € is waived (§ 46 ods. 1) → r1100 = 0; + just over the threshold it is paid in full. The waiver is asymmetric + by law: an overpayment (preplatok, negative settle) is reported on + r1101 with NO 5 € floor — that floor is applied by the tax office at + refund time, not on the form line. + + NOTE (open sign convention — flagged for review): r1101 currently + stores the preplatok as a NEGATIVE number. If the DPPOv25 eForm + expects it as a positive magnitude, change the model and flip the + expected value on the marked assertion below (the XSD does not + constrain the sign, so only this test guards it).""" + ret = self._make() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + setv("r560", 0.0) # no min tax → r800 dominates + setv("r301", 100.0) # → r600 = r800 = 10 → r1050 = r1080 = 10 + ret.action_compute_lines() + self.assertAlmostEqual( + ret.line_ids.filtered(lambda l: l.code == "r1080").value, 10.0, 2) + + def settle(preddavky): + setv("r1040", preddavky) + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + return v["r1100"], v["r1101"] + + # nedoplatok exactly 5.00 € → waived + self.assertEqual(settle(5.0), (0.0, 0.0)) + # nedoplatok 5.01 € → paid in full (no de-minimis deduction) + r1100, r1101 = settle(4.99) + self.assertAlmostEqual(r1100, 5.01, 2) + self.assertAlmostEqual(r1101, 0.0, 2) + # preplatok 5.00 € → recorded despite equal magnitude (asymmetry) + r1100, r1101 = settle(15.0) + self.assertAlmostEqual(r1100, 0.0, 2) + self.assertAlmostEqual(r1101, -5.0, 2) # <-- sign convention (see NOTE) + + def test_r1080_total_tax_components(self): + """r1080 = r1050 + r1060 + r1070 + r1071 (all added). + + NOTE (flagged for review): r1061 and r1062 exist as form lines but are + deliberately NOT in this sum — confirm against the DPPOv25 poučenie + that they do not belong in r1080.""" + ret = self._make() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + setv("r560", 40000.0) + setv("r301", 50000.0) # → r1050 = 5000 + setv("r1060", 10.0) + setv("r1070", 5.0) + setv("r1071", 3.0) + setv("r1061", 100.0) # must be ignored by r1080 + setv("r1062", 100.0) # must be ignored by r1080 + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertAlmostEqual(v["r1050"], 5000.0, 2) + self.assertAlmostEqual(v["r1080"], 5018.0, 2) # 5000 + 10 + 5 + 3 + + def test_r900_zero_when_normal_tax(self): + """r900 (suma na účely určenia výšky preddavkov) stays 0 when normal + tax dominates (r810 ≤ r800) — the spine only sets r900 = r810 when the + payer actually pays the minimálna daň. + + NOTE (open domain decision — flagged for review): this asserts the + CURRENT behaviour (r900 = 0 in the normal-tax case). If r900 should + instead derive from r800 for advance-payment purposes, change the + model and update this expected value — the test exists to make that + decision explicit rather than silent.""" + ret = self._make() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + setv("r560", 40000.0) # § 46b min tax = 340 + setv("r301", 50000.0) # → r800 = 5000 ≫ 340, so normal tax dominates + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertAlmostEqual(v["r800"], 5000.0, 2) + self.assertAlmostEqual(v["r810"], 340.0, 2) + self.assertGreater(v["r800"], v["r810"]) # regime precondition + self.assertAlmostEqual(v["r900"], 0.0, 2) # <-- see NOTE + + # Recompute after the min-tax regime had set r900: the base evaluator + # unlinks+rebuilds every line, so r900 must return to 0 (guards the + # "stale value" false-positive from the second-opinion review). + setv("r301", 1000.0) # r800 = 100 < 340 → r900 = 340 + ret.action_compute_lines() + self.assertAlmostEqual( + ret.line_ids.filtered(lambda l: l.code == "r900").value, 340.0, 2) + setv("r301", 50000.0) # back to normal-tax regime + ret.action_compute_lines() + self.assertAlmostEqual( + ret.line_ids.filtered(lambda l: l.code == "r900").value, 0.0, 2) + + def test_dodatocne_differences(self): + """Dodatočné DPPO auto-fills r1120 (posledná známa daň = previous r1050) + and r1130 (rozdiel = this − previous); loss rows stay 0 (tax→tax).""" + orig = self._make() + orig.line_ids.filtered(lambda l: l.code == "r1050").write( + {"is_overridden": True, "manual_value": 420.0}) + orig.action_compute_lines() + orig.action_export_xml() + orig.action_submit() + + amend = self.env["cssk.income.tax.return"].browse( + orig.action_create_amendment()["res_id"]) + amend.statement_type_id = self.env.ref("l10n_sk_dppo.dppo_type_D_2025").id + self.assertEqual(amend.original_return_id, orig) + # corrected (higher) tax + amend.line_ids.filtered(lambda l: l.code == "r1050").write( + {"is_overridden": True, "manual_value": 500.0}) + amend.action_compute_lines() + + vals = {l.code: l.value for l in amend.line_ids} + self.assertAlmostEqual(vals["r1120"], 420.0, places=2) # posledná známa daň + self.assertAlmostEqual(vals["r1130"], 80.0, places=2) # rozdiel 500 − 420 + self.assertAlmostEqual(vals["r1140"], 0.0, places=2) # žiadna strata + self.assertAlmostEqual(vals["r1150"], 0.0, places=2) + + def test_export_validates_against_official_xsd(self): + ret = self._make() + # action_export_xml renders + asserts validity against dppo2025.xsd + ret.action_export_xml() + self.assertEqual(ret.state, "exported") + self.assertTrue(ret.xml_attachment_id) + + def test_dppo_2024_export_validates_against_official_xsd(self): + """The DPPOv24 export renders the computed spine into the official + form.597 structure and validates against dppo2024.xsd (root , + r1050 carries the computed daň).""" + import base64 + import os + from lxml import etree + v24 = self.env.ref("l10n_sk_dppo.dppo_version_2024") + # wire template + official XSD (the version record is noupdate, so a + # pre-existing test DB may not have them on the record yet) + xsd = os.path.join(os.path.dirname(__file__), "..", "data", "dppo2024.xsd") + with open(xsd, "rb") as fh: + v24.write({"xml_template_ref_id": + self.env.ref("l10n_sk_dppo.dppo2024_xml").id, + "xml_schema_filename": "dppo2024.xsd", + "xml_schema_data": base64.b64encode(fh.read())}) + self.company.write({"city": "Bratislava", "zip": "81101", + "street": "Test 1"}) + ret = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, "version_id": v24.id, + "statement_type_id": self.env.ref("l10n_sk_dppo.dppo_type_R_2024").id, + "date_from": "2024-01-01", "date_to": "2024-12-31"}) + ret.action_compute_lines() + for code, val in (("r100", 19790.86), ("r110", 927.14), + ("r410", 10359.00), ("r560", 116161.85)): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + ret.action_compute_lines() + ret.action_export_xml() # validates vs dppo2024.xsd + self.assertEqual(ret.state, "exported") + root = etree.fromstring(base64.b64decode(ret.xml_attachment_id.datas)) + self.assertEqual(etree.QName(root).localname, "dokument") + self.assertEqual(root.findtext(".//r1050"), "2175.39") # daň renders + self.assertEqual(root.findtext(".//r301"), "20718.00") # base renders + self.assertIsNone(root.find(".//r503")) # 2025-only, absent + + def test_r100_from_profit_and_loss(self): + # Post a simple P&L: revenue 6xx 5000, expense 5xx 3000 -> profit 2000. + Account = self.env["account.account"] + income = Account.search( + [("code", "=like", "602%"), ("company_ids", "in", self.company.id)], + limit=1, + ) + expense = Account.search( + [("code", "=like", "501%"), ("company_ids", "in", self.company.id)], + limit=1, + ) + bank = Account.search( + [("code", "=like", "221%"), ("company_ids", "in", self.company.id)], + limit=1, + ) + self.assertTrue(income and expense and bank, "SK chart accounts present") + move = self.env["account.move"].create({ + "move_type": "entry", + "journal_id": self.company_data["default_journal_misc"].id, + "date": "2025-06-30", + "line_ids": [ + Command.create({"account_id": income.id, "credit": 5000.0}), + Command.create({"account_id": expense.id, "debit": 3000.0}), + Command.create({"account_id": bank.id, "debit": 2000.0}), + ], + }) + move.action_post() + ret = self._make() + r100 = ret.line_ids.filtered(lambda l: l.code == "r100").value + self.assertAlmostEqual(r100, 2000.0, places=2) + + def test_post_income_tax_provision(self): + """The provision action books the computed tax MD 591 / D 341 at period + end, links the entry, guards double-posting, and reverses cleanly.""" + self.version.provision_line_code = "r1050" + + def acc(code, atype): + a = self.env["account.account"].search( + [("code", "=like", code + "%"), + ("company_ids", "in", self.company.id)], limit=1) + if not a: + a = self.env["account.account"].create({ + "name": code, "code": code + "000", "account_type": atype, + "company_ids": [Command.set([self.company.id])]}) + return a + exp, pay = acc("591", "expense"), acc("341", "liability_current") + + ret = self._make() + for code, val in (("r560", 40000.0), ("r301", 50000.0)): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + ret.action_compute_lines() + self.assertAlmostEqual(ret.provision_amount, 5000.0, 2) # r1050 + + ret.action_post_provision() + move = ret.provision_move_id + self.assertTrue(move and move.state == "posted") + self.assertEqual(move.date.isoformat(), "2025-12-31") # period end + deb = move.line_ids.filtered(lambda l: l.debit > 0) + cred = move.line_ids.filtered(lambda l: l.credit > 0) + self.assertEqual(deb.account_id, exp) # MD 591 + self.assertEqual(cred.account_id, pay) # D 341 + self.assertAlmostEqual(deb.debit, 5000.0, 2) + self.assertAlmostEqual(cred.credit, 5000.0, 2) + self.assertTrue(ret.provision_posted) + + # double-post is blocked + with self.assertRaises(UserError): + ret.action_post_provision() + + # reverse clears the link (and books a cancelling reversal) + ret.action_reverse_provision() + self.assertFalse(ret.provision_move_id) + self.assertFalse(ret.provision_posted) + + def test_rate_bands_come_from_version_data(self): + """§ 15 rates are version DATA (rate_bands on the version record), + not code: inclusive upper bounds, null-bounded top band.""" + ret = self._make() + self.assertEqual( + self.version.rate_bands, + [[100000.0, 10.0], [5000000.0, 21.0], [None, 24.0]]) + self.assertEqual(ret._dppo_rate(0.0), 10.0) + self.assertEqual(ret._dppo_rate(100000.0), 10.0) # bound inclusive + self.assertEqual(ret._dppo_rate(100000.01), 21.0) + self.assertEqual(ret._dppo_rate(5000000.0), 21.0) # bound inclusive + self.assertEqual(ret._dppo_rate(5000000.01), 24.0) + + ret24 = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, + "version_id": self.env.ref("l10n_sk_dppo.dppo_version_2024").id, + "statement_type_id": self.env.ref( + "l10n_sk_dppo.dppo_type_R_2024").id, + "date_from": "2024-01-01", "date_to": "2024-12-31"}) + self.assertEqual(ret24._dppo_rate(60000.0), 15.0) # bound inclusive + self.assertEqual(ret24._dppo_rate(60000.01), 21.0) + + def test_min_tax_bands_come_from_version_data(self): + """§ 46b minimum-tax amounts are version DATA (min_tax_bands): + 340/960/1920/3840 € by r560, inclusive bounds; r560 ≤ 0 → no min + tax; short periods prorate by months (HALF-UP).""" + ret = self._make() + self.assertEqual( + self.version.min_tax_bands, + [[50000.0, 340.0], [250000.0, 960.0], [500000.0, 1920.0], + [None, 3840.0]]) + self.assertEqual(ret._dppo_min_tax(0.0), 0.0) + self.assertEqual(ret._dppo_min_tax(-5.0), 0.0) + self.assertEqual(ret._dppo_min_tax(1.0), 340.0) + self.assertEqual(ret._dppo_min_tax(50000.0), 340.0) + self.assertEqual(ret._dppo_min_tax(50000.01), 960.0) + self.assertEqual(ret._dppo_min_tax(250000.0), 960.0) + self.assertEqual(ret._dppo_min_tax(250000.01), 1920.0) + self.assertEqual(ret._dppo_min_tax(500000.0), 1920.0) + self.assertEqual(ret._dppo_min_tax(500000.01), 3840.0) + # month proration: 5-month period → 340 / 12 × 5 = 141.67 (HALF-UP) + ret.write({"date_from": "2025-08-01", "date_to": "2025-12-31"}) + self.assertAlmostEqual(ret._dppo_min_tax(1000.0), 141.67, places=2) + + def test_version_without_bands_raises_loudly(self): + """A version lacking (or with malformed) bands must raise a UserError + instead of silently applying a wrong statutory rate.""" + ret = self._make() + self.version.rate_bands = False + with self.assertRaises(UserError): + ret._dppo_rate(1000.0) + with self.assertRaises(UserError): + ret.action_compute_lines() # the spine calls _dppo_rate + # malformed: no null-bounded catch-all top band + self.version.rate_bands = [[100000.0, 10.0]] + with self.assertRaises(UserError): + ret._dppo_rate(1000.0) + # min-tax bands are guarded the same way + self.version.rate_bands = [[None, 21.0]] + self.version.min_tax_bands = False + with self.assertRaises(UserError): + ret._dppo_min_tax(1000.0) + + def test_dppo_2024_version_rate(self): + """The SK DPPO 2024 version applies the 2024 § 15 schedule (15 % ≤ 60k € + else 21 %), not 2025's 10/21/24 %. Confirms the year-aware rate via the + proper 2024 version — the import's r550 = 15 % was just r560 = 0.""" + v2024 = self.env.ref("l10n_sk_dppo.dppo_version_2024") + self.assertEqual(v2024.valid_from.isoformat(), "2024-01-01") + self.assertEqual(v2024.valid_to.isoformat(), "2024-12-31") + self.assertEqual(v2024.provision_line_code, "r1050") + + ret = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, "version_id": v2024.id, + "statement_type_id": self.env.ref("l10n_sk_dppo.dppo_type_R_2024").id, + "date_from": "2024-01-01", "date_to": "2024-12-31"}) + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + ret.action_compute_lines() + setv("r301", 10000.0) # tax base (after adjustments) + + # úhrn príjmov > 60 000 € → 21 % (the FY2024 real case) + setv("r560", 116161.85) + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertEqual(v["r550"], 21.0) + self.assertAlmostEqual(v["r600"], 2100.0, 2) # 10000 × 21 % + + # úhrn príjmov ≤ 60 000 € → 15 % + setv("r560", 40000.0) + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertEqual(v["r550"], 15.0) + self.assertAlmostEqual(v["r600"], 1500.0, 2) # 10000 × 15 % + + def test_base_cascade_reproduces_mrp_2024(self): + """The base flows from r100: r200 = Σ add-backs, r300 = Σ deductions, + r301 = r100 + r200 − r300. Reproduces the FY2024 real case to the cent — + the import's 960 came from entering r301 as the add-backs (927) instead + of the base (20 718); the user now enters only the judgment rows.""" + v2024 = self.env.ref("l10n_sk_dppo.dppo_version_2024") + ret = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, "version_id": v2024.id, + "statement_type_id": self.env.ref("l10n_sk_dppo.dppo_type_R_2024").id, + "date_from": "2024-01-01", "date_to": "2024-12-31"}) + ret.action_compute_lines() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + setv("r100", 19790.86) # účtovný výsledok pred zdanením (GL) + setv("r110", 927.14) # pripočítateľné (PHM/reprez./pokuty/…) + setv("r410", 10359.00) # odpočet daňovej straty 2023 (50 % cap) + setv("r560", 116161.85) # úhrn zdaniteľných príjmov → 21 % + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertAlmostEqual(v["r200"], 927.14, 2) # add-backs súčet + self.assertAlmostEqual(v["r301"], 20718.00, 2) # base = r100 + 927.14 + self.assertAlmostEqual(v["r510"], 10359.00, 2) # − loss 10 359 + self.assertEqual(v["r550"], 21.0) # > 60k € → 21 % + self.assertAlmostEqual(v["r600"], 2175.39, 2) + self.assertAlmostEqual(v["r1050"], 2175.39, 2) # = MRP, to the cent + + +@tagged("post_install", "-at_install") +class TestLicenciaBlock(AccountTestInvoicingCommon): + """§ 46b: the bands, and which row carries the kladný rozdiel. + + Both were wrong in ways nothing on the reference agenda could show. Its + two filed DPPO years are losses, so the computed daň is 0,00 and the + licencia, the daň na úhradu and the kladný rozdiel all collapse onto one + number — a fixture whose inputs collapse several quantities cannot tell + them apart, however exactly it reconciles. + + So these test the shape against Finančná správa's own worked example + (usmernenie 20. 12. 2018) rather than against any agenda. + """ + + @classmethod + @AccountTestInvoicingCommon.setup_country("sk") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.l10n_sk_dic = "2020317068" + cls.company.company_registry = "36000001" + cls.company.vat = "SK2020317068" + cls.version = cls.env.ref("l10n_sk_dppo.dppo_version_2025") + cls.type_r = cls.env.ref("l10n_sk_dppo.dppo_type_R_2025") + + def _make(self): + ret = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, + "version_id": self.version.id, + "statement_type_id": self.type_r.id, + "date_from": "2025-01-01", "date_to": "2025-12-31", + }) + ret.action_compute_lines() + return ret + + def _version(self, year): + return self.env.ref("l10n_sk_dppo.dppo_version_%s" % year) + + # ------------------------------------------------------------------ + # which row carries the kladný rozdiel + # ------------------------------------------------------------------ + def test_the_licencia_vzory_have_no_r830_to_put_it_on(self): + """The fact the routing turns on, asserted from the shipped data. + + The FS schemas give 2017/2018/2019 as r800/r810/r820 with no r830, + while 2013-2015 and 2024+ define r830 as well. If a future vintage + breaks that, the spine's branch silently changes meaning. + """ + for year in ("2017", "2018", "2019"): + codes = set(self._version(year).line_def_ids.mapped("code")) + self.assertIn("r820", codes, "%s must define r820" % year) + self.assertNotIn( + "r830", codes, + "%s defines r830 — the kladný rozdiel would move rows" % year) + for year in ("2013", "2014", "2015", "2024"): + codes = set(self._version(year).line_def_ids.mapped("code")) + self.assertIn( + "r830", codes, "%s is expected to define r830" % year) + + def test_the_worked_example_from_the_usmernenie(self): + """FS SR's own example, driven through the real spine. + + Usmernenie 20. 12. 2018, licencia-era vzor:: + + r. 800 – Daň po úľavách a po zápočte dane 600 + r. 810 – Daňová licencia 960 + r. 820 – Kladný rozdiel medzi licenciou a daňou 360 + r. 900 – Daňová licencia na úhradu 960 + + r820 is 360, not 960. The spine used to write r810 there on every + vintage — correct for the minimálna daň forms, wrong for these three. + """ + version = self.env.ref("l10n_sk_dppo.dppo_version_2017") + # The 2017 vzor has NO r560, so the band is keyed on class 6 revenue. + # 100 000 keeps us under the 500 000 bound; the company carries a VAT + # number, so the platiteľ band applies: 960. + self.init_invoice( + "out_invoice", partner=self.partner_a, invoice_date="2017-06-30", + amounts=[100000.0], taxes=self.env["account.tax"], post=True, + ) + ret = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, + "version_id": version.id, + "statement_type_id": self.env.ref( + "l10n_sk_dppo.dppo_type_R_2017").id, + "date_from": "2017-01-01", "date_to": "2017-12-31", + }) + ret.action_compute_lines() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + # 21 % of 2 857,14 = 600,00 exactly once rounded — the example's daň. + setv("r301", 2857.14) + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + + self.assertAlmostEqual(v["r800"], 600.0, places=2, + msg="fixture: daň po úľavách must be 600,00") + self.assertAlmostEqual(v["r810"], 960.0, places=2, + msg="platiteľ DPH, obrat ≤ 500 000 → 960") + self.assertAlmostEqual( + v["r820"], 360.0, places=2, + msg="the licencia vzor files the KLADNÝ ROZDIEL on r820, not the " + "licencia itself") + self.assertNotIn("r830", v, "the 2017 vzor has no r830") + + def test_the_min_tax_vzory_keep_the_other_shape(self): + """2024+ has r830, so the rozdiel goes there and r820 keeps its own + meaning. Guards the branch from being 'simplified' into one rule.""" + ret = self._make() + + def setv(code, val): + ret.line_ids.filtered(lambda l: l.code == code).write( + {"is_overridden": True, "manual_value": val}) + + setv("r560", 40000.0) # § 46b minimálna daň 340 + setv("r301", 1000.0) # → r800 = 100 + ret.action_compute_lines() + v = {l.code: l.value for l in ret.line_ids} + self.assertAlmostEqual(v["r800"], 100.0, places=2) + self.assertAlmostEqual(v["r810"], 340.0, places=2) + self.assertAlmostEqual(v["r820"], 340.0, places=2) + self.assertAlmostEqual(v["r830"], 240.0, places=2) + + # ------------------------------------------------------------------ + # the bands + # ------------------------------------------------------------------ + def test_the_licencia_bands_are_seeded_on_the_licencia_vintages(self): + for year in ("2014", "2015", "2017", "2018", "2019"): + bands = self._version(year).min_tax_bands + self.assertTrue( + bands, "%s carries no § 46b bands — r810 would compute 0 " + "while the taxpayer owed a licencia" % year) + + def test_480_and_960_sit_at_the_same_turnover_and_differ_by_vat_status(self): + """The reason a band needs a condition at all. + + A one-dimensional table keyed on turnover cannot express § 46b ods. 2: + both amounts apply at ≤ 500 000 € and only the platiteľ status + separates them. + """ + Return = self.env["cssk.income.tax.return"] + bands = self._version("2017").min_tax_bands + payer = {"vat_payer": True, "not_vat_payer": False} + nonpayer = {"vat_payer": False, "not_vat_payer": True} + + self.assertAlmostEqual( + Return._dppo_band_value(bands, 400000.0, payer), 960.0, places=2) + self.assertAlmostEqual( + Return._dppo_band_value(bands, 400000.0, nonpayer), 480.0, places=2) + # Above the bound neither condition matters. + for conds in (payer, nonpayer): + self.assertAlmostEqual( + Return._dppo_band_value(bands, 900000.0, conds), 2880.0, + places=2) + + def test_an_unevaluable_condition_is_skipped_not_applied(self): + """A band nobody can evaluate must not be chosen by accident.""" + Return = self.env["cssk.income.tax.return"] + bands = [[1000.0, 111.0, "never_set"], [None, 222.0]] + self.assertAlmostEqual( + Return._dppo_band_value(bands, 500.0, {}), 222.0, places=2) + + def test_a_band_without_a_condition_still_works(self): + """The 2024 minimálna daň table has no conditions and must not break.""" + Return = self.env["cssk.income.tax.return"] + bands = self._version("2024").min_tax_bands + self.assertTrue(bands) + self.assertAlmostEqual( + Return._dppo_band_value(bands, 100000.0, {}), 960.0, places=2) + + +class TestDerivedCodesHook(TransactionCase): + """``_cssk_derived_codes`` must agree with what the spine actually writes. + + A comparator against a filed return needs three states, and ``kind`` only + gives two: it names where a row's INPUT comes from, not whether this layer + then overwrites the row. Nineteen DPPO rows are ``kind='manual'`` and + derived — r310 and r400 among them — so a bridge filtering on ``kind`` + either compares rows it cannot check or skips rows it can. + + The set lived only inside ``_compute_tax_spine`` until now, which meant the + only way for a second consumer to have it was to copy it. That is the + duplicated-formula defect, so the declaration is single and ``put()`` + enforces it; this holds the hook to the declaration. + """ + + def _sk_versions(self): + versions = self.env["cssk.income.tax.version"].search( + [("country_id.code", "=", "SK")]) + self.assertTrue(versions, "no SK DPPO versions seeded") + return versions + + def test_the_hook_returns_only_rows_the_vzor_defines(self): + for version in self._sk_versions(): + codes = set(version.line_def_ids.mapped("code")) + derived = version._cssk_derived_codes() + self.assertTrue( + derived, "%s derives nothing at all" % version.name) + self.assertFalse( + derived - codes, + "%s: hook names rows the vzor has not got: %s" + % (version.name, sorted(derived - codes))) + + def test_the_hook_matches_the_spine_declaration(self): + from odoo.addons.l10n_sk_dppo.models.cssk_income_tax import _SPINE_ROWS + for version in self._sk_versions(): + codes = set(version.line_def_ids.mapped("code")) + self.assertEqual( + version._cssk_derived_codes(), codes & set(_SPINE_ROWS), + "%s: the hook and the spine's declaration disagree" + % version.name) + + def test_derived_rows_are_not_distinguishable_by_kind(self): + """The fact that makes the hook necessary, asserted so it stays true. + + If a future change ever gave the derived rows a kind of their own, + this fails — and at that point the hook can be retired rather than + left as a second way of asking the same question. + """ + for version in self._sk_versions(): + derived = version._cssk_derived_codes() + kinds = set(version.line_def_ids.filtered( + lambda d: d.code in derived).mapped("kind")) + self.assertEqual( + kinds, {"manual"}, + "%s: derived rows now carry kinds %s — if `kind` can express " + "'derived', _cssk_derived_codes is redundant" + % (version.name, sorted(kinds))) + + def test_a_non_sk_version_derives_nothing(self): + """The base's contract: only a country layer knows its own spine.""" + other = self.env["cssk.income.tax.version"].search( + [("country_id.code", "!=", "SK")], limit=1) + if not other: + self.skipTest("no non-SK income-tax version installed") + self.assertFalse(other._cssk_derived_codes()) + + +class TestLoadBearingRowsAreLabelled(TransactionCase): + """Labels, and the one block where a row number means two different things. + + Most DPPO rows carry no label and that is deliberate: this repository has + no source for their statutory names, and a plausible-looking invented one + is worse than a blank because it reads as authoritative. + + The rows that ARE labelled split in two: + + * outside the 800/900 block, a row means the same on every vzor — DPPO + numbering is otherwise stable — so a label on one is a label on all; + * inside it, the number was **reused**. r820 is the kladný rozdiel on the + licencia vzory and something else once minimálna daň replaced it, and + r900 moves from "Daňová licencia na úhradu" to "Suma na účely určenia + výšky preddavkov". Those are labelled only where a source covers the + vzor, and left bare everywhere else. + + That split is the whole lesson: an earlier version of this file asserted + the first rule over the second and carried two wrong labels into the + 800-block, extrapolated from the v24/v25 spine onto vzory it does not + describe. Finančná správa's own usmernenie prints those captions and + disagreed. + """ + + # Where a row number was reused between the daňová licencia and the + # minimálna daň eras. Nothing here may be assumed from another vzor. + VOLATILE = ("r800", "r810", "r820", "r830", "r900") + + def _versions(self): + versions = self.env["cssk.income.tax.version"].search( + [("country_id.code", "=", "SK")]) + self.assertTrue(versions, "no SK DPPO versions seeded") + return versions + + def test_a_stable_row_labelled_once_is_labelled_everywhere(self): + versions = self._versions() + labelled = {} + for version in versions: + for ldef in version.line_def_ids: + if ldef.code in self.VOLATILE: + continue + if ldef.name and ldef.name != ldef.code: + labelled.setdefault(ldef.code, ldef.name) + self.assertTrue(labelled, "no stable DPPO row carries a label at all") + + missing = [] + for version in versions: + for ldef in version.line_def_ids: + if ldef.code in self.VOLATILE: + continue + if ldef.code in labelled and ldef.name == ldef.code: + missing.append("%s/%s" % (version.name, ldef.code)) + self.assertFalse( + missing, + "labelled elsewhere but bare here: %s" % ", ".join(sorted(missing))) + + def test_a_stable_row_carries_the_same_label_everywhere(self): + """Two spellings of one row would mean somebody guessed one of them.""" + seen = {} + for version in self._versions(): + for ldef in version.line_def_ids: + if ldef.code in self.VOLATILE or ldef.name == ldef.code: + continue + seen.setdefault(ldef.code, set()).add(ldef.name) + clashes = {c: sorted(n) for c, n in seen.items() if len(n) > 1} + self.assertFalse(clashes, "one row, two labels: %s" % clashes) + + def test_the_provision_row_a_version_names_is_labelled(self): + """``provision_line_code`` posts the year-end tax provision. + + A version field naming a row makes that row load-bearing by + definition — somebody reading the booked entry has to be able to see + which figure it came from. + """ + for version in self._versions(): + code = version.provision_line_code + if not code: + continue + ldef = version.line_def_ids.filtered(lambda d: d.code == code) + self.assertEqual( + len(ldef), 1, + "%s: provision_line_code %s names no line" % (version.name, code)) + self.assertNotEqual( + ldef.name, ldef.code, + "%s: the provision row %s is unlabelled" % (version.name, code)) + + def test_no_era_label_leaks_into_the_other_era(self): + """Daňová licencia to 2017, minimálna daň from 2024 — same § 46b. + + Naming a licencia-era row "minimálna daň" would misname a figure that + was filed under a levy of a different name, and the two eras differ by + more than wording: the row numbers were reused. + """ + for version in self._versions(): + era_is_licencia = version.valid_from.year <= 2019 + for ldef in version.line_def_ids: + if ldef.name == ldef.code: + continue + low = ldef.name.lower() + if era_is_licencia: + self.assertNotIn( + "minimálna daň", low, + "%s (%s): %s is named for the wrong era: %r" + % (version.name, version.valid_from, ldef.code, ldef.name)) + else: + self.assertNotIn( + "licenci", low, + "%s (%s): %s is named for the wrong era: %r" + % (version.name, version.valid_from, ldef.code, ldef.name)) + + +@tagged("post_install", "-at_install") +class TestDppoSchemaPins(TransactionCase): + """The bundled schemas must match data/SCHEMA_VERSION. + + Not a check that our copy is CURRENT — nothing local can answer that, which + is the whole reason the pin file exists. Doubly so here: FS SR revises the + DPPO under a NEW FILENAME (dppo2025_v2.xsd appeared beside an unchanged + dppo2025.xsd), so even re-fetching the pinned name would report "no + change" while a revision sat one URL away. See data/SCHEMA_VERSION. This checks the weaker thing that + is still worth having: that the bundled bytes are the ones somebody + verified against the published copy on a stated date. Replacing a schema + then has to be a deliberate two-file change rather than a silent one, and + the refresh procedure lives next to the digest that would have to move. + """ + + def test_bundled_schemas_match_the_pinned_digests(self): + import hashlib + import os + here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + data = os.path.join(here, "data") + pins = {} + with open(os.path.join(data, "SCHEMA_VERSION"), encoding="utf-8") as fh: + for raw in fh: + parts = raw.split() + if len(parts) == 3 and parts[0].endswith(".xsd"): + pins[parts[0]] = (parts[1], int(parts[2])) + self.assertTrue(pins, "SCHEMA_VERSION lists no schemas") + for name, (digest, size) in pins.items(): + path = os.path.join(data, name) + self.assertTrue(os.path.exists(path), "%s is pinned but missing" % name) + blob = open(path, "rb").read() + self.assertEqual(len(blob), size, "%s size moved" % name) + self.assertEqual( + hashlib.md5(blob).hexdigest(), digest, + "%s does not match its pin — if this is a deliberate refresh, " + "update data/SCHEMA_VERSION and re-run the export tests" % name) + + def test_every_bundled_schema_is_pinned(self): + """A schema added without a pin is the gap this file exists to close.""" + import os + here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + data = os.path.join(here, "data") + on_disk = {f for f in os.listdir(data) if f.endswith(".xsd")} + pinned = set() + with open(os.path.join(data, "SCHEMA_VERSION"), encoding="utf-8") as fh: + for raw in fh: + parts = raw.split() + if len(parts) == 3 and parts[0].endswith(".xsd"): + pinned.add(parts[0]) + self.assertEqual(on_disk, pinned, + "unpinned schema(s): %s" % sorted(on_disk - pinned)) + + +@tagged("post_install", "-at_install") +class TestSkDppoVintages(AccountTestInvoicingCommon): + """One version per vzor, 2013 onwards. + + A DPPO is filed on the tlačivo for its zdaňovacie obdobie, and FS SR keeps + every one it has published. Until now the module carried two, so any period + before 2024 resolved to no version at all — not a wrong figure but NO + figure, the return refusing to compute. That matters for a dodatočné + priznanie, which is exactly the case where an old period comes back. + """ + + #: xmlid suffix -> (schema, first year, last year) + VINTAGES = ( + ("2013", "dppo2013.xsd", 2013, 2013), + ("2014", "dppo2014.xsd", 2014, 2014), + # No dppo2016 exists: 2016 is filed on the 2015 vzor. + ("2015", "dppo2015.xsd", 2015, 2016), + ("2017", "dppo2017.xsd", 2017, 2017), + ("2018", "dppo2018.xsd", 2018, 2018), + ("2019", "dppo2019.xsd", 2019, 2019), + ("2020", "dppo2020.xsd", 2020, 2020), + ("2021", "dppo2021.xsd", 2021, 2021), + # No dppo2023 either: 2023 is filed on the 2022 vzor, which is why the + # 2022 poučenie ships separate instructions for rok 2022 and rok 2023. + ("2022", "dppo2022.xsd", 2022, 2023), + ("2024", "dppo2024.xsd", 2024, 2024), + ("2025", "dppo2025_v2.xsd", 2025, 2025), + ) + + @classmethod + @AccountTestInvoicingCommon.setup_country("sk") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.l10n_sk_dic = "2020317068" + cls.company.company_registry = "36000001" + cls.company.write({"city": "Bratislava", "zip": "81101", + "street": "Test 1"}) + + def _version(self, suffix): + return self.env.ref("l10n_sk_dppo.dppo_version_%s" % suffix) + + def test_every_published_vzor_has_a_version(self): + for suffix, schema, first, last in self.VINTAGES: + version = self._version(suffix) + self.assertEqual(version.country_id.code, "SK") + self.assertEqual(version.xml_root_element, "dokument") + self.assertEqual(version.xml_schema_filename, schema) + self.assertTrue(version.xml_schema_data, "%s XSD not loaded" % suffix) + self.assertEqual(str(version.valid_from), "%d-01-01" % first) + self.assertEqual(str(version.valid_to), "%d-12-31" % last) + self.assertEqual(len(version.statement_type_ids), 3) + + def test_the_vintages_tile_every_year_since_2013(self): + """No year without a vzor, and no year with two. + + 2016 and 2023 are the interesting ones: FS SR published no schema for + either, and they are filed on the preceding vzor rather than being + gaps. + """ + sk = self.env.ref("base.sk") + expected = {} + for suffix, _schema, first, last in self.VINTAGES: + for year in range(first, last + 1): + expected[year] = suffix + for year, suffix in sorted(expected.items()): + found = self.env["cssk.income.tax.version"].search([ + ("country_id", "=", sk.id), + ("valid_from", "<=", "%d-12-31" % year), + "|", ("valid_to", "=", False), ("valid_to", ">=", "%d-01-01" % year), + ]) + self.assertEqual(len(found), 1, + "%d resolves to %d versions" % (year, len(found))) + self.assertEqual(found, self._version(suffix), + "%d reached the wrong vzor" % year) + + def test_each_vintage_defines_exactly_its_schema_s_rows(self): + """Both directions, per vintage. + + A row the XSD has and the version lacks never gets computed; a row the + version has and the XSD lacks would file on a row that did not exist + in that year. Only the nine generated vintages are checked — the 2024 + and 2025 records were written by hand and carry a deliberate subset. + """ + import re + + from odoo.tools.misc import file_path + + generated = [v for v in self.VINTAGES if v[0] not in ("2024", "2025")] + for suffix, schema, _first, _last in generated: + with open(file_path("l10n_sk_dppo/data/%s" % schema), + encoding="utf-8", errors="replace") as handle: + rows = set(re.findall(r'…"`` fragments arrived in the chatter as + literal markup. Nothing failed: the post succeeded and the log was clean, + which is why it survived to a user. + +- Built as ``Markup(template) % args`` rather than by wrapping the finished + string, and the difference is not cosmetic. ``%`` on a ``Markup`` escapes + what it substitutes; wrapping the join renders it. The interpolated values + here are not ours — a row code carries an invoice reference or a + counterparty VAT, a warning detail carries a partner name — so the + shorter fix would have turned an escaping bug into an injection one. + +- One site: the VZS ↔ DPPO reconciliation summary and its *Nezhody* list. + +[19.0.1.0.0] — 2026-09-06 +------------------------- + +Added +~~~~~ + +- Bridge module (auto-install) linking ``l10n_sk_dppo`` × ``l10n_sk_fs``: the + VZS r56 → DPPO r100 reconciliation, the ``VZSDPPO_RECON`` kontrola and the + *Porovnať s účtovnou závierkou* button on the DPPO form. +- Carries the code out of ``l10n_sk_datadance``, where it had been left behind + when both modules moved from ``depends`` to settings toggles in + ``l10n_sk_datadance`` 19.0.1.2.0. The umbrella ``_inherit``ed + ``cssk.income.tax.return`` without the module that defines it, so installing + the Slovak bundle on a clean database failed outright with + ``TypeError: Model 'cssk.income.tax.return' does not exist in registry.`` diff --git a/l10n_sk_dppo_fs/LICENSE b/l10n_sk_dppo_fs/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/l10n_sk_dppo_fs/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/l10n_sk_dppo_fs/README.rst b/l10n_sk_dppo_fs/README.rst new file mode 100644 index 00000000..5ad78989 --- /dev/null +++ b/l10n_sk_dppo_fs/README.rst @@ -0,0 +1,55 @@ +===================================== +SK DPPO ↔ účtovná závierka porovnanie +===================================== + +Glue module that wires the Slovak **DPPO** income-tax return (``l10n_sk_dppo``) +to the **účtovná závierka** — Súvaha + VZS, UZPODv14 (``l10n_sk_fs``) — and adds +the cross-form reconciliation between them to the shared SK reconciliation +engine in ``l10n_sk_datadance``. + +The income-tax return starts from the accounting result: DPPO **r100** +(*výsledok hospodárenia pred zdanením*, ``5,6,-591,-592,-595,-596``) is the same +figure as VZS **r56**. The tie is exact — both exclude daň z príjmov (591/592/595) +and prevod podielov (596) — so a difference is not a rounding artefact but a +disagreement between two filings about the same accounting result, and it is +reported as an error. + +It ``auto_install``s when its two optional pieces are both present. + +Why a separate module +===================== + +``l10n_sk_fs`` and ``l10n_sk_dppo`` are **settings toggles** of the Slovak +bundle, not dependencies of it: both are delivered to subscribers rather than +published, and a hard dependency would make ``l10n_sk_datadance`` +uninstallable from the public repository. Glue that ``_inherit``s +``cssk.income.tax.return`` therefore cannot live in the umbrella — the model is +not in the graph unless the DPPO toggle was ticked. It lives here instead. + +Features +======== + +* *Porovnať s účtovnou závierkou* on the DPPO form: finds the UZPODv14 závierka + covering the return's period (an exact period match wins over an overlap), + compares VZS r56 against DPPO r100 and posts the comparison to the chatter. +* ``VZSDPPO_RECON`` — an **error**-severity kontrola when the two disagree + beyond the reconciliation tolerance, typically a manual r100 override or a + závierka built for a different obdobie. +* ``reconcile_vzs_dppo`` / ``check_kontroly_vzs_dppo`` on + ``l10n.sk.dph.reconciliation``, alongside the KV ↔ priznanie, súhrnný výkaz ↔ + priznanie and priznanie ↔ účtovníctvo reconciliations. + +Usage +===== + +Build the účtovná závierka for the year (*Accounting ▸ Reporting ▸ Účtovná +závierka*) and the DPPO return for the same period, compute both, then press +*Porovnať s účtovnou závierkou* on the return. With no závierka for the period +the action explains that and stops rather than comparing against nothing. + +Credits +======= + +* Data Dance s.r.o. + +License: AGPL-3.0 or later (see the LICENSE file). diff --git a/l10n_sk_dppo_fs/__init__.py b/l10n_sk_dppo_fs/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_sk_dppo_fs/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_sk_dppo_fs/__manifest__.py b/l10n_sk_dppo_fs/__manifest__.py new file mode 100644 index 00000000..a36a7162 --- /dev/null +++ b/l10n_sk_dppo_fs/__manifest__.py @@ -0,0 +1,39 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Porovnanie DPPO s účtovnou závierkou (SK)", + "summary": "Tie the DPPO r100 to the VZS r56 of the účtovná závierka " + "(UZPODv14) and flag a disagreement before filing.", + "description": """ +Porovnanie účtovnej závierky ↔ DPPO +=================================== + +The income-tax return starts from the accounting result: DPPO **r100** +(výsledok hospodárenia pred zdanením) is the same figure as VZS **r56** of the +účtovná závierka. This adds the *Porovnať s účtovnou závierkou* button to the +DPPO form, which finds the UZPODv14 závierka covering the period, compares the +two, and posts the result — a mismatch means the two filings disagree on the +accounting result. + +Bridge module: it exists only where both optional pieces of the bundle are +installed (``l10n_sk_dppo`` for the return, ``l10n_sk_fs`` for the závierka), +and installs itself automatically when they are. The umbrella +(``l10n_sk_datadance``) must not depend on either — both are settings toggles — +which is why this glue cannot live there. +""", + "author": "Data Dance s.r.o.", + "website": "https://www.datadance.eu", + "version": "19.0.1.1.2", + "category": "Accounting/Localizations", + "license": "AGPL-3", + "depends": [ + "l10n_sk_datadance", + "l10n_sk_dppo", + "l10n_sk_fs", + ], + "data": [ + "views/cssk_income_tax_views.xml", + ], + "auto_install": True, + "installable": True, +} diff --git a/l10n_sk_dppo_fs/i18n/l10n_sk_dppo_fs.pot b/l10n_sk_dppo_fs/i18n/l10n_sk_dppo_fs.pot new file mode 100644 index 00000000..e05fe992 --- /dev/null +++ b/l10n_sk_dppo_fs/i18n/l10n_sk_dppo_fs.pot @@ -0,0 +1,111 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_dppo_fs +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-09-06 18:48+0000\n" +"PO-Revision-Date: 2026-09-06 18:48+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "%d nezhôd — pozri záznam." +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model:ir.model.fields,field_description:l10n_sk_dppo_fs.field_cssk_income_tax_return__display_name +#: model:ir.model.fields,field_description:l10n_sk_dppo_fs.field_l10n_sk_dph_reconciliation__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model:ir.model.fields,field_description:l10n_sk_dppo_fs.field_cssk_income_tax_return__id +#: model:ir.model.fields,field_description:l10n_sk_dppo_fs.field_l10n_sk_dph_reconciliation__id +msgid "ID" +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model:ir.model,name:l10n_sk_dppo_fs.model_cssk_income_tax_return +msgid "Income Tax Return" +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "Nezhody:" +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "" +"No UZPODv14 účtovná závierka found for this period — create the financial " +"statements for %(df)s – %(dt)s first." +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "Porovnanie VZS ↔ DPPO" +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "Porovnanie účtovnej závierky ↔ DPPO" +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_dppo_fs.cssk_income_tax_return_view_form_reconcile +msgid "Porovnať s účtovnou závierkou" +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model:ir.model,name:l10n_sk_dppo_fs.model_l10n_sk_dph_reconciliation +msgid "SK cross-form reconciliation (KV↔DP, SV↔DP, DP↔účtovníctvo)" +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "VZS a DPPO sa zhodujú." +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/l10n_sk_dph_reconciliation.py:0 +msgid "Výsledok hospodárenia pred zdanením (VZS r56 = DPPO r100)" +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "DPPO ↔ účtovná závierka — %s" +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model:ir.module.module,shortdesc:base.module_l10n_sk_dppo_fs +msgid "Porovnanie DPPO s účtovnou závierkou (SK)" +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model:ir.module.module,summary:base.module_l10n_sk_dppo_fs +msgid "" +"Tie the DPPO r100 to the VZS r56 of the účtovná závierka (UZPODv14) and flag" +" a disagreement before filing." +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "účtovná závierka %s" +msgstr "" diff --git a/l10n_sk_dppo_fs/i18n/sk.po b/l10n_sk_dppo_fs/i18n/sk.po new file mode 100644 index 00000000..8fb3152a --- /dev/null +++ b/l10n_sk_dppo_fs/i18n/sk.po @@ -0,0 +1,114 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_dppo_fs +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-09-06 18:48+0000\n" +"PO-Revision-Date: 2026-09-06 18:48+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: sk_SK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "%d nezhôd — pozri záznam." +msgstr "%d nezhôd — pozri záznam." + +#. module: l10n_sk_dppo_fs +#: model:ir.model.fields,field_description:l10n_sk_dppo_fs.field_cssk_income_tax_return__display_name +#: model:ir.model.fields,field_description:l10n_sk_dppo_fs.field_l10n_sk_dph_reconciliation__display_name +msgid "Display Name" +msgstr "Zobrazovaný názov" + +#. module: l10n_sk_dppo_fs +#: model:ir.model.fields,field_description:l10n_sk_dppo_fs.field_cssk_income_tax_return__id +#: model:ir.model.fields,field_description:l10n_sk_dppo_fs.field_l10n_sk_dph_reconciliation__id +msgid "ID" +msgstr "ID" + +#. module: l10n_sk_dppo_fs +#: model:ir.model,name:l10n_sk_dppo_fs.model_cssk_income_tax_return +msgid "Income Tax Return" +msgstr "Daňové priznanie k dani z príjmov" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "Nezhody:" +msgstr "Nezhody:" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "" +"No UZPODv14 účtovná závierka found for this period — create the financial " +"statements for %(df)s – %(dt)s first." +msgstr "" +"Pre toto obdobie sa nenašla účtovná závierka UZPODv14 — najprv vytvorte " +"účtovnú závierku za %(df)s – %(dt)s." + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "Porovnanie VZS ↔ DPPO" +msgstr "Porovnanie VZS ↔ DPPO" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "Porovnanie účtovnej závierky ↔ DPPO" +msgstr "Porovnanie účtovnej závierky ↔ DPPO" + +#. module: l10n_sk_dppo_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_dppo_fs.cssk_income_tax_return_view_form_reconcile +msgid "Porovnať s účtovnou závierkou" +msgstr "Porovnať s účtovnou závierkou" + +#. module: l10n_sk_dppo_fs +#: model:ir.model,name:l10n_sk_dppo_fs.model_l10n_sk_dph_reconciliation +msgid "SK cross-form reconciliation (KV↔DP, SV↔DP, DP↔účtovníctvo)" +msgstr "Krížová kontrola SK výkazov (KV↔DP, SV↔DP, DP↔účtovníctvo)" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "VZS a DPPO sa zhodujú." +msgstr "VZS a DPPO sa zhodujú." + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/l10n_sk_dph_reconciliation.py:0 +msgid "Výsledok hospodárenia pred zdanením (VZS r56 = DPPO r100)" +msgstr "Výsledok hospodárenia pred zdanením (VZS r56 = DPPO r100)" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "DPPO ↔ účtovná závierka — %s" +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model:ir.module.module,shortdesc:base.module_l10n_sk_dppo_fs +msgid "Porovnanie DPPO s účtovnou závierkou (SK)" +msgstr "" + +#. module: l10n_sk_dppo_fs +#: model:ir.module.module,summary:base.module_l10n_sk_dppo_fs +msgid "" +"Tie the DPPO r100 to the VZS r56 of the účtovná závierka (UZPODv14) and flag" +" a disagreement before filing." +msgstr "" + +#. module: l10n_sk_dppo_fs +#. odoo-python +#: code:addons/l10n_sk_dppo_fs/models/cssk_income_tax.py:0 +msgid "účtovná závierka %s" +msgstr "" diff --git a/l10n_sk_dppo_fs/models/__init__.py b/l10n_sk_dppo_fs/models/__init__.py new file mode 100644 index 00000000..f9774cfa --- /dev/null +++ b/l10n_sk_dppo_fs/models/__init__.py @@ -0,0 +1,2 @@ +from . import l10n_sk_dph_reconciliation +from . import cssk_income_tax diff --git a/l10n_sk_dppo_fs/models/cssk_income_tax.py b/l10n_sk_dppo_fs/models/cssk_income_tax.py new file mode 100644 index 00000000..70a6619b --- /dev/null +++ b/l10n_sk_dppo_fs/models/cssk_income_tax.py @@ -0,0 +1,47 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""DPPO → reconcile-with-účtovná-závierka action (umbrella module).""" +from odoo import _, models +from odoo.exceptions import UserError + + +class CSSKIncomeTaxReturn(models.Model): + _inherit = "cssk.income.tax.return" + + def _find_uzpod(self): + """The UZPODv14 účtovná závierka covering this DPPO period.""" + self.ensure_one() + uz = self.env["l10n.sk.uzpod"].search([ + ("company_id", "=", self.company_id.id), + ("date_from", "<=", self.date_to), + ("date_to", ">=", self.date_from), + ], order="date_to desc") + exact = uz.filtered( + lambda u: u.date_from == self.date_from and u.date_to == self.date_to) + return (exact or uz)[:1] + + def action_reconcile_vzs(self): + """Porovnanie účtovnej závierky (VZS) ↔ DPPO r100 — on screen.""" + self.ensure_one() + uzpod = self._find_uzpod() + if not uzpod: + raise UserError(_( + "No UZPODv14 účtovná závierka found for this period — create " + "the financial statements for %(df)s – %(dt)s first.", + df=self.date_from, dt=self.date_to)) + recon = self.env["l10n.sk.dph.reconciliation"] + details = {e["desc"]: e["detail"] + for e in recon.check_kontroly_vzs_dppo(uzpod, self)} + # ``reconcile_vzs_dppo`` reads VZS-first; the screen reads + # THIS-filing-first, on every comparator, so the left column always + # holds the record the user is standing on. Flipped here rather than + # in the reconciliation, which other callers and its tests share. + self._cssk_upsert_comparison_rows( + [{"code": r["block"], "label": r["label"], + "filed": r["right"], "computed": r["left"], "diff": -r["diff"], + "kind": recon.recon_kind(r["status"]), + "detail": details.get(r["label"])} + for r in recon.reconcile_vzs_dppo(uzpod, self)], + "filing", _("účtovná závierka %s", uzpod.display_name)) + return self._cssk_comparison_action( + "filing", _("DPPO ↔ účtovná závierka — %s", self.display_name)) diff --git a/l10n_sk_dppo_fs/models/l10n_sk_dph_reconciliation.py b/l10n_sk_dppo_fs/models/l10n_sk_dph_reconciliation.py new file mode 100644 index 00000000..5ad73d3d --- /dev/null +++ b/l10n_sk_dppo_fs/models/l10n_sk_dph_reconciliation.py @@ -0,0 +1,45 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Účtovná závierka (VZS) → DPPO cross-form reconciliation. + +The income-tax return starts from the accounting result: DPPO r100 ("výsledok +hospodárenia pred zdanením", account_formula 5,6,-591,-592,-595,-596) equals +VZS r56 (the same result before income tax — both exclude daň z príjmov +591/592/595 and prevod podielov 596). This is an EXACT tie — a mismatch means +the two filings disagree on the accounting result (a manual r100 override or a +different obdobie), so it is an error, not info. + +It lives in this bridge rather than next to the KV/SV/DP reconciliations in +``l10n_sk_datadance`` because it is the only one of the four that needs modules +the umbrella deliberately does not depend on. +""" +from odoo import _, api, models + + +class L10nSkDphReconciliation(models.TransientModel): + _inherit = "l10n.sk.dph.reconciliation" + + @api.model + def reconcile_vzs_dppo(self, uzpod, dppo): + """Return ``{block, label, left, right, diff, status}`` (``left`` = VZS, + ``right`` = DPPO r100).""" + vzs = uzpod.vzs_before_tax() + r100 = next((l.value for l in dppo.line_ids if l.code == "r100"), 0.0) + diff = vzs - r100 + return [{ + "block": "vzs_r100", "left": vzs, "right": r100, "diff": diff, + "status": "ok" if abs(diff) <= self._RECON_TOL else "mismatch", + "label": _("Výsledok hospodárenia pred zdanením (VZS r56 = DPPO r100)")}] + + @api.model + def check_kontroly_vzs_dppo(self, uzpod, dppo): + out = [] + for row in self.reconcile_vzs_dppo(uzpod, dppo): + if row["status"] == "mismatch": + out.append({ + "code": "VZSDPPO_RECON", "severity": "error", + "desc": row["label"], + "detail": "VZS %.2f != DPPO r100 %.2f (rozdiel %.2f) — " + "skontrolujte obdobie alebo manuálnu úpravu r100" + % (row["left"], row["right"], row["diff"])}) + return out diff --git a/l10n_sk_dppo_fs/static/description/icon.png b/l10n_sk_dppo_fs/static/description/icon.png new file mode 100644 index 00000000..550cb40e Binary files /dev/null and b/l10n_sk_dppo_fs/static/description/icon.png differ diff --git a/l10n_sk_dppo_fs/tests/__init__.py b/l10n_sk_dppo_fs/tests/__init__.py new file mode 100644 index 00000000..4879355f --- /dev/null +++ b/l10n_sk_dppo_fs/tests/__init__.py @@ -0,0 +1 @@ +from . import test_vzs_dppo diff --git a/l10n_sk_dppo_fs/tests/test_vzs_dppo.py b/l10n_sk_dppo_fs/tests/test_vzs_dppo.py new file mode 100644 index 00000000..9f7cafec --- /dev/null +++ b/l10n_sk_dppo_fs/tests/test_vzs_dppo.py @@ -0,0 +1,144 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.exceptions import UserError +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestSkVzsDppo(AccountTestInvoicingCommon): + """VZS r56 → DPPO r100, both built from the same posted SK data.""" + + @classmethod + @AccountTestInvoicingCommon.setup_country("sk") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.write({"vat": "SK2023456787", "city": "Bratislava", + "country_id": cls.env.ref("base.sk").id}) + cls.recon = cls.env["l10n.sk.dph.reconciliation"] + cls.journal = cls.env["account.journal"].search( + [("type", "=", "general"), ("company_id", "=", cls.company.id)], limit=1) + + def _acc(self, code, account_type="expense"): + acc = self.env["account.account"].search( + [("code", "=like", code + "%"), ("company_ids", "in", self.company.id)], + limit=1) + if not acc: + acc = self.env["account.account"].create({ + "name": code, "code": code, "account_type": account_type, + "company_ids": [Command.set([self.company.id])]}) + return acc + + def _post_pnl(self): + # P&L: revenue 602 = 5000, expense 501 = 3000 -> VH pred zdanením 2000. + # Also post income tax 595 (dodatočné odvody) = 200 and prevod podielov + # 596 = 100: both sit BELOW VH pred zdanením (VZS r57+/r60), so neither + # the VZS r56 nor the corrected DPPO r100 (…,-595,-596) may include them. + rev = self._acc("602", "income") + exp, a595, a596 = self._acc("501"), self._acc("595"), self._acc("596") + bank = self._acc("221", "asset_cash") + self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2025-06-30", "line_ids": [ + Command.create({"account_id": rev.id, "credit": 5000.0}), + Command.create({"account_id": exp.id, "debit": 3000.0}), + Command.create({"account_id": a595.id, "debit": 200.0}), + Command.create({"account_id": a596.id, "debit": 100.0}), + Command.create({"account_id": bank.id, "debit": 1700.0})], + }).action_post() + + def _dppo(self): + dppo = self.env["cssk.income.tax.return"].create({ + "company_id": self.company.id, + "version_id": self.env.ref("l10n_sk_dppo.dppo_version_2025").id, + "statement_type_id": self.env.ref("l10n_sk_dppo.dppo_type_R_2025").id, + "date_from": "2025-01-01", "date_to": "2025-12-31"}) + dppo.action_compute_lines() + return dppo + + def _uzpod(self): + return self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, + "date_from": "2025-01-01", "date_to": "2025-12-31"}) + + def test_vzs_dppo(self): + self._post_pnl() + dppo, uzpod = self._dppo(), self._uzpod() + + row = self.recon.reconcile_vzs_dppo(uzpod, dppo)[0] + self.assertAlmostEqual(row["left"], 2000.0, places=2) # VZS pred zdanením + self.assertAlmostEqual(row["right"], 2000.0, places=2) # DPPO r100 + self.assertEqual(row["status"], "ok") + self.assertEqual(self.recon.check_kontroly_vzs_dppo(uzpod, dppo), []) + + # Break the tie (manual r100 override) -> VZSDPPO_RECON (error). + r100 = dppo.line_ids.filtered(lambda l: l.code == "r100") + r100.write({"is_overridden": True, "manual_value": 9999.0}) + dppo.action_compute_lines() + self.assertIn("VZSDPPO_RECON", + [v["code"] for v in self.recon.check_kontroly_vzs_dppo(uzpod, dppo)]) + + def test_action_reconcile_vzs_records_the_comparison(self): + """The form button: find the závierka, record the rows, open them. + + It used to post the comparison to the chatter and return a toast + saying "pozri záznam". The rows are the record now — and the left + column holds THIS filing on every comparator, so the r100 the user is + standing on is on the left and the VZS it is checked against is on the + right, even though the reconciliation itself reads VZS-first. + """ + self._post_pnl() + self._uzpod() + dppo = self._dppo() + before = dppo.message_ids + result = dppo.action_reconcile_vzs() + self.assertFalse(dppo.message_ids - before, + "reconciling must not post to the chatter") + self.assertEqual(result["type"], "ir.actions.act_window") + self.assertEqual(result["res_model"], "cssk.filing.discrepancy") + + row = self.env["cssk.filing.discrepancy"].search([ + ("res_model", "=", "cssk.income.tax.return"), + ("res_id", "=", dppo.id), ("basis", "=", "filing")]) + self.assertEqual(len(row), 1) + self.assertEqual(row.kind, "ok") + self.assertEqual(row.state, "agrees") + self.assertAlmostEqual(row.filed, 2000.0, places=2) # DPPO r100 + self.assertAlmostEqual(row.computed, 2000.0, places=2) # VZS + + def test_two_comparisons_of_one_filing_do_not_overwrite_each_other(self): + """``basis`` is in the key, and it has to be. + + A filing is compared against more than one right-hand side — itself + recomputed, the ledger, another filing — and those comparisons can + carry the same row code with different figures. Keyed without the + basis, the second run overwrites the first and the screen shows one + comparison wearing another's numbers. + """ + self._post_pnl() + self._uzpod() + dppo = self._dppo() + dppo.action_reconcile_vzs() + Disc = self.env["cssk.filing.discrepancy"] + row = Disc.search([("res_id", "=", dppo.id), + ("res_model", "=", "cssk.income.tax.return")]) + self.assertEqual(len(row), 1) + dppo._cssk_upsert_comparison_rows( + [{"code": row.code, "label": "same code, other basis", + "filed": 1.0, "computed": 2.0, "diff": -1.0, "kind": "amount"}], + "ledger", "účtovníctvo") + both = Disc.search([("res_id", "=", dppo.id), + ("res_model", "=", "cssk.income.tax.return")]) + self.assertEqual(len(both), 2, "the same code on two bases is two rows") + self.assertEqual(set(both.mapped("basis")), {"filing", "ledger"}) + + def test_action_reconcile_vzs_without_a_zavierka(self): + """And it must be *that* UserError — any other one would pass a bare + ``assertRaises`` while the no-závierka branch never ran.""" + self._post_pnl() + dppo = self._dppo() + with self.assertRaisesRegex(UserError, "UZPODv14"): + dppo.action_reconcile_vzs() diff --git a/l10n_sk_dppo_fs/views/cssk_income_tax_views.xml b/l10n_sk_dppo_fs/views/cssk_income_tax_views.xml new file mode 100644 index 00000000..ddee1143 --- /dev/null +++ b/l10n_sk_dppo_fs/views/cssk_income_tax_views.xml @@ -0,0 +1,19 @@ + + + + + + cssk.income.tax.return.form.reconcile + cssk.income.tax.return + + + + + + + diff --git a/l10n_sk_ekasa_receipt/CHANGELOG.rst b/l10n_sk_ekasa_receipt/CHANGELOG.rst new file mode 100644 index 00000000..810682c5 --- /dev/null +++ b/l10n_sk_ekasa_receipt/CHANGELOG.rst @@ -0,0 +1,39 @@ +========= +Changelog +========= + +All notable changes to **l10n_sk_ekasa_receipt** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[19.0.1.0.0] — 2026-10-06 +------------------------- + +Added +~~~~~ + +- ``cssk.receipt.provider.sk_ekasa``: reads the QR code on a Slovak fiscal + receipt and fetches the registered receipt — items, quantities, per-rate VAT + recap, seller identity — from Finančná správa's document-verification service. +- Both receipt shapes: the on-line identifier (``O-``/``V-`` + 32 hex) and the + off-line composite key (OKP, cash-register code, timestamp, number, total), + with the service's returned identifier stored as the deduplication key. +- ``sk.ekasa.call`` ledger: every lookup recorded with its outcome, serving both + as the hourly-budget counter and as the company's own record of what it + verified (§ 18 ods. 11 requires truthful results be sent to Finančná správa). +- Hourly budget enforcement (default 60 per clock hour), refusing the capture + rather than risking the IP address being blocked. +- § 18 ods. 11 gate: lookups are refused until the company declares that + Finančná správa has been notified of the IP address it calls from. Finančná + správa had not published the conditions of use as of 2026-10-05. +- Mapping pinned by tests against two verbatim service responses: VAT recap from + ``vatSummary`` only (the legacy basic/reduced pair carries stale rate labels or + no data at all), ``price`` treated as the VAT-inclusive line total, + ``organization`` separated from ``unit``, ``icDph`` not derived from ``dic``, + ``issueDate`` rather than ``createDate``, and a null body read as "not + registered" rather than as an empty success. + +- When a response carries no ``vatSummary``, the recap is now derived from the + items' own rates rather than the legacy ``basic``/``reduced`` pair. The pair + holds two rates and a receipt can carry three, so reading it would silently + flatten a three-rate receipt; it is used only when there are no items either, + and either path raises a warning. (From the same review.) diff --git a/l10n_sk_ekasa_receipt/LICENSE b/l10n_sk_ekasa_receipt/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/l10n_sk_ekasa_receipt/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/l10n_sk_ekasa_receipt/README.rst b/l10n_sk_ekasa_receipt/README.rst new file mode 100644 index 00000000..f55d69c2 --- /dev/null +++ b/l10n_sk_ekasa_receipt/README.rst @@ -0,0 +1,107 @@ +============================================ +Slovak eKasa Receipt Capture (bločky z QR) +============================================ + +A Slovak fiscal receipt carries a QR code, and behind it Finančná správa holds +the receipt the seller actually registered: every item with its quantity and VAT +rate, and the per-rate VAT recap. This module reads the code and fetches that +record, so a bloček is posted from the seller's own figures instead of an OCR +guess. + +The two receipt shapes +====================== + +**On-line.** The QR code *is* the identifier — ``O-`` and 32 hexadecimal +characters, or ``V-`` from the virtual register — and carries nothing else, so +the lookup is not an optimisation, it is the only way to learn anything. + +**Off-line.** Printed when the till exceeded the response-time limit, with no +identifier assigned yet. The QR instead carries +``OKP:kódPokladnice:dátumČas:poradovéČíslo:celkováSuma``, which the service takes +as a composite key. An off-line receipt is **not permanently off-line**: once the +till catches up the service returns its real identifier, and that is what this +module stores as the deduplication key — the same purchase otherwise has two +possible QR payloads and only one identity. + +Legal basis, and the duty that comes with it +============================================ + +Act **384/2025 Z. z. o evidencii tržieb**, in force since 1 January 2026, +creates the service outright. Before it, Finančná správa's standing answer was +that the *Over doklad* API could not be made available to third parties at all. + +* § 2 ai) — the *služba na overovanie dokladov* lets one verify the data held in + the eKasa system through the receipt's QR code **and "sprístupňovať a získavať + tieto údaje"**. +* § 2 aj) — an *overovateľ* is a person entitled to do so under § 18 ods. 11. +* § 18 ods. 3 — the seller **"je povinný strpieť"** the disclosure. +* § 18 ods. 11 — before first use the overovateľ must **notify Finančná správa + of the IP address** it will call from, must send truthful verification results, + and must follow conditions Finančná správa determines and publishes. + +**Those conditions were not published when this module was written** (checked +5 October 2026). The module therefore does not pretend the obligation away: the +company settings carry the declaration, and every lookup is refused until +somebody states that the notification has been made. Satisfying it today means +writing to Finančná správa. + +Rate limit +========== + +The service permits roughly **60 lookups per clock hour per IP address** and +blocks the address beyond that. This module keeps its own ledger +(``sk.ekasa.call``) and refuses to exceed the configured budget rather than lose +access. Note that the real limit is per *address*: several companies behind one +egress address share one budget, which is the argument for eventually moving the +call into the accountant's browser — the service sends +``Access-Control-Allow-Origin: *``, so that is possible without a proxy, and each +user would then have their own budget and their own notified address. + +What the mapping gets right on purpose +====================================== + +Each of these was measured on a real receipt, and each is a way to post the wrong +number: + +**The VAT recap is read from ``vatSummary`` and nowhere else.** The legacy +``vatRateBasic`` / ``vatRateReduced`` pair was observed carrying **stale 20/10 +labels** on a receipt whose items and summary said 23/5 — the amounts beside them +were right, the rates were from the pre-2025 regime — and arriving **entirely +null** on another. It also has only two slots, and three rates on one receipt is +routine in Slovak hospitality. + +**``items[].price`` is the VAT-inclusive line total, not a unit price.** The +twelve items on the restaurant bill sum to exactly its total, and the quantity-2 +lines divide cleanly. Dividing to get a unit price loses money: 57.85 for +31.17 litres is 1.855951… and 1.86 × 31.17 is 57.98. + +**Item names arrive multi-line, with modifiers already paid for.** ``HOVÄDZÍ BBQ +SET\n1x Príloha navyše +3EUR`` is one line of 48.00 that already contains the +3.00. The name is flattened for a label and never split. + +**``organization`` is the seller; ``unit`` is the premises.** A motorway service +station is not Vzorová čerpacia stanica's registered seat, so the premises becomes a note and +never reaches the partner record. + +**``icDph`` is not ``"SK" + dic``.** A VAT-group member files under the group's +number: on one receipt ``dic`` is 2077000002 while ``icDph`` is SK7199000006. +Both are stored; neither is derived. + +**``issueDate``, not ``createDate``** — they differ, and the issue date is the +accounting date. And ``okp`` comes back in either case, so it is stored +upper-cased. + +A receipt that is not in the system comes back as **HTTP 200 with a null body**, +never as an error, so that is tested for explicitly. + +Configuration +============= + +*Settings ▸ Accounting ▸ Fiscal Receipt Capture ▸ Slovak eKasa*. + +Credits +======= + +* Data Dance s.r.o. + +License: AGPL-3.0 or later (see the LICENSE file). diff --git a/l10n_sk_ekasa_receipt/__init__.py b/l10n_sk_ekasa_receipt/__init__.py new file mode 100644 index 00000000..cc6b6354 --- /dev/null +++ b/l10n_sk_ekasa_receipt/__init__.py @@ -0,0 +1,2 @@ +from . import models +from .hooks import post_init_hook diff --git a/l10n_sk_ekasa_receipt/__manifest__.py b/l10n_sk_ekasa_receipt/__manifest__.py new file mode 100644 index 00000000..e5ca11ac --- /dev/null +++ b/l10n_sk_ekasa_receipt/__manifest__.py @@ -0,0 +1,80 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Slovak eKasa Receipt Capture (bločky z QR kódu)", + "version": "19.0.1.0.0", + "summary": "Read the QR code on a Slovak fiscal receipt and fetch the " + "registered receipt — items, quantities and the VAT recap — " + "from Finančná správa's document-verification service.", + "description": """ +Slovak eKasa Receipt Capture +============================ + +A Slovak fiscal receipt carries a QR code, and behind it Finančná správa holds +the receipt the seller actually registered: every item, its quantity, its VAT +rate, and the per-rate VAT recap. This module reads the code and fetches that +record, so a bloček is posted from the seller's own figures rather than from an +OCR guess. + +Both receipt shapes are handled: + +* **On-line** — the QR code *is* the identifier (``O-`` + 32 hex, or ``V-`` for + a VRP receipt) and nothing else, so the lookup is unavoidable. +* **Off-line** — printed when the till exceeded the response-time limit, with no + identifier. The QR instead carries + ``OKP:kódPokladnice:dátumČas:poradovéČíslo:celkováSuma``, which the service + accepts as a composite key. An off-line receipt is not permanently off-line: + once the till catches up the service returns its real identifier, which is + what this module stores as the deduplication key. + +Legal basis +----------- + +Act **384/2025 Z. z. o evidencii tržieb**, in force since 1 January 2026, +creates the service outright. § 2 ai) defines the *služba na overovanie dokladov* +as the service that, through the QR code, allows one to verify the data held in +the eKasa system **and "sprístupňovať a získavať tieto údaje"**; § 2 aj) defines +an *overovateľ* as a person entitled to do so; § 18 ods. 3 obliges the seller to +tolerate the disclosure. + +§ 18 ods. 11 is the duty side, and this module surfaces it rather than hiding it: +an overovateľ must **notify Finančná správa of the IP address** it will use +before first use, must send truthful verification results, and must follow +conditions Finančná správa determines and publishes. Those conditions were **not +yet published** when this module was written (checked 5 October 2026), so the +company settings carry the declaration fields and a warning, and lookups are +refused until someone states that the notification has been made. + +Rate limit +---------- + +The service allows roughly **60 receipts per clock hour per IP address**. This +module counts its own calls per company and per hour and refuses to exceed the +budget rather than getting the address blocked. That budget is per *address*, not +per company, which is the argument for eventually moving the call into the +accountant's browser: each user then has their own. + +Configuration +------------- + +*Settings ▸ Accounting ▸ Fiscal Receipt Capture ▸ Slovak eKasa*: enable the +lookup, declare the notified IP address and the date it was notified, and +optionally lower the hourly budget. +""", + "author": "Data Dance s.r.o.", + "website": "https://www.datadance.eu", + "license": "AGPL-3", + "category": "Accounting/Localizations", + "depends": ["l10n_cssk_receipt_capture"], + "external_dependencies": {"python": ["requests"]}, + "data": [ + "security/ir.model.access.csv", + "security/record_rules.xml", + "data/cssk_receipt_provider.xml", + "views/sk_ekasa_call_views.xml", + "views/cssk_receipt_views.xml", + "views/res_config_settings_views.xml", + ], + "post_init_hook": "post_init_hook", + "installable": True, +} diff --git a/l10n_sk_ekasa_receipt/data/cssk_receipt_provider.xml b/l10n_sk_ekasa_receipt/data/cssk_receipt_provider.xml new file mode 100644 index 00000000..71442004 --- /dev/null +++ b/l10n_sk_ekasa_receipt/data/cssk_receipt_provider.xml @@ -0,0 +1,9 @@ + + + + Slovak eKasa (QR lookup) + sk_ekasa + 10 + + + diff --git a/l10n_sk_ekasa_receipt/hooks.py b/l10n_sk_ekasa_receipt/hooks.py new file mode 100644 index 00000000..8bca459b --- /dev/null +++ b/l10n_sk_ekasa_receipt/hooks.py @@ -0,0 +1,25 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import logging + +_logger = logging.getLogger(__name__) + +DEFAULT_HOURLY_BUDGET = 60 + + +def post_init_hook(env): + """Give companies that already existed the default hourly budget. + + A column added by an install is NULL on existing rows, which an Integer + field reads as 0 — and zero budget means no lookups. Without this, every + company that predates the install would silently refuse to capture + anything, with the field's own default sitting right there in the form. + """ + companies = env["res.company"].sudo().search( + [("l10n_sk_ekasa_hourly_budget", "<=", 0)]) + if companies: + companies.write({"l10n_sk_ekasa_hourly_budget": DEFAULT_HOURLY_BUDGET}) + _logger.info( + "l10n_sk_ekasa_receipt: set the default hourly lookup budget on " + "%s existing compan%s", + len(companies), "y" if len(companies) == 1 else "ies") diff --git a/l10n_sk_ekasa_receipt/models/__init__.py b/l10n_sk_ekasa_receipt/models/__init__.py new file mode 100644 index 00000000..454583e7 --- /dev/null +++ b/l10n_sk_ekasa_receipt/models/__init__.py @@ -0,0 +1,5 @@ +from . import cssk_receipt_provider_sk_ekasa +from . import cssk_receipt +from . import res_company +from . import res_config_settings +from . import sk_ekasa_call diff --git a/l10n_sk_ekasa_receipt/models/cssk_receipt.py b/l10n_sk_ekasa_receipt/models/cssk_receipt.py new file mode 100644 index 00000000..364e3f40 --- /dev/null +++ b/l10n_sk_ekasa_receipt/models/cssk_receipt.py @@ -0,0 +1,40 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class CSSKReceipt(models.Model): + _inherit = "cssk.receipt" + + l10n_sk_ekasa_okp = fields.Char( + string="OKP", copy=False, + help="Overovací kód podnikateľa, as returned by the service. Stored " + "upper-cased: the service emits it in either case.") + l10n_sk_ekasa_is_paragon = fields.Boolean( + string="Paragón", copy=False, + help="A substitute receipt written out while the till was out of " + "service.") + l10n_sk_ekasa_call_ids = fields.One2many( + "sk.ekasa.call", "receipt_id", string="Verification calls") + l10n_sk_ekasa_call_count = fields.Integer( + compute="_compute_l10n_sk_ekasa_call_count") + + def _compute_l10n_sk_ekasa_call_count(self): + grouped = {} + if self.ids: + for group in self.env["sk.ekasa.call"]._read_group( + [("receipt_id", "in", self.ids)], + groupby=["receipt_id"], aggregates=["__count"]): + grouped[group[0].id] = group[1] + for receipt in self: + receipt.l10n_sk_ekasa_call_count = grouped.get(receipt.id, 0) + + def action_l10n_sk_ekasa_open_calls(self): + self.ensure_one() + return { + "type": "ir.actions.act_window", + "name": self.env._("Verification calls"), + "res_model": "sk.ekasa.call", + "view_mode": "list,form", + "domain": [("receipt_id", "=", self.id)], + } diff --git a/l10n_sk_ekasa_receipt/models/cssk_receipt_provider_sk_ekasa.py b/l10n_sk_ekasa_receipt/models/cssk_receipt_provider_sk_ekasa.py new file mode 100644 index 00000000..44b94ba0 --- /dev/null +++ b/l10n_sk_ekasa_receipt/models/cssk_receipt_provider_sk_ekasa.py @@ -0,0 +1,177 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import json +import logging + +import requests + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +from odoo.addons.l10n_cssk_receipt_capture.tools.amounts import flatten_label + +from ..tools.ekasa import ( + BROWSER_HEADERS, ENDPOINT, EkasaQrError, map_receipt, parse_qr, +) + +_logger = logging.getLogger(__name__) + + +class CSSKReceiptProviderSkEkasa(models.AbstractModel): + """Fetches a registered receipt from Finančná správa. + + The service is reached over plain HTTPS with no authentication; what makes + a company entitled to use it is the notification under § 18 ods. 11, not a + credential, which is why the gate is a declaration rather than a key. + """ + + _name = "cssk.receipt.provider.sk_ekasa" + _inherit = "cssk.receipt.provider.mixin" + _description = "Slovak eKasa Receipt Lookup" + + @api.model + def _can_capture(self, receipt): + """Only a receipt whose QR payload the service can be asked about.""" + if not receipt.qr_raw: + return False + try: + parse_qr(receipt.qr_raw) + except EkasaQrError: + return False + return True + + @api.model + def _capture(self, receipt): + company = receipt.company_id + company._l10n_sk_ekasa_check_ready() + try: + request = parse_qr(receipt.qr_raw) + except EkasaQrError as err: + raise UserError(str(err)) from err + + self.env["sk.ekasa.call"]._check_budget(company) + payload, call_vals = self._fetch(company, request) + call_vals.update({ + "company_id": company.id, + "receipt_id": receipt.id, + "request_key": self._request_key(request), + }) + self.env["sk.ekasa.call"].create(call_vals) + + if call_vals["outcome"] != "found": + raise UserError(call_vals["message"]) + + mapped = map_receipt(payload, flatten=flatten_label) + if not mapped: + raise UserError(_( + "The service answered but returned no receipt body. Nothing " + "was changed.")) + if mapped.get("warning"): + receipt.message_post(body=mapped["warning"]) + + vals = { + "receipt_uid": mapped["receipt_uid"], + "issue_date": mapped["issue_date"], + "amount_total": mapped["amount_total"], + "seller_name": mapped["seller_name"], + "seller_vat": mapped["seller_vat"], + "seller_tax_id": mapped["seller_tax_id"], + "seller_reg_id": mapped["seller_reg_id"], + "seller_vat_payer": mapped["seller_vat_payer"], + "premises_note": mapped["premises_note"], + "l10n_sk_ekasa_okp": mapped["okp"], + "l10n_sk_ekasa_is_paragon": mapped["is_paragon"], + "payload_raw": receipt._store_payload(payload), + "line_ids": [fields.Command.create(line) for line in mapped["lines"]], + "tax_summary_ids": [ + fields.Command.create(tax) for tax in mapped["taxes"]], + } + # An off-line receipt is printed without an identifier, so the QR is the + # only key we had; the service hands back the real one once the till has + # caught up. Keep theirs: it is what a second capture would collide on. + if not vals["receipt_uid"]: + vals.pop("receipt_uid") + return vals + + # ------------------------------------------------------------------ + @api.model + def _request_key(self, request): + if "receiptId" in request: + return request["receiptId"] + return "%s:%s:%s:%s:%s" % ( + request["okp"], request["cashRegisterCode"], + request["issueDateFormatted"], request["receiptNumber"], + request["totalAmount"]) + + @api.model + def _fetch(self, company, request): + """POST the request and classify the answer. + + Returns ``(payload, call_vals)``; ``call_vals`` is always recorded, so a + refused or failed lookup still counts against the budget and still + leaves a trace. + """ + timeout = company.l10n_sk_ekasa_timeout or 30 + try: + response = requests.post( + ENDPOINT, data=json.dumps(request).encode(), + headers=BROWSER_HEADERS, timeout=timeout) + except requests.RequestException as err: + _logger.warning("eKasa lookup failed: %s", err) + return None, { + "outcome": "transport", + "message": _( + "Could not reach Finančná správa's verification service " + "(%s). The receipt was left untouched; try again.", err), + } + + status = response.status_code + if status != 200: + # The service's firewall answers a request it does not like with + # 499 and an HTML page rather than JSON. + return None, { + "outcome": "refused", + "http_status": status, + "message": _( + "The verification service refused the request (HTTP " + "%(status)s). This is usually the service's firewall " + "rather than the receipt.", status=status), + } + try: + payload = response.json() + except ValueError: + return None, { + "outcome": "refused", + "http_status": status, + "message": _( + "The verification service answered with something that is " + "not JSON."), + } + + return_value = payload.get("returnValue") + if return_value not in (0, None): + return payload, { + "outcome": "refused", + "http_status": status, + "return_value": return_value, + "message": _( + "The verification service returned code %s.", return_value), + } + if not payload.get("receipt"): + # A receipt that is not in the system comes back as HTTP 200 with a + # null body, never as an error, so this must be tested for + # explicitly or a miss reads as a success with empty values. + return payload, { + "outcome": "not_found", + "http_status": status, + "return_value": return_value or 0, + "message": _( + "Finančná správa holds no receipt for this code. Either " + "the seller never registered it — which is worth " + "reporting — or the code was misread."), + } + return payload, { + "outcome": "found", + "http_status": status, + "return_value": return_value or 0, + } diff --git a/l10n_sk_ekasa_receipt/models/res_company.py b/l10n_sk_ekasa_receipt/models/res_company.py new file mode 100644 index 00000000..6e6e18a0 --- /dev/null +++ b/l10n_sk_ekasa_receipt/models/res_company.py @@ -0,0 +1,59 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import _, fields, models +from odoo.exceptions import UserError + + +class ResCompany(models.Model): + _inherit = "res.company" + + l10n_sk_ekasa_enabled = fields.Boolean( + string="eKasa Receipt Lookup", + help="Fetch registered receipts from Finančná správa's " + "document-verification service when a receipt's QR code is known.") + # § 18 ods. 11 of act 384/2025 Z. z.: before first use an overovateľ must + # notify Finančná správa of the address it will call from. There is no way + # for software to verify that this was done, so it is declared here and the + # declaration gates the lookup — which at least makes the obligation + # visible to whoever switches the feature on. + l10n_sk_ekasa_ip_notified = fields.Boolean( + string="IP Address Notified (§ 18/11)", + help="Tick once Finančná správa has been notified of the IP address " + "this database calls the verification service from, as act " + "384/2025 Z. z. § 18 ods. 11 requires of an overovateľ.") + l10n_sk_ekasa_ip_address = fields.Char( + string="Notified IP Address", + help="The address declared to Finančná správa. Recorded here so the " + "declaration can be checked against the egress address actually " + "in use.") + l10n_sk_ekasa_ip_notified_on = fields.Date(string="Notified On") + l10n_sk_ekasa_hourly_budget = fields.Integer( + string="Hourly Lookup Budget", default=60, + help="Maximum lookups per clock hour. The service permits roughly 60 " + "per hour per IP address and blocks the address beyond that, so " + "this module refuses to exceed the budget rather than lose " + "access. Note the real limit is per address: several companies " + "sharing one egress address share one budget.") + l10n_sk_ekasa_timeout = fields.Integer( + string="Lookup Timeout (s)", default=30) + + def _l10n_sk_ekasa_check_ready(self): + """Raise unless this company may call the verification service.""" + self.ensure_one() + if not self.l10n_sk_ekasa_enabled: + raise UserError(_( + "The eKasa receipt lookup is switched off for %s. Enable it in " + "Settings ▸ Accounting ▸ Fiscal Receipt Capture.", + self.display_name)) + if not self.l10n_sk_ekasa_ip_notified: + raise UserError(_( + "Act 384/2025 Z. z. § 18 ods. 11 requires an overovateľ to " + "notify Finančná správa of the IP address it will use before " + "calling the document-verification service, and to send " + "truthful verification results. Confirm the notification in " + "Settings ▸ Accounting ▸ Fiscal Receipt Capture before using " + "the lookup.\n\n" + "Finančná správa had not yet published the conditions of use " + "for this service when this module was written; until it does, " + "satisfying the obligation means contacting them directly.")) + return True diff --git a/l10n_sk_ekasa_receipt/models/res_config_settings.py b/l10n_sk_ekasa_receipt/models/res_config_settings.py new file mode 100644 index 00000000..e1852727 --- /dev/null +++ b/l10n_sk_ekasa_receipt/models/res_config_settings.py @@ -0,0 +1,20 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + l10n_sk_ekasa_enabled = fields.Boolean( + related="company_id.l10n_sk_ekasa_enabled", readonly=False) + l10n_sk_ekasa_ip_notified = fields.Boolean( + related="company_id.l10n_sk_ekasa_ip_notified", readonly=False) + l10n_sk_ekasa_ip_address = fields.Char( + related="company_id.l10n_sk_ekasa_ip_address", readonly=False) + l10n_sk_ekasa_ip_notified_on = fields.Date( + related="company_id.l10n_sk_ekasa_ip_notified_on", readonly=False) + l10n_sk_ekasa_hourly_budget = fields.Integer( + related="company_id.l10n_sk_ekasa_hourly_budget", readonly=False) + l10n_sk_ekasa_timeout = fields.Integer( + related="company_id.l10n_sk_ekasa_timeout", readonly=False) diff --git a/l10n_sk_ekasa_receipt/models/sk_ekasa_call.py b/l10n_sk_ekasa_receipt/models/sk_ekasa_call.py new file mode 100644 index 00000000..635fb8e4 --- /dev/null +++ b/l10n_sk_ekasa_receipt/models/sk_ekasa_call.py @@ -0,0 +1,88 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class SkEkasaCall(models.Model): + """One call to the document-verification service. + + Two jobs. It is the hourly budget counter — the service permits about 60 + lookups per clock hour per IP address and blocks the address beyond that. + And it is the local record of what was verified, which is the only evidence + a company has of its own compliance with § 18 ods. 11's duty to send + truthful verification results. + """ + + _name = "sk.ekasa.call" + _description = "eKasa Verification Call" + _order = "create_date desc, id desc" + _rec_name = "request_key" + + company_id = fields.Many2one( + "res.company", required=True, index=True, + default=lambda s: s.env.company) + receipt_id = fields.Many2one( + "cssk.receipt", ondelete="set null", index=True) + request_key = fields.Char( + required=True, + help="The identifier or composite key the service was asked about.") + called_at = fields.Datetime( + required=True, default=fields.Datetime.now, index=True) + outcome = fields.Selection( + [ + ("found", "Receipt found"), + ("not_found", "Not registered"), + ("refused", "Refused by the service"), + ("transport", "Network or transport error"), + ], + required=True, index=True) + http_status = fields.Integer() + return_value = fields.Integer( + help="The service's own returnValue field.") + message = fields.Text() + + @api.model + def _hour_start(self, now=None): + now = now or fields.Datetime.now() + return now.replace(minute=0, second=0, microsecond=0) + + @api.model + def _used_this_hour(self, company): + """Calls already made in the current clock hour. + + The service's window is the clock hour (hh:00 to hh:59), not a rolling + sixty minutes, so the count resets on the hour. + """ + return self.search_count([ + ("company_id", "=", company.id), + ("called_at", ">=", fields.Datetime.to_string(self._hour_start())), + ]) + + @api.model + def _check_budget(self, company): + """Raise unless there is budget left in this clock hour. + + A budget of zero means no lookups, not "use the default": writing + ``or 60`` here made setting the limit to 0 silently grant 60. Existing + companies are given the default by this module's post-install hook, so + a zero in the field is always deliberate. + """ + budget = company.l10n_sk_ekasa_hourly_budget + if budget <= 0: + raise UserError(_( + "The hourly eKasa lookup budget for %(company)s is set to " + "zero, so no lookups are made. Raise it in Settings ▸ " + "Accounting ▸ Fiscal Receipt Capture.", + company=company.display_name)) + used = self._used_this_hour(company) + if used >= budget: + raise UserError(_( + "The hourly eKasa lookup budget for %(company)s is spent " + "(%(used)s of %(budget)s used since %(since)s). Finančná " + "správa limits the service to roughly 60 lookups per clock " + "hour per IP address and blocks the address beyond that, so " + "the remaining receipts have to wait for the next hour.", + company=company.display_name, used=used, budget=budget, + since=fields.Datetime.to_string(self._hour_start())[-8:-3])) + return budget - used diff --git a/l10n_sk_ekasa_receipt/security/ir.model.access.csv b/l10n_sk_ekasa_receipt/security/ir.model.access.csv new file mode 100644 index 00000000..edb79c56 --- /dev/null +++ b/l10n_sk_ekasa_receipt/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_sk_ekasa_call_user,sk.ekasa.call user,model_sk_ekasa_call,account.group_account_user,1,0,1,0 +access_sk_ekasa_call_manager,sk.ekasa.call manager,model_sk_ekasa_call,account.group_account_manager,1,1,1,1 diff --git a/l10n_sk_ekasa_receipt/security/record_rules.xml b/l10n_sk_ekasa_receipt/security/record_rules.xml new file mode 100644 index 00000000..63322e16 --- /dev/null +++ b/l10n_sk_ekasa_receipt/security/record_rules.xml @@ -0,0 +1,8 @@ + + + + eKasa verification call: multi-company + + [('company_id', 'in', company_ids)] + + diff --git a/l10n_sk_ekasa_receipt/static/description/icon.png b/l10n_sk_ekasa_receipt/static/description/icon.png new file mode 100644 index 00000000..6ebfcf8c Binary files /dev/null and b/l10n_sk_ekasa_receipt/static/description/icon.png differ diff --git a/l10n_sk_ekasa_receipt/tests/__init__.py b/l10n_sk_ekasa_receipt/tests/__init__.py new file mode 100644 index 00000000..18421043 --- /dev/null +++ b/l10n_sk_ekasa_receipt/tests/__init__.py @@ -0,0 +1,3 @@ +from . import test_ekasa_qr +from . import test_ekasa_mapping +from . import test_ekasa_provider diff --git a/l10n_sk_ekasa_receipt/tests/common.py b/l10n_sk_ekasa_receipt/tests/common.py new file mode 100644 index 00000000..7162e612 --- /dev/null +++ b/l10n_sk_ekasa_receipt/tests/common.py @@ -0,0 +1,24 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +import json +import os + +FIXTURES = os.path.join(os.path.dirname(__file__), "fixtures") + +# The QR payloads that resolve to the fixtures. See fixtures/README.rst: +# the amounts and structure are from real service responses, the +# identities and identifiers are synthetic. +QR_ONLINE_FUEL = "O-ABCDEF0000000002ABCDEF0000000002" +QR_OFFLINE_RESTAURANT = ( + "AAAA0001-BBBB0002-CCCC0003-DDDD0004-EEEE0005" + ":88820990000020004:260314195902:5:181.90") + + +def load_fixture(name): + """A response from Finančná správa's verification service. + + Structure and figures verbatim; identities anonymised. See + ``fixtures/README.rst``. + """ + with open(os.path.join(FIXTURES, name), encoding="utf-8") as handle: + return json.load(handle) diff --git a/l10n_sk_ekasa_receipt/tests/fixtures/README.rst b/l10n_sk_ekasa_receipt/tests/fixtures/README.rst new file mode 100644 index 00000000..18ff952f --- /dev/null +++ b/l10n_sk_ekasa_receipt/tests/fixtures/README.rst @@ -0,0 +1,53 @@ +========================= +About these fixtures +========================= + +Two responses from Finančná správa's document-verification service, used to pin +the mapping in ``test_ekasa_mapping.py`` and the provider in +``test_ekasa_provider.py``. + +**They are anonymised.** Everything that identifies a business or a purchase was +replaced with synthetic values: company names, IČO / DIČ / IČ DPH, addresses, +cash-register codes, receipt identifiers, OKP, item names and the dates. The +``pkp`` signature is elided. + +**Everything that makes them useful is verbatim**, because the point of a fixture +fetched from a real service is that it reproduces what the real service does: + +* every amount, quantity and VAT rate, so both receipts still reconcile to the + cent — 181.90 across three rates, and 57.85 at 23 %; +* the response's field structure, including the fields that look like mistakes + and are not. + +The replacement identifiers were chosen to keep the structure meaningful: the +VAT numbers pass the Slovak check (divisible by eleven), and a cash-register +code is still ``888`` + the tax number + four digits. + +What each fixture is here to prove +================================== + +``receipt_offline_restaurant.json`` — an off-line receipt (the QR carries a +composite key, not an identifier), with: + +* **three VAT rates at once** — 5 % food, 19 % a non-alcoholic drink, 23 % + alcohol — which is routine in Slovak hospitality; +* a **legacy ``vatRateBasic`` / ``vatRateReduced`` pair still reading 20 / 10**, + labels from the regime before 2025, sitting beside a correct ``vatSummary``. + *Do not "fix" it.* It is the trap the mapper exists to avoid; +* a **zero-priced item** at 19 %, hence a VAT bucket with a zero base; +* an item name carrying **embedded newlines** whose modifiers are already inside + the parent line's price; +* an ``okp`` in **upper case**; +* a real ``receiptId`` returned even though the printed receipt had none, + because the till synced afterwards. + +``receipt_online_fuel.json`` — an on-line receipt (the QR *is* the identifier), +with: + +* the **entire legacy pair null** while ``vatSummary`` is populated — the other + half of the reason that pair is never read; +* ``icDph`` that is **not** ``"SK" + dic``, as for a member of a VAT group; +* a **fractional quantity** (31.17) whose unit price is not representable at two + decimals, which is why line quantities are never posted as quantities; +* ``issueDate`` one second **after** ``createDate``; +* an ``okp`` in **lower case**. diff --git a/l10n_sk_ekasa_receipt/tests/fixtures/receipt_offline_restaurant.json b/l10n_sk_ekasa_receipt/tests/fixtures/receipt_offline_restaurant.json new file mode 100644 index 00000000..6070934a --- /dev/null +++ b/l10n_sk_ekasa_receipt/tests/fixtures/receipt_offline_restaurant.json @@ -0,0 +1,217 @@ +{ + "receipt": { + "cashRegisterCode": "88820990000020004", + "createDate": "14.03.2026 19:59:02", + "customerId": null, + "dic": "2099000002", + "exemption": false, + "freeTaxAmount": null, + "icDph": "SK2099000002", + "ico": "99000011", + "invoiceNumber": null, + "issueDate": "14.03.2026 19:59:02", + "items": [ + { + "itemType": "K", + "name": "HLAVNÉ JEDLO Č. 1", + "price": 14.9, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 5.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "PREDJEDLO Č. 2", + "price": 17.8, + "quantity": 2.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 5.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "GRILOVANÉ MENU PRE 2 OSOBY\n1x Príloha navyše +3EUR (3,00 EUR)\n1x Druhá príloha +0EUR", + "price": 48.0, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 5.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "ZELENINOVÁ PRÍLOHA", + "price": 7.9, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 5.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "POLIEVKA PRE 2 OSOBY", + "price": 32.0, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 5.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "PREDJEDLO Č. 6", + "price": 8.9, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 5.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "Miešaný nápoj", + "price": 19.8, + "quantity": 2.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 23.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "Destilát 40 %\n1x Prémiová značka (7,90 EUR)", + "price": 7.9, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 23.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "Fľaša vína 0,75 l", + "price": 13.9, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 23.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "Voda z vodovodu", + "price": 0.0, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 19.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "Rum 4 cl\n1x Biely rum (4,90 EUR)", + "price": 9.8, + "quantity": 2.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 23.0, + "voucherNumber": null + }, + { + "itemType": "K", + "name": "Obal na jedlo so sebou", + "price": 1.0, + "quantity": 1.0, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 5.0, + "voucherNumber": null + } + ], + "okp": "AAAA0001-BBBB0002-CCCC0003-DDDD0004-EEEE0005", + "organization": { + "buildingNumber": "1", + "country": "Slovensko", + "dic": "2099000002", + "icDph": "SK2099000002", + "ico": "99000011", + "manualVatPayer": null, + "municipality": "Vzorové Mesto", + "name": "Vzorová reštaurácia, s. r. o.", + "postalCode": "99901", + "propertyRegistrationNumber": "1", + "streetName": "Vzorová", + "vatPayer": true + }, + "paragon": false, + "paragonNumber": null, + "pkp": "", + "receiptId": "O-ABCDEF0000000001ABCDEF0000000001", + "receiptNumber": 5, + "taxBaseBasic": 41.79, + "taxBaseReduced": 124.29, + "totalPrice": 181.9, + "type": "PD", + "unit": { + "buildingNumber": null, + "cashRegisterCode": "88820990000020004", + "country": "Slovensko", + "municipality": "Skúšobná Ves", + "name": null, + "postalCode": "99902", + "propertyRegistrationNumber": "2", + "streetName": "Skúšobná", + "unitType": "STANDARD" + }, + "vatAmountBasic": 9.61, + "vatAmountReduced": 6.21, + "vatRateBasic": 20.0, + "vatRateReduced": 10.0, + "vatSummary": [ + { + "vat": { + "vatRate": 5.0 + }, + "vatAmount": 6.21, + "vatBase": 124.29 + }, + { + "vat": { + "vatRate": 23.0 + }, + "vatAmount": 9.61, + "vatBase": 41.79 + }, + { + "vat": { + "vatRate": 19.0 + }, + "vatAmount": 0.0, + "vatBase": 0.0 + } + ] + }, + "returnValue": 0, + "searchIdentification": { + "bucket": 0, + "createDate": 1773500000000, + "internalReceiptId": "AAAA0001-BBBB0002-CCCC0003-DDDD0004-EEEE0005:88820990000020004:5:2026-03-14T19:59:02+01:00:181.9", + "searchUuid": "00000000-0000-4000-8000-000000000001" + } +} \ No newline at end of file diff --git a/l10n_sk_ekasa_receipt/tests/fixtures/receipt_online_fuel.json b/l10n_sk_ekasa_receipt/tests/fixtures/receipt_online_fuel.json new file mode 100644 index 00000000..088caba5 --- /dev/null +++ b/l10n_sk_ekasa_receipt/tests/fixtures/receipt_online_fuel.json @@ -0,0 +1,82 @@ +{ + "receipt": { + "cashRegisterCode": "88820770000020444", + "createDate": "20.03.2026 09:36:35", + "customerId": null, + "dic": "2077000002", + "exemption": false, + "freeTaxAmount": null, + "icDph": "SK7199000006", + "ico": "99000022", + "invoiceNumber": null, + "issueDate": "20.03.2026 09:36:36", + "items": [ + { + "itemType": "K", + "name": "Benzín 95", + "price": 57.85, + "quantity": 31.17, + "sellerId": null, + "sellerIdType": null, + "specialRegulation": null, + "vatRate": 23.0, + "voucherNumber": null + } + ], + "okp": "aaaa0011-bbbb0012-cccc0013-dddd0014-eeee0015", + "organization": { + "buildingNumber": null, + "country": "Slovensko", + "dic": "2077000002", + "icDph": "SK7199000006", + "ico": "99000022", + "manualVatPayer": null, + "municipality": "Vzorové Mesto", + "name": "Vzorová čerpacia stanica, a. s.", + "postalCode": "99901", + "propertyRegistrationNumber": "3", + "streetName": "Vzorová", + "vatPayer": true + }, + "paragon": false, + "paragonNumber": null, + "pkp": "", + "receiptId": "O-ABCDEF0000000002ABCDEF0000000002", + "receiptNumber": 26287, + "taxBaseBasic": null, + "taxBaseReduced": null, + "totalPrice": 57.85, + "type": "PD", + "unit": { + "buildingNumber": null, + "cashRegisterCode": "88820770000020444", + "country": "Slovensko", + "municipality": "Skúšobné Pole", + "name": null, + "postalCode": "99903", + "propertyRegistrationNumber": null, + "streetName": "Diaľnica D99", + "unitType": "STANDARD" + }, + "vatAmountBasic": null, + "vatAmountReduced": null, + "vatRateBasic": null, + "vatRateReduced": null, + "vatSummary": [ + { + "vat": { + "vatRate": 23.0 + }, + "vatAmount": 10.82, + "vatBase": 47.03 + } + ] + }, + "returnValue": 0, + "searchIdentification": { + "bucket": 0, + "createDate": 1773500000001, + "internalReceiptId": "O-ABCDEF0000000002ABCDEF0000000002", + "searchUuid": "00000000-0000-4000-8000-000000000002" + } +} \ No newline at end of file diff --git a/l10n_sk_ekasa_receipt/tests/test_ekasa_mapping.py b/l10n_sk_ekasa_receipt/tests/test_ekasa_mapping.py new file mode 100644 index 00000000..646ad57f --- /dev/null +++ b/l10n_sk_ekasa_receipt/tests/test_ekasa_mapping.py @@ -0,0 +1,185 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests.common import TransactionCase + +from odoo.addons.l10n_cssk_receipt_capture.tools.amounts import flatten_label + +from ..tools.ekasa import map_receipt +from .common import load_fixture + + +class TestEkasaMapping(TransactionCase): + """Mapping, against two responses from Finančná správa. + + Every assertion here corresponds to a way of getting it wrong that these + two receipts actually expose. + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.fuel_payload = load_fixture("receipt_online_fuel.json") + cls.rest_payload = load_fixture("receipt_offline_restaurant.json") + cls.fuel = map_receipt(cls.fuel_payload, flatten=flatten_label) + cls.rest = map_receipt(cls.rest_payload, flatten=flatten_label) + + # ------------------------------------------------------------------ + # the VAT recap + # ------------------------------------------------------------------ + def test_recap_comes_from_vat_summary_not_the_legacy_pair(self): + """The restaurant payload carries both, and they disagree. + + ``vatRateBasic`` says 20 and ``vatRateReduced`` says 10 — stale labels + from the regime before 2025 — while the items and ``vatSummary`` say + 23, 19 and 5. Reading the legacy pair would post 20 % tax on a 23 % + purchase. + """ + self.assertEqual(self.rest_payload["receipt"]["vatRateBasic"], 20.0) + self.assertEqual(self.rest_payload["receipt"]["vatRateReduced"], 10.0) + rates = sorted(t["vat_rate"] for t in self.rest["taxes"]) + self.assertEqual(rates, [5.0, 19.0, 23.0]) + self.assertNotIn(20.0, rates) + self.assertNotIn(10.0, rates) + self.assertIsNone(self.rest["warning"]) + + def test_recap_survives_a_null_legacy_pair(self): + """On the fuel payload the entire legacy pair is null.""" + receipt = self.fuel_payload["receipt"] + self.assertIsNone(receipt["vatRateBasic"]) + self.assertIsNone(receipt["taxBaseBasic"]) + self.assertIsNone(receipt["vatAmountReduced"]) + self.assertEqual(self.fuel["taxes"], [ + {"vat_rate": 23.0, "amount_untaxed": 47.03, "amount_tax": 10.82}]) + + def test_without_a_summary_the_recap_is_derived_from_the_items(self): + """Not from the legacy pair, which has two slots and stale labels. + + The restaurant receipt has three rates. Reading the legacy + basic/reduced fields would flatten it to two and mislabel both; the + items each carry their own rate, so they are the better source. + """ + payload = {"receipt": dict(self.rest_payload["receipt"])} + payload["receipt"].pop("vatSummary") + mapped = map_receipt(payload, flatten=flatten_label) + self.assertEqual(sorted(t["vat_rate"] for t in mapped["taxes"]), + [5.0, 19.0, 23.0]) + by_rate = {t["vat_rate"]: t for t in mapped["taxes"]} + self.assertAlmostEqual(by_rate[5.0]["amount_untaxed"], 124.29, places=2) + self.assertAlmostEqual(by_rate[5.0]["amount_tax"], 6.21, places=2) + self.assertAlmostEqual(by_rate[23.0]["amount_untaxed"], 41.79, places=2) + self.assertAlmostEqual(by_rate[23.0]["amount_tax"], 9.61, places=2) + self.assertIn("derived from the items", mapped["warning"]) + + def test_legacy_pair_is_used_only_with_neither_summary_nor_items(self): + payload = {"receipt": dict(self.fuel_payload["receipt"])} + payload["receipt"].pop("vatSummary") + payload["receipt"]["items"] = [] + payload["receipt"].update({ + "vatRateBasic": 23.0, "taxBaseBasic": 47.03, + "vatAmountBasic": 10.82, + }) + mapped = map_receipt(payload, flatten=flatten_label) + self.assertEqual(len(mapped["taxes"]), 1) + self.assertEqual(mapped["taxes"][0]["vat_rate"], 23.0) + self.assertIn("no items", mapped["warning"]) + + def test_three_rate_recap_foots_to_the_total(self): + base = sum(t["amount_untaxed"] for t in self.rest["taxes"]) + tax = sum(t["amount_tax"] for t in self.rest["taxes"]) + self.assertAlmostEqual(base, 166.08, places=2) + self.assertAlmostEqual(tax, 15.82, places=2) + self.assertAlmostEqual(base + tax, self.rest["amount_total"], places=2) + + # ------------------------------------------------------------------ + # the items + # ------------------------------------------------------------------ + def test_item_price_is_the_line_total(self): + """The twelve restaurant item prices sum to exactly the receipt total. + + Which is how we know ``price`` is the VAT-inclusive extended amount and + not a unit price: the qty-2 lines divide cleanly (17.80/2 = 8.90). + """ + self.assertEqual(len(self.rest["lines"]), 12) + self.assertAlmostEqual( + sum(line["amount_total"] for line in self.rest["lines"]), + 181.90, places=2) + gimmari = next(l for l in self.rest["lines"] if "PREDJEDLO Č. 2" in l["name"]) + self.assertEqual(gimmari["quantity"], 2.0) + self.assertEqual(gimmari["amount_total"], 17.80) + + def test_fuel_quantity_is_kept_but_not_turned_into_a_price(self): + line = self.fuel["lines"][0] + self.assertEqual(line["name"], "Benzín 95") + self.assertEqual(line["quantity"], 31.17) + self.assertEqual(line["amount_total"], 57.85) + + def test_multiline_item_name_is_flattened_never_split(self): + """Modifiers come inside the name, with their money already in the line.""" + grill = next(l for l in self.rest["lines"] if l["name"].startswith("GRILOVANÉ")) + self.assertNotIn("\n", grill["name"]) + self.assertIn("Príloha navyše", grill["name"]) + # 48.00 already includes the +3.00 modifier; it is one line, not three. + self.assertEqual(grill["amount_total"], 48.00) + self.assertEqual(len(self.rest["lines"]), 12) + + def test_zero_priced_item_is_kept(self): + """Tap water at 19 % for 0.00 is why a bucket can have a zero base.""" + water = next(l for l in self.rest["lines"] if "Voda" in l["name"]) + self.assertEqual(water["amount_total"], 0.0) + self.assertEqual(water["vat_rate"], 19.0) + zero_bucket = next(t for t in self.rest["taxes"] if t["vat_rate"] == 19.0) + self.assertEqual(zero_bucket["amount_untaxed"], 0.0) + + # ------------------------------------------------------------------ + # the seller + # ------------------------------------------------------------------ + def test_vat_number_is_not_derived_from_the_tax_number(self): + """Vzorová čerpacia stanica files in a VAT group, so the two are unrelated.""" + self.assertEqual(self.fuel["seller_vat"], "SK7199000006") + self.assertEqual(self.fuel["seller_tax_id"], "2077000002") + self.assertNotEqual(self.fuel["seller_vat"], + "SK" + self.fuel["seller_tax_id"]) + self.assertEqual(self.fuel["seller_reg_id"], "99000022") + self.assertTrue(self.fuel["seller_vat_payer"]) + + def test_seller_is_the_organization_and_premises_only_a_note(self): + """The seat and the premises are different addresses. + + Vzorová čerpacia stanica's seat is in Bratislava-Vzorové Mesto; this receipt was issued at a + motorway service station in Skúšobné Pole. Putting the premises on the partner + would give the vendor the wrong address. + """ + self.assertEqual(self.fuel["seller_name"], "Vzorová čerpacia stanica, a. s.") + self.assertIn("Skúšobné Pole", self.fuel["premises_note"]) + self.assertNotIn("Vzorové Mesto", self.fuel["premises_note"] or "") + + # ------------------------------------------------------------------ + # identifiers and dates + # ------------------------------------------------------------------ + def test_offline_receipt_yields_a_real_identifier(self): + """An "OFF-LINE DOKLAD" is not permanently off-line. + + The QR carried no identifier, but the till synced afterwards, so the + service hands one back — and that is the deduplication key, not the QR. + """ + self.assertTrue(self.rest["receipt_uid"].startswith("O-")) + self.assertEqual(len(self.rest["receipt_uid"]), 34) + + def test_issue_date_not_create_date(self): + """They differ by a second on the fuel receipt.""" + receipt = self.fuel_payload["receipt"] + self.assertEqual(receipt["issueDate"], "20.03.2026 09:36:36") + self.assertEqual(receipt["createDate"], "20.03.2026 09:36:35") + self.assertEqual(self.fuel["issue_date"].second, 36) + + def test_okp_is_upper_cased(self): + """The service emits it in either case; the fuel one is lower.""" + self.assertEqual(self.fuel_payload["receipt"]["okp"], + "aaaa0011-bbbb0012-cccc0013-dddd0014-eeee0015") + self.assertEqual(self.fuel["okp"], + "AAAA0011-BBBB0012-CCCC0013-DDDD0014-EEEE0015") + self.assertEqual(self.rest["okp"], self.rest["okp"].upper()) + + def test_empty_body_maps_to_nothing(self): + self.assertEqual(map_receipt({"returnValue": 0, "receipt": None}), {}) + self.assertEqual(map_receipt(None), {}) diff --git a/l10n_sk_ekasa_receipt/tests/test_ekasa_provider.py b/l10n_sk_ekasa_receipt/tests/test_ekasa_provider.py new file mode 100644 index 00000000..c6b8be9c --- /dev/null +++ b/l10n_sk_ekasa_receipt/tests/test_ekasa_provider.py @@ -0,0 +1,271 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from unittest.mock import patch + +from odoo import fields +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.exceptions import UserError +from odoo.tests import tagged + +from .common import QR_OFFLINE_RESTAURANT, QR_ONLINE_FUEL, load_fixture + + +@tagged("post_install", "-at_install") +class TestEkasaProvider(AccountTestInvoicingCommon): + """The provider end to end, with the network call replaced by a fixture.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.write({ + "l10n_sk_ekasa_enabled": True, + "l10n_sk_ekasa_ip_notified": True, + "l10n_sk_ekasa_ip_address": "192.0.2.10", + "l10n_sk_ekasa_ip_notified_on": "2026-10-01", + "cssk_receipt_expense_account_id": + cls.company_data["default_account_expense"].id, + }) + for rate in (23.0, 19.0, 5.0): + cls.env["account.tax"].create({ + "name": "DPH %g%% (test)" % rate, + "amount_type": "percent", + "amount": rate, + "type_tax_use": "purchase", + "company_id": cls.company.id, + }) + cls.provider = cls.env.ref("l10n_sk_ekasa_receipt.provider_sk_ekasa") + # The provider class, not our instance: patching the instance leaves + # the registry class untouched and the override never runs. + cls.provider_cls = type(cls.env["cssk.receipt.provider.sk_ekasa"]) + + def _receipt(self, qr): + return self.env["cssk.receipt"].create({ + "company_id": self.company.id, + "currency_id": self.company.currency_id.id, + "qr_raw": qr, + }) + + def _answer(self, fixture=None, outcome="found", message=None): + """Stand in for ``_fetch``, returning (payload, call_vals).""" + payload = load_fixture(fixture) if fixture else None + call_vals = {"outcome": outcome, "http_status": 200, "return_value": 0} + if message: + call_vals["message"] = message + + def _fake_fetch(self_model, company, request): + return payload, dict(call_vals) + return _fake_fetch + + # ------------------------------------------------------------------ + # provider selection + # ------------------------------------------------------------------ + def test_provider_claims_only_an_ekasa_qr(self): + impl = self.env["cssk.receipt.provider.sk_ekasa"] + self.assertTrue(impl._can_capture(self._receipt(QR_ONLINE_FUEL))) + self.assertTrue(impl._can_capture(self._receipt(QR_OFFLINE_RESTAURANT))) + self.assertFalse(impl._can_capture(self._receipt("a photograph"))) + self.assertFalse(impl._can_capture(self._receipt(False))) + + def test_provider_is_picked_automatically(self): + receipt = self._receipt(QR_ONLINE_FUEL) + self.assertIn(self.provider, receipt._candidate_providers()) + + # ------------------------------------------------------------------ + # the § 18 ods. 11 gate + # ------------------------------------------------------------------ + def test_lookup_refused_until_the_ip_notification_is_declared(self): + self.company.l10n_sk_ekasa_ip_notified = False + receipt = self._receipt(QR_ONLINE_FUEL) + receipt.action_capture() + self.assertEqual(receipt.state, "error") + self.assertIn("§ 18 ods. 11", receipt.review_reason) + + def test_lookup_refused_when_switched_off(self): + self.company.l10n_sk_ekasa_enabled = False + receipt = self._receipt(QR_ONLINE_FUEL) + receipt.action_capture() + self.assertEqual(receipt.state, "error") + self.assertIn("switched off", receipt.review_reason) + + # ------------------------------------------------------------------ + # the hourly budget + # ------------------------------------------------------------------ + def test_budget_is_counted_per_clock_hour(self): + Call = self.env["sk.ekasa.call"] + self.company.l10n_sk_ekasa_hourly_budget = 2 + for index in range(2): + Call.create({ + "company_id": self.company.id, + "request_key": "O-%032X" % index, + "outcome": "found", + }) + self.assertEqual(Call._used_this_hour(self.company), 2) + with self.assertRaisesRegex(UserError, "hourly eKasa lookup budget"): + Call._check_budget(self.company) + + def test_zero_budget_blocks_rather_than_meaning_the_default(self): + """Zero must not read as "unset, use 60".""" + self.company.l10n_sk_ekasa_hourly_budget = 0 + receipt = self._receipt(QR_ONLINE_FUEL) + receipt.action_capture() + self.assertEqual(receipt.state, "error") + self.assertIn("zero", receipt.review_reason) + + def test_spent_budget_refuses_the_capture_rather_than_the_address(self): + Call = self.env["sk.ekasa.call"] + self.company.l10n_sk_ekasa_hourly_budget = 1 + Call.create({"company_id": self.company.id, + "request_key": "O-%032X" % 1, "outcome": "found"}) + receipt = self._receipt(QR_ONLINE_FUEL) + receipt.action_capture() + self.assertEqual(receipt.state, "error") + self.assertIn("budget", receipt.review_reason) + # Nothing was sent: the refusal happens before the network. + self.assertEqual( + Call.search_count([("receipt_id", "=", receipt.id)]), 0) + + def test_post_init_hook_gives_existing_companies_a_budget(self): + """A NULL column reads as 0, which would block every existing company.""" + from odoo.addons.l10n_sk_ekasa_receipt.hooks import ( + DEFAULT_HOURLY_BUDGET, post_init_hook, + ) + self.company.l10n_sk_ekasa_hourly_budget = 0 + post_init_hook(self.env) + self.assertEqual(self.company.l10n_sk_ekasa_hourly_budget, + DEFAULT_HOURLY_BUDGET) + + # ------------------------------------------------------------------ + # a successful capture + # ------------------------------------------------------------------ + def test_fuel_receipt_captures_and_reconciles(self): + receipt = self._receipt(QR_ONLINE_FUEL) + with patch.object(self.provider_cls, "_fetch", + self._answer("receipt_online_fuel.json")): + receipt.action_capture() + self.assertEqual(receipt.state, "captured", receipt.review_reason) + self.assertEqual(receipt.receipt_uid, QR_ONLINE_FUEL) + self.assertEqual(receipt.amount_total, 57.85) + self.assertEqual(receipt.amount_untaxed, 47.03) + self.assertEqual(receipt.amount_tax, 10.82) + self.assertEqual(receipt.seller_vat, "SK7199000006") + self.assertEqual(receipt.seller_tax_id, "2077000002") + self.assertEqual(len(receipt.line_ids), 1) + self.assertEqual(receipt.line_ids.quantity, 31.17) + self.assertTrue(receipt.payload_raw) + self.assertEqual(len(receipt.l10n_sk_ekasa_call_ids), 1) + self.assertEqual(receipt.l10n_sk_ekasa_call_ids.outcome, "found") + self.assertEqual(receipt.l10n_sk_ekasa_call_count, 1) + + def test_restaurant_receipt_captures_three_rates(self): + receipt = self._receipt(QR_OFFLINE_RESTAURANT) + with patch.object(self.provider_cls, "_fetch", + self._answer("receipt_offline_restaurant.json")): + receipt.action_capture() + self.assertEqual(receipt.state, "captured", receipt.review_reason) + self.assertEqual(len(receipt.line_ids), 12) + self.assertEqual(len(receipt.tax_summary_ids), 3) + self.assertEqual( + sorted(receipt.tax_summary_ids.mapped("vat_rate")), + [5.0, 19.0, 23.0]) + self.assertEqual(receipt.amount_total, 181.90) + self.assertEqual(receipt.amount_untaxed, 166.08) + self.assertEqual(receipt.amount_tax, 15.82) + + def test_offline_capture_stores_the_returned_identifier(self): + """Not the QR string: the same purchase has two possible QR payloads.""" + receipt = self._receipt(QR_OFFLINE_RESTAURANT) + with patch.object(self.provider_cls, "_fetch", + self._answer("receipt_offline_restaurant.json")): + receipt.action_capture() + self.assertTrue(receipt.receipt_uid.startswith("O-")) + self.assertNotEqual(receipt.receipt_uid, QR_OFFLINE_RESTAURANT) + # The log records the key as it was SENT, with the timestamp in the + # service's own dotted format rather than the QR's YYMMDDHHMMSS. + key = receipt.l10n_sk_ekasa_call_ids.request_key + self.assertTrue(key.startswith("AAAA0001-BBBB0002")) + self.assertIn("14.03.2026 19:59:02", key) + self.assertNotIn("260314195902", key) + + def test_recapture_does_not_duplicate_the_detail(self): + receipt = self._receipt(QR_OFFLINE_RESTAURANT) + with patch.object(self.provider_cls, "_fetch", + self._answer("receipt_offline_restaurant.json")): + receipt.action_capture() + self.assertEqual(len(receipt.line_ids), 12) + receipt.action_capture() + self.assertEqual(len(receipt.line_ids), 12) + self.assertEqual(len(receipt.tax_summary_ids), 3) + + # ------------------------------------------------------------------ + # failures + # ------------------------------------------------------------------ + def test_unregistered_receipt_is_an_error_not_an_empty_success(self): + """A miss is HTTP 200 with a null body, so it must be tested for.""" + receipt = self._receipt(QR_ONLINE_FUEL) + with patch.object( + self.provider_cls, "_fetch", + self._answer(outcome="not_found", + message="Finančná správa holds no receipt")): + receipt.action_capture() + self.assertEqual(receipt.state, "error") + self.assertIn("no receipt", receipt.review_reason) + self.assertFalse(receipt.line_ids) + self.assertEqual(receipt.l10n_sk_ekasa_call_ids.outcome, "not_found") + + def test_transport_failure_leaves_the_receipt_alone(self): + receipt = self._receipt(QR_ONLINE_FUEL) + with patch.object( + self.provider_cls, "_fetch", + self._answer(outcome="transport", + message="Could not reach the service")): + receipt.action_capture() + self.assertEqual(receipt.state, "error") + self.assertFalse(receipt.amount_total) + self.assertEqual(receipt.l10n_sk_ekasa_call_ids.outcome, "transport") + + def test_malformed_qr_never_reaches_the_network(self): + receipt = self._receipt(QR_ONLINE_FUEL) + receipt.qr_raw = "O-NOTHEXADECIMAL" + receipt.provider_id = self.provider + called = [] + + def _should_not_run(self_model, company, request): + called.append(request) + return None, {"outcome": "found"} + + with patch.object(self.provider_cls, "_fetch", _should_not_run): + receipt.action_capture() + self.assertEqual(receipt.state, "error") + self.assertFalse(called) + self.assertEqual(self.env["sk.ekasa.call"].search_count( + [("receipt_id", "=", receipt.id)]), 0) + + # ------------------------------------------------------------------ + # all the way to a document + # ------------------------------------------------------------------ + def test_capture_then_bill_posts_the_sellers_own_vat(self): + receipt = self._receipt(QR_OFFLINE_RESTAURANT) + with patch.object(self.provider_cls, "_fetch", + self._answer("receipt_offline_restaurant.json")): + receipt.action_capture() + receipt.action_create_partner() + move = receipt.action_create_bill() + self.assertEqual(len(move.invoice_line_ids), 3) + self.assertAlmostEqual(move.amount_untaxed, 166.08, places=2) + self.assertAlmostEqual(move.amount_tax, 15.82, places=2) + self.assertAlmostEqual(move.amount_total, 181.90, places=2) + self.assertEqual(move.state, "draft") + self.assertEqual(receipt.state, "done") + + def test_fuel_bill_matches_to_the_cent(self): + receipt = self._receipt(QR_ONLINE_FUEL) + with patch.object(self.provider_cls, "_fetch", + self._answer("receipt_online_fuel.json")): + receipt.action_capture() + receipt.action_create_partner() + move = receipt.action_create_bill() + self.assertAlmostEqual(move.amount_total, 57.85, places=2) + self.assertAlmostEqual(move.amount_tax, 10.82, places=2) + self.assertEqual(move.invoice_line_ids.price_unit, 47.03) + self.assertEqual(move.invoice_date, fields.Date.to_date("2026-03-20")) diff --git a/l10n_sk_ekasa_receipt/tests/test_ekasa_qr.py b/l10n_sk_ekasa_receipt/tests/test_ekasa_qr.py new file mode 100644 index 00000000..13a47080 --- /dev/null +++ b/l10n_sk_ekasa_receipt/tests/test_ekasa_qr.py @@ -0,0 +1,58 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests.common import TransactionCase + +from ..tools.ekasa import EkasaQrError, is_online_qr, parse_fs_datetime, parse_qr +from .common import QR_OFFLINE_RESTAURANT, QR_ONLINE_FUEL + + +class TestEkasaQr(TransactionCase): + """The QR payloads, pinned to the requests that the service actually + accepted.""" + + def test_online_qr_is_the_identifier_and_nothing_else(self): + self.assertEqual(parse_qr(QR_ONLINE_FUEL), + {"receiptId": "O-ABCDEF0000000002ABCDEF0000000002"}) + self.assertTrue(is_online_qr(QR_ONLINE_FUEL)) + + def test_online_qr_is_upper_cased_and_vrp_accepted(self): + self.assertEqual(parse_qr("o-abcdef0000000002abcdef0000000002"), + {"receiptId": "O-ABCDEF0000000002ABCDEF0000000002"}) + self.assertEqual(parse_qr("V-ABCDEF0000000002ABCDEF0000000002"), + {"receiptId": "V-ABCDEF0000000002ABCDEF0000000002"}) + + def test_offline_qr_becomes_the_composite_key(self): + """Five fields in, and the numbers must be numbers. + + Sent as strings the service rejects the request; the timestamp has to be + reformatted from YYMMDDHHMMSS to the service's own dotted form. + """ + parsed = parse_qr(QR_OFFLINE_RESTAURANT) + self.assertEqual(parsed, { + "okp": "AAAA0001-BBBB0002-CCCC0003-DDDD0004-EEEE0005", + "cashRegisterCode": "88820990000020004", + "issueDateFormatted": "14.03.2026 19:59:02", + "receiptNumber": 5, + "totalAmount": 181.90, + }) + self.assertIsInstance(parsed["receiptNumber"], int) + self.assertIsInstance(parsed["totalAmount"], float) + self.assertFalse(is_online_qr(QR_OFFLINE_RESTAURANT)) + + def test_rejects_what_the_service_would_only_reject_later(self): + for bad in ("", + None, + "nonsense", + "O-NOTHEX", + "a:b:c", + "OKP:88820000000000001:991399999999:1:2.0", + "OKP:88820000000000001:260314195902:five:2.0"): + with self.assertRaises(EkasaQrError): + parse_qr(bad) + + def test_fs_datetime(self): + parsed = parse_fs_datetime("14.03.2026 19:59:02") + self.assertEqual((parsed.year, parsed.month, parsed.day), (2026, 3, 14)) + self.assertEqual((parsed.hour, parsed.minute, parsed.second), (19, 59, 2)) + self.assertIsNone(parse_fs_datetime("")) + self.assertIsNone(parse_fs_datetime("2026-03-14")) diff --git a/l10n_sk_ekasa_receipt/tools/__init__.py b/l10n_sk_ekasa_receipt/tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/l10n_sk_ekasa_receipt/tools/ekasa.py b/l10n_sk_ekasa_receipt/tools/ekasa.py new file mode 100644 index 00000000..05259617 --- /dev/null +++ b/l10n_sk_ekasa_receipt/tools/ekasa.py @@ -0,0 +1,272 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Pure parsing and mapping for Finančná správa's receipt-verification service. + +No model access and no network, so every trap below is unit-testable against +the real payloads committed under ``tests/fixtures``. +""" +import re +from datetime import datetime + +ENDPOINT = "https://ekasa.financnasprava.sk/mdu/api/v1/opd/receipt/find" + +# The service sits behind an application firewall that rejects a default +# ``requests`` user agent with HTTP 499 and an HTML error page. These three +# headers are what make it answer. +BROWSER_HEADERS = { + "User-Agent": ( + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/140.0.0.0 Safari/537.36" + ), + "Origin": "https://ekasa.financnasprava.sk", + "Referer": "https://ekasa.financnasprava.sk/mdu/public/", + "Accept": "application/json, text/plain, */*", + "Content-Type": "application/json", +} + +# ``O-`` for an on-line eKasa receipt, ``V-`` for one from the virtual register. +RECEIPT_ID_RE = re.compile(r"\b([A-Za-z])-([0-9A-Fa-f]{32})\b") +FS_DATETIME = "%d.%m.%Y %H:%M:%S" + + +class EkasaQrError(ValueError): + """The QR payload is not something this service can be asked about.""" + + +def parse_qr(qr): + """Turn a decoded QR payload into the request body the service expects. + + Two shapes, because a receipt printed while the till was off-line has no + identifier to look up:: + + on-line O-ABCDEF0000000001ABCDEF0000000001 + off-line OKP:kódPokladnice:YYMMDDHHMMSS:poradovéČíslo:celkováSuma + + ``receiptNumber`` and ``totalAmount`` must be JSON *numbers*; sent as + strings the service rejects the request. + """ + raw = (qr or "").strip() + if not raw: + raise EkasaQrError("The QR payload is empty.") + + if ":" not in raw: + match = RECEIPT_ID_RE.search(raw) + if not match: + raise EkasaQrError( + "%r is not a receipt identifier: expected a letter, a hyphen " + "and 32 hexadecimal characters." % raw[:80]) + return {"receiptId": "%s-%s" % ( + match.group(1).upper(), match.group(2).upper())} + + parts = raw.split(":") + if len(parts) != 5: + raise EkasaQrError( + "An off-line receipt QR code has five colon-separated fields " + "(OKP, cash-register code, date-time, number, total); this one has " + "%d." % len(parts)) + okp, register_code, stamp, number, total = (p.strip() for p in parts) + if not re.fullmatch(r"\d{12}", stamp): + raise EkasaQrError( + "%r is not a YYMMDDHHMMSS timestamp." % stamp) + try: + number = int(number) + total = float(total) + except (TypeError, ValueError) as err: + raise EkasaQrError( + "The receipt number and total must be numbers (%s)." % err) from err + issued = "%s.%s.20%s %s:%s:%s" % ( + stamp[4:6], stamp[2:4], stamp[0:2], stamp[6:8], stamp[8:10], stamp[10:12]) + # Validate rather than trust: a malformed stamp would otherwise be rejected + # by the service with an opaque error. strptime raises a bare ValueError, + # which a caller catching EkasaQrError would not see, so it is re-raised. + try: + datetime.strptime(issued, FS_DATETIME) + except ValueError as err: + raise EkasaQrError( + "%r is not a valid date and time (%s)." % (stamp, err)) from err + return { + "okp": okp, + "cashRegisterCode": register_code, + "issueDateFormatted": issued, + "receiptNumber": number, + "totalAmount": total, + } + + +def is_online_qr(qr): + """True when the payload is a bare receipt identifier.""" + try: + return "receiptId" in parse_qr(qr) + except EkasaQrError: + return False + + +def parse_fs_datetime(value): + """``14.03.2026 19:59:02`` → ``datetime``, or None.""" + if not value: + return None + try: + return datetime.strptime(value.strip(), FS_DATETIME) + except (TypeError, ValueError): + return None + + +def _vat_buckets(receipt): + """The per-rate VAT recap, and whether it came from a trustworthy field. + + Returns ``(buckets, warning)``. ``vatSummary`` is the only field that can be + believed: + + * on a restaurant receipt it read 5 / 19 / 23 % while the legacy + ``vatRateBasic`` / ``vatRateReduced`` pair beside it still said **20 / 10** + — stale labels from the pre-2025 regime, over amounts that were correct; + * on a fuel receipt the whole legacy pair was **null** while ``vatSummary`` + was populated. + + It also cannot represent a third rate, which Slovak hospitality produces as + a matter of routine. The legacy pair is therefore read only when there is no + summary at all, and doing so raises a warning. + """ + summary = receipt.get("vatSummary") or [] + buckets = [] + for row in summary: + rate = ((row.get("vat") or {}).get("vatRate") + if isinstance(row.get("vat"), dict) else row.get("vatRate")) + if rate is None: + continue + buckets.append({ + "vat_rate": float(rate), + "amount_untaxed": float(row.get("vatBase") or 0.0), + "amount_tax": float(row.get("vatAmount") or 0.0), + }) + if buckets: + return buckets, None + + legacy = [] + for rate_key, base_key, tax_key in ( + ("vatRateBasic", "taxBaseBasic", "vatAmountBasic"), + ("vatRateReduced", "taxBaseReduced", "vatAmountReduced")): + rate, base, tax = ( + receipt.get(rate_key), receipt.get(base_key), receipt.get(tax_key)) + if rate is None and base is None and tax is None: + continue + legacy.append({ + "vat_rate": float(rate or 0.0), + "amount_untaxed": float(base or 0.0), + "amount_tax": float(tax or 0.0), + }) + # The legacy pair has two slots and stale labels. The items carry a rate + # each, so when there is no summary they are the better source: a receipt + # with three rates would otherwise be silently flattened into two. + derived = _buckets_from_items(receipt) + if derived: + return derived, ( + "This receipt carries no vatSummary, so the VAT recap was derived " + "from the items' own rates. The legacy basic/reduced fields were " + "not used: they hold only two rates and their labels have been " + "observed to be stale (20/10 where the real rates were 23/5). " + "Check the rates against the paper before posting.") + if legacy: + return legacy, ( + "This receipt carries no vatSummary and no items, so the VAT recap " + "was read from the legacy basic/reduced fields. Their rate labels " + "have been observed to be stale (20/10 where the real rates were " + "23/5) — check the rates before posting.") + return [], None + + +def _buckets_from_items(receipt, rounding=0.01): + """Group the items by rate and gross up each group.""" + from odoo.tools import float_round + + grouped = {} + for item in receipt.get("items") or []: + rate = item.get("vatRate") + if rate is None: + return [] + grouped.setdefault(float(rate), 0.0) + grouped[float(rate)] += float(item.get("price") or 0.0) + buckets = [] + for rate in sorted(grouped, reverse=True): + gross = float_round(grouped[rate], precision_rounding=rounding) + base = float_round(gross / (1.0 + rate / 100.0), + precision_rounding=rounding) + buckets.append({ + "vat_rate": rate, + "amount_untaxed": base, + "amount_tax": float_round(gross - base, precision_rounding=rounding), + }) + return buckets + + +def map_receipt(payload, flatten=None): + """Map a service response onto ``cssk.receipt`` values. + + ``flatten`` collapses an item name to one line; the caller passes the + framework's helper. Returns a plain dict of scalars plus ``lines`` and + ``taxes`` lists, leaving ORM commands to the provider. + """ + receipt = (payload or {}).get("receipt") + if not receipt: + return {} + flatten = flatten or (lambda text, limit=None: (text or "").replace("\n", " ")) + org = receipt.get("organization") or {} + unit = receipt.get("unit") or {} + + lines = [] + for index, item in enumerate(receipt.get("items") or []): + item_type = (item.get("itemType") or "").upper() + lines.append({ + "sequence": (index + 1) * 10, + "name": flatten(item.get("name") or "", 240) or "—", + "quantity": float(item.get("quantity") or 0.0), + "vat_rate": float(item.get("vatRate") or 0.0), + # 'price' is the VAT-INCLUSIVE LINE TOTAL. On the fuel receipt a + # single line of 31.17 l reads 57.85, which is the receipt total; + # on the restaurant bill the twelve item prices sum to exactly the + # total. Dividing by the quantity to obtain a unit price loses + # money: 57.85 / 31.17 is 1.855951…, and 1.86 x 31.17 is 57.98. + "amount_total": float(item.get("price") or 0.0), + # An off-setting item. The sign convention is not documented and we + # have never seen one, so it is flagged and left unsigned: the + # framework's reconciliation gate will then park the receipt rather + # than let a guessed sign through. + "is_negative": item_type == "Z", + }) + + taxes, warning = _vat_buckets(receipt) + return { + "receipt_uid": receipt.get("receiptId") or None, + # issueDate, not createDate: they differ, by a second on the fuel + # receipt, and the issue date is the accounting date. + "issue_date": parse_fs_datetime(receipt.get("issueDate")), + "amount_total": float(receipt.get("totalPrice") or 0.0), + # organization is the legal entity; `unit` is the premises, a different + # address entirely (a motorway service station against a city seat), so + # it is kept as a note and never reaches the partner. + "seller_name": org.get("name") or None, + # icDph is NOT "SK" + dic: a VAT-group member files under the group's + # number. On the fuel receipt dic is 2077000002 and icDph is + # SK7199000006. + "seller_vat": receipt.get("icDph") or org.get("icDph") or None, + "seller_tax_id": receipt.get("dic") or org.get("dic") or None, + "seller_reg_id": receipt.get("ico") or org.get("ico") or None, + "seller_vat_payer": bool(org.get("vatPayer")), + "premises_note": _premises_label(unit), + "okp": (receipt.get("okp") or "").upper() or None, + "is_paragon": bool(receipt.get("paragon")), + "lines": lines, + "taxes": taxes, + "warning": warning, + } + + +def _premises_label(unit): + """One-line address of the premises, for a note.""" + street = " ".join(p for p in ( + unit.get("streetName"), + unit.get("propertyRegistrationNumber"), + ) if p) + bits = [p for p in (street, unit.get("postalCode"), + unit.get("municipality")) if p] + return ", ".join(bits) or None diff --git a/l10n_sk_ekasa_receipt/views/cssk_receipt_views.xml b/l10n_sk_ekasa_receipt/views/cssk_receipt_views.xml new file mode 100644 index 00000000..cc15693f --- /dev/null +++ b/l10n_sk_ekasa_receipt/views/cssk_receipt_views.xml @@ -0,0 +1,24 @@ + + + + cssk.receipt.form.sk.ekasa + cssk.receipt + + + + + + + +
+ +
+
+
+
+
diff --git a/l10n_sk_ekasa_receipt/views/res_config_settings_views.xml b/l10n_sk_ekasa_receipt/views/res_config_settings_views.xml new file mode 100644 index 00000000..613e4fe8 --- /dev/null +++ b/l10n_sk_ekasa_receipt/views/res_config_settings_views.xml @@ -0,0 +1,44 @@ + + + + res.config.settings.form.sk.ekasa + res.config.settings + + + + + + + + + +
+
+
+
+
+
+
+ + + + + + +
+
+
+
+
diff --git a/l10n_sk_ekasa_receipt/views/sk_ekasa_call_views.xml b/l10n_sk_ekasa_receipt/views/sk_ekasa_call_views.xml new file mode 100644 index 00000000..b4e93337 --- /dev/null +++ b/l10n_sk_ekasa_receipt/views/sk_ekasa_call_views.xml @@ -0,0 +1,59 @@ + + + + sk.ekasa.call.list + sk.ekasa.call + + + + + + + + + + + + + + + + sk.ekasa.call.form + sk.ekasa.call + +
+ + + + + + + + + + + + + + + + +
+
+
+ + + eKasa Verification Calls + sk.ekasa.call + list,form + + + +
diff --git a/l10n_sk_fs/CHANGELOG.rst b/l10n_sk_fs/CHANGELOG.rst new file mode 100644 index 00000000..890459a5 --- /dev/null +++ b/l10n_sk_fs/CHANGELOG.rst @@ -0,0 +1,354 @@ +========= +Changelog +========= + +All notable changes to **l10n_sk_fs** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +[19.0.1.7.0] — 2026-10-05 +------------------------- + +Changed +~~~~~~~ + +- **One UZPODv14 generator.** The Úč POD statement's export and the + ``l10n.sk.uzpod`` builder used to produce the same document independently. + They agreed on the netto figures and on little else: the statement's export + hardcoded SK NACE 00.00.0, malá / riadna and no poznámky and ran none of the + kontrolné pravidlá, while the builder ignored the accountant's overrides. + ``l10n.sk.uzpod`` now renders through the Úč POD statement of its period + (creating and computing one when there is none), so what is filed is what + was reviewed; its header choices (size class, druh, poznámky) are passed + over the statement's own. + +Added +~~~~~ + +- The statement carries the UZPODv14 header choices itself: *Veľkostná + trieda*, *Druh závierky*, *Poznámky priložené*. The header takes SK NACE + from the partner and the trade register from the company, and + ``datZostavenia`` is the export day as ``dd.mm.yyyy``, the format the other + Finančná správa forms of this repository use. +- The statement's **Export XML** runs the UZPODv14 kontrolné pravidlá + (bilančná rovnosť, netto = brutto − korekcia, medzisúčty; unmapped accounts + as a warning) and refuses a document that fails them. +- Amounts are filed in whole euros, each cell rounded on its own. + +[19.0.1.6.0] — 2026-10-05 +------------------------- + +Fixed +~~~~~ + +- **v01 (Čistý obrat) showed 0 on screen while the filed XML read it off + the ledger.** It was transcribed as a ``manual`` row, and + ``tools/gen_uzpod_v14_version.py`` skipped every non-``accounts`` row, so + it never got the reference formula (``-601,-602,-604,-606,-607``) — the + same defect 19.0.1.5.0 fixed by hand for s113. The generator now promotes + a manual row whose filed counterpart reads the ledger; a migration writes + v01 on existing databases (the data file is ``noupdate``), leaving it + alone if somebody has since given it a formula. A new test refuses a + manual row that the filed export computes. + +[19.0.1.5.0] — 2026-10-03 +------------------------- + +Fixed +~~~~~ + +These change filed figures. Each one moves a balance that was missing from, +doubled on, or misplaced in the Súvaha onto the row the tlačivo (UZPODv14, +MF SR č. 18009/2014) names for it. + +- **34 balance-sheet accounts of the l10n_sk chart reached no row**, among them + 325000 Iné záväzky, 461000 bank loans, 471000/471200, 473000/473100 issued + bonds, 474000 leasing, 372000, 377000, 371000/375000, 354000/358000, 351000, + 313000, 111000, 055000, 068000 and the synthetic allowances 091000, 092000, + 095000 and 096000. All now map to rows. +- **16 codes the chart does not have** were replaced with the ones it does + (091120 → 091200 and so on, 0391120 → 391120, 391314 → 391341, 322300/322500 + → 322100/322200, 361300 → 361200, 471300 → 471200, 475400 → 475300), or + dropped (078000, 323100, 336200). +- **Doubled balances:** 221100 (blocked accounts > 1 year) was on r030 and r073; + 475000 on r111 and r126; 316100 on r107 and r127. +- **Two-sided accounts are gated in the filed export.** 316, 336, 373, 398 and + 481 go to the asset side when the balance is a debit and to the liability side + when it is a credit, as the on-screen statement already did. Before this, a + deferred tax asset was filed on r052 and, negated, on r117. +- **Korekcia sign:** 391336 (r062) and 391700 (r051) raised the netto by the + allowance instead of reducing it. +- **Totals are roll-ups of their rows** (``_SUM`` in ``uzpod14_rows.py``) and no + longer carry account lists of their own. Those lists had drifted: 098000 and + 481000 were missing from the totals, and 255100/473100 appeared only in them. +- r113 Vydané dlhopisy is computed (473 minus own bonds 255100) on screen as + well, instead of being a manual row. + +Needs an accountant's confirmation: 066000, 091000, 092000, 095000, 096000, +461000 and 473000 are synthetic accounts the chart does not split by row, so +each one was assigned to the most general matching row. Also 391600 (r058 +korekcia), 391730 (r050 korekcia) and 395000/431000 (not reported). + +[19.0.1.4.0] — 2026-09-29 +------------------------- + +Fixed +~~~~~ + +- **Úč POD** ``skNace`` read ``l10n_sk_nace_code``, a field nothing defined, so every + statement was filed with SK NACE 00.00.0. It now reads the company's NACE code + (``partner_nace``). +- The statement template no longer fails without ``l10n_sk_base`` + (``l10n_sk_dic``). + +[19.0.1.3.5] — 2026-09-17 +------------------------- + +Fixed +~~~~~ + +- **An Administrator could not open it.** An accounting **Administrator** + could reach the notes (poznámky) but not their depreciation lines and hit + *not allowed to access*. On Community, ``account.group_account_manager`` + does not imply ``account.group_account_user`` (and on Enterprise + ``account_accountant`` only adds ``group_account_basic``), so a model + granted to ``group_account_user`` alone is closed to an Administrator who + lacks "Show Full Accounting Features". The notes already granted it; the + lines did not. ``group_account_manager`` now has the same access as + ``group_account_user`` on ``l10n.sk.poznamky.odpis.line``. Takes effect on + module update. + +[19.0.1.3.4] — 2026-09-15 +------------------------- + +Fixed +~~~~~ + +- **An upgrade from 19.0.1.0.0 that could not finish.** The 19.0.1.2.0 + caption migration compared ``name`` with text, but on a database coming + from 19.0.1.0.0 that column is still ``jsonb`` when the migration runs: + ``name`` was translatable then, and Odoo 19 keeps a field that the + database records as translated patched as translated for the whole load. + The upgrade stopped with + ``operator does not exist: jsonb = text`` and left every module after + ``l10n_cssk_fs_base`` un-upgraded. The migration now reads and writes the + ``en_US`` value when the column is ``jsonb``. + +[19.0.1.3.3] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- **The mixin's field labels reached the mixin and nothing else.** Odoo keeps + one ``ir.model.fields`` row per CONCRETE model, so the translation of + ``legacy`` — addressed to ``cssk.statutory.submission.mixin`` — landed there + and on none of the six models that inherit it. A live instance showed a + Slovak statusbar and an English ``Historical filing`` ribbon on the same + record. +- ``--source`` could not see this: the string WAS in the template, addressed to + one row out of seven. All seven mixin fields are now referenced per concrete + model, with the Slovak and Czech already written for the mixin. + +[19.0.1.3.2] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- **"Historical filing" was untranslatable, and it is the most visible label on + a migrated filing.** It arrived with the ``legacy`` state and the templates + were never regenerated, so the string existed in the source, in no ``.pot`` + and in no ``.po`` anywhere — 217 materialised filings on a live instance + showed an English status on an otherwise fully Slovak form. +- It was not alone. Regenerating and diffing found **209 terms** across the + statutory modules that exist in source and had never entered a template. A + string missing from the TEMPLATE is invisible to coverage, to distinctness + and to a fuzzy count alike, because all three compare a catalogue against its + template — which is why the modules read as healthy. +- Merged additively rather than by regeneration: the committed templates carry + terms a source-only export cannot see (menu and action names, view arch from + data records), and replacing them destroyed 186 translations once already. +- Slovak and Czech filled for the new terms. ``Historical filing`` is + **Historické podanie** / **Historické podání** — ``podanie`` is the statutory + word for a filing made to the authority, and the sibling states already read + Podané / Podáno. + +[19.0.1.3.1] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- Slovak catalogue completed. Terms already stated in Slovak in the source are + deliberately left untranslated — the source IS the translation, per the + hybrid convention for these modules — so an empty ``msgstr`` there is correct + rather than missing. + +[19.0.1.3.0] — 2026-09-12 +------------------------- + +Added +~~~~~ + +- ``("legacy", "Historical filing")`` on ``state``, and the list badge + distinguishes it. Set automatically with the ``legacy`` flag — see + ``l10n_cssk_core`` 19.0.1.9.0 for the reasoning and for why ``submitted`` + was the wrong value to reuse. + +Fixed +~~~~~ + +- Buttons gated on ``state`` were written when ``legacy`` records sat in + ``draft``, so the new value would have made some of them appear on a + historical filing — Export XML, and the reconciliation / mapping indicators + that count against a computation never run for one. Their conditions now + name ``legacy`` alongside ``draft``. +- A post-migration moves existing historical filings onto the new state. Raw + SQL, and not out of laziness: a write carrying ``state`` on a record that is + already ``legacy`` is refused by ``_cssk_check_not_legacy``, which is exactly + the guarantee the state exists to give, so the migration goes under it rather + than around it. + +Carry-over to 18.0 +~~~~~~~~~~~~~~~~~~ + +- **The UZPODv14 rework (19.0.1.1.0) and the corrected row captions + (19.0.1.2.0) are owed to 18.0.** The one that matters is the mapping: the + on-screen statement was a second transcription of the form and disagreed + with the filed XML on 47 of 114 súvaha leaf rows, hidden because A.VIII was + a plug. 18.0 still carries that. Port the generator, the corrected captions, + the two-sided account gating and the unmapped-account diagnostic + (l10n_cssk_fs_base) together — the gating and the diagnostic are what stop + the same class of error coming back. + +[19.0.1.2.0] — 2026-09-09 +------------------------- + +Fixed +~~~~~ + +- **The row captions.** They were transcribed from a plain ``pdftotext`` dump + of the tlačivo, where a two-line caption bleeds into the row below it: + "Poskytnuté" sat on r009 and r010 read "7. preddavky na dlhodobý nehmotný + majetok". About a third of the form was shifted like that, and a shifted + caption is worse than a missing one — it names the row above's accounts + while reading perfectly ordinarily. + + They are now read off the form's OWN column layout (označenie / text / číslo + riadku), which is what ties a caption printed above a row number to that + row. ``tools/parse_uzpod14_labels.py`` produces + ``data/uzpod14_row_labels.tsv``; the generator writes them into the version + record and a test asserts all 206 still match. + + Figures are untouched. A migration corrects the captions on an existing + database — the data file cannot, because re-creating a ``noupdate="1"`` + record's one2many payload only happens on a CREATE. + +[19.0.1.1.0] — 2026-09-09 +------------------------- + +Changed +~~~~~~~ + +- **One UZPODv14 mapping, not two.** The on-screen Súvaha/VZS statement was a + second, hand-transcribed mapping of the same form the filed XML already had + in ``models/uzpod14_rows.py``, and the two disagreed on 47 of 114 súvaha + leaf rows — rows r043/r044/r045 each claimed the whole of 311-315, tripling + trade receivables. The statement's leaf rows are now generated from the + filed mapping by ``tools/gen_uzpod_v14_version.py``, and + ``tests/test_uzpod_one_mapping.py`` keeps them there. +- **A.VIII is no longer a plug.** It was ``SPOLU MAJETOK`` minus the other + passive rows, so it silently absorbed every mapping error on that side and + the sheet balanced regardless — which is why the 47 divergent rows went + unnoticed. It now reads the movement of triedy 5/6, so it equals VZS r61 + even on a ledger whose prior year is not yet closed to účet 431. +- **The Súvaha and the VZS are one document again.** They were filed as two, + with roots ``UZSUV``/``UZVZS`` against two schemas we wrote ourselves; FS SR + publishes neither root nor schema, and ``uzpod-2014.xsd`` was sitting in the + module unreferenced. One version record now covers both blocks, reading the + cumulative balance for the súvaha and the period movement for the výkaz, and + validates against the official schema. The two invented XSDs are deleted and + the stand-in version records are retired on upgrade. + +Added +~~~~~ + +- ``data/SCHEMA_VERSION`` pinning ``uzpod-2014.xsd`` by size and digest, with + the refresh procedure and a test that no bundled schema sits unpinned. +- **Accounts that reach no row are now reported** on the statement + (``unmapped_count`` / "Accounts on no row"). With the rows naming specific + chart codes, a company whose chart puts a balance somewhere the mapping does + not name would otherwise lose that money from the form silently — the sheet + still foots and still balances. +- Two-sided accounts (341-347, 316100, 373100, 481000) are gated on the sign + of each account's own balance, so a deferred tax liability no longer shows + as a liability AND a negative asset of the same size. + +Fixed +~~~~~ + +- The residual-token mechanism read an ``X`` in a TAG name (``666&X_1``) as an + account-code residual, which switched on a rule under which a six-digit + token ending in 000 excludes its own account — ``013000`` matched nothing. + +[19.0.1.0.5] — 2026-09-06 +------------------------- + +Fixed +~~~~~ + +- The two prehľady (peňažných tokov, zmien vlastného imania) could not be + exported: Finančná správa publishes ``uzsuv``/``uzvzs`` for the Súvaha and + the VZS and nothing for these, and the core mixin had been tightened to + refuse an export it could not validate. Both versions now declare + ``xml_schema_optional``, with a migration — they live in ``noupdate="1"`` + data. The Súvaha and VZS keep validating against their schemas. + +Changed +~~~~~~~ + +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.1.0.2] — 2026-07-04 +------------------------- + +Added +~~~~~ + +- Poznámky (Úč PODV 3-01) generated as **PDF v1**: new ``l10n.sk.poznamky`` model + QWeb report, with depreciation figures auto-populated. No structured Poznámky XSD exists, so the PDF is the definitive artefact. +- Cash-flow and statement-of-changes-in-equity datasets alongside Súvaha + VZS. + +Changed +~~~~~~~ + +- Evaluator performance (Wave 3): Súvaha compute uses ``_read_group`` balance helpers (~600 queries → 2); form-load reconcile reduced from N-per-line to ≤3 queries. +- Export pipeline consolidated into the shared ``cssk.statutory.submission.mixin``. + +[2026-07-01] — Wave 1 (P0 correctness) +-------------------------------------- + +Fixed +~~~~~ + +- Multi-company leak closed with an ``ir.rule [('company_id','in',company_ids)]``. + +Changed +~~~~~~~ + +- Statutory rounding unified on ``statutory_round()`` (HALF-UP). + +[2026-06-30] — Initial baseline +------------------------------- + +Added +~~~~~ + +- Slovak Súvaha + Výkaz ziskov a strát (druhové členenie) on ``l10n_cssk_fs_base``, computed CE-clean (no ``account_reports``): full statutory line partition, so SPOLU MAJETOK = SPOLU VLASTNÉ IMANIE A ZÁVÄZKY and VH = výnosy − náklady; current-year result (A.V) computed from triedy 5/6. +- Official **UZPODv14** export (``l10n.sk.uzpod``): combined Súvaha Úč POD 1 (r001–r145, Brutto/Korekcia/Netto) + Výkaz ziskov a strát Úč POD 2 (r01–r61), validated against ``data/uzpod-2014.xsd``. +- sk_SK translations (Odoo 19 jsonb) as part of the localization i18n sweep. diff --git a/l10n_sk_fs/LICENSE b/l10n_sk_fs/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/l10n_sk_fs/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/l10n_sk_fs/README.rst b/l10n_sk_fs/README.rst new file mode 100644 index 00000000..8403f2df --- /dev/null +++ b/l10n_sk_fs/README.rst @@ -0,0 +1,29 @@ +========================================== +Slovakia — Financial Statements (Súvaha) +========================================== + +Slovak balance sheet (Súvaha) on ``l10n_cssk_fs_base``. Account-code line +mapping + comparison period + XML export, computed CE-clean. + +Status +====== + +**Functional skeleton.** A representative line set (asset side, equity & +liabilities side, totals) computes from SK account classes and exports to a +stand-in ``UZSUV`` XML (verified by a test: a balanced entry → assets 1000 = +equity 1000). To complete: + +* The 3-column **Brutto / Korekcia / Netto** split, the full ~145-line Súvaha + and **VZS** (P&L). +* Poznámky, mikro (UZMUJv14) variant. +* The official FS SR **UZPODv14** XSD + XML structure (the shipped XSD is a + stand-in). +* **Accountant validation of the account→line mapping** (the current mapping is + a draft starting subset). + +Credits +======= + +* Data Dance s.r.o. + +License: AGPL-3.0 or later (see the LICENSE file). diff --git a/l10n_sk_fs/__init__.py b/l10n_sk_fs/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_sk_fs/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_sk_fs/__manifest__.py b/l10n_sk_fs/__manifest__.py new file mode 100644 index 00000000..f163314d --- /dev/null +++ b/l10n_sk_fs/__manifest__.py @@ -0,0 +1,60 @@ +{ + "name": "Slovakia — Financial Statements (Súvaha / VZS)", + "version": "19.0.1.7.0", + "summary": "Slovak balance sheet (Súvaha) on the shared " + "l10n_cssk_fs_base framework — account-code line mapping + " + "comparison period + XML export.", + "description": """ +Slovakia — Financial Statements (Súvaha) +======================================== + +Concrete Slovak Súvaha + Výkaz ziskov a strát (druhové členenie) on top of +``l10n_cssk_fs_base``, computed CE-clean (no ``account_reports``). + +**Scope:** the full statutory line structure (Opatrenie MF SR pre podnikateľov +v PÚ) — every account of the l10n_sk účtová osnova is mapped to exactly one leaf +line (partition verified in tests), so the aggregates reconcile: **SPOLU MAJETOK += SPOLU VLASTNÉ IMANIE A ZÁVÄZKY** and VH = výnosy − náklady. The current-year +result (A.V) is computed from triedy 5/6 so the súvaha ties out before the +result is closed to účtu 431. + +**Official UZPODv14 export** (``l10n.sk.uzpod``): the combined účtovná závierka +``dokument`` (Súvaha Úč POD 1 r001–r145 with Brutto/Korekcia/Netto + Výkaz +ziskov a strát Úč POD 2 r01–r61), built CE-clean from ``account.move.line`` and +**validated against the official ``data/uzpod-2014.xsd``** (FS SR). The row→ +account formulas (``models/uzpod14_rows.py``) mirror the authoritative statutory +mapping. + +**One mapping, two views.** The on-screen statement is not a second +transcription of the form: its leaf rows are generated from the same table the +filed XML uses, so what an accountant reviews and what leaves the building are +the same numbers by construction. What the statement adds is the drillable +tree — every total is the sum of the rows shown beneath it, and every leaf +drills through to its journal items. + +**Accountant note:** the row mapping follows the official form; confirm the +NACE code and header fields on the company and validate with a SK accountant +before live filing. Watch the "Accounts on no row" warning: the rows name +specific chart codes, so a non-standard chart can put a balance where no row +claims it, and a statement that omits it still foots and still balances. +""", + "author": "Data Dance s.r.o.", + "website": "https://www.datadance.eu", + "license": "AGPL-3", + "category": "Accounting/Localizations", + "depends": ["l10n_cssk_fs_base", "l10n_sk", "partner_nace"], + "data": [ + "security/ir.model.access.csv", + "security/record_rules.xml", + "data/cssk_pl_split_tags.xml", + "report/l10n_sk_fs_templates.xml", + "report/l10n_sk_poznamky_report.xml", + "data/cssk_fs_version_data.xml", + "data/cssk_uzpod_v14_version_data.xml", + "data/cssk_cashflow_version_data.xml", + "data/cssk_equity_changes_version_data.xml", + "views/l10n_sk_poznamky_views.xml", + "views/cssk_fs_statement_views.xml", + ], + "installable": True, +} diff --git a/l10n_sk_fs/data/SCHEMA_VERSION b/l10n_sk_fs/data/SCHEMA_VERSION new file mode 100644 index 00000000..15c5bc03 --- /dev/null +++ b/l10n_sk_fs/data/SCHEMA_VERSION @@ -0,0 +1,35 @@ +uzpod-2014.xsd d95c607d24512b974546adefc1200018 17667 + +Verified byte-identical to the published copy on 2026-09-09, fetched from +https://ekr.financnasprava.sk/Formulare/XSD/uzpod-2014.xsd + +WHAT THIS SCHEMA IS. The whole účtovná závierka in one document: root +`dokument`, with `telo` carrying `ucPod1Suvaha` (145 rows, tRiadok14 = +brutto/korekcia/netto/netto-minulé) beside `ucPod2VykazZS` (61 rows, +tRiadok12 = bežné/minulé). It stamps itself only "Suvaha", which understates +what it contains. + +⚠️ THIS MODULE PREVIOUSLY SHIPPED TWO INVENTED SCHEMAS. `uzsuv_schema.xsd` +and `uzvzs_schema.xsd` (7 elements each, roots UZSUV / UZVZS) were +hand-written stand-ins; those roots exist nowhere in anything FS SR publishes. +The templates said so — "WORKING STAND-IN … replace with the official FS SR +UZPODv14 structure before live filing" — but the version records and the +schema files did not, so an export validated cleanly against a schema no +authority had ever seen. Both are deleted. If you find a reference to either, +it is stale. + +NOT SHIPPED, and deliberately: + uzmuj-2014.xsd — Účtovná závierka MIKRO účtovnej jednotky. A different + form for companies under the micro thresholds, which this module does not + model at all. A mikro účtovná jednotka files UZMUJ, not UZPOD. + vp_uct2015.xsd — the všeobecné podanie wrapper both závierky are submitted + inside. Out of scope until submission is automated. + +REFRESH PROCEDURE + 1. Fetch https://ekr.financnasprava.sk/Formulare/XSD/uzpod-2014.xsd and + compare the md5 with the line here. + 2. Check the status code, not the size: a miss returns 404 with a + ~1 245-byte HTML error page. + 3. Probe for a _v2 suffix as well as a newer year — that is how FS SR + published a DPPO revision (dppo2025_v2.xsd beside an unchanged + dppo2025.xsd), and a digest comparison on the pinned name cannot see it. diff --git a/l10n_sk_fs/data/cssk_cashflow_version_data.xml b/l10n_sk_fs/data/cssk_cashflow_version_data.xml new file mode 100644 index 00000000..59a2e5c3 --- /dev/null +++ b/l10n_sk_fs/data/cssk_cashflow_version_data.xml @@ -0,0 +1,79 @@ + + + + + + Prehľad peňažných tokov 2025 + + cash_flow + 2025-01-01 + + PrehladPP + + + + + + diff --git a/l10n_sk_fs/data/cssk_equity_changes_version_data.xml b/l10n_sk_fs/data/cssk_equity_changes_version_data.xml new file mode 100644 index 00000000..d2c4e272 --- /dev/null +++ b/l10n_sk_fs/data/cssk_equity_changes_version_data.xml @@ -0,0 +1,66 @@ + + + + + + Prehľad zmien vlastného imania 2025 + + equity_changes + 2025-01-01 + + PrehladVI + + + + + + diff --git a/l10n_sk_fs/data/cssk_fs_version_data.xml b/l10n_sk_fs/data/cssk_fs_version_data.xml new file mode 100644 index 00000000..8224092e --- /dev/null +++ b/l10n_sk_fs/data/cssk_fs_version_data.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/l10n_sk_fs/data/cssk_pl_split_tags.xml b/l10n_sk_fs/data/cssk_pl_split_tags.xml new file mode 100644 index 00000000..08b50620 --- /dev/null +++ b/l10n_sk_fs/data/cssk_pl_split_tags.xml @@ -0,0 +1,45 @@ + + + + + + VZS IX.1 — výnosy z CP a podielov od prepojených ÚJ (665) + accounts + + + + VZS IX.2 — výnosy z CP a podielov v podielovej účasti (665) + accounts + + + + VZS X.1 — výnosy z krátkodob. fin. majetku od prepojených ÚJ (666) + accounts + + + + VZS X.2 — výnosy z krátkodob. fin. majetku v podielovej účasti (666) + accounts + + + + VZS XI.1 — výnosové úroky od prepojených ÚJ (662) + accounts + + + + VZS N.1 — nákladové úroky pre prepojené ÚJ (562) + accounts + + + diff --git a/l10n_sk_fs/data/cssk_uzpod_v14_version_data.xml b/l10n_sk_fs/data/cssk_uzpod_v14_version_data.xml new file mode 100644 index 00000000..85f3a6cd --- /dev/null +++ b/l10n_sk_fs/data/cssk_uzpod_v14_version_data.xml @@ -0,0 +1,251 @@ + + + + + Účtovná závierka podnikateľov (UZPODv14) + + + balance_sheet + 2014-12-31 + + dokument + uzpod-2014.xsd + + + + diff --git a/l10n_sk_fs/data/uzpod-2014.xsd b/l10n_sk_fs/data/uzpod-2014.xsd new file mode 100644 index 00000000..39e5e319 --- /dev/null +++ b/l10n_sk_fs/data/uzpod-2014.xsd @@ -0,0 +1,422 @@ + + + + + Suvaha + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n_sk_fs/data/uzpod14_row_labels.tsv b/l10n_sk_fs/data/uzpod14_row_labels.tsv new file mode 100644 index 00000000..870c0a46 --- /dev/null +++ b/l10n_sk_fs/data/uzpod14_row_labels.tsv @@ -0,0 +1,206 @@ +s001 SPOLU MAJETOK +s002 A. Neobežný majetok +s003 A.I. Dlhodobý nehmotný majetok súčet +s004 A.I.1. Aktivované náklady na vývoj +s005 2. Softvér +s006 3. Oceniteľné práva +s007 4. Goodwill +s008 5. Ostatný dlhodobý nehmotný majetok +s009 6. Obstarávaný dlhodobý nehmotný majetok +s010 7. Poskytnuté preddavky na dlhodobý nehmotný majetok +s011 A.II. Dlhodobý hmotný majetok súčet +s012 A.II.1. Pozemky +s013 2. Stavby +s014 3. Samostatné hnuteľné veci a súbory hnuteľných vecí +s015 4. Pestovateľské celky trvalých porastov +s016 5. Základné stádo a ťažné zvieratá +s017 6. Ostatný dlhodobý hmotný majetok +s018 7. Obstarávaný dlhodobý hmotný majetok +s019 8. Poskytnuté preddavky na dlhodobý hmotný majetok +s020 9. Opravná položka k nadobudnutému majetku +s021 A.III. Dlhodobý finančný majetok súčet +s022 A.III.1. Podielové cenné papiere a podiely v prepojených účtovných jednotkách +s023 2. Podielové cenné papiere a podiely s podielovou účasťou okrem v prepojených účtovných jednotkách +s024 3. Ostatné realizovateľné cenné papiere a podiely +s025 4. Pôžičky prepojeným účtovným jednotkám +s026 5. Pôžičky v rámci podielovej účasti okrem prepojeným účtovným jednotkám +s027 6. Ostatné pôžičky +s028 7. Dlhové cenné papiere a ostatný dlhodobý finančný majetok +s029 8. dlhodobý finančný majetok so zostatkovou dobou splatnosti najviac jeden rok +s030 9. Účty v bankách s dobou viazanosti dlhšou ako jeden rok +s031 10. Obstarávaný dlhodobý finančný majetok +s032 11. Poskytnuté preddavky na dlhodobý finančný majetok +s033 B. Obežný majetok +s034 B.I. Zásoby súčet +s035 B.I.1. Materiál +s036 2. Nedokončená výroba a polotovary vlastnej výroby +s037 3. Výrobky +s038 4. Zvieratá +s039 5. Tovar +s040 6. Poskytnuté preddavky na zásoby +s041 B.II. Dlhodobé pohľadávky súčet +s042 B.II.1. Pohľadávky z obchodného styku súčet +s043 1.a. Pohľadávky z obchodného styku voči prepojeným účtovným jednotkám +s044 1.b. Pohľadávky z obchodného styku v rámci podielovej účasti okrem pohľadávok voči prepojeným účtovným jednotkám +s045 1.c. Ostatné pohľadávky z obchodného styku +s046 2. Čistá hodnota zákazky +s047 3. Ostatné pohľadávky voči prepojeným účtovným jednotkám +s048 4. Ostatné pohľadávky v rámci podielovej účasti okrem pohľadávok voči prepojeným účtovným jednotkám +s049 5. Pohľadávky voči spoločníkom, členom a združeniu +s050 6. Pohľadávky z derivátových operácií +s051 7. Iné pohľadávky +s052 8. Odložená daňová pohľadávka +s053 B.III. Krátkodobé pohľadávky súčet +s054 B.III.1. Pohľadávky z obchodného styku súčet +s055 1.a. Pohľadávky z obchodného styku voči prepojeným účtovným jednotkám +s056 1.b. Pohľadávky z obchodného styku v rámci podielovej účasti okrem pohľadávok voči prepojeným účtovným jednotkám +s057 1.c. Ostatné pohľadávky z obchodného styku +s058 2. Čistá hodnota zákazky +s059 3. Ostatné pohľadávky voči prepojeným účtovným jednotkám +s060 4. Ostatné pohľadávky v rámci podielovej účasti okrem pohľadávok voči prepojeným účtovným jednotkám +s061 5. Pohľadávky voči spoločníkom, členom a združeniu +s062 6. Sociálne poistenie +s063 7. Daňové pohľadávky a dotácie +s064 8. Pohľadávky z derivátových operácií +s065 9. Iné pohľadávky +s066 B.IV. Krátkodobý finančný majetok súčet +s067 B.IV.1. Krátkodobý finančný majetok v prepojených účtovných jednotkách +s068 2. Krátkodobý finančný majetok bez krátkodobého finančného majetku v prepojených účtovných jednotkách +s069 3. Vlastné akcie a vlastné obchodné podiely +s070 4. Obstarávaný krátkodobý finančný majetok +s071 B.V. Finančné účty +s072 B.V.1. Peniaze +s073 2. Účty v bankách (221A, 22X, +s074 C. Časové rozlíšenie súčet +s075 C.1. Náklady budúcich období dlhodobé +s076 2. Náklady budúcich období krátkodobé +s077 3. Príjmy budúcich období dlhodobé +s078 4. Príjmy budúcich období krátkodobé +s079 SPOLU VLASTNÉ IMANIE A ZÁVÄZKY +s080 A. Vlastné imanie +s081 A.I. Základné imanie súčet +s082 A.I.1. Základné imanie (411 alebo +s083 2. Zmena základného imania +s084 3. Pohľadávky za upísané vlastné imanie +s085 A.II. Emisné ážio +s086 A.III. Ostatné kapitálové fondy +s087 A.IV. Zákonné rezervné fondy +s088 A.IV.1. Zákonný rezervný fond a nedeliteľný fond +s089 2. Rezervný fond na vlastné akcie a vlastné podiely +s090 A.V. Ostatné fondy zo zisku +s091 A.V.1. Štatutárne fondy +s092 2. Ostatné fondy +s093 A.VI. Oceňovacie rozdiely z precenenia súčet +s094 A.VI.1. Oceňovacie rozdiely z precenenia majetku a záväzkov +s095 2. Oceňovacie rozdiely z kapitálových účastín +s096 3. Oceňovacie rozdiely z precenenia pri zlúčení, splynutí a rozdelení +s097 A.VII. Výsledok hospodárenia minulých rokov +s098 A.VII.1. Nerozdelený zisk minulých rokov +s099 2. Neuhradená strata minulých rokov +s100 A.VIII. Výsledok hospodárenia za účtovné obdobie po zdanení /+-/ +s101 B. Záväzky +s102 B.I. Dlhodobé záväzky súčet +s103 B.I.1. Dlhodobé záväzky z obchodného styku súčet +s104 1.a. Záväzky z obchodného styku voči prepojeným účtovným jednotkám +s105 1.b. Záväzky z obchodného styku v rámci podielovej účasti okrem záväzkov voči prepojeným účtovným jednotkám +s106 1.c. Ostatné záväzky z obchodného styku +s107 2. Čistá hodnota zákazky +s108 3. Ostatné záväzky voči prepojeným účtovným jednotkám +s109 4. Ostatné záväzky v rámci podielovej účasti okrem záväzkov voči prepojeným účtovným jednotkám +s110 5. Ostatné dlhodobé záväzky +s111 6. Dlhodobé prijaté preddavky +s112 7. Dlhodobé zmenky na úhradu +s113 8. Vydané dlhopisy (473A/ +s114 9. Záväzky zo sociálneho fondu +s115 10. Iné dlhodobé záväzky +s116 11. Dlhodobé záväzky z derivátových operácií +s117 12. Odložený daňový záväzok +s118 B.II. Dlhodobé rezervy +s119 B.II.1. Zákonné rezervy +s120 2. Ostatné rezervy +s121 B.III. Dlhodobé bankové úvery +s122 B.IV. Krátkodobé záväzky súčet +s123 B.IV.1. Záväzky z obchodného styku súčet +s124 1.a. Záväzky z obchodného styku voči prepojeným účtovným jednotkám +s125 Záväzky z obchodného styku v rámci podielovej 1.b. účasti okrem záväzkov voči prepojeným účtovným jednotkám +s126 1.c. Ostatné záväzky z obchodného styku +s127 2. Čistá hodnota zákazky +s128 3. Ostatné záväzky voči prepojeným účtovným jednotkám +s129 4. Ostatné záväzky v rámci podielovej účasti okrem záväzkov voči prepojeným účtovným jednotkám +s130 5. Záväzky voči spoločníkom a združeniu +s131 6. Záväzky voči zamestnancom +s132 7. Záväzky zo sociálneho poistenia +s133 8. Daňové záväzky a dotácie +s134 9. Záväzky z derivátových operácií +s135 10. Iné záväzky +s136 B.V. Krátkodobé rezervy +s137 B.V.1. Zákonné rezervy +s138 2. Ostatné rezervy +s139 B.VI. Bežné bankové úvery +s140 B.VII. Krátkodobé finančné výpomoci (241, 249, 24X, 473A, / +s141 C. Časové rozlíšenie súčet +s142 C.1. Výdavky budúcich období dlhodobé +s143 2. Výdavky budúcich období krátkodobé +s144 3. Výnosy budúcich období dlhodobé +s145 4. Výnosy budúcich období krátkodobé +v01 * Čistý obrat (časť účt. tr. 6 podľa zákona) +v02 ** Výnosy z hospodárskej činnosti spolu súčet +v03 I. Tržby z predaja tovaru +v04 II. Tržby z predaja vlastných výrobkov +v05 III. Tržby z predaja služieb +v06 IV. Zmeny stavu vnútroorganizačných zásob +v07 V. Aktivácia (účtová skupina 62) Tržby z predaja dlhodobého nehmotného +v08 VI. majetku, dlhodobého hmotného majetku a materiálu +v09 VII. Ostatné výnosy z hospodárskej činnosti +v10 ** Náklady na hospodársku činnosť spolu +v11 A. Náklady vynaložené na obstaranie predaného tovaru +v12 B. Spotreba materiálu, energie a ostatných neskladovateľných dodávok +v13 C. Opravné položky k zásobám +v14 D. Služby (účtová skupina 51) +v15 E. Osobné náklady +v16 E.1. Mzdové náklady +v17 2. Odmeny členom orgánov spoločnosti a družstva +v18 3. Náklady na sociálne poistenie +v19 4. Sociálne náklady +v20 F. Dane a poplatky (účtová skupina 53) Odpisy a opravné položky k dlhodobému +v21 G. nehmotnému majetku a dlhodobému hmotnému majetku +v22 G.1. Odpisy dlhodobého nehmotného majetku a dlhodobého hmotného majetku +v23 2. Opravné položky k dlhodobému nehmotnému majetku a dlhodobému hmotnému majetku +v24 H. Zostatková cena predaného dlhodobého majetku a predaného materiálu +v25 I. Opravné položky k pohľadávkam +v26 J. Ostatné náklady na hospodársku činnosť +v27 *** Výsledok hospodárenia z hospodárskej činnosti (+/-) +v28 * Pridaná hodnota +v29 ** Výnosy z finančnej činnosti spolu +v30 VIII. Tržby z predaja cenných papierov a podielov +v31 IX. Výnosy z dlhodobého finančného majetku súčet +v32 IX.1. Výnosy z cenných papierov a podielov od prepojených účtovných jednotiek +v33 2. Výnosy z cenných papierov a podielov v podielovej účasti okrem výnosov prepojených účtovných jednotiek +v34 3. Ostatné výnosy z cenných papierov a podielov +v35 X. Výnosy z krátkodobého finančného majetku súčet +v36 X.1. Výnosy z krátkodobého finančného majetku od prepojených účtovných jednotiek +v37 2. Výnosy z krátkodobého finančného majetku v podielovej účasti okrem výnosov prepojených účtovných jednotiek +v38 3. Ostatné výnosy z krátkodobého finančného majetku +v39 XI. Výnosové úroky +v40 XI.1. Výnosové úroky od prepojených účtovných jednotiek +v41 2. Ostatné výnosové úroky +v42 XII. Kurzové zisky +v43 XIII. Výnosy z precenenia cenných papierov a výnosy z derivátových operácií +v44 XIV. Ostatné výnosy z finančnej činnosti +v45 ** Náklady na finančnú činnosť spolu +v46 K. Predané cenné papiere a podiely +v47 L. Náklady na krátkodobý finančný majetok +v48 M. Opravné položky k finančnému majetku +v49 N. Nákladové úroky +v50 N.1. Nákladové úroky pre prepojené účtovné jednotky +v51 2. Ostatné nákladové úroky +v52 O. Kurzové straty +v53 P. Náklady na precenenie cenných papierov a náklady na derivátové operácie +v54 Q. Ostatné náklady na finančnú činnosť +v55 *** Výsledok hospodárenia z finančnej činnosti (+/-) +v56 **** Výsledok hospodárenia za účtovné obdobie pred zdanením (+/-) +v57 R. Daň z príjmov +v58 R.1. Daň z príjmov splatná +v59 2. Daň z príjmov odložená +v60 S. Prevod podielov na výsledku hospodárenia spoločníkom +v61 **** Výsledok hospodárenia za účtovné obdobie po zdanení diff --git a/l10n_sk_fs/i18n/l10n_sk_fs.pot b/l10n_sk_fs/i18n/l10n_sk_fs.pot new file mode 100644 index 00000000..670a667b --- /dev/null +++ b/l10n_sk_fs/i18n/l10n_sk_fs.pot @@ -0,0 +1,879 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_fs +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-27 08:43+0000\n" +"PO-Revision-Date: 2026-06-27 08:43+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "&nbsp; DIČ:" +msgstr "" + +#. module: l10n_sk_fs +#. odoo-python +#: code:addons/l10n_sk_fs/models/l10n_sk_poznamky.py:0 +msgid "(corrective)" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "(opravná)" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid ", DNM" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "" +"Dátum schválenia predchádzajúcej účtovnej\n" +" závierky:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Hranica drobného majetku: DDHM" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "IČO:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Je súčasťou konsolidovaného celku:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Obchodné meno:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Odpisový plán:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Opis hospodárskej činnosti:
" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Predpoklad nepretržitého pokračovania v činnosti:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Právny dôvod zostavenia:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Spôsob oceňovania:
" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Sídlo:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Zmeny účtovných zásad a metód:
" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Záväzky so splatnosťou nad 5 rokov:" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Záväzky zabezpečené záložným právom:" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Bežné obdobie" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__company_id +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__company_id +msgid "Company" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__submission_type__opravna +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__submission_type__opravna +msgid "Corrective" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__create_uid +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__create_uid +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__create_uid +msgid "Created by" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__create_date +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__create_date +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__create_date +msgid "Created on" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__currency_id +msgid "Currency" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__date_from +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__date_from +msgid "Date From" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__date_to +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__date_to +msgid "Date To" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__odpis_line_ids +msgid "Depreciation schedule" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__display_name +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__display_name +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Doba (roky)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__doba_rokov +msgid "Doba odpisovania (roky)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__state__draft +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__state__draft +msgid "Draft" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__schvalenie_date +msgid "Dátum schválenia predchádzajúcej závierky" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__state__exported +msgid "Exported" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__pravny_dovod__mimoriadna +msgid "Extraordinary" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__submitted_attachment_id +msgid "Filed XML" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__state__generated +msgid "Generated" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__has_message +msgid "Has Message" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__id +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__id +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__id +msgid "ID" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_has_error +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl4 +msgid "Informácie k položkám výkazu ziskov a strát" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl5 +msgid "Iné aktíva a iné pasíva" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__je_konsolidovana +msgid "Je súčasťou konsolidovaného celku" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__write_uid +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__write_uid +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__write_date +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__write_date +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__write_date +msgid "Last Updated on" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__pravny_dovod +msgid "Legal basis for preparation" +msgstr "" + +#. module: l10n_sk_fs +#. odoo-python +#: code:addons/l10n_sk_fs/models/l10n_sk_poznamky.py:0 +msgid "Majetok" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_ids +msgid "Messages" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__name +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__name +msgid "Name" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Načítať odpisový plán z majetku" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__going_concern +msgid "Nepretržité pokračovanie v činnosti" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model,name:l10n_sk_fs.model_l10n_sk_poznamky_odpis_line +msgid "Notes — depreciation-schedule line" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cinnost +msgid "Opis hospodárskej činnosti" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__amendment_ids +msgid "Opravné podania" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__original_return_id +msgid "Opravuje" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl2_organy +msgid "Orgány — príjmy a výhody" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl7 +msgid "Ostatné informácie" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__pdf_attachment_id +msgid "Pdf Attachment" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__poznamky_id +msgid "Poznamky" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.actions.act_window,name:l10n_sk_fs.action_l10n_sk_poznamky +msgid "Poznámky k závierke" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.ui.menu,name:l10n_sk_fs.menu_l10n_sk_poznamky +msgid "Poznámky k závierke (SK)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.actions.report,name:l10n_sk_fs.action_report_l10n_sk_poznamky +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Poznámky k účtovnej závierke" +msgstr "" + +#: model:ir.model.fields,field_description:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_poznamky_attached +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__poznamky_attached +msgid "Poznámky priložené" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__ddhm_prah +msgid "Prah DDHM" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__dnm_prah +msgid "Prah DNM" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Predch. obdobie" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Priemerný prepočítaný počet zamestnancov" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zamestnanci_bezne +msgid "Priemerný prepočítaný počet zamestnancov (bežné)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__pravny_dovod__riadna +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__submission_type__radna +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__submission_type__radna +msgid "Regular" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__submission_type +msgid "" +"Regular = original filing; Corrective = a corrective financial statement of " +"an already-submitted one for the same period (RÚZ document type 'opravná'; " +"the body XML stays druh=riadna)." +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Ročná sadzba" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__sadzba +msgid "Ročná sadzba (%)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model,name:l10n_sk_fs.model_l10n_sk_uzpod +msgid "SK Annual Statements (UZPODv14)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model,name:l10n_sk_fs.model_l10n_sk_poznamky +msgid "SK Notes to the financial statements (Úč PODV 3-01)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_has_sms_error +msgid "SMS Delivery error" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__sequence +msgid "Sequence" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__poznamky_attached +msgid "" +"Set when the Notes (Úč PODV 3-01) are filed with this statement; drives the " +"header prilozeneSucasti/poznamky flag (see l10n.sk.poznamky)." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__nazov +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Skupina / účet majetku" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl3_ocenovanie +msgid "Spôsob oceňovania majetku a záväzkov" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__state +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__state +msgid "State" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__submission_reference +msgid "Submission Reference" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__submission_type +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__submission_type +msgid "Submission Type" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__submission_reference +msgid "Submission identifier / confirmation number from the FS portal." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__state__submitted +msgid "Submitted" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__submitted_date +msgid "Submitted Date" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__submitted_attachment_id +msgid "The exact XML that was submitted; preserved across reset-to-draft." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__original_return_id +msgid "The originally-filed UZPODv14 this corrective statement corrects." +msgstr "" + +#. module: l10n_sk_fs +#. odoo-python +#: code:addons/l10n_sk_fs/models/l10n_sk_uzpod.py:0 +msgid "" +"UZPODv14 XML failed schema validation:\n" +"%s" +msgstr "" + +#. module: l10n_sk_fs +#. odoo-python +#: code:addons/l10n_sk_fs/models/l10n_sk_uzpod.py:0 +msgid "" +"UZPODv14 failed kontrolné pravidlá (XSD-valid but the výkaz would be rejected):\n" +"%s" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl6 +msgid "Udalosti po dni, ku ktorému sa zostavuje závierka" +msgstr "" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_IX_1 +msgid "VZS IX.1 — výnosy z CP a podielov od prepojených ÚJ (665)" +msgstr "" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_IX_2 +msgid "VZS IX.2 — výnosy z CP a podielov v podielovej účasti (665)" +msgstr "" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_N_1 +msgid "VZS N.1 — nákladové úroky pre prepojené ÚJ (562)" +msgstr "" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_X_1 +msgid "VZS X.1 — výnosy z krátkodob. fin. majetku od prepojených ÚJ (666)" +msgstr "" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_X_2 +msgid "VZS X.2 — výnosy z krátkodob. fin. majetku v podielovej účasti (666)" +msgstr "" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_XI_1 +msgid "VZS XI.1 — výnosové úroky od prepojených ÚJ (662)" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Vygenerovať PDF" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__xml_attachment_id +msgid "Xml Attachment" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zamestnanci_veduci +msgid "Z toho vedúci zamestnanci" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl3_zmeny_metod +msgid "Zmeny účtovných zásad a metód" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zavazky_nad_5_rokov +msgid "Záväzky so splatnosťou nad 5 rokov" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zavazky_zabezpecene +msgid "Záväzky zabezpečené záložným právom" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "z toho vedúci zamestnanci" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "" +"zostavené podľa Opatrenia MF/23378/2014-74 (malé účtovné jednotky)\n" +" za obdobie" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Čl. I — Všeobecné" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. I — Všeobecné informácie" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. II — Orgány účtovnej jednotky" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Čl. II, IV–VII — Texty" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. III — Použité účtovné zásady a metódy" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Čl. III — Zásady a odpisy" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. IV — Informácie k položkám výkazu ziskov a strát" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. V — Iné aktíva a iné pasíva" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. VI — Udalosti po dni, ku ktorému sa zostavuje účtovná závierka" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. VII — Ostatné informácie" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zamestnanci_predch +msgid "… (predchádzajúce obdobie)" +msgstr "" + +#: model:ir.model.fields,field_description:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_statement_nature +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__statement_nature +msgid "Druh závierky" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__statement_nature +msgid "" +"Druh účtovnej závierky (§ 17/18 zákona o účtovníctve): riadna (k poslednému " +"dňu účtovného obdobia), mimoriadna (iný deň — napr. vstup do likvidácie), " +"priebežná (v priebehu obdobia)." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__state__legacy +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__legacy +msgid "Historical filing" +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_size_class__mala +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__size_class__mala +msgid "Malá účtovná jednotka" +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_statement_nature__mimoriadna +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__statement_nature__mimoriadna +msgid "Mimoriadna" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_uzpod_form +msgid "Opravná závierka" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_uzpod_form +msgid "Označiť ako podané" +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_statement_nature__priebezna +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__statement_nature__priebezna +msgid "Priebežná" +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_statement_nature__riadna +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__statement_nature__riadna +msgid "Riadna" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.module.module,summary:base.module_l10n_sk_fs +msgid "" +"Slovak balance sheet (Súvaha) on the shared l10n_cssk_fs_base framework — " +"account-code line mapping + comparison period + XML export." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.module.module,shortdesc:base.module_l10n_sk_fs +msgid "Slovakia — Financial Statements (Súvaha / VZS)" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_uzpod_form +msgid "Späť na návrh" +msgstr "" + +#: model:ir.model.fields,field_description:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_size_class +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__size_class +msgid "Veľkostná trieda" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__size_class +msgid "" +"Veľkostná trieda účtovnej jednotky podľa § 2 ods. 5 až 8 zákona č. 431/2002 " +"Z. z. o účtovníctve (kritériá: celková suma majetku, čistý obrat a priemerný" +" prepočítaný počet zamestnancov za dve po sebe idúce účtovné obdobia). " +"Mandatory in the UZPODv14 header (mikro jednotky file UZMIKv14, not " +"UZPODv14)." +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_size_class__velka +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__size_class__velka +msgid "Veľká účtovná jednotka" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_uzpod_form +msgid "Vygenerovať XML (UZPODv14)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.actions.act_window,name:l10n_sk_fs.action_l10n_sk_uzpod +msgid "Účtovná závierka (UZPODv14)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.ui.menu,name:l10n_sk_fs.menu_l10n_sk_uzpod +msgid "Účtovná závierka UZPODv14 (SK)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__filed_history_ids +msgid "Filed history" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__filed_history_ids +msgid "" +"Every XML that was ever marked submitted for this filing. 'Filed " +"XML' is the latest one; after a reset → recompute → resubmit cycle " +"the previously-filed copies stay reachable here." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__legacy +msgid "" +"This filing was not produced here — it records what was actually " +"submitted for a period that predates this system, loaded from a " +"migration. It is a historical fact, so it can never be exported, " +"submitted or recomputed, and it is invisible to anything that looks " +"for 'the return of the previous period'. It CAN be amended: a " +"dodatočné filed later against a historical period is an ordinary " +"submittable return.\n\nSetting this puts the record in state " +"'Historical filing', which is terminal — the two are written " +"together and cannot come apart." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__legacy_source +msgid "Source system" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__legacy_source +msgid "" +"Where the filing came from, e.g. 'PREMIER', 'Money S4', 'i6', 'MRP' " +"— or how it was captured if by hand." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__legacy_reference +msgid "Source reference" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__legacy_reference +msgid "" +"Identifier of the import batch or source document this filing was " +"materialised from, so a figure can be traced back to what produced " +"it." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_statement_nature +msgid "Druh účtovnej závierky (§ 17/18 zákona o účtovníctve)." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_is_uzpod +msgid "Úč POD statement" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_poznamky_attached +msgid "" +"Set when the Notes (Úč PODV 3-01) are filed with this statement; drives the " +"header prilozeneSucasti/poznamky flag." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_size_class +msgid "" +"Veľkostná trieda účtovnej jednotky podľa § 2 ods. 5 až 8 zákona č. 431/2002 " +"Z. z. o účtovníctve. Mandatory in the UZPODv14 header (mikro jednotky file " +"UZMIKv14, not UZPODv14)." +msgstr "" diff --git a/l10n_sk_fs/i18n/sk.po b/l10n_sk_fs/i18n/sk.po new file mode 100644 index 00000000..920602ae --- /dev/null +++ b/l10n_sk_fs/i18n/sk.po @@ -0,0 +1,890 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_fs +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-06-27 08:43+0000\n" +"PO-Revision-Date: 2026-06-26 22:20+0000\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "&nbsp; DIČ:" +msgstr "&nbsp; DIČ:" + +#. module: l10n_sk_fs +#. odoo-python +#: code:addons/l10n_sk_fs/models/l10n_sk_poznamky.py:0 +msgid "(corrective)" +msgstr "(opravná)" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "(opravná)" +msgstr "(opravná)" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid ", DNM" +msgstr ", DNM" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "" +"Dátum schválenia predchádzajúcej účtovnej\n" +" závierky:" +msgstr "" +"Dátum schválenia predchádzajúcej účtovnej\n" +" závierky:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Hranica drobného majetku: DDHM" +msgstr "Hranica drobného majetku: DDHM" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "IČO:" +msgstr "IČO:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Je súčasťou konsolidovaného celku:" +msgstr "Je súčasťou konsolidovaného celku:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Obchodné meno:" +msgstr "Obchodné meno:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Odpisový plán:" +msgstr "Odpisový plán:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Opis hospodárskej činnosti:
" +msgstr "Opis hospodárskej činnosti:
" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Predpoklad nepretržitého pokračovania v činnosti:" +msgstr "Predpoklad nepretržitého pokračovania v činnosti:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Právny dôvod zostavenia:" +msgstr "Právny dôvod zostavenia:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Spôsob oceňovania:
" +msgstr "Spôsob oceňovania:
" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Sídlo:" +msgstr "Sídlo:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Zmeny účtovných zásad a metód:
" +msgstr "Zmeny účtovných zásad a metód:
" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Záväzky so splatnosťou nad 5 rokov:" +msgstr "Záväzky so splatnosťou nad 5 rokov:" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Záväzky zabezpečené záložným právom:" +msgstr "Záväzky zabezpečené záložným právom:" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_needaction +msgid "Action Needed" +msgstr "Vyžaduje akciu" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_attachment_count +msgid "Attachment Count" +msgstr "Počet príloh" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Bežné obdobie" +msgstr "Bežné obdobie" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__company_id +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__company_id +msgid "Company" +msgstr "Spoločnosť" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__submission_type__opravna +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__submission_type__opravna +msgid "Corrective" +msgstr "Opravná" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__create_uid +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__create_uid +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__create_uid +msgid "Created by" +msgstr "Vytvoril" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__create_date +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__create_date +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__create_date +msgid "Created on" +msgstr "Vytvorené dňa" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__currency_id +msgid "Currency" +msgstr "Mena" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__date_from +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__date_from +msgid "Date From" +msgstr "Dátum od" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__date_to +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__date_to +msgid "Date To" +msgstr "Dátum do" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__odpis_line_ids +msgid "Depreciation schedule" +msgstr "Odpisový plán" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__display_name +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__display_name +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__display_name +msgid "Display Name" +msgstr "Zobrazovaný názov" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Doba (roky)" +msgstr "Doba (roky)" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__doba_rokov +msgid "Doba odpisovania (roky)" +msgstr "Doba odpisovania (roky)" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__state__draft +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__state__draft +msgid "Draft" +msgstr "Koncept" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__schvalenie_date +msgid "Dátum schválenia predchádzajúcej závierky" +msgstr "Dátum schválenia predchádzajúcej závierky" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__state__exported +msgid "Exported" +msgstr "Exportované" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__pravny_dovod__mimoriadna +msgid "Extraordinary" +msgstr "Mimoriadna" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__submitted_attachment_id +msgid "Filed XML" +msgstr "Podané XML" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_follower_ids +msgid "Followers" +msgstr "Sledovatelia" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_partner_ids +msgid "Followers (Partners)" +msgstr "Sledovatelia (partneri)" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__state__generated +msgid "Generated" +msgstr "Vygenerované" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__has_message +msgid "Has Message" +msgstr "Má správu" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__id +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__id +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__id +msgid "ID" +msgstr "ID" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Ak je zaškrtnuté, nové správy vyžadujú vašu pozornosť." + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_has_error +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "Ak je zaškrtnuté, niektoré správy majú chybu doručenia." + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl4 +msgid "Informácie k položkám výkazu ziskov a strát" +msgstr "Informácie k položkám výkazu ziskov a strát" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl5 +msgid "Iné aktíva a iné pasíva" +msgstr "Iné aktíva a iné pasíva" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_is_follower +msgid "Is Follower" +msgstr "Je sledovateľ" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__je_konsolidovana +msgid "Je súčasťou konsolidovaného celku" +msgstr "Je súčasťou konsolidovaného celku" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__write_uid +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__write_uid +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__write_uid +msgid "Last Updated by" +msgstr "Naposledy upravil" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__write_date +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__write_date +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__write_date +msgid "Last Updated on" +msgstr "Naposledy upravené dňa" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__pravny_dovod +msgid "Legal basis for preparation" +msgstr "Právny dôvod zostavenia" + +#. module: l10n_sk_fs +#. odoo-python +#: code:addons/l10n_sk_fs/models/l10n_sk_poznamky.py:0 +msgid "Majetok" +msgstr "Majetok" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_has_error +msgid "Message Delivery error" +msgstr "Chyba doručenia správy" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_ids +msgid "Messages" +msgstr "Správy" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__name +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__name +msgid "Name" +msgstr "Názov" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Načítať odpisový plán z majetku" +msgstr "Načítať odpisový plán z majetku" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__going_concern +msgid "Nepretržité pokračovanie v činnosti" +msgstr "Nepretržité pokračovanie v činnosti" + +#. module: l10n_sk_fs +#: model:ir.model,name:l10n_sk_fs.model_l10n_sk_poznamky_odpis_line +msgid "Notes — depreciation-schedule line" +msgstr "Poznámky — riadok odpisového plánu" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_needaction_counter +msgid "Number of Actions" +msgstr "Počet akcií" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_has_error_counter +msgid "Number of errors" +msgstr "Počet chýb" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Počet správ vyžadujúcich akciu" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Počet správ s chybou doručenia" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cinnost +msgid "Opis hospodárskej činnosti" +msgstr "Opis hospodárskej činnosti" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__amendment_ids +msgid "Opravné podania" +msgstr "Opravné podania" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__original_return_id +msgid "Opravuje" +msgstr "Opravuje" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl2_organy +msgid "Orgány — príjmy a výhody" +msgstr "Orgány — príjmy a výhody" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl7 +msgid "Ostatné informácie" +msgstr "Ostatné informácie" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__pdf_attachment_id +msgid "Pdf Attachment" +msgstr "PDF príloha" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__poznamky_id +msgid "Poznamky" +msgstr "Poznamky" + +#. module: l10n_sk_fs +#: model:ir.actions.act_window,name:l10n_sk_fs.action_l10n_sk_poznamky +msgid "Poznámky k závierke" +msgstr "Poznámky k závierke" + +#. module: l10n_sk_fs +#: model:ir.ui.menu,name:l10n_sk_fs.menu_l10n_sk_poznamky +msgid "Poznámky k závierke (SK)" +msgstr "Poznámky k závierke (SK)" + +#. module: l10n_sk_fs +#: model:ir.actions.report,name:l10n_sk_fs.action_report_l10n_sk_poznamky +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Poznámky k účtovnej závierke" +msgstr "Poznámky k účtovnej závierke" + +#: model:ir.model.fields,field_description:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_poznamky_attached +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__poznamky_attached +msgid "Poznámky priložené" +msgstr "Poznámky priložené" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__ddhm_prah +msgid "Prah DDHM" +msgstr "Prah DDHM" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__dnm_prah +msgid "Prah DNM" +msgstr "Prah DNM" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Predch. obdobie" +msgstr "Predch. obdobie" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Priemerný prepočítaný počet zamestnancov" +msgstr "Priemerný prepočítaný počet zamestnancov" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zamestnanci_bezne +msgid "Priemerný prepočítaný počet zamestnancov (bežné)" +msgstr "Priemerný prepočítaný počet zamestnancov (bežné)" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__pravny_dovod__riadna +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_poznamky__submission_type__radna +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__submission_type__radna +msgid "Regular" +msgstr "Riadna" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__submission_type +msgid "" +"Regular = original filing; Corrective = a corrective financial statement of " +"an already-submitted one for the same period (RÚZ document type 'opravná'; " +"the body XML stays druh=riadna)." +msgstr "Riadne = pôvodné podanie; Opravné = opravná účtovná závierka už podanej závierky za rovnaké obdobie (typ dokumentu RÚZ 'opravná'; telo XML zostáva druh=riadna)." + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Ročná sadzba" +msgstr "Ročná sadzba" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__sadzba +msgid "Ročná sadzba (%)" +msgstr "Ročná sadzba (%)" + +#. module: l10n_sk_fs +#: model:ir.model,name:l10n_sk_fs.model_l10n_sk_uzpod +msgid "SK Annual Statements (UZPODv14)" +msgstr "Slovenská účtovná závierka (UZPODv14)" + +#. module: l10n_sk_fs +#: model:ir.model,name:l10n_sk_fs.model_l10n_sk_poznamky +msgid "SK Notes to the financial statements (Úč PODV 3-01)" +msgstr "Slovenské poznámky k účtovnej závierke (Úč PODV 3-01)" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__message_has_sms_error +msgid "SMS Delivery error" +msgstr "Chyba doručenia SMS" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__sequence +msgid "Sequence" +msgstr "Poradie" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__poznamky_attached +msgid "" +"Set when the Notes (Úč PODV 3-01) are filed with this statement; drives the " +"header prilozeneSucasti/poznamky flag (see l10n.sk.poznamky)." +msgstr "Nastavené, keď sa Poznámky (Úč PODV 3-01) podávajú s touto závierkou; riadi príznak prilozeneSucasti/poznamky v hlavičke (pozri l10n.sk.poznamky)." + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky_odpis_line__nazov +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Skupina / účet majetku" +msgstr "Skupina / účet majetku" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl3_ocenovanie +msgid "Spôsob oceňovania majetku a záväzkov" +msgstr "Spôsob oceňovania majetku a záväzkov" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__state +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__state +msgid "State" +msgstr "Stav" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__submission_reference +msgid "Submission Reference" +msgstr "Referencia podania" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__submission_type +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__submission_type +msgid "Submission Type" +msgstr "Typ podania" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__submission_reference +msgid "Submission identifier / confirmation number from the FS portal." +msgstr "Identifikátor podania / potvrdzovacie číslo z portálu FS." + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__state__submitted +msgid "Submitted" +msgstr "Podané" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__submitted_date +msgid "Submitted Date" +msgstr "Dátum podania" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__submitted_attachment_id +msgid "The exact XML that was submitted; preserved across reset-to-draft." +msgstr "Presné XML, ktoré bolo podané; zachované aj po vrátení do konceptu." + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__original_return_id +msgid "The originally-filed UZPODv14 this corrective statement corrects." +msgstr "Pôvodne podaná UZPODv14, ktorú táto opravná závierka opravuje." + +#. module: l10n_sk_fs +#. odoo-python +#: code:addons/l10n_sk_fs/models/l10n_sk_uzpod.py:0 +msgid "" +"UZPODv14 XML failed schema validation:\n" +"%s" +msgstr "Validácia schémy UZPODv14 XML zlyhala:\n%s" + +#. module: l10n_sk_fs +#. odoo-python +#: code:addons/l10n_sk_fs/models/l10n_sk_uzpod.py:0 +msgid "" +"UZPODv14 failed kontrolné pravidlá (XSD-valid but the výkaz would be " +"rejected):\n" +"%s" +msgstr "UZPODv14 nevyhovela kontrolným pravidlám (XSD-validná, ale výkaz by bol zamietnutý):\n%s" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl6 +msgid "Udalosti po dni, ku ktorému sa zostavuje závierka" +msgstr "Udalosti po dni, ku ktorému sa zostavuje závierka" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_IX_1 +msgid "VZS IX.1 — výnosy z CP a podielov od prepojených ÚJ (665)" +msgstr "VZS IX.1 — výnosy z CP a podielov od prepojených ÚJ (665)" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_IX_2 +msgid "VZS IX.2 — výnosy z CP a podielov v podielovej účasti (665)" +msgstr "VZS IX.2 — výnosy z CP a podielov v podielovej účasti (665)" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_N_1 +msgid "VZS N.1 — nákladové úroky pre prepojené ÚJ (562)" +msgstr "VZS N.1 — nákladové úroky pre prepojené ÚJ (562)" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_X_1 +msgid "VZS X.1 — výnosy z krátkodob. fin. majetku od prepojených ÚJ (666)" +msgstr "VZS X.1 — výnosy z krátkodob. fin. majetku od prepojených ÚJ (666)" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_X_2 +msgid "VZS X.2 — výnosy z krátkodob. fin. majetku v podielovej účasti (666)" +msgstr "VZS X.2 — výnosy z krátkodob. fin. majetku v podielovej účasti (666)" + +#. module: l10n_sk_fs +#: model:account.account.tag,name:l10n_sk_fs.account_tag_pl_XI_1 +msgid "VZS XI.1 — výnosové úroky od prepojených ÚJ (662)" +msgstr "VZS XI.1 — výnosové úroky od prepojených ÚJ (662)" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Vygenerovať PDF" +msgstr "Vygenerovať PDF" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__website_message_ids +msgid "Website Messages" +msgstr "Správy z webovej stránky" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__website_message_ids +msgid "Website communication history" +msgstr "História komunikácie cez web" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__xml_attachment_id +msgid "Xml Attachment" +msgstr "XML príloha" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zamestnanci_veduci +msgid "Z toho vedúci zamestnanci" +msgstr "Z toho vedúci zamestnanci" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__cl3_zmeny_metod +msgid "Zmeny účtovných zásad a metód" +msgstr "Zmeny účtovných zásad a metód" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zavazky_nad_5_rokov +msgid "Záväzky so splatnosťou nad 5 rokov" +msgstr "Záväzky so splatnosťou nad 5 rokov" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zavazky_zabezpecene +msgid "Záväzky zabezpečené záložným právom" +msgstr "Záväzky zabezpečené záložným právom" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "z toho vedúci zamestnanci" +msgstr "z toho vedúci zamestnanci" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "" +"zostavené podľa Opatrenia MF/23378/2014-74 (malé účtovné jednotky)\n" +" za obdobie" +msgstr "" +"zostavené podľa Opatrenia MF/23378/2014-74 (malé účtovné jednotky)\n" +" za obdobie" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Čl. I — Všeobecné" +msgstr "Čl. I — Všeobecné" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. I — Všeobecné informácie" +msgstr "Čl. I — Všeobecné informácie" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. II — Orgány účtovnej jednotky" +msgstr "Čl. II — Orgány účtovnej jednotky" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Čl. II, IV–VII — Texty" +msgstr "Čl. II, IV–VII — Texty" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. III — Použité účtovné zásady a metódy" +msgstr "Čl. III — Použité účtovné zásady a metódy" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_poznamky_form +msgid "Čl. III — Zásady a odpisy" +msgstr "Čl. III — Zásady a odpisy" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. IV — Informácie k položkám výkazu ziskov a strát" +msgstr "Čl. IV — Informácie k položkám výkazu ziskov a strát" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. V — Iné aktíva a iné pasíva" +msgstr "Čl. V — Iné aktíva a iné pasíva" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. VI — Udalosti po dni, ku ktorému sa zostavuje účtovná závierka" +msgstr "Čl. VI — Udalosti po dni, ku ktorému sa zostavuje účtovná závierka" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.report_l10n_sk_poznamky +msgid "Čl. VII — Ostatné informácie" +msgstr "Čl. VII — Ostatné informácie" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_poznamky__zamestnanci_predch +msgid "… (predchádzajúce obdobie)" +msgstr "… (predchádzajúce obdobie)" + +#: model:ir.model.fields,field_description:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_statement_nature +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__statement_nature +msgid "Druh závierky" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__statement_nature +msgid "" +"Druh účtovnej závierky (§ 17/18 zákona o účtovníctve): riadna (k poslednému " +"dňu účtovného obdobia), mimoriadna (iný deň — napr. vstup do likvidácie), " +"priebežná (v priebehu obdobia)." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__state__legacy +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__legacy +msgid "Historical filing" +msgstr "Historické podanie" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_size_class__mala +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__size_class__mala +msgid "Malá účtovná jednotka" +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_statement_nature__mimoriadna +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__statement_nature__mimoriadna +msgid "Mimoriadna" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_uzpod_form +msgid "Opravná závierka" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_uzpod_form +msgid "Označiť ako podané" +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_statement_nature__priebezna +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__statement_nature__priebezna +msgid "Priebežná" +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_statement_nature__riadna +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__statement_nature__riadna +msgid "Riadna" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.module.module,summary:base.module_l10n_sk_fs +msgid "" +"Slovak balance sheet (Súvaha) on the shared l10n_cssk_fs_base framework — " +"account-code line mapping + comparison period + XML export." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.module.module,shortdesc:base.module_l10n_sk_fs +msgid "Slovakia — Financial Statements (Súvaha / VZS)" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_uzpod_form +msgid "Späť na návrh" +msgstr "" + +#: model:ir.model.fields,field_description:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_size_class +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__size_class +msgid "Veľkostná trieda" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__size_class +msgid "" +"Veľkostná trieda účtovnej jednotky podľa § 2 ods. 5 až 8 zákona č. 431/2002 " +"Z. z. o účtovníctve (kritériá: celková suma majetku, čistý obrat a priemerný" +" prepočítaný počet zamestnancov za dve po sebe idúce účtovné obdobia). " +"Mandatory in the UZPODv14 header (mikro jednotky file UZMIKv14, not " +"UZPODv14)." +msgstr "" + +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__cssk_fs_statement__l10n_sk_size_class__velka +#. module: l10n_sk_fs +#: model:ir.model.fields.selection,name:l10n_sk_fs.selection__l10n_sk_uzpod__size_class__velka +msgid "Veľká účtovná jednotka" +msgstr "" + +#. module: l10n_sk_fs +#: model_terms:ir.ui.view,arch_db:l10n_sk_fs.view_l10n_sk_uzpod_form +msgid "Vygenerovať XML (UZPODv14)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.actions.act_window,name:l10n_sk_fs.action_l10n_sk_uzpod +msgid "Účtovná závierka (UZPODv14)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.ui.menu,name:l10n_sk_fs.menu_l10n_sk_uzpod +msgid "Účtovná závierka UZPODv14 (SK)" +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__filed_history_ids +msgid "Filed history" +msgstr "História podaní" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__filed_history_ids +msgid "" +"Every XML that was ever marked submitted for this filing. 'Filed " +"XML' is the latest one; after a reset → recompute → resubmit cycle " +"the previously-filed copies stay reachable here." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__legacy +msgid "" +"This filing was not produced here — it records what was actually " +"submitted for a period that predates this system, loaded from a " +"migration. It is a historical fact, so it can never be exported, " +"submitted or recomputed, and it is invisible to anything that looks " +"for 'the return of the previous period'. It CAN be amended: a " +"dodatočné filed later against a historical period is an ordinary " +"submittable return.\n\nSetting this puts the record in state " +"'Historical filing', which is terminal — the two are written " +"together and cannot come apart." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__legacy_source +msgid "Source system" +msgstr "Zdrojový systém" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__legacy_source +msgid "" +"Where the filing came from, e.g. 'PREMIER', 'Money S4', 'i6', 'MRP' " +"— or how it was captured if by hand." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_l10n_sk_uzpod__legacy_reference +msgid "Source reference" +msgstr "Zdrojová referencia" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_l10n_sk_uzpod__legacy_reference +msgid "" +"Identifier of the import batch or source document this filing was " +"materialised from, so a figure can be traced back to what produced " +"it." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_statement_nature +msgid "Druh účtovnej závierky (§ 17/18 zákona o účtovníctve)." +msgstr "" + +#. module: l10n_sk_fs +#: model:ir.model.fields,field_description:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_is_uzpod +msgid "Úč POD statement" +msgstr "Výkaz Úč POD" + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_poznamky_attached +msgid "" +"Set when the Notes (Úč PODV 3-01) are filed with this statement; drives the " +"header prilozeneSucasti/poznamky flag." +msgstr "" +"Označte, ak sa s touto závierkou podávajú Poznámky (Úč PODV 3-01); určuje " +"príznak prilozeneSucasti/poznamky v hlavičke." + +#. module: l10n_sk_fs +#: model:ir.model.fields,help:l10n_sk_fs.field_cssk_fs_statement__l10n_sk_size_class +msgid "" +"Veľkostná trieda účtovnej jednotky podľa § 2 ods. 5 až 8 zákona č. 431/2002 " +"Z. z. o účtovníctve. Mandatory in the UZPODv14 header (mikro jednotky file " +"UZMIKv14, not UZPODv14)." +msgstr "" +"Veľkostná trieda účtovnej jednotky podľa § 2 ods. 5 až 8 zákona č. 431/2002 " +"Z. z. o účtovníctve. V hlavičke UZPODv14 je povinná (mikro účtovné jednotky " +"podávajú UZMIKv14, nie UZPODv14)." diff --git a/l10n_sk_fs/migrations/19.0.1.0.5/post-migrate.py b/l10n_sk_fs/migrations/19.0.1.0.5/post-migrate.py new file mode 100644 index 00000000..998f7838 --- /dev/null +++ b/l10n_sk_fs/migrations/19.0.1.0.5/post-migrate.py @@ -0,0 +1,40 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Declare, on existing databases, that these forms have no published XSD. + +``_validate_against_schema`` was tightened to refuse an export it could not +validate — right, because a KV DPH once exported unvalidated and looked as +though it had passed. It broke every financial-statement export where no +schema exists to load, which is a published fact about the form and not a +missing file: Finančná správa publishes uzsuv/uzvzs for the Súvaha and the +VZS and nothing for the two prehľady, which are filed inside the Úč POD +envelope. Only the prehľady are touched here. + +The version record now carries ``xml_schema_optional`` to say so, but those +records live in a ``noupdate="1"`` data file, so a module upgrade will NOT +rewrite them — an existing database would take the fix and stay broken, with +the failure appearing only when somebody tries to file. Hence this. + +Deliberately does not touch a version that HAS a schema, and does not create +the flag anywhere it was not already true. +""" + +XMLIDS = ['cashflow_version_2025', 'equity_changes_version_2025'] + + +def migrate(cr, version): + if not version: + return + cr.execute( + """ + UPDATE cssk_fs_statement_version v + SET xml_schema_optional = TRUE + FROM ir_model_data d + WHERE d.model = 'cssk.fs.statement.version' + AND d.module = %s + AND d.name = ANY(%s) + AND d.res_id = v.id + AND COALESCE(v.xml_schema_optional, FALSE) = FALSE + """, + ('l10n_sk_fs', list(XMLIDS)), + ) diff --git a/l10n_sk_fs/migrations/19.0.1.1.0/post-migrate.py b/l10n_sk_fs/migrations/19.0.1.1.0/post-migrate.py new file mode 100644 index 00000000..fb2d467c --- /dev/null +++ b/l10n_sk_fs/migrations/19.0.1.1.0/post-migrate.py @@ -0,0 +1,89 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Retire the two stand-in závierka versions in favour of the real UZPODv14. + +Until 19.0.1.1.0 this module filed the Súvaha and the VZS as two documents +with roots ``UZSUV`` / ``UZVZS`` against two XSDs we wrote ourselves — the +template said as much in its own header ("WORKING STAND-IN … replace with the +official FS SR UZPODv14 structure before live filing"). Finančná správa +publishes neither root nor schema; the real form is ONE ``dokument`` whose +``telo`` carries ``ucPod1Suvaha`` beside ``ucPod2VykazZS``, and +``uzpod-2014.xsd`` was already sitting in this module unreferenced. + +Runs POST, not pre: it needs the surviving ``uzpod_v14_template`` to exist, +and Odoo drops a module's obsolete xmlids in ``_process_end`` — after every +module's post-migration — so the foreign key is clear by the time the old +views are deleted. + +Two things have to happen here rather than in the data files: + +* The stand-in QWeb templates are gone from the module, but + ``cssk_fs_statement_version.xml_template_ref_id`` is a RESTRICT foreign key + on ``ir_ui_view``, so an upgrade that tries to drop them aborts the registry + load outright — "Failed to load registry", nowhere near the cause. +* The version records live in a ``noupdate="1"`` file, so nothing would + rewrite them on their own. + +The stand-ins are kept (an existing statement points at one, and its filed +attachment is what a retention obligation actually asks for) but retired: +deactivated, closed off with ``valid_to``, repointed at the surviving +template, and stripped of their invented schema. That last part is what stops +a stale draft from quietly re-exporting the old made-up structure — export +refuses without a schema, which is the correct answer for a form that never +had one. Recompute such a statement on the UZPODv14 version instead. +""" + +OLD = ["suvaha_version_2025", "vzs_version_2025"] + + +def migrate(cr, version): + if not version: + return + cr.execute( + """ + SELECT d.name, d.res_id FROM ir_model_data d + WHERE d.model = 'cssk.fs.statement.version' + AND d.module = 'l10n_sk_fs' AND d.name = ANY(%s) + """, + (OLD,), + ) + old_ids = [row[1] for row in cr.fetchall()] + if not old_ids: + return + + cr.execute( + """ + SELECT res_id FROM ir_model_data + WHERE model = 'ir.ui.view' AND module = 'l10n_sk_fs' + AND name = 'uzpod_v14_template' + """ + ) + row = cr.fetchone() + if not row: + # Nothing to repoint onto; leave the records alone rather than + # breaking a required foreign key. + return + + cr.execute( + """ + UPDATE cssk_fs_statement_version + SET active = FALSE, + valid_to = COALESCE(valid_to, DATE '2024-12-31'), + xml_template_ref_id = %s, + xml_schema_filename = NULL, + xml_schema_optional = FALSE + WHERE id = ANY(%s) + """, + (row[0], old_ids), + ) + # ``xml_schema_data`` is attachment=True, so it has no column: the + # invented XSD lives in ir_attachment and has to be dropped there. + cr.execute( + """ + DELETE FROM ir_attachment + WHERE res_model = 'cssk.fs.statement.version' + AND res_field = 'xml_schema_data' + AND res_id = ANY(%s) + """, + (old_ids,), + ) diff --git a/l10n_sk_fs/migrations/19.0.1.1.0/pre-migrate.py b/l10n_sk_fs/migrations/19.0.1.1.0/pre-migrate.py new file mode 100644 index 00000000..a9bb950a --- /dev/null +++ b/l10n_sk_fs/migrations/19.0.1.1.0/pre-migrate.py @@ -0,0 +1,59 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Let the corrected UZPODv14 row definitions reach existing databases. + +The version record lives in a ``noupdate="1"`` data file — it has to, because +its 206 line definitions are a one2many payload and ``_load_data`` APPENDS +those rather than replacing them, so an ordinary reload would double every +row. The consequence is that an upgrade alone leaves an existing database on +the OLD mapping: the one where r043/r044/r045 each claimed the whole of +311-315 and A.VIII was a plug that hid it. Nothing would look wrong — the +sheet still footed and still balanced. + +So: drop the existing line definitions and clear the record's noupdate flag +before the data file loads. The payload then appends onto nothing, which is +the same result a fresh install gets. Runs PRE, because the data file loads +after pre-migration and before post. + +Statements already computed keep their stored lines; they are a record of +what was computed at the time. Recompute one to move it onto the corrected +mapping — and expect A.VIII to change, because that is the row that was +absorbing the error. +""" + + +def migrate(cr, version): + if not version: + return + cr.execute( + """ + SELECT res_id FROM ir_model_data + WHERE model = 'cssk.fs.statement.version' + AND module = 'l10n_sk_fs' AND name = 'uzpod_v14' + """ + ) + row = cr.fetchone() + if not row: + return # never installed; the fresh load is right + version_id = row[0] + + cr.execute( + """ + DELETE FROM ir_model_data + WHERE model = 'cssk.fs.statement.line.def' + AND res_id IN (SELECT id FROM cssk_fs_statement_line_def + WHERE version_id = %s) + """, + (version_id,), + ) + cr.execute( + "DELETE FROM cssk_fs_statement_line_def WHERE version_id = %s", + (version_id,), + ) + cr.execute( + """ + UPDATE ir_model_data SET noupdate = FALSE + WHERE model = 'cssk.fs.statement.version' + AND module = 'l10n_sk_fs' AND name = 'uzpod_v14' + """ + ) diff --git a/l10n_sk_fs/migrations/19.0.1.2.0/post-migrate.py b/l10n_sk_fs/migrations/19.0.1.2.0/post-migrate.py new file mode 100644 index 00000000..3555ed8c --- /dev/null +++ b/l10n_sk_fs/migrations/19.0.1.2.0/post-migrate.py @@ -0,0 +1,111 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Carry the corrected UZPODv14 row captions to an existing database. + +The captions shipped in 19.0.1.1.0 were transcribed from a plain pdftotext +dump of the tlačivo, where a two-line caption bleeds into the row below it: +"Poskytnuté" sat on r009 and r010 read "7. preddavky na dlhodobý nehmotný +majetok". About a third of the form was shifted like that. They are now read +off the form's own column layout — ``tools/parse_uzpod14_labels.py`` produces +``data/uzpod14_row_labels.tsv``, and the generator writes them into the +version record. + +The FIGURES do not change; only the names. But the names are what an +accountant reads when checking a statement, and a caption naming the row +above's accounts is worse than no caption at all. + +Why the names are written here directly, rather than by reloading the data +file the way 19.0.1.1.0 did: that record lives in a ``noupdate="1"`` file, and +clearing the flag only gets an UPDATE. Its 206 line definitions are a +one2many payload, and on an update the ``(0, 0, {...})`` commands do not +re-create rows that a pre-migration has just deleted — which leaves the +version with no lines at all. It worked in 19.0.1.1.0 only because the record +did not exist yet and was CREATED. Writing the captions is also the narrower +change: nothing else about the rows moves. + +A statement already computed keeps its own stored lines, captions and all; +recompute it to pick up the corrected ones. + +The column may still be ``jsonb`` when this runs. ``name`` was translatable +in 19.0.1.0.0, and Odoo 19 does not convert such a column when the module +that dropped ``translate`` is upgraded: it snapshots ``ir_model_fields`` +at the start of the load and keeps every field translated there patched as +translated for the whole load — converting a column a pre-migration has +flattened straight back to ``jsonb``. The end-of-load step meant to flatten +them (``odoo/modules/loading.py``) re-runs setup for no model, so the patch +survives it and the column stays ``jsonb`` after a successful upgrade too. +Either way, a migration on a database coming from 19.0.1.0.0 sees ``jsonb``. Comparing that +with text failed the whole upgrade with ``operator does not exist: jsonb = +text``. +""" + +import logging +import os + +_logger = logging.getLogger(__name__) + +LABELS = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + "data", "uzpod14_row_labels.tsv", +) + + +def migrate(cr, version): + if not version: + return + if not os.path.exists(LABELS): + _logger.warning("l10n_sk_fs: %s is missing; captions left as they are", + LABELS) + return + + labels = {} + with open(LABELS, encoding="utf-8") as handle: + for line in handle: + code, _sep, label = line.rstrip("\n").partition("\t") + if code and label: + labels[code] = label + if not labels: + return + + cr.execute( + """ + SELECT res_id FROM ir_model_data + WHERE model = 'cssk.fs.statement.version' + AND module = 'l10n_sk_fs' AND name = 'uzpod_v14' + """ + ) + row = cr.fetchone() + if not row: + return + + cr.execute( + """ + SELECT data_type FROM information_schema.columns + WHERE table_schema = current_schema() + AND table_name = 'cssk_fs_statement_line_def' AND column_name = 'name' + """ + ) + translated = (cr.fetchone() or [None])[0] == "jsonb" + if translated: + current = "d.name->>'en_US'" + # Keep any other language; Odoo keeps only en_US when it flattens. + caption = ( + "CASE WHEN jsonb_typeof(d.name) = 'object' THEN d.name " + "ELSE '{}'::jsonb END || jsonb_build_object('en_US', c.label)" + ) + else: + current = "d.name" + caption = "c.label" + + cr.execute( + f""" + UPDATE cssk_fs_statement_line_def d + SET name = {caption} + FROM (SELECT unnest(%s::text[]) AS code, + unnest(%s::text[]) AS label) c + WHERE d.version_id = %s AND d.code = c.code + AND {current} IS DISTINCT FROM c.label + """, + (list(labels), [labels[c] for c in labels], row[0]), + ) + _logger.info("l10n_sk_fs: %d UZPODv14 row caption(s) corrected", cr.rowcount) diff --git a/l10n_sk_fs/migrations/19.0.1.3.0/post-migration.py b/l10n_sk_fs/migrations/19.0.1.3.0/post-migration.py new file mode 100644 index 00000000..47663669 --- /dev/null +++ b/l10n_sk_fs/migrations/19.0.1.3.0/post-migration.py @@ -0,0 +1,33 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Move historical filings onto the state that describes them. + +Until now the importer set ``legacy`` and never touched ``state``, so every +materialised filing kept the default and read "Draft" — a list of completed, +years-old submissions all claiming to be unfinished work. ``legacy`` and +``state == 'legacy'`` are now one fact, joined in create/write. + +Raw SQL, and that is not laziness: the ORM path is closed by design. A write +carrying ``state`` on a record that is already ``legacy`` is refused by +``_cssk_check_not_legacy`` — which is exactly the guarantee the new state +exists to give, so the migration must go under it rather than around it. +""" + +import logging + +_logger = logging.getLogger(__name__) + + +def migrate(cr, version): + if not version: + return + cr.execute(""" + UPDATE l10n_sk_uzpod + SET state = 'legacy' + WHERE legacy IS TRUE + AND state IS DISTINCT FROM 'legacy' + """) + if cr.rowcount: + _logger.info( + "l10n_sk_fs 19.0.1.3.0: %s historical UZPODv14 statements moved onto the " + "'legacy' state", cr.rowcount) diff --git a/l10n_sk_fs/migrations/19.0.1.5.0/post-migrate.py b/l10n_sk_fs/migrations/19.0.1.5.0/post-migrate.py new file mode 100644 index 00000000..3fa6a60b --- /dev/null +++ b/l10n_sk_fs/migrations/19.0.1.5.0/post-migrate.py @@ -0,0 +1,71 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Carry the corrected UZPODv14 row formulas to an existing database. + +19.0.1.5.0 fixes the Súvaha mapping against the l10n_sk chart: 16 codes the +chart does not have, 34 balance-sheet accounts that reached no row, two +korekcia accounts carried with the wrong sign, and s113 (vydané dlhopisy) +turned from a manual row into an accounts row as the filed export always had +it. The version record lives in a ``noupdate="1"`` file, so an upgrade does +not touch it; the formulas are written here, read from that same data file so +there is one source for them (see 19.0.1.2.0 for why the file is not simply +reloaded). + +The filed export reads ``models/uzpod14_rows.py`` directly and needs nothing. +A statement already computed keeps its stored lines; recompute it. +""" + +import ast +import logging +import os + +from lxml import etree + +_logger = logging.getLogger(__name__) + +DATA = os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + "data", "cssk_uzpod_v14_version_data.xml", +) +FIELDS = ("kind", "account_formula", "account_formula_correction") + + +def _shipped_rows(): + root = etree.parse(DATA) + node = root.find(".//record[@id='uzpod_v14']/field[@name='line_def_ids']") + if node is None or not node.get("eval"): + return {} + return {vals["code"]: vals for _c, _i, vals in ast.literal_eval(node.get("eval"))} + + +def migrate(cr, version): + if not version: + return + cr.execute(""" + SELECT res_id FROM ir_model_data + WHERE module = 'l10n_sk_fs' AND name = 'uzpod_v14' + AND model = 'cssk.fs.statement.version' + """) + found = cr.fetchone() + if not found: + return + rows = _shipped_rows() + if not rows: + _logger.warning("l10n_sk_fs 19.0.1.5.0: no UZPODv14 rows found in %s; " + "row definitions left as they are", DATA) + return + changed = 0 + for code, vals in rows.items(): + cr.execute(""" + UPDATE cssk_fs_statement_line_def + SET kind = %s, account_formula = %s, + account_formula_correction = %s + WHERE version_id = %s AND code = %s + AND (kind IS DISTINCT FROM %s + OR account_formula IS DISTINCT FROM %s + OR account_formula_correction IS DISTINCT FROM %s) + """, (*(vals.get(f) or None for f in FIELDS), found[0], code, + *(vals.get(f) or None for f in FIELDS))) + changed += cr.rowcount + _logger.info("l10n_sk_fs 19.0.1.5.0: %s UZPODv14 row definition(s) corrected", + changed) diff --git a/l10n_sk_fs/migrations/19.0.1.6.0/post-migration.py b/l10n_sk_fs/migrations/19.0.1.6.0/post-migration.py new file mode 100644 index 00000000..4f20814a --- /dev/null +++ b/l10n_sk_fs/migrations/19.0.1.6.0/post-migration.py @@ -0,0 +1,45 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Read v01 (čistý obrat) of the UZPODv14 version off the ledger. + +It was transcribed as a ``manual`` row, which the generator skipped, so the +screen showed 0 while the filed XML read r01 off 601/602/604/606/607. (s113 +had the same defect; 19.0.1.5.0 fixed it.) The data file is ``noupdate``, so +an existing database keeps the manual row unless it is written here. + +Only a row still ``manual`` is touched: one somebody has since given a +formula of their own is left alone. A statement already computed keeps its +stored lines until it is recomputed; a figure typed into the row stays, as an +override. +""" + +import logging + +_logger = logging.getLogger(__name__) + +ROWS = { + "v01": "-601,-602,-604,-606,-607", +} + + +def migrate(cr, version): + if not version: + return + cr.execute(""" + SELECT res_id FROM ir_model_data + WHERE module = 'l10n_sk_fs' AND name = 'uzpod_v14' + AND model = 'cssk.fs.statement.version' + """) + row = cr.fetchone() + if not row: + return + for code, formula in ROWS.items(): + cr.execute(""" + UPDATE cssk_fs_statement_line_def + SET kind = 'accounts', account_formula = %s + WHERE version_id = %s AND code = %s AND kind = 'manual' + """, [formula, row[0], code]) + if cr.rowcount: + _logger.info( + "l10n_sk_fs 19.0.1.6.0: UZPODv14 row %s now reads %s", + code, formula) diff --git a/l10n_sk_fs/models/__init__.py b/l10n_sk_fs/models/__init__.py new file mode 100644 index 00000000..180e54bb --- /dev/null +++ b/l10n_sk_fs/models/__init__.py @@ -0,0 +1,3 @@ +from . import l10n_sk_uzpod +from . import l10n_sk_poznamky +from . import cssk_fs_statement diff --git a/l10n_sk_fs/models/cssk_fs_statement.py b/l10n_sk_fs/models/cssk_fs_statement.py new file mode 100644 index 00000000..d1e18b11 --- /dev/null +++ b/l10n_sk_fs/models/cssk_fs_statement.py @@ -0,0 +1,231 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""SK-specific hooks on the shared financial-statement framework. + +Both hooks hang off the VERSION rather than the statement, because the +reverse-drill footprint reads them too and a hook the reverse direction +cannot reach is how the two came to disagree about which row an account +feeds. +""" + +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models +from odoo.tools import float_round + +from .uzpod14_rows import CLAIMED_ANALYTICS + + +class CSSKFsVersion(models.Model): + _inherit = "cssk.fs.statement.version" + + def _cssk_tag_codes(self): + """Úč POD 2 splits IX/X/XI/N by account TAG, not by code. + + Ten VZS rows separate financial income and cost with a prepojená + účtovná jednotka from the rest, and no account code distinguishes + them. The filed export (``l10n.sk.uzpod``) already resolves this from + the same six tags, and the on-screen statement has to agree with what + gets filed — so it reads the same map rather than keeping a second one. + + Only the UZPODv14 version uses the grammar; for every other version + the map is harmless, because a formula with no ``&`` or ``!`` never + consults it. + + On the VERSION, not the statement. The reverse drill reads it from an + EMPTY ``cssk.fs.statement`` recordset, where ``self.version_id`` is + empty and its country code is False — so a statement-level override + would fall through to ``super()`` and silently answer None, which is + the failure it exists to prevent. + """ + self.ensure_one() + if self.country_id.code != "SK": + return super()._cssk_tag_codes() + return self.env["l10n.sk.uzpod"]._tag_account_codes() + + def _cssk_claimed_codes(self): + """Which analytics a main account may NOT absorb, from one source. + + UZPODv14's rows name specific chart codes, so ``022000`` has to take + 022001 and 022002 with it or those balances reach no row at all — but + it must not take 311110, which r043 routes to a line of its own. The + set of accounts "another row names" is the whole content of that rule, + and the filed export computes it in + ``uzpod14_rows._claimed_analytics``. Read it from there rather than + re-deriving from the version's formulas: three codes (255100, 473100, + 479100) are named only by TOTAL rows, which this version renders as + aggregates, so a re-derivation cannot see them and their balances + would land on a different row on screen than in the filed XML. + + Overridden on the VERSION and not on the statement, because the + reverse-drill footprint reads it from here too. On the statement it + would answer the forward direction only, and the two would disagree + about which row an account feeds — which is the drift this whole + arrangement exists to prevent. + + Every other version — the CZ statements, the SK cash flow and changes + in equity — is written in group prefixes where '022' already covers + its analytics, and keeps the framework's own answer. + """ + self.ensure_one() + uzpod = self.env.ref("l10n_sk_fs.uzpod_v14", raise_if_not_found=False) + if uzpod and self == uzpod: + return CLAIMED_ANALYTICS + return super()._cssk_claimed_codes() + + +class CSSKFsStatement(models.Model): + """The Úč POD statement is the ONE source of the filed UZPODv14 XML. + + There used to be two generators: this statement's QWeb export and the + ``l10n.sk.uzpod`` builder, each with its own header and its own reading + of the rows. They agreed on the netto figures and disagreed on everything + else — the export hardcoded SK NACE 00.00.0, malá / riadna and no + poznámky, filed brutto and korekcia as 0 on the totals, and ran none of + the kontrolné pravidlá — while the builder ignored the accountant's + overrides and the manual rows. The statement is what the accountant + reviews, so it is what gets filed; ``l10n.sk.uzpod`` now renders through + it, and the header and the content checks live here.""" + + _inherit = "cssk.fs.statement" + + l10n_sk_is_uzpod = fields.Boolean( + string="Úč POD statement", compute="_compute_l10n_sk_is_uzpod") + l10n_sk_size_class = fields.Selection( + [("mala", "Malá účtovná jednotka"), ("velka", "Veľká účtovná jednotka")], + string="Veľkostná trieda", default="mala", copy=True, + help="Veľkostná trieda účtovnej jednotky podľa § 2 ods. 5 až 8 zákona " + "č. 431/2002 Z. z. o účtovníctve. Mandatory in the UZPODv14 header " + "(mikro jednotky file UZMIKv14, not UZPODv14).") + l10n_sk_statement_nature = fields.Selection( + [("riadna", "Riadna"), ("mimoriadna", "Mimoriadna"), + ("priebezna", "Priebežná")], + string="Druh závierky", default="riadna", copy=True, + help="Druh účtovnej závierky (§ 17/18 zákona o účtovníctve).") + l10n_sk_poznamky_attached = fields.Boolean( + "Poznámky priložené", copy=True, + help="Set when the Notes (Úč PODV 3-01) are filed with this statement; " + "drives the header prilozeneSucasti/poznamky flag.") + + @api.depends("version_id") + def _compute_l10n_sk_is_uzpod(self): + version = self.env.ref("l10n_sk_fs.uzpod_v14", raise_if_not_found=False) + for st in self: + st.l10n_sk_is_uzpod = bool(version) and st.version_id == version + + # ------------------------------------------------------------------ + # header + # ------------------------------------------------------------------ + def _l10n_sk_uzpod_header(self, size_class=None, statement_nature=None, + poznamky_attached=None): + """The UZPODv14 ``hlavicka`` values. The keyword arguments let + ``l10n.sk.uzpod`` file its own header choices over the same rows.""" + self.ensure_one() + company = self.company_id + partner = company.partner_id + dic = ((company.l10n_sk_dic if "l10n_sk_dic" in company._fields + else "") or company.vat or "").strip() + if dic[:2].upper() == "SK": + dic = dic[2:] + # SK NACE: division, group+class, subclass (k1 k2 k3). + nace = ((partner.nace_code if "nace_code" in partner._fields else "") + or "").replace(".", "").ljust(5, "0") + size = size_class or self.l10n_sk_size_class or "mala" + nature = statement_nature or self.l10n_sk_statement_nature or "riadna" + poznamky = (self.l10n_sk_poznamky_attached if poznamky_attached is None + else poznamky_attached) + prior_from = self.date_from - relativedelta(years=1) + prior_to = self.date_to - relativedelta(years=1) + name = company.name or "" + return { + "den": str(self.date_to.day), + "mesiac": str(self.date_to.month), + "rok": str(self.date_to.year), + "dic": dic, + "ico": company.company_registry or "", + "k1": nace[:2], "k2": nace[2:4], "k3": nace[4:5], + "druh": {tag: "1" if tag == nature else "0" + for tag in ("riadna", "mimoriadna", "priebezna")}, + "velkost": {tag: "1" if tag == size else "0" + for tag in ("mala", "velka")}, + "obdobie": [(d.month, d.year) for d in (self.date_from, self.date_to)], + "predobdobie": [(d.month, d.year) for d in (prior_from, prior_to)], + "poznamky": "1" if poznamky else "0", + # obchMeno / oznObchodReg require exactly 2 lines + "meno": [name[:36], name[36:72]], + "ulica": partner.street or "", + "psc": (partner.zip or "").replace(" ", ""), + "obec": partner.city or "", + "register": [getattr(company, "trade_registry", "") or "", ""], + "telefon": partner.phone or "", + "email": partner.email or "", + # The day the statement is compiled, written as the other + # Finančná správa forms of this repository write their dates. + "zostavena": fields.Date.context_today(self).strftime("%d.%m.%Y"), + } + + def _l10n_sk_uzpod_cells(self): + """``{code: {g, k, n, p}}`` — brutto, korekcia, netto and prior netto + of every row, in WHOLE EUROS, as the form is completed. + + Rounding each cell on its own does not survive the portal's + kontrolné pravidlá: a total is checked against the sum of the rounded + rows beneath it, and over a few dozen rows independent rounding drifts + past a euro. So only the leaves are rounded — brutto and korekcia, with + netto = brutto − korekcia — and every total is its own formula over + its children's ROUNDED figures. The screen keeps the cents.""" + self.ensure_one() + lines = {line.code: line for line in self.line_ids} + cells = {} + + def whole(value): + return int(float_round(value or 0.0, precision_digits=0)) + + for ldef in self._cssk_eval_order(): + line = lines.get(ldef.code) + if not line: + continue + if ldef.kind == "aggregate": + cells[ldef.code] = { + col: int(round(self._eval_aggregate( + ldef.aggregate_formula, + {c: v[col] for c, v in cells.items()}) or 0)) + for col in ("g", "k", "n", "p") + } + else: + g, k = whole(line.gross_value), whole(line.correction_value) + cells[ldef.code] = {"g": g, "k": k, "n": g - k, + "p": whole(line.prior_value)} + return cells + + def _cssk_render_context(self): + ctx = super()._cssk_render_context() + if self.l10n_sk_is_uzpod: + header = self.env.context.get("l10n_sk_uzpod_header") or {} + ctx["uzpod"] = self._l10n_sk_uzpod_header(**header) + ctx["cells"] = self._l10n_sk_uzpod_cells() + return ctx + + # ------------------------------------------------------------------ + # kontrolné pravidlá + # ------------------------------------------------------------------ + def _cssk_check_kontroly(self): + self.ensure_one() + if self.l10n_sk_is_uzpod: + self._cssk_enforce_kontroly() + return super()._cssk_check_kontroly() + + def check_kontroly(self, xml_bytes=None): + """The UZPODv14 rules (bilančná rovnosť, netto = brutto − korekcia, + medzisúčty, unmapped accounts) on the document this statement files.""" + self.ensure_one() + if not self.l10n_sk_is_uzpod: + parent = getattr(super(), "check_kontroly", None) + return parent(xml_bytes) if parent else [] + if xml_bytes is None: + xml_bytes = self._render_xml() + return self.env["l10n.sk.uzpod"].new({ + "company_id": self.company_id.id, + "date_from": self.date_from, + "date_to": self.date_to, + })._cssk_uzpod_violations(xml_bytes) diff --git a/l10n_sk_fs/models/l10n_sk_poznamky.py b/l10n_sk_fs/models/l10n_sk_poznamky.py new file mode 100644 index 00000000..3fd8704a --- /dev/null +++ b/l10n_sk_fs/models/l10n_sk_poznamky.py @@ -0,0 +1,162 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""SK Poznámky k účtovnej závierke — Úč PODV 3-01 (malé účtovné jednotky). + +The third statutory part of the SK účtovná závierka (Súvaha + VZS + Poznámky), +per Opatrenie MF/23378/2014-74. Unlike Súvaha/VZS it is ~90 % narrative/policy +text + a few small tables, so this is a structured-content + template model, not +a GL calculation. v1 renders a fileable **PDF** (QWeb); the narrative fields +default to the standard malá-UJ boilerplate so the accountant edits only the +exceptions. A structured POZv14 XML export is a follow-up. +""" +import base64 + +from odoo import api, fields, models, _ + +# Standard malá-UJ "nemá náplň" boilerplate so a clean filing needs no editing. +_DEF_ORGANY = ("Členom štatutárneho orgánu, dozorného orgánu ani iného orgánu " + "účtovnej jednotky neboli v účtovnom období poskytnuté žiadne " + "príjmy, preddavky, pôžičky ani iné plnenia.") +_DEF_OCENOVANIE = ( + "Majetok a záväzky sa oceňujú v súlade so zákonom č. 431/2002 Z. z. " + "o účtovníctve: dlhodobý hmotný a nehmotný majetok obstaraný kúpou " + "obstarávacou cenou, vytvorený vlastnou činnosťou vlastnými nákladmi; " + "zásoby obstarávacou cenou / vlastnými nákladmi; pohľadávky menovitou " + "hodnotou; peňažné prostriedky a ceniny menovitou hodnotou; záväzky " + "menovitou hodnotou. Opravné položky ani rezervy účtovná jednotka " + "netvorila, ak nie je nižšie uvedené inak.") +_DEF_ZMENY = ("V účtovnom období nedošlo k zmenám účtovných zásad a účtovných " + "metód oproti predchádzajúcemu účtovnému obdobiu.") +_DEF_CL4 = ("Účtovná jednotka neeviduje goodwill, deriváty, vlastné akcie ani " + "výnimočné náklady a výnosy. Záväzky so zostatkovou dobou splatnosti " + "nad päť rokov a záväzky zabezpečené záložným právom sú uvedené nižšie " + "(ak existujú).") +_DEF_CL5 = ("Účtovná jednotka neeviduje podmienený majetok ani podmienené záväzky, " + "ostatné finančné povinnosti ani významné položky podsúvahových účtov.") +_DEF_CL6 = ("Po dni, ku ktorému sa zostavuje účtovná závierka, do dňa jej " + "zostavenia nenastali udalosti osobitného významu, ktoré by si " + "vyžadovali úpravu alebo zverejnenie v poznámkach.") +_DEF_CL7 = ("Účtovná jednotka nemá výlučné práva ani povolenia, nepatrí do " + "osobitnej kategórie priemyselnej výroby a nemá osobitné finančné " + "vzťahy s orgánmi verejnej moci nad rámec bežných daňových povinností.") + + +class L10nSkPoznamkyOdpisLine(models.Model): + _name = "l10n.sk.poznamky.odpis.line" + _description = "Notes — depreciation-schedule line" + _order = "sequence, id" + + poznamky_id = fields.Many2one("l10n.sk.poznamky", required=True, ondelete="cascade") + sequence = fields.Integer(default=10) + nazov = fields.Char("Skupina / účet majetku", required=True) + doba_rokov = fields.Float("Doba odpisovania (roky)") + sadzba = fields.Float("Ročná sadzba (%)") + + +class L10nSkPoznamky(models.Model): + _name = "l10n.sk.poznamky" + _description = "SK Notes to the financial statements (Úč PODV 3-01)" + _order = "date_to desc, id desc" + + name = fields.Char(compute="_compute_name") + company_id = fields.Many2one( + "res.company", required=True, default=lambda s: s.env.company) + currency_id = fields.Many2one(related="company_id.currency_id") + date_from = fields.Date(required=True) + date_to = fields.Date(required=True) + submission_type = fields.Selection( + [("radna", "Regular"), ("opravna", "Corrective")], + default="radna", required=True, copy=False) + state = fields.Selection( + [("draft", "Draft"), ("generated", "Generated")], + default="draft", readonly=True, copy=False) + pdf_attachment_id = fields.Many2one("ir.attachment", readonly=True, copy=False) + + # --- Čl. I — Všeobecné informácie --- + cinnost = fields.Text("Opis hospodárskej činnosti") + schvalenie_date = fields.Date("Dátum schválenia predchádzajúcej závierky") + pravny_dovod = fields.Selection( + [("riadna", "Regular"), ("mimoriadna", "Extraordinary")], + string="Legal basis for preparation", default="riadna") + je_konsolidovana = fields.Boolean( + "Je súčasťou konsolidovaného celku", default=False) + zamestnanci_bezne = fields.Integer("Priemerný prepočítaný počet zamestnancov (bežné)") + zamestnanci_predch = fields.Integer("… (predchádzajúce obdobie)") + zamestnanci_veduci = fields.Integer("Z toho vedúci zamestnanci") + + # --- Čl. II — Orgány --- + cl2_organy = fields.Text("Orgány — príjmy a výhody", default=_DEF_ORGANY) + + # --- Čl. III — Účtovné zásady a metódy --- + going_concern = fields.Boolean("Nepretržité pokračovanie v činnosti", default=True) + cl3_zmeny_metod = fields.Text("Zmeny účtovných zásad a metód", default=_DEF_ZMENY) + cl3_ocenovanie = fields.Text("Spôsob oceňovania majetku a záväzkov", default=_DEF_OCENOVANIE) + odpis_line_ids = fields.One2many( + "l10n.sk.poznamky.odpis.line", "poznamky_id", string="Depreciation schedule") + ddhm_prah = fields.Monetary("Prah DDHM", default=1700.0) + dnm_prah = fields.Monetary("Prah DNM", default=2400.0) + + # --- Čl. IV — Položky VZS --- + cl4 = fields.Text("Informácie k položkám výkazu ziskov a strát", default=_DEF_CL4) + zavazky_nad_5_rokov = fields.Monetary("Záväzky so splatnosťou nad 5 rokov") + zavazky_zabezpecene = fields.Monetary("Záväzky zabezpečené záložným právom") + + # --- Čl. V / VI / VII --- + cl5 = fields.Text("Iné aktíva a iné pasíva", default=_DEF_CL5) + cl6 = fields.Text("Udalosti po dni, ku ktorému sa zostavuje závierka", default=_DEF_CL6) + cl7 = fields.Text("Ostatné informácie", default=_DEF_CL7) + + @api.depends("date_to", "submission_type") + def _compute_name(self): + for rec in self: + suffix = _("(corrective)") if rec.submission_type == "opravna" else "" + rec.name = "Poznámky %s%s" % ( + rec.date_to or "", (" " + suffix) if suffix else "") + + def action_populate_odpis(self): + """Fill the odpisový plán from the company's posted assets (group by the + depreciation duration). Best-effort: durations come from each asset's + ``method_number`` (years); rate = 100 / years.""" + self.ensure_one() + Asset = self.env.get("account.asset") + self.odpis_line_ids.unlink() + if Asset is None: + return + assets = Asset.search([("company_id", "=", self.company_id.id)]) + period_months = {"month": 1, "quarter": 3, "year": 12} + groups = {} + for a in assets: + # method_number = number of depreciations; method_period = its length + months = (a.method_number or 0) * period_months.get(a.method_period, 1) + years = round(months / 12.0, 2) if months else 0.0 + cls = a.tax_class_id.name if "tax_class_id" in a._fields and a.tax_class_id else _("Majetok") + groups.setdefault((cls, years), True) + lines = [] + for (cls, years), _v in sorted(groups.items()): + lines.append((0, 0, { + "nazov": cls, + "doba_rokov": years, + "sadzba": round(100.0 / years, 2) if years else 0.0, + })) + self.odpis_line_ids = lines + + def action_generate_pdf(self): + """Render the Poznámky as a PDF and store it on the record.""" + self.ensure_one() + report = self.env.ref("l10n_sk_fs.action_report_l10n_sk_poznamky") + pdf, _ext = report._render_qweb_pdf("l10n_sk_fs.report_l10n_sk_poznamky", self.ids) + att = self.env["ir.attachment"].create({ + "name": "%s.pdf" % (self.name or "Poznamky"), + "type": "binary", + "datas": base64.b64encode(pdf), + "res_model": self._name, + "res_id": self.id, + "mimetype": "application/pdf", + }) + self.pdf_attachment_id = att.id + self.state = "generated" + return { + "type": "ir.actions.act_url", + "url": "/web/content/%s?download=true" % att.id, + "target": "self", + } diff --git a/l10n_sk_fs/models/l10n_sk_uzpod.py b/l10n_sk_fs/models/l10n_sk_uzpod.py new file mode 100644 index 00000000..61f707f3 --- /dev/null +++ b/l10n_sk_fs/models/l10n_sk_uzpod.py @@ -0,0 +1,472 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""SK UZPODv14 — official účtovná závierka podnikateľov (PÚ) XML export. + +The filing record of the official ``dokument`` (Súvaha Úč POD 1 + Výkaz ziskov +a strát Úč POD 2): its header choices, its state, its corrective filings. The +document itself is rendered by the Úč POD ``cssk.fs.statement`` of the same +period (``_build_xml``), so what is filed is what the accountant reviewed — +overrides and manual rows included — and there is one generator, not two. It +is validated against the official ``uzpod-2014.xsd`` and the kontrolné pravidlá +below. The row tables in ``uzpod14_rows.py`` remain the mapping the statement's +version is generated from, and still drive the coverage diagnostic and +``vzs_before_tax``. +""" +import base64 +import logging +import os + +from lxml import etree + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +from .uzpod14_rows import CLAIMED_ANALYTICS, SUVAHA_AKTIVA, SUVAHA_PASIVA, TWO_SIDED, VZS +from .uzpod14_kontroly import SUCET_BS, SUCET_PL + +_logger = logging.getLogger(__name__) + +_XSD_PATH = os.path.join( + os.path.dirname(__file__), "..", "data", "uzpod-2014.xsd") + +# Účet 431 (VH v schvaľovaní) sa zámerne nemapuje — v OTVORENEJ knihe stojí proti +# neuzavretému začiatočnému stavu tried 5/6 a obe sa z výkazu vypúšťajú spoločne. +# +# POZOR, tento predpoklad neplatí vždy. Platí len vtedy, keď sa triedy 5/6 +# na 431 ešte neuzavreli. Ak sa uzávierka KAŽDÝ rok vykonala (a jej zápisy sú +# vylúčené cez l10n_cssk_closing_journal_ids), potom na 431 nestojí protistrana +# v triedach 5/6 — je to skutočný zostatok vlastného imania, nahromadený +# výsledok minulých rokov, ktorý valné zhromaždenie nikdy neschválilo a +# nerozdelilo na 428/429. Overené na reálnej migrácii: 3 630 289,70 EUR +# nahromadených za roky 2014–2022, ktoré do súvahy nevstúpili. +# +# Bežný výsledok sa NEZDVOJÍ ani v tomto prípade: r100 sa počíta z POHYBU tried +# 5/6 a uzávierkový zápis bežného roka je vylúčený, takže r100 a zostatok 431 sú +# disjunktné. Napriek tomu sa 431 nemapuje automaticky — nenulový zostatok na +# prechodnom účte je ZISTENIE (neschválená závierka), nie chyba mapovania, a +# jeho preklasifikovanie softvérom by problém zakrylo. Vypisuje ho kontrola +# BS_UNMAPPED; zaradenie do riadku patrí účtovníkovi a poučeniu k Úč POD 1. + + +class L10nSkUzpod(models.Model): + _name = "l10n.sk.uzpod" + _description = "SK Annual Statements (UZPODv14)" + _inherit = ["mail.thread", "cssk.statutory.submission.mixin"] + _order = "date_to desc, id desc" + + name = fields.Char(compute="_compute_name", store=True) + company_id = fields.Many2one( + "res.company", required=True, default=lambda s: s.env.company) + date_from = fields.Date(required=True) + date_to = fields.Date(required=True) + state = fields.Selection( + [("draft", "Draft"), ("exported", "Exported"), + ("submitted", "Submitted"), + # Terminal and off the workflow — see cssk.statutory.submission.mixin. + ("legacy", "Historical filing")], + default="draft", tracking=True) + xml_attachment_id = fields.Many2one("ir.attachment", readonly=True) + + # Opravná účtovná závierka. The UZPODv14 body schema (uzpod-2014.xsd) has NO + # corrective element — its typUzavierky is a fixed sequence of + # riadna/mimoriadna/priebezna + mala/velka. A correction is still filed with + # druh = riadna; the "opravná" nature is a RÚZ submission document-type, so it + # rides the record + filing metadata (filename / log / link to the corrected + # statement) and the e-filing envelope, NOT the XSD-validated body (adding a + # non-schema element would fail validation). + submission_type = fields.Selection( + [("radna", "Regular"), ("opravna", "Corrective")], + default="radna", required=True, copy=False, tracking=True, + help="Regular = original filing; Corrective = a corrective financial " + "statement of an already-submitted one for the same period (RÚZ " + "document type 'opravná'; the body XML stays druh=riadna).") + original_return_id = fields.Many2one( + "l10n.sk.uzpod", string="Opravuje", readonly=True, copy=False, + help="The originally-filed UZPODv14 this corrective statement corrects.") + amendment_ids = fields.One2many( + "l10n.sk.uzpod", "original_return_id", string="Opravné podania") + size_class = fields.Selection( + [("mala", "Malá účtovná jednotka"), ("velka", "Veľká účtovná jednotka")], + string="Veľkostná trieda", required=True, default="mala", + help="Veľkostná trieda účtovnej jednotky podľa § 2 ods. 5 až 8 zákona " + "č. 431/2002 Z. z. o účtovníctve (kritériá: celková suma majetku, " + "čistý obrat a priemerný prepočítaný počet zamestnancov za dve po " + "sebe idúce účtovné obdobia). Mandatory in the UZPODv14 header " + "(mikro jednotky file UZMIKv14, not UZPODv14).") + statement_nature = fields.Selection( + [("riadna", "Riadna"), ("mimoriadna", "Mimoriadna"), + ("priebezna", "Priebežná")], + string="Druh závierky", required=True, default="riadna", + help="Druh účtovnej závierky (§ 17/18 zákona o účtovníctve): riadna " + "(k poslednému dňu účtovného obdobia), mimoriadna (iný deň — " + "napr. vstup do likvidácie), priebežná (v priebehu obdobia).") + poznamky_attached = fields.Boolean( + "Poznámky priložené", default=False, + help="Set when the Notes (Úč PODV 3-01) are filed with this statement; " + "drives the header prilozeneSucasti/poznamky flag (see l10n.sk.poznamky).") + + @api.depends("company_id", "date_to", "submission_type") + def _compute_name(self): + for rec in self: + rec.name = "UZPODv14 %s — %s%s" % ( + rec.company_id.country_id.code or "SK", rec.date_to or "", + " (opravná)" if rec.submission_type == "opravna" else "") + + # ------------------------------------------------------------------ + # account summation + # ------------------------------------------------------------------ + def _balances(self, date_from, date_to, period): + """Return {account_code: balance}. ``period`` False = cumulative to + date_to (Súvaha); True = movement in [date_from, date_to] (VZS). + + This used to run its own ``_read_group`` with a hand-copied domain + identical to the shared one. It now goes through + ``_cssk_account_balance_map`` in ``l10n_cssk_core`` — the same query + every other statutory statement reads. That is not only de-duplication: + the company's year-end closing/reopening journals are excluded there, + and a filing that kept its own copy of the query would have quietly + gone on including the close/reopen entries while the on-screen preview + excluded them.""" + self.ensure_one() + return self._cssk_account_balance_map( + date_from if period else None, date_to) + + # VZS financial-income/cost related-party split (Úč POD 2 lines IX/X/XI/N). + # A company that has financial flows with prepojené/podielové entities tags + # the relevant accounts; otherwise everything stays "ostatné" (no setup). + _PL_SPLIT_TAGS = ("IX_1", "IX_2", "X_1", "X_2", "XI_1", "N_1") + + def _tag_account_codes(self): + """{tag_key: set(account_codes)} for the related-party split tags.""" + out = {} + Account = self.env["account.account"] + for key in self._PL_SPLIT_TAGS: + tag = self.env.ref("l10n_sk_fs.account_tag_pl_%s" % key, + raise_if_not_found=False) + out[key] = (set(Account.search([("tag_ids", "in", tag.id)]).mapped("code")) + if tag else set()) + return out + + def _eval(self, acct, conds, balances, tag_codes=None): + """Plain signed account-prefix sum + conditional domain terms. + + A conditional term ``[low, high, mode, sign]`` adds ``sign × net`` where + ``net`` is the sum of accounts with ``low <= code < high``, but only when + the net is positive (``pos``) or negative (``neg``) — the sum_if_pos / + sum_if_neg split that routes tax accounts to the receivable or payable + row by balance sign. + + A token may carry **account-tag filters** for the VZS financial + related-party split: ``665&IX_1`` keeps only accounts tagged ``IX_1``, + ``665!IX_1!IX_2`` excludes them (so the *ostatné* leaf = everything not + tagged prepojené/podielová, hence the full amount when nothing is + tagged). ``tag_codes`` maps a tag key to the set of account codes that + carry it. + + Delegates to the shared matcher in ``l10n_cssk_core`` — the same code + path the FS preview and the income-tax return use — so the figures an + accountant checks on screen and the figures written into the filed XML + can no longer drift apart. ``CLAIMED_ANALYTICS`` is passed as + ``claimed``, which is what turns the synthetic-absorb rule on here and + leaves it off for callers whose row definitions do not expect it. + + ``TWO_SIDED`` gates the accounts the form names on both sides (316, + 336, 373, 398, 481) on their own balance sign, as the on-screen + statement always did. The export used not to, so a deferred tax ASSET + on 481000 was filed on r052 and, negated, on r117 as well. + """ + return self._cssk_eval_formula( + acct, balances, conds=conds, default_sign=1.0, + claimed=CLAIMED_ANALYTICS, tag_codes=tag_codes or {}, + two_sided=TWO_SIDED) + + def _cssk_uzpod_cells(self): + """Every ``(formula, conds)`` cell of Súvaha and VZS, for the coverage + diagnostic. AKTÍVA rows are 4-tuples (gross, gross_conds, adjustment, + adjustment_conds); PASÍVA and VZS rows are 2-tuples.""" + cells = [] + for row in SUVAHA_AKTIVA: + cells.append((row[0], row[1])) + cells.append((row[2], row[3])) + for row in SUVAHA_PASIVA: + cells.append((row[0], row[1])) + for row in VZS: + cells.append((row[0], row[1])) + return cells + + def _cssk_unmapped_report(self, balances): + """``[(code, balance)]`` for accounts this statement maps to no row. + + Account 431 is expected here and is not a gap: in an open ledger it + stands against the not-yet-closed opening balance of classes 5/6, and + both are left out of the statement together — r100 takes the year's + result from the MOVEMENT of 5/6 instead, so that it equals VZS r61 even + when last year was never closed. Mapping 431 onto a PASÍVA row on top + of that books the result twice. + + Účtová trieda 7 sa vynecháva. Trieda 7 sú závierkové a podsúvahové účty + (701/702/710/711 a podsúvahové 75x–79x s vyrovnávacím účtom 799) — na + súvahu ani do VZS nepatria z definície, nie preto, že by nám chýbal + riadok. Overené: oficiálna štruktúra neodkazuje ani na + jediný účet triedy 7. Bez tohto filtra sa podsúvahové páry (napr. + 722/799, 751/772), ktoré sa navzájom rušia na nulu, hlásia ako chýbajúce + mapovanie a topia skutočné nálezy v šume. Prvá číslica = účtová trieda + je v CZ/SK osnove záväzná a modul sa na ňu spolieha aj inde (r100 = + pohyb tried 5/6). Účty typované ``off_balance`` sa vynechávajú rovnako + — l10n_sk tak typuje 701/702/710/711.""" + off_balance = { + code for code, account in self._cssk_account_by_code().items() + if account.account_type == "off_balance" + } + probe = { + code: balance for code, balance in balances.items() + if not (code or "").startswith("7") and code not in off_balance + } + return self._cssk_unmapped_codes( + probe, self._cssk_uzpod_cells(), + claimed=CLAIMED_ANALYTICS, tag_codes=self._tag_account_codes()) + + def _cssk_account_by_code(self): + """``{code: account}`` for this company — lets the diagnostic spot + account TYPES the Súvaha must ignore whatever the row mapping says.""" + accounts = self.env["account.account"].search( + [("company_ids", "in", self.company_id.ids)]) + return {a.code: a for a in accounts if a.code} + + # ------------------------------------------------------------------ + # document build — through the statement, the one generator + # ------------------------------------------------------------------ + def _l10n_sk_statement(self): + """The Úč POD statement this record files. + + The latest one for the same company, period and submission type + (riadna / opravná) that is not cancelled, a live statement before a + historical filing. When there is none, one is created and computed: + a filing record without its statement has nothing to file. A draft or + preview statement is recomputed first, so the file reads the ledger as + it stands — overrides survive a recompute; an exported or submitted + statement is filed exactly as it was exported.""" + self.ensure_one() + version = self.env.ref("l10n_sk_fs.uzpod_v14") + Statement = self.env["cssk.fs.statement"] + candidates = Statement.search([ + ("company_id", "=", self.company_id.id), + ("version_id", "=", version.id), + ("date_from", "=", self.date_from), + ("date_to", "=", self.date_to), + ("submission_type", "=", self.submission_type), + ("state", "!=", "cancelled"), + ], order="id desc") + statement = (candidates.filtered(lambda st: st.state != "legacy") + or candidates)[:1] + if not statement: + statement = Statement.create({ + "company_id": self.company_id.id, + "version_id": version.id, + "date_from": self.date_from, + "date_to": self.date_to, + "l10n_sk_size_class": self.size_class, + "l10n_sk_statement_nature": self.statement_nature, + "l10n_sk_poznamky_attached": self.poznamky_attached, + "submission_type": self.submission_type, + }) + if statement.state in ("draft", "preview"): + statement.action_compute_lines() + return statement + + def vzs_before_tax(self): + """VZS r56 — *Výsledok hospodárenia za účtovné obdobie pred zdanením*. + + Computed from the same P&L movement that fills the výkaz; equals DPPO + r100 (the income-tax return starts from this accounting result). Used by + the VZS→DPPO cross-form reconciliation. + """ + self.ensure_one() + pl = self._balances(self.date_from, self.date_to, True) + acct, cond = VZS[55] # 0-based index 55 == r56 == sk_pl_before_tax + return self._eval(acct, cond, pl) + + def _build_xml(self): + """The UZPODv14 document, rendered by the statement's template with + this record's header choices. There is no second generator.""" + self.ensure_one() + return self._l10n_sk_statement().with_context(l10n_sk_uzpod_header={ + "size_class": self.size_class, + "statement_nature": self.statement_nature, + "poznamky_attached": self.poznamky_attached, + })._render_xml() + + def _validate(self, xml_bytes): + schema = etree.XMLSchema(etree.parse(_XSD_PATH)) + root = etree.fromstring(xml_bytes) + try: + schema.assertValid(root) + except etree.DocumentInvalid as exc: + raise UserError(_("UZPODv14 XML failed schema validation:\n%s") % exc) + + # ------------------------------------------------------------------ + # kontrolné pravidlá (content checks the XSD cannot do) + # ------------------------------------------------------------------ + # The schema only checks structure (XSD-valid != portal-accepted). These + # rules enforce the statutory content invariants of the účtovná závierka + # (Opatrenie MF SR č. MF/23377/2014-74, prílohy — Súvaha Úč POD 1-01 a + # Výkaz ziskov a strát Úč POD 2-01): + # * AKTÍVA (r001 netto) = PASÍVA (r079 netto) — bilančná rovnosť + # * netto = brutto − korekcia — stĺpcová kontrola + # * každý medzisúčtový riadok = súčet zložkových riadkov (SUCET_BS/PL) + # A non-empty result means the XML would be XSD-valid but rejected/incorrect. + _KONTROLY_EPS = 1.0 # whole-euro rounding tolerance + # Below this an unmapped account is not worth naming: the výkaz is filed in + # whole euros, so a few cents stranded on a rounding account says nothing. + # Deliberately far below any balance that could move a statutory row — + # the point is to catch the 473,762 sitting on an unmapped 131, not to be + # quiet. A company whose 431 is properly approved out to 428/429 shows + # nothing here; one carrying eight years of it shows the whole balance. + _UNMAPPED_MATERIALITY = 100.0 + + def _kontroly_values(self, xml_bytes): + """{(section_key, r_int, s_tag): float} for both výkazy.""" + root = etree.fromstring(xml_bytes) + vals = {} + for tag, key in (("ucPod1Suvaha", "suvaha"), ("ucPod2VykazZS", "vzs")): + sec = root.find(".//%s" % tag) + if sec is None: + continue + for r in sec: + rnum = int(r.tag.lstrip("r")) + for s in r: + vals[(key, rnum, s.tag)] = float(s.text or 0.0) + return vals + + def check_kontroly(self, xml_bytes=None): + """Run the UZPODv14 kontrolné pravidlá against the generated document. + + Returns a list of ``{code, desc, detail}`` violations (empty = clean). + """ + self.ensure_one() + if xml_bytes is None: + xml_bytes = self._build_xml() + return self._cssk_uzpod_violations(xml_bytes) + + def _cssk_uzpod_violations(self, xml_bytes): + """The rules themselves, shared with the statement's export: they + need only the document and this record's company and period, so a + ``new()`` record serves the statement as well.""" + v = self._kontroly_values(xml_bytes) + eps = self._KONTROLY_EPS + out = [] + + def g(key, rnum, s): + return v.get((key, rnum, s), 0.0) + + # 1. bilančná rovnosť: AKTÍVA (r001 netto) = PASÍVA (r079 netto) + akt, pas = g("suvaha", 1, "s3"), g("suvaha", 79, "s5") + if abs(akt - pas) > eps: + out.append({"code": "BS_BALANCE", + "desc": "AKTÍVA (r001) = PASÍVA (r079)", + "detail": "%.0f != %.0f" % (akt, pas)}) + # 1b. obe strany nulové = bilancia síce "sedí" (0 = 0), ale účtovná + # jednotka v PÚ má vždy nejaké aktíva — nulové súčty signalizujú výpadok + # mapovania účtov (napr. analytické účty nezachytené v riadkoch súvahy). + if abs(akt) <= eps and abs(pas) <= eps: + out.append({"code": "BS_ZERO", + "desc": "AKTÍVA aj PASÍVA sú nulové — pravdepodobne " + "výpadok mapovania účtov na riadky súvahy", + "detail": "AKTÍVA=%.0f PASÍVA=%.0f" % (akt, pas)}) + # 2. stĺpcová kontrola: netto (s3) = brutto (s1) − korekcia (s2), AKTÍVA + for rn in range(1, 79): + b, k, n = g("suvaha", rn, "s1"), g("suvaha", rn, "s2"), g("suvaha", rn, "s3") + if abs((b - k) - n) > eps: + out.append({"code": "BS_NETTO", + "desc": "netto = brutto − korekcia (r%03d)" % rn, + "detail": "%.0f − %.0f != %.0f" % (b, k, n)}) + # 3. medzisúčty Súvaha: parent netto = Σ component netto + for parent, kids in SUCET_BS: + col = "s3" if parent <= 78 else "s5" + tot = sum(g("suvaha", c, col) for c in kids) + if abs(g("suvaha", parent, col) - tot) > eps: + out.append({"code": "BS_SUCET", + "desc": "r%03d = Σ r%s" % (parent, kids), + "detail": "%.0f != %.0f" % (g("suvaha", parent, col), tot)}) + # 4. medzisúčty VZS: parent (s1 bežné) = Σ component + for parent, kids in SUCET_PL: + tot = sum(g("vzs", c, "s1") for c in kids) + if abs(g("vzs", parent, "s1") - tot) > eps: + out.append({"code": "PL_SUCET", + "desc": "VZS r%02d = Σ r%s" % (parent, kids), + "detail": "%.0f != %.0f" % (g("vzs", parent, "s1"), tot)}) + # 5. Účty, ktoré nesadli do žiadneho riadku súvahy ani VZS. + # + # Diagnostika, nie blokácia (severity=warning): bilančnú nerovnosť už + # blokuje BS_BALANCE. Zmyslom je povedať PREČO — bez toho účtovník vidí + # len "AKTÍVA != PASÍVA o 3 630 289" a nemá kde začať. Presne takto sa + # sedem syntetík (078, 131, 261, 325, 395, 461, 474) stratilo zo súvahy + # bez jediného varovania, kým sa výkaz neporovnal s pôvodným systémom. + # + # Účet 431 sa tu objaví vždy, keď na ňom niečo zostáva — a to je + # správne. 431 (výsledok v schvaľovaní) je prechodný účet; nenulový + # zostatok k súvahovému dňu znamená, že valné zhromaždenie výsledok + # neschválilo a nerozdelilo na 428/429. To je zistenie pre účtovníka, + # nie chyba mapovania, ktorú by mal softvér potichu preklasifikovať. + bs_now = self._balances(self.date_from, self.date_to, False) + unmapped = [(code, bal) for code, bal in self._cssk_unmapped_report(bs_now) + if abs(bal) > self._UNMAPPED_MATERIALITY] + if unmapped: + out.append({ + "code": "BS_UNMAPPED", + "severity": "warning", + "desc": "Účty nezachytené v žiadnom riadku výkazu", + "detail": "; ".join( + "%s %.0f" % (code, bal) for code, bal in unmapped[:15]) + + ("; …(%d ďalších)" % (len(unmapped) - 15) + if len(unmapped) > 15 else ""), + }) + return out + + def action_export_xml(self): + self.ensure_one() + self._ensure_not_submitted() + xml_bytes = self._build_xml() + self._validate(xml_bytes) + violations = self.check_kontroly(xml_bytes) + # Severity-aware, matching the shared contract in + # cssk.statutory.submission.mixin._cssk_enforce_kontroly: only 'error' + # blocks. Every rule that predates this carries no severity key and so + # still defaults to 'error' — blocking behaviour for them is unchanged. + # Warnings are diagnostics (BS_UNMAPPED) that must reach the accountant + # without refusing a statement whose totals are sound. + errors = [v for v in violations if v.get("severity", "error") == "error"] + if errors: + raise UserError(_( + "UZPODv14 failed kontrolné pravidlá (XSD-valid but the výkaz " + "would be rejected):\n%s") % "\n".join( + " • %s: %s" % (vio["desc"], vio["detail"]) for vio in errors)) + for vio in violations: + if vio not in errors: + _logger.warning("UZPODv14 %s — %s: %s", + self.display_name, vio["desc"], vio["detail"]) + att = self.env["ir.attachment"].create({ + "name": "UZPODv14%s-%s.xml" % ( + "-opravna" if self.submission_type == "opravna" else "", + self.date_to or ""), + "res_model": self._name, "res_id": self.id, + "datas": base64.b64encode(xml_bytes), "mimetype": "application/xml"}) + self.xml_attachment_id = att.id + self.state = "exported" + + # ------------------------------------------------------------------ + # Amendment (opravná účtovná závierka) + # ------------------------------------------------------------------ + def action_create_amendment(self): + """The shared mixin copies the original into a fresh draft and links + original_return_id; here we flag the copy Opravná. The amended statement + re-exports from the corrected accounting (full restatement — UZPODv14 has + no difference rows). The corrective marker is submission-level metadata + (filename + link + log), not a body element (see submission_type).""" + action = super().action_create_amendment() + amendment = ( + self.browse(action.get("res_id")) + if isinstance(action, dict) else self.browse()) + if amendment and amendment.exists(): + amendment.submission_type = "opravna" + return action diff --git a/l10n_sk_fs/models/uzpod14_kontroly.py b/l10n_sk_fs/models/uzpod14_kontroly.py new file mode 100644 index 00000000..02b38b72 --- /dev/null +++ b/l10n_sk_fs/models/uzpod14_kontroly.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +"""UZPODv14 kontrolné pravidlá (business rules beyond the XSD). + +Source of truth: Opatrenie MF SR č. MF/23377/2014-74 (a nadv.), prílohy — +Súvaha Úč POD 1-01 a Výkaz ziskov a strát Úč POD 2-01, kde každý medzisúčtový +riadok je definovaný ako súčet uvedených riadkov; the per-row súčty below are +the official cross-line totals. + +SUCET_BS / SUCET_PL: ``(parent_row, [component_rows])`` — parent NETTO must +equal the sum of the component NETTO values (Súvaha AKTÍVA netto = s3, +PASÍVA netto = s5; VZS = s1). Plus two cross-statement / per-row invariants +checked in code: AKTÍVA (r001) = PASÍVA (r079); netto = brutto − korekcia.""" + +SUCET_BS = [ # Súvaha: (parent_r, [component_r]) — official medzisúčty + (1, [2, 33, 74]), + (2, [3, 11, 21]), + (3, [4, 5, 6, 7, 8, 9, 10]), + (11, [12, 13, 14, 15, 16, 17, 18, 19, 20]), + (21, [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]), + (33, [34, 41, 53, 66, 71]), + (34, [35, 36, 37, 38, 39, 40]), + (41, [42, 46, 47, 48, 49, 50, 51, 52]), + (42, [43, 44, 45]), + (53, [54, 58, 59, 60, 61, 62, 63, 64, 65]), + (54, [55, 56, 57]), + (66, [67, 68, 69, 70]), + (71, [72, 73]), + (74, [75, 76, 77, 78]), + (79, [80, 101, 141]), + (80, [81, 85, 86, 87, 90, 93, 97, 100]), + (81, [82, 83, 84]), + (87, [88, 89]), + (90, [91, 92]), + (93, [94, 95, 96]), + (97, [98, 99]), + (101, [102, 118, 121, 122, 136, 139, 140]), + (102, [103, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117]), + (103, [104, 105, 106]), + (118, [119, 120]), + (122, [123, 127, 128, 129, 130, 131, 132, 133, 134, 135]), + (123, [124, 125, 126]), + (136, [137, 138]), + (141, [142, 143, 144, 145]), +] + +SUCET_PL = [ # Výkaz ziskov a strát: (parent_r, [component_r]) + (2, [3, 4, 5, 6, 7, 8, 9]), + (10, [11, 12, 13, 14, 15, 20, 21, 24, 25, 26]), + (15, [16, 17, 18, 19]), + (21, [22, 23]), + (29, [30, 31, 35, 39, 42, 43, 44]), + (31, [32, 33, 34]), # IX = IX.1 prepojené + IX.2 podiel. + IX.3 ostatné + (35, [36, 37, 38]), # X = X.1 + X.2 + X.3 + (39, [40, 41]), # XI = XI.1 prepojené + XI.2 ostatné + (45, [46, 47, 48, 49, 52, 53, 54]), + (49, [50, 51]), # N = N.1 prepojené + N.2 ostatné + (57, [58, 59]), +] diff --git a/l10n_sk_fs/models/uzpod14_rows.py b/l10n_sk_fs/models/uzpod14_rows.py new file mode 100644 index 00000000..4bd4d91a --- /dev/null +++ b/l10n_sk_fs/models/uzpod14_rows.py @@ -0,0 +1,327 @@ +# -*- coding: utf-8 -*- +"""UZPODv14 row -> account mapping, flattened from the official SK uctovna +zavierka structure (mirrors the authoritative statutory definitions). +Each cell is ``(acct_formula, conds)``: acct_formula = signed comma-separated +account-code prefixes; conds = ``[low, high, mode, sign]`` conditional terms +(net of accounts in ``[low, high)`` contributed x sign only when net is +positive (pos) / negative (neg)) — the sum_if_pos / sum_if_neg tax-account +receivable/payable split. NOTE: a few financial income/cost subdivisions +(IX/X/XI, N) select accounts by tax TAG in EE and are not mapped here (gap). +AKTIVA r001-r078: (gross_acct, gross_conds, adj_acct, adj_conds). +PASIVA r079-r145 and VZS r01-r61: (net_acct, net_conds).""" + + +class _SUM(tuple): + """A total row, written as the rows it adds up rather than as accounts. + + The totals used to carry their own account lists, and those drifted from + their leaves in both directions: 098000 and 481000 were named by a leaf + and by no total, 255100 and 473100 by the totals and by no leaf, and + 221100 by two sibling leaves so r001 counted it twice. A total that IS the + sum of its leaves cannot disagree with them. Resolved into an ordinary + ``(formula, conds, ...)`` tuple at import by ``_roll_up``, so every reader + of these tables still sees accounts. + """ + + def __new__(cls, *codes): + return super().__new__(cls, codes) + + +SUVAHA_AKTIVA = [ # r001..r078: (gross_acct, gross_conds, adj_acct, adj_conds) + _SUM('s002', 's033', 's074'), # sk_bs_total_assets + _SUM('s003', 's011', 's021'), # sk_bs_A + _SUM('s004', 's005', 's006', 's007', 's008', 's009', 's010'), # sk_bs_A_I + ('012000', [], '-072000,-091100', []), # sk_bs_A_I_1 + ('013000', [], '-073000,-091200', []), # sk_bs_A_I_2 + ('014000', [], '-074000,-091300', []), # sk_bs_A_I_3 + ('015000', [], '-075000,-091400', []), # sk_bs_A_I_4 + ('019000', [], '-079000,-091500,-091000', []), # sk_bs_A_I_5 + ('041000', [], '-093000', []), # sk_bs_A_I_6 + ('051000', [], '-095100', []), # sk_bs_A_I_7 + _SUM('s012', 's013', 's014', 's015', 's016', 's017', 's018', 's019', 's020'), # sk_bs_A_II + ('031000', [], '-092300', []), # sk_bs_A_II_1 + ('021000', [], '-081000,-092100', []), # sk_bs_A_II_2 + ('022000', [], '-082000,-092200', []), # sk_bs_A_II_3 + ('025000', [], '-085000,-092500', []), # sk_bs_A_II_4 + ('026000', [], '-086000,-092600', []), # sk_bs_A_II_5 + ('029000,032000', [], '-089000,-092320,-092900,-092000', []), # sk_bs_A_II_6 + ('042000', [], '-094000', []), # sk_bs_A_II_7 + ('052000', [], '-095200,-095000', []), # sk_bs_A_II_8 + ('097000', [], '-098000', []), # sk_bs_A_II_9 + _SUM('s022', 's023', 's024', 's025', 's026', 's027', 's028', 's029', 's030', 's031', 's032'), # sk_bs_A_III + ('061000', [], '-096100', []), # sk_bs_A_III_1 + ('062000', [], '-096200', []), # sk_bs_A_III_2 + ('063000', [], '-096300', []), # sk_bs_A_III_3 + ('066100,066000', [], '-096610', []), # sk_bs_A_III_4 + ('066200', [], '-096620', []), # sk_bs_A_III_5 + ('067000', [], '-096700', []), # sk_bs_A_III_6 + ('065000,069000', [], '-096500,-096900,-096000', []), # sk_bs_A_III_7 + ('068000', [], '-096800', []), # sk_bs_A_III_8 + ('221100', [], '', []), # sk_bs_A_III_9 + ('043000', [], '-096043', []), # sk_bs_A_III_10 + ('053000,055000', [], '-095300', []), # sk_bs_A_III_11 + _SUM('s034', 's041', 's053', 's066', 's071'), # sk_bs_B + _SUM('s035', 's036', 's037', 's038', 's039', 's040'), # sk_bs_B_I + ('112000,119000,111000', [], '-191000', []), # sk_bs_B_I_1 + ('121000,122000', [], '-192000,-193000', []), # sk_bs_B_I_2 + ('123000', [], '-194000', []), # sk_bs_B_I_3 + ('124000', [], '-195000', []), # sk_bs_B_I_4 + ('132000,133000,139000,131000', [], '-196000', []), # sk_bs_B_I_5 + ('314100', [], '-391341', []), # sk_bs_B_I_6 + _SUM('s042', 's046', 's047', 's048', 's049', 's050', 's051', 's052'), # sk_bs_B_II + _SUM('s043', 's044', 's045'), # sk_bs_B_II_1 + ('311110', [], '-391110', []), # sk_bs_B_II_1_a + ('311120', [], '-391120', []), # sk_bs_B_II_1_b + ('311100', [], '-391100', []), # sk_bs_B_II_1_c + ('316100', [], '', []), # sk_bs_B_II_2 + ('351100', [], '-391510', []), # sk_bs_B_II_3 + ('351200', [], '-391520', []), # sk_bs_B_II_4 + ('355100', [], '-391550', []), # sk_bs_B_II_5 + ('373100', [], '-391730', []), # sk_bs_B_II_6 + ('374000,378100', [], '-391700', []), # sk_bs_B_II_7 + ('481000', [], '', []), # sk_bs_B_II_8 + _SUM('s054', 's058', 's059', 's060', 's061', 's062', 's063', 's064', 's065'), # sk_bs_B_III + _SUM('s055', 's056', 's057'), # sk_bs_B_III_1 + ('311200', [], '-391200', []), # sk_bs_B_III_1_a + ('311300', [], '-391300', []), # sk_bs_B_III_1_b + ('311000,312000,313000,314000,315000', [], '-391000', []), # sk_bs_B_III_1_c + ('316000', [], '-391600', []), # sk_bs_B_III_2 + ('351300,351000', [], '-391530', []), # sk_bs_B_III_3 + ('351400', [], '-391540', []), # sk_bs_B_III_4 + ('355000,398000,354000,358000,398100', [], '-391500', []), # sk_bs_B_III_5 + ('336000', [], '-391336', []), # sk_bs_B_III_6 + ('', [['341', '342', 'pos_each', 1], ['342', '343', 'pos_each', 1], ['343', '344', 'pos_each', 1], ['345', '346', 'pos_each', 1], ['346', '347', 'pos_each', 1], ['347', '348', 'pos_each', 1]], '-391340', []), # sk_bs_B_III_7 + ('373000,376000', [], '', []), # sk_bs_B_III_8 + ('335000,378000,371000,375000', [], '-391800', []), # sk_bs_B_III_9 + _SUM('s067', 's068', 's069', 's070'), # sk_bs_B_IV + ('251100,253100', [], '-291100', []), # sk_bs_B_IV_1 + ('251000,253000,256000,257000', [], '-291000', []), # sk_bs_B_IV_2 + ('252000', [], '', []), # sk_bs_B_IV_3 + ('259000,314200', [], '-291200', []), # sk_bs_B_IV_4 + _SUM('s072', 's073'), # sk_bs_B_V + ('211000,213000', [], '', []), # sk_bs_B_V_1 + ('221000,261000', [], '', []), # sk_bs_B_V_2 + _SUM('s075', 's076', 's077', 's078'), # sk_bs_C + ('381100,382100', [], '', []), # sk_bs_C_1 + ('381000,382000', [], '', []), # sk_bs_C_2 + ('385100', [], '', []), # sk_bs_C_3 + ('385000', [], '', []), # sk_bs_C_4 +] + +SUVAHA_PASIVA = [ # r079..r145: (net_acct, net_conds) + _SUM('s080', 's101', 's141'), # sk_bs_total_equity_liabilities + _SUM('s081', 's085', 's086', 's087', 's090', 's093', 's097', 's100'), # sk_bs_2_A + _SUM('s082', 's083', 's084'), # sk_bs_2_A_I + ('-411000,-491000', []), # sk_bs_2_A_I_1 + ('-419000', []), # sk_bs_2_A_I_2 + ('-353000', []), # sk_bs_2_A_I_3 + ('-412000', []), # sk_bs_2_A_II + ('-413000', []), # sk_bs_2_A_III + _SUM('s088', 's089'), # sk_bs_2_A_IV + ('-417000,-418000,-421000,-422000', []), # sk_bs_2_A_IV_1 + ('-417100,-421100', []), # sk_bs_2_A_IV_2 + _SUM('s091', 's092'), # sk_bs_2_A_V + ('-423000', []), # sk_bs_2_A_V_1 + ('-427000', []), # sk_bs_2_A_V_2 + _SUM('s094', 's095', 's096'), # sk_bs_2_A_VI + ('-414000', []), # sk_bs_2_A_VI_1 + ('-415000', []), # sk_bs_2_A_VI_2 + ('-416000', []), # sk_bs_2_A_VI_3 + _SUM('s098', 's099'), # sk_bs_2_A_VII + ('-428000', []), # sk_bs_2_A_VII_1 + ('-429000', []), # sk_bs_2_A_VII_2 + ('-5,-6', []), # sk_bs_2_A_VIII (the export reads it off the movement) + _SUM('s102', 's118', 's121', 's122', 's136', 's139', 's140'), # sk_bs_2_B + _SUM('s103', 's107', 's108', 's109', 's110', 's111', 's112', 's113', 's114', 's115', 's116', 's117'), # sk_bs_2_B_I + _SUM('s104', 's105', 's106'), # sk_bs_2_B_I_1 + ('-321400,-475300,-476100', []), # sk_bs_2_B_I_1_a + ('-321600,-475600,-476200', []), # sk_bs_2_B_I_1_b + ('-321200,-475200,-476000', []), # sk_bs_2_B_I_1_c + ('-316100', []), # sk_bs_2_B_I_2 + ('-471100,-471000', []), # sk_bs_2_B_I_3 + ('-471200', []), # sk_bs_2_B_I_4 + ('-479000', []), # sk_bs_2_B_I_5 + ('-475000', []), # sk_bs_2_B_I_6 + ('-478000', []), # sk_bs_2_B_I_7 + ('-473100,-255100,-473000', []), # sk_bs_2_B_I_8 + ('-472000', []), # sk_bs_2_B_I_9 + ('-372200,-474000', []), # sk_bs_2_B_I_10 + ('-373200,-373100', []), # sk_bs_2_B_I_11 + ('-481000', []), # sk_bs_2_B_I_12 + _SUM('s119', 's120'), # sk_bs_2_B_II + ('-451200', []), # sk_bs_2_B_II_1 + ('-459200', []), # sk_bs_2_B_II_2 + ('-461100,-461000', []), # sk_bs_2_B_III + _SUM('s123', 's127', 's128', 's129', 's130', 's131', 's132', 's133', 's134', 's135'), # sk_bs_2_B_IV + _SUM('s124', 's125', 's126'), # sk_bs_2_B_IV_1 + ('-321300,-322100,-324300', []), # sk_bs_2_B_IV_1_a + ('-321500,-322200,-324500', []), # sk_bs_2_B_IV_1_b + ('-321000,-322000,-324000,-325000,-326000', []), # sk_bs_2_B_IV_1_c + ('-316000', []), # sk_bs_2_B_IV_2 + ('-361100,-361000', []), # sk_bs_2_B_IV_3 + ('-361200', []), # sk_bs_2_B_IV_4 + ('-364000,-365000,-366000,-367000,-368000,-398100,-398000', []), # sk_bs_2_B_IV_5 + ('-331000,-333000', []), # sk_bs_2_B_IV_6 + ('-336000', []), # sk_bs_2_B_IV_7 + ('', [['341', '342', 'neg_each', -1], ['342', '343', 'neg_each', -1], ['343', '344', 'neg_each', -1], ['345', '346', 'neg_each', -1], ['346', '347', 'neg_each', -1], ['347', '348', 'neg_each', -1]]), # sk_bs_2_B_IV_8 + ('-373000,-377000', []), # sk_bs_2_B_IV_9 + ('-379000,-474100,-372000', []), # sk_bs_2_B_IV_10 + _SUM('s137', 's138'), # sk_bs_2_B_V + ('-323000,-451000', []), # sk_bs_2_B_V_1 + ('-459000', []), # sk_bs_2_B_V_2 + ('-221900,-231000,-232000,-461200', []), # sk_bs_2_B_VI + ('-241000,-249000,-255000', []), # sk_bs_2_B_VII + _SUM('s142', 's143', 's144', 's145'), # sk_bs_2_C + ('-383200', []), # sk_bs_2_C_1 + ('-383000,-383100', []), # sk_bs_2_C_2 + ('-384200', []), # sk_bs_2_C_3 + ('-384000,-384100', []), # sk_bs_2_C_4 +] + + +def _roll_up(): + """Replace every ``_SUM`` with the concatenation of its children's cells. + + Concatenation is exact: the evaluator sums per token, so a total of the + leaves' tokens is the sum of the leaves, two-sided gating included.""" + rows = {} + for i, row in enumerate(SUVAHA_AKTIVA, start=1): + rows["s%03d" % i] = (SUVAHA_AKTIVA, i - 1) + for i, row in enumerate(SUVAHA_PASIVA, start=1): + rows["s%03d" % (78 + i)] = (SUVAHA_PASIVA, i - 1) + + def resolve(code): + table, pos = rows[code] + row = table[pos] + if not isinstance(row, _SUM): + return row + cells = [resolve(child) for child in row] + width = len(cells[0]) + merged = [] + for col in range(width): + if col % 2: # conds + merged.append([c for cell in cells for c in (cell[col] or [])]) + else: + merged.append(",".join(cell[col] for cell in cells if cell[col])) + table[pos] = tuple(merged) + return table[pos] + + for code in rows: + resolve(code) + + +_roll_up() + + +def _two_sided(): + """Accounts the Súvaha names on BOTH sides: a pohľadávka when the balance + is a debit, a záväzok when it is a credit (316, 336, 373, 398, 481 — the + form's "A" accounts). The export gates each such account on its own + balance sign; summed ungated, one balance lands on both sides at once. + Read off the leaf cells: positive in an AKTÍVA gross, negative in a PASÍVA. + The 341-347 tax split says the same thing with conds and is not here.""" + debit, credit = set(), set() + for row in SUVAHA_AKTIVA: + debit |= {t.strip() for t in row[0].split(",") + if t.strip() and not t.strip().startswith("-")} + for row in SUVAHA_PASIVA: + credit |= {t.strip()[1:] for t in row[0].split(",") + if t.strip().startswith("-")} + return frozenset(debit & credit) + + +TWO_SIDED = _two_sided() + +VZS = [ # r01..r61: (net_acct, net_conds) + ('-601,-602,-604,-606,-607', []), # sk_pl_net_turnover + ('-601,-602,-604,-606,-607,-61,-62,-641,-642,-644,-645,-646,-648,-655,-657', []), # sk_pl_total_revenue + ('-604,-607', []), # sk_pl_revenue_I + ('-601', []), # sk_pl_revenue_II + ('-602,-606', []), # sk_pl_revenue_III + ('-61', []), # sk_pl_revenue_IV + ('-62', []), # sk_pl_revenue_V + ('-641,-642', []), # sk_pl_revenue_VI + ('-644,-645,-646,-648,-655,-657', []), # sk_pl_revenue_VII + ('501,502,503,504,505,507,51,521,522,523,524,525,526,527,528,53,541,542,543,544,545,546,547,548,549,551,553,555,557', []), # sk_pl_total_cost + ('504,507', []), # sk_pl_total_cost_A + ('501,502,503', []), # sk_pl_total_cost_B + ('505', []), # sk_pl_total_cost_C + ('51', []), # sk_pl_total_cost_D + ('521,522,523,524,525,526,527,528', []), # sk_pl_total_cost_E + ('521,522', []), # sk_pl_total_cost_E_1 + ('523', []), # sk_pl_total_cost_E_2 + ('524,525,526', []), # sk_pl_total_cost_E_3 + ('527,528', []), # sk_pl_total_cost_E_4 + ('53', []), # sk_pl_total_cost_F + ('551,553', []), # sk_pl_total_cost_G + ('551', []), # sk_pl_total_cost_G_1 + ('553', []), # sk_pl_total_cost_G_2 + ('541,542', []), # sk_pl_total_cost_H + ('547', []), # sk_pl_total_cost_I + ('543,544,545,546,548,549,555,557', []), # sk_pl_total_cost_J + ('-501,-502,-503,-504,-505,-507,-51,-521,-522,-523,-524,-525,-526,-527,-528,-53,-541,-542,-543,-544,-545,-546,-547,-548,-549,-551,-553,-555,-557,-601,-602,-604,-606,-607,-61,-62,-641,-642,-644,-645,-646,-648,-655,-657', []), # sk_pl_result + ('-501,-502,-503,-504,-505,-507,-51,-601,-602,-604,-606,-607,-61,-62', []), # sk_pl_added_value + ('-661,-662,-663,-664,-665,-666,-667,-668', []), # sk_pl_total_income + ('-661', []), # sk_pl_total_income_VIII + ('-665', []), # sk_pl_total_income_IX (Výnosy z krátkodob. fin. majetku, 665) + ('-665&IX_1', []), # sk_pl_total_income_IX_1 (prepojené — accounts tagged IX_1) + ('-665&IX_2', []), # sk_pl_total_income_IX_2 (podielová účasť — tagged IX_2) + ('-665!IX_1!IX_2', []), # sk_pl_total_income_IX_3 (ostatné — 665 not tagged prepojené/podiel.) + ('-666', []), # sk_pl_total_income_X (Výnosy z dlhodob. fin. majetku, 666) + ('-666&X_1', []), # sk_pl_total_income_X_1 (prepojené — tagged X_1) + ('-666&X_2', []), # sk_pl_total_income_X_2 (podielová účasť — tagged X_2) + ('-666!X_1!X_2', []), # sk_pl_total_income_X_3 (ostatné — 666 not tagged) + ('-662', []), # sk_pl_total_income_XI (Výnosové úroky, 662) + ('-662&XI_1', []), # sk_pl_total_income_XI_1 (prepojené — tagged XI_1) + ('-662!XI_1', []), # sk_pl_total_income_XI_2 (ostatné — 662 not tagged prepojené) + ('-663', []), # sk_pl_total_income_XII + ('-664,-667', []), # sk_pl_total_income_XIII + ('-668', []), # sk_pl_total_income_XIV + ('561,562,563,564,565,566,567,568,569', []), # sk_pl_total_financial_cost + ('561', []), # sk_pl_total_financial_cost_K + ('566', []), # sk_pl_total_financial_cost_L + ('565', []), # sk_pl_total_financial_cost_M + ('562', []), # sk_pl_total_financial_cost_N (Nákladové úroky, 562) + ('562&N_1', []), # sk_pl_total_financial_cost_N_1 (prepojené — tagged N_1) + ('562!N_1', []), # sk_pl_total_financial_cost_N_2 (ostatné — 562 not tagged prepojené) + ('563', []), # sk_pl_total_financial_cost_O + ('564,567', []), # sk_pl_total_financial_cost_P + ('568,569', []), # sk_pl_total_financial_cost_Q + ('-561,-562,-563,-564,-565,-566,-567,-568,-569,-661,-662,-663,-664,-665,-666,-667,-668', []), # sk_pl_result_financial + ('-501,-502,-503,-504,-505,-507,-51,-521,-522,-523,-524,-525,-526,-527,-528,-53,-541,-542,-543,-544,-545,-546,-547,-548,-549,-551,-553,-555,-557,-561,-562,-563,-564,-565,-566,-567,-568,-569,-601,-602,-604,-606,-607,-61,-62,-641,-642,-644,-645,-646,-648,-655,-657,-661,-662,-663,-664,-665,-666,-667,-668', []), # sk_pl_before_tax + ('591,592,595', []), # sk_pl_income_tax + ('591,595', []), # sk_pl_income_tax_1 + ('592', []), # sk_pl_income_tax_2 + ('596', []), # sk_pl_transfer + ('-5,-6', []), # sk_pl_after_tax — VH po zdanení = výnosy − náklady vrátane + # dane = −(Σ trieda 5 + Σ trieda 6). Rovná sa Súvaha r100 (A.VIII) z rovnakého + # pohybu tried 5/6, takže bilancia sedí čestne (bez dopočtu). Predtým sa tento + # riadok počítal enumeráciou účtov a vynechával 562/662/665/666 → r61 ≠ r56 − daň. +] + + +def _claimed_analytics(): + """Six-digit analytic codes (NNNxxx, xxx != '000') that some row routes + explicitly. A synthetic-main token NNN000 then absorbs only the analytics of + NNN that are NOT explicitly claimed — so analytic sub-accounts (e.g. 022001, + 022002) land on their synthetic's Súvaha line, while genuine per-analytic + splits stay precise and aren't double-counted.""" + claimed = set() + + def add(f): + for tok in (f or "").replace("-", "").split(","): + tok = tok.strip() + if len(tok) == 6 and not tok.endswith("000"): + claimed.add(tok) + + for r in SUVAHA_AKTIVA: + add(r[0]) + add(r[2]) + for r in SUVAHA_PASIVA: + add(r[0]) + for r in VZS: + add(r[0]) + return claimed + + +CLAIMED_ANALYTICS = _claimed_analytics() diff --git a/l10n_sk_fs/report/l10n_sk_fs_templates.xml b/l10n_sk_fs/report/l10n_sk_fs_templates.xml new file mode 100644 index 00000000..3411525c --- /dev/null +++ b/l10n_sk_fs/report/l10n_sk_fs_templates.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + diff --git a/l10n_sk_fs/report/l10n_sk_poznamky_report.xml b/l10n_sk_fs/report/l10n_sk_poznamky_report.xml new file mode 100644 index 00000000..3808b3b6 --- /dev/null +++ b/l10n_sk_fs/report/l10n_sk_poznamky_report.xml @@ -0,0 +1,89 @@ + + + + Poznámky k účtovnej závierke + l10n.sk.poznamky + qweb-pdf + l10n_sk_fs.report_l10n_sk_poznamky + l10n_sk_fs.report_l10n_sk_poznamky + + report + + + + diff --git a/l10n_sk_fs/security/ir.model.access.csv b/l10n_sk_fs/security/ir.model.access.csv new file mode 100644 index 00000000..dcde12d2 --- /dev/null +++ b/l10n_sk_fs/security/ir.model.access.csv @@ -0,0 +1,7 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_l10n_sk_uzpod_user,l10n.sk.uzpod user,model_l10n_sk_uzpod,account.group_account_user,1,1,1,0 +access_l10n_sk_uzpod_manager,l10n.sk.uzpod manager,model_l10n_sk_uzpod,account.group_account_manager,1,1,1,1 +access_l10n_sk_poznamky_user,l10n.sk.poznamky user,model_l10n_sk_poznamky,account.group_account_user,1,1,1,0 +access_l10n_sk_poznamky_manager,l10n.sk.poznamky manager,model_l10n_sk_poznamky,account.group_account_manager,1,1,1,1 +access_l10n_sk_poznamky_odpis_user,l10n.sk.poznamky.odpis user,model_l10n_sk_poznamky_odpis_line,account.group_account_user,1,1,1,1 +access_l10n_sk_poznamky_odpis_manager,l10n.sk.poznamky.odpis manager,model_l10n_sk_poznamky_odpis_line,account.group_account_manager,1,1,1,1 diff --git a/l10n_sk_fs/security/record_rules.xml b/l10n_sk_fs/security/record_rules.xml new file mode 100644 index 00000000..31f0c2aa --- /dev/null +++ b/l10n_sk_fs/security/record_rules.xml @@ -0,0 +1,18 @@ + + + + SK účtovná závierka (UZPOD): multi-company + + [('company_id', 'in', company_ids)] + + + SK poznámky: multi-company + + [('company_id', 'in', company_ids)] + + + SK poznámky odpis line: multi-company + + [('poznamky_id.company_id', 'in', company_ids)] + + diff --git a/l10n_sk_fs/static/description/icon.png b/l10n_sk_fs/static/description/icon.png new file mode 100644 index 00000000..3843643d Binary files /dev/null and b/l10n_sk_fs/static/description/icon.png differ diff --git a/l10n_sk_fs/static/description/index.html b/l10n_sk_fs/static/description/index.html new file mode 100644 index 00000000..ac38a3dc --- /dev/null +++ b/l10n_sk_fs/static/description/index.html @@ -0,0 +1,71 @@ +
+
+

+ 🌐 EN · + CZ · + SK +

+

Slovakia — Financial Statements (Súvaha / VZS)

+

Slovak balance sheet and profit & loss statement on the shared l10n_cssk_fs_base framework, with the official UZPODv14 XML export

+
+

This app produces the concrete Slovak Súvaha (balance sheet) and Výkaz ziskov + a strát (profit & loss, druhové členenie) on top of the shared + l10n_cssk_fs_base framework, computed CE-clean (no + account_reports).

+

It maps every account of the l10n_sk účtová osnova to exactly one statutory + leaf line (a partition verified in the tests), so the aggregates reconcile: + SPOLU MAJETOK = SPOLU VLASTNÉ IMANIE A ZÁVÄZKY and the result of the period equals + výnosy − náklady. The current-year result (A.V) is computed from triedy 5/6, so the súvaha + ties out before the result is closed to účtu 431.

+
+
+
+ +
+
+

Key features

+
+
    +
  • Full statutory line structure per the Opatrenie MF SR pre podnikateľov účtujúcich + v sústave podvojného účtovníctva — semantic Súvaha and VZS for on-screen review.
  • +
  • Account-code line mapping with a verified partition (every account maps to exactly one + leaf line) and a comparison period.
  • +
  • Current-year result (A.V) computed from triedy 5/6 so the balance sheet ties out before + the result is closed to účtu 431.
  • +
  • Official UZPODv14 export (l10n.sk.uzpod): the combined účtovná + závierka dokument — Súvaha Úč POD 1 (r001–r145 with Brutto / Korekcia / + Netto) plus Výkaz ziskov a strát Úč POD 2 (r01–r61) — built CE-clean from + account.move.line and validated against the official + data/uzpod-2014.xsd (FS SR).
  • +
  • Row→account formulas (models/uzpod14_rows.py) mirror the authoritative + statutory mapping.
  • +
+

Accountant note: confirm the NACE code and header fields on the company and validate + the row mapping with a Slovak accountant before live filing. On Enterprise, SK FS may + instead defer to the native UZPODv14 export.

+
+
+
+ +
+
+

Usage

+
+

Create a financial-statements record for a fiscal period, compute it to populate the line + structure from the period's postings, review the Súvaha and VZS detail with the comparison + period, then export the official UZPODv14 dokument XML for the FS portal. The + semantic statements remain available for on-screen review independently of the fileable + export.

+
+
+
+ +
+
+
+

Published by Data Dance s.r.o. — + www.datadance.eu

+

License: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_sk_fs/static/description/index_cz.html b/l10n_sk_fs/static/description/index_cz.html new file mode 100644 index 00000000..c7fb8e27 --- /dev/null +++ b/l10n_sk_fs/static/description/index_cz.html @@ -0,0 +1,69 @@ +
+
+

+ 🌐 EN · + CZ · + SK +

+

Slovensko — Účetní závěrka (Súvaha / VZS)

+

Slovenská rozvaha a výkaz zisku a ztráty nad sdíleným rámcem l10n_cssk_fs_base, s oficiálním exportem UZPODv14 do XML

+
+

Tato aplikace sestavuje konkrétní slovenskou Súvahu (rozvahu) a Výkaz ziskov + a strát (výkaz zisku a ztráty, druhové členění) nad sdíleným rámcem + l10n_cssk_fs_base, počítáno CE-čistě (bez account_reports).

+

Každý účet slovenské účtové osnovy l10n_sk přiřazuje právě jednomu zákonnému + koncovému řádku (rozklad ověřený testy), takže agregáty souhlasí: + SPOLU MAJETOK = SPOLU VLASTNÉ IMANIE A ZÁVÄZKY a výsledek hospodaření za období se + rovná výnosům minus nákladům. Výsledek běžného roku (A.V) se počítá z tříd 5/6, takže + rozvaha souhlasí ještě před uzavřením výsledku na účet 431.

+
+
+
+ +
+
+

Klíčové funkce

+
+
    +
  • Úplná zákonná struktura řádků podle Opatrenia MF SR pre podnikateľov účtujúcich + v sústave podvojného účtovníctva — sémantická Súvaha a VZS pro kontrolu na obrazovce.
  • +
  • Mapování řádků podle účtových kódů s ověřeným rozkladem (každý účet patří právě do + jednoho koncového řádku) a srovnávací období.
  • +
  • Výsledek běžného roku (A.V) počítaný z tříd 5/6, aby rozvaha souhlasila ještě před + uzavřením výsledku na účet 431.
  • +
  • Oficiální export UZPODv14 (l10n.sk.uzpod): sloučená účetní závěrka + dokument — Súvaha Úč POD 1 (r001–r145 s Brutto / Korekcia / Netto) plus + Výkaz ziskov a strát Úč POD 2 (r01–r61) — sestavená CE-čistě z + account.move.line a validovaná proti oficiálnímu + data/uzpod-2014.xsd (FS SR).
  • +
  • Vzorce řádek→účet (models/uzpod14_rows.py) odpovídají autoritativnímu + zákonnému mapování.
  • +
+

Poznámka pro účetní: ověřte kód NACE a hlavičková pole na společnosti a nechte + mapování řádků potvrdit slovenským účetním před ostrým podáním. Na Enterprise může SK FS + místo toho použít nativní export UZPODv14.

+
+
+
+ +
+
+

Použití

+
+

Vytvořte záznam účetní závěrky za účetní období, spočítejte jej pro naplnění struktury + řádků z účetních zápisů období, zkontrolujte detail Súvahy a VZS se srovnávacím obdobím + a poté exportujte oficiální dokument XML UZPODv14 pro portál FS. Sémantické + výkazy zůstávají k dispozici pro kontrolu na obrazovce nezávisle na podávaném exportu.

+
+
+
+ +
+
+
+

Vydává Data Dance s.r.o. — + www.datadance.eu

+

Licence: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_sk_fs/static/description/index_sk.html b/l10n_sk_fs/static/description/index_sk.html new file mode 100644 index 00000000..63afcccd --- /dev/null +++ b/l10n_sk_fs/static/description/index_sk.html @@ -0,0 +1,69 @@ +
+
+

+ 🌐 EN · + CZ · + SK +

+

Slovensko — Účtovná závierka (Súvaha / VZS)

+

Slovenská súvaha a výkaz ziskov a strát nad spoločným rámcom l10n_cssk_fs_base, s oficiálnym exportom UZPODv14 do XML

+
+

Táto aplikácia zostavuje konkrétnu slovenskú Súvahu a Výkaz ziskov a strát + (druhové členenie) nad spoločným rámcom l10n_cssk_fs_base, počítané CE-čisto + (bez account_reports).

+

Každý účet slovenskej účtovej osnovy l10n_sk zatriedi presne do jedného + zákonného koncového riadka (rozklad overený testami), takže súčty sedia: + SPOLU MAJETOK = SPOLU VLASTNÉ IMANIE A ZÁVÄZKY a výsledok hospodárenia za obdobie sa + rovná výnosom mínus náklady. Výsledok bežného roka (A.V) sa počíta z tried 5/6, takže + súvaha sedí ešte pred uzavretím výsledku na účet 431.

+
+
+
+ +
+
+

Kľúčové funkcie

+
+
    +
  • Úplná zákonná štruktúra riadkov podľa Opatrenia MF SR pre podnikateľov účtujúcich + v sústave podvojného účtovníctva — sémantická Súvaha a VZS na kontrolu na obrazovke.
  • +
  • Mapovanie riadkov podľa účtových kódov s overeným rozkladom (každý účet patrí presne do + jedného koncového riadka) a porovnávacie obdobie.
  • +
  • Výsledok bežného roka (A.V) počítaný z tried 5/6, aby súvaha sedela ešte pred uzavretím + výsledku na účet 431.
  • +
  • Oficiálny export UZPODv14 (l10n.sk.uzpod): zlúčená účtovná závierka + dokument — Súvaha Úč POD 1 (r001–r145 s Brutto / Korekcia / Netto) plus + Výkaz ziskov a strát Úč POD 2 (r01–r61) — zostavená CE-čisto z + account.move.line a validovaná voči oficiálnemu + data/uzpod-2014.xsd (FS SR).
  • +
  • Vzorce riadok→účet (models/uzpod14_rows.py) zodpovedajú autoritatívnemu + zákonnému mapovaniu.
  • +
+

Poznámka pre účtovníka: overte kód NACE a hlavičkové polia na spoločnosti a nechajte + mapovanie riadkov potvrdiť slovenským účtovníkom pred ostrým podaním. Na Enterprise môže SK + FS namiesto toho použiť natívny export UZPODv14.

+
+
+
+ +
+
+

Použitie

+
+

Vytvorte záznam účtovnej závierky za účtovné obdobie, vypočítajte ho na naplnenie štruktúry + riadkov z účtovných zápisov obdobia, skontrolujte detail Súvahy a VZS s porovnávacím obdobím + a následne exportujte oficiálny dokument XML UZPODv14 pre portál FS. Sémantické + výkazy zostávajú k dispozícii na kontrolu na obrazovke nezávisle od podávaného exportu.

+
+
+
+ +
+
+
+

Vydáva Data Dance s.r.o. — + www.datadance.eu

+

Licencia: Data Dance Proprietary License v1.0

+
+
+
diff --git a/l10n_sk_fs/tests/__init__.py b/l10n_sk_fs/tests/__init__.py new file mode 100644 index 00000000..018a9be8 --- /dev/null +++ b/l10n_sk_fs/tests/__init__.py @@ -0,0 +1,4 @@ +from . import test_fs +from . import test_uzpod +from . import test_uzpod_one_mapping +from . import test_row_partition diff --git a/l10n_sk_fs/tests/test_fs.py b/l10n_sk_fs/tests/test_fs.py new file mode 100644 index 00000000..0dfb7edc --- /dev/null +++ b/l10n_sk_fs/tests/test_fs.py @@ -0,0 +1,616 @@ +import base64 + +from lxml import etree + +from odoo.exceptions import UserError +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged +from odoo.tests.common import TransactionCase + + +class TestSkFs(TransactionCase): + """Smoke tests for the SK Súvaha country layer (run at install).""" + + def test_version_seeded(self): + """One version, the whole závierka, against the official schema.""" + version = self.env.ref("l10n_sk_fs.uzpod_v14") + self.assertEqual(version.country_id.code, "SK") + self.assertEqual(version.xml_root_element, "dokument") + self.assertTrue(version.xml_schema_data, "UZPODv14 XSD not loaded") + codes = version.line_def_ids.mapped("code") + self.assertEqual(len(codes), 206, "145 súvaha + 61 výkaz rows") + self.assertEqual(len([c for c in codes if c.startswith("s")]), 145) + self.assertEqual(len([c for c in codes if c.startswith("v")]), 61) + self.assertEqual(len(set(codes)), len(codes), "codes must be unique") + + def test_the_two_blocks_read_different_bases(self): + """The reason `basis` exists: one document, two bases. + + The súvaha reports the cumulative balance as of the period end and the + výkaz the movement within it. A single ``statement_kind`` cannot say + that, so before ``basis`` these had to be two version records — and + two records cannot produce the one ``dokument`` the form is. + """ + version = self.env.ref("l10n_sk_fs.uzpod_v14") + suvaha = version.line_def_ids.filtered(lambda d: d.code.startswith("s")) + vykaz = version.line_def_ids.filtered(lambda d: d.code.startswith("v")) + # A.VIII is the exception, and deliberately: the current-year result is + # the MOVEMENT of triedy 5/6, so that it equals VZS r61 even when last + # year's classes have not yet been closed to účet 431. Reading it as a + # cumulative balance would report 0 on an open ledger. + self.assertEqual( + {d.code for d in suvaha if d._cssk_reads_movement()}, {"s100"}) + self.assertTrue(all(d._cssk_reads_movement() for d in vykaz)) + + def test_the_korekcia_column_is_modelled(self): + """52 súvaha rows are filed brutto / korekcia / netto. + + ``tRiadok14`` requires s1/s2/s3/s4, so a row that stores only a net + figure cannot be filed. The tlačivo states both groups per row — + r005 Softvér is "(013) - /073, 091A/". + """ + version = self.env.ref("l10n_sk_fs.uzpod_v14") + with_corr = version.line_def_ids.filtered("account_formula_correction") + self.assertGreaterEqual(len(with_corr), 50) + softver = version.line_def_ids.filtered(lambda d: d.code == "s005") + # Specific chart codes, not 3-digit groups: the leaves are generated + # from the filed export's mapping so the screen and the XML cannot + # drift apart. See test_uzpod_one_mapping. + self.assertEqual(softver.account_formula, "013000") + self.assertEqual(softver.account_formula_correction, "073000,091200") + + +@tagged("post_install", "-at_install") +class TestSkFsCompute(AccountTestInvoicingCommon): + """Functional: post a balanced entry, compute + export the Súvaha.""" + + @classmethod + @AccountTestInvoicingCommon.setup_country("sk") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.write({"vat": "SK2023456787", "city": "Bratislava"}) + cls.journal = cls.env["account.journal"].search( + [("type", "=", "general"), ("company_id", "=", cls.company.id)], + limit=1, + ) + cls.acc_asset = cls.env["account.account"].create({ + "name": "DHM", "code": "022999", + "account_type": "asset_non_current", + "company_ids": [(6, 0, [cls.company.id])], + }) + cls.acc_eq = cls.env["account.account"].create({ + "name": "Základné imanie", "code": "411999", + "account_type": "equity", + "company_ids": [(6, 0, [cls.company.id])], + }) + cls.acc_rev = cls.env["account.account"].create({ + "name": "Tržby", "code": "602999", "account_type": "income", + "company_ids": [(6, 0, [cls.company.id])], + }) + cls.acc_exp = cls.env["account.account"].create({ + "name": "Spotreba", "code": "501999", "account_type": "expense", + "company_ids": [(6, 0, [cls.company.id])], + }) + cls.acc_bank = cls.env["account.account"].create({ + "name": "Banka", "code": "221999", "account_type": "asset_current", + "company_ids": [(6, 0, [cls.company.id])], + }) + cls.version = cls.env.ref("l10n_sk_fs.uzpod_v14") + + def test_compute_and_export(self): + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": self.acc_asset.id, + "debit": 1000.0, "credit": 0.0}), + (0, 0, {"account_id": self.acc_eq.id, + "debit": 0.0, "credit": 1000.0}), + ], + }) + move.action_post() + + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "date_from": "2026-01-01", "date_to": "2026-12-31", + }) + st.action_compute_lines() + rows = {line.code: line.current_value for line in st.line_ids} + self.assertAlmostEqual(rows["s002"], 1000.0, places=2) # asset (022) + self.assertAlmostEqual(rows["s080"], 1000.0, places=2) # equity (411, negated) + self.assertAlmostEqual(rows["s001"], 1000.0, places=2) # total assets + # The balance sheet balances: assets == equity + liabilities. + self.assertAlmostEqual(rows["s001"], rows["s079"], places=2) + + st.action_export_xml() + self.assertEqual(st.state, "exported") + root = etree.fromstring(base64.b64decode(st.xml_attachment_id.datas)) + self.assertEqual(etree.QName(root).localname, "dokument") + # rows are emitted as ..…, per tRiadok14/12 + self.assertTrue(root.find(".//ucPod1Suvaha") is not None) + self.assertTrue(root.find(".//ucPod2VykazZS") is not None) + + def test_cash_flow_reconciles(self): + """Prehľad peňažných tokov (indirect): A+B+C = D = closing − opening + cash; the partition reconciles by construction (G = 0).""" + loan = self.env["account.account"].create({ + "name": "Bankový úver", "code": "461999", + "account_type": "liability_non_current", + "company_ids": [(6, 0, [self.company.id])]}) + J = self.journal.id + + def entry(date, lines): + self.env["account.move"].create({ + "move_type": "entry", "journal_id": J, "date": date, + "line_ids": [(0, 0, ln) for ln in lines]}).action_post() + + # sale paid to bank (+1000 operating) + entry("2026-03-10", [{"account_id": self.acc_bank.id, "debit": 1000.0}, + {"account_id": self.acc_rev.id, "credit": 1000.0}]) + # buy a machine (−600 investing) + entry("2026-04-10", [{"account_id": self.acc_asset.id, "debit": 600.0}, + {"account_id": self.acc_bank.id, "credit": 600.0}]) + # draw a loan (+300 financing) + entry("2026-05-10", [{"account_id": self.acc_bank.id, "debit": 300.0}, + {"account_id": loan.id, "credit": 300.0}]) + + cf = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, + "version_id": self.env.ref("l10n_sk_fs.cashflow_version_2025").id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + cf.action_compute_lines() + v = {l.code: l.current_value for l in cf.line_ids} + self.assertAlmostEqual(v["A"], 1000.0, 2) # prevádzková + self.assertAlmostEqual(v["B"], -600.0, 2) # investičná + self.assertAlmostEqual(v["C"], 300.0, 2) # finančná + self.assertAlmostEqual(v["D"], 700.0, 2) # čistá zmena PP + self.assertAlmostEqual(v["E"], 0.0, 2) # stav na začiatku + self.assertAlmostEqual(v["F"], 700.0, 2) # stav na konci + self.assertAlmostEqual(v["G"], 0.0, 2) # kontrola E+D−F=0 + cf.action_export_xml() # renders, no official XSD + self.assertEqual(cf.state, "exported") + + def test_changes_in_equity_reconciles(self): + """Prehľad zmien VI: opening + Σ component changes = closing equity + (incl. the current-period result); G = 0 by construction.""" + J = self.journal.id + + def entry(date, lines): + self.env["account.move"].create({ + "move_type": "entry", "journal_id": J, "date": date, + "line_ids": [(0, 0, ln) for ln in lines]}).action_post() + + # capital increase (+10000 equity) + entry("2026-01-15", [{"account_id": self.acc_bank.id, "debit": 10000.0}, + {"account_id": self.acc_eq.id, "credit": 10000.0}]) + # profit for the year (+2000 current result) + entry("2026-06-30", [{"account_id": self.acc_bank.id, "debit": 2000.0}, + {"account_id": self.acc_rev.id, "credit": 2000.0}]) + + eq = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, + "version_id": self.env.ref("l10n_sk_fs.equity_changes_version_2025").id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + eq.action_compute_lines() + v = {l.code: l.current_value for l in eq.line_ids} + self.assertAlmostEqual(v["ZI"], 10000.0, 2) # základné imanie + self.assertAlmostEqual(v["VHO"], 2000.0, 2) # výsledok bežného obdobia + self.assertAlmostEqual(v["ZMENA"], 12000.0, 2) + self.assertAlmostEqual(v["E"], 0.0, 2) # opening equity + self.assertAlmostEqual(v["F"], 12000.0, 2) # closing equity (incl. result) + self.assertAlmostEqual(v["G"], 0.0, 2) # kontrola E+B−C=0 + + def test_submit_lock_and_retention(self): + """Submitting an účtovná závierka freezes the filed XML and locks it; + reset keeps the filed copy.""" + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": self.acc_asset.id, "debit": 1000.0}), + (0, 0, {"account_id": self.acc_eq.id, "credit": 1000.0}), + ], + }) + move.action_post() + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + st.action_compute_lines() + st.action_export_xml() + filed = st.xml_attachment_id + + st.submission_reference = "FS-ZB-2026-001" + st.action_submit() + self.assertEqual(st.state, "submitted") + self.assertEqual(st.submitted_attachment_id, filed) + self.assertTrue(st.submitted_date) + with self.assertRaises(UserError): + st.action_export_xml() + with self.assertRaises(UserError): + st.action_compute_lines() + st.action_reset_to_draft() + self.assertEqual(st.state, "draft") + self.assertEqual(st.submitted_attachment_id, filed) + + def test_vzs_compute_and_export(self): + # P&L uses the period movement (not cumulative as-of). + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": self.acc_bank.id, + "debit": 1200.0, "credit": 0.0}), + (0, 0, {"account_id": self.acc_rev.id, + "debit": 0.0, "credit": 2000.0}), + (0, 0, {"account_id": self.acc_exp.id, + "debit": 800.0, "credit": 0.0}), + ], + }) + move.action_post() + + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "date_from": "2026-01-01", "date_to": "2026-12-31", + }) + st.action_compute_lines() + rows = {line.code: line.current_value for line in st.line_ids} + # II = tržby z vlastných výrobkov/služieb (602); B = spotreba (501) + self.assertAlmostEqual(rows["v05"], 2000.0, places=2) + self.assertAlmostEqual(rows["v12"], 800.0, places=2) + # v27 *** Výsledok hospodárenia z hospodárskej činnosti = v02 - v10 + self.assertAlmostEqual(rows["v27"], 1200.0, places=2) + # v61 **** VH za účtovné obdobie po zdanení = v56 - v57 - v60 + self.assertAlmostEqual(rows["v61"], 1200.0, places=2) + + st.action_export_xml() + self.assertEqual(st.state, "exported") + root = etree.fromstring(base64.b64decode(st.xml_attachment_id.datas)) + self.assertEqual(etree.QName(root).localname, "dokument") + # rows are emitted as ..…, per tRiadok14/12 + self.assertTrue(root.find(".//ucPod1Suvaha") is not None) + self.assertTrue(root.find(".//ucPod2VykazZS") is not None) + + def test_suvaha_reconciles_with_current_result(self): + """Súvaha ties out with a current-year result: bank +1200, revenue + 2000, expense 800 -> A.V (PAV) = 1200 balances the sheet.""" + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": self.acc_bank.id, "debit": 1200.0}), + (0, 0, {"account_id": self.acc_rev.id, "credit": 2000.0}), + (0, 0, {"account_id": self.acc_exp.id, "debit": 800.0}), + ], + }) + move.action_post() + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "date_from": "2026-01-01", "date_to": "2026-12-31", + }) + st.action_compute_lines() + rows = {line.code: line.current_value for line in st.line_ids} + self.assertAlmostEqual(rows["s100"], 1200.0, places=2) # current result + self.assertAlmostEqual(rows["s001"], 1200.0, places=2) # bank + self.assertAlmostEqual(rows["s001"], rows["s079"], places=2) + + def test_a_two_sided_account_lands_on_one_side_only(self): + """481 is a deferred tax ASSET in debit and a LIABILITY in credit. + + UZPODv14 names 481000 twice — r052 (A.III.11 odložená daňová + pohľadávka) and, negated, r117 (B.I.12 odložený daňový záväzok) — and + says nothing about which one a given balance belongs to, because the + sign is what says it. Summed ungated the same balance lands on both + rows at once: a company owing deferred tax would show the liability + AND a negative asset of the same size. + + Nothing in the sheet objects to that. It still foots and, while A.VIII + was a plug, it still balanced — which is how the whole class of error + went unnoticed until the two mappings of this form were compared. + """ + # ``code`` is per-company in 19.0 (a jsonb code_store keyed by + # company), so the account has to be looked up in the test company's + # own context — without with_company the code resolves against the env + # company and finds a DIFFERENT company's 481000, whose balance never + # reaches this statement. + deferred = self.env["account.account"].with_company(self.company).search( + [("code", "=", "481000"), + ("company_ids", "in", self.company.id)], limit=1) + self.assertTrue(deferred, "481000 missing from the SK chart") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": self.acc_bank.id, "debit": 3000.0}), + (0, 0, {"account_id": deferred.id, "credit": 3000.0}), + ], + }) + move.action_post() + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "date_from": "2026-01-01", "date_to": "2026-12-31", + }) + st.action_compute_lines() + rows = {line.code: line.current_value for line in st.line_ids} + self.assertAlmostEqual(rows["s117"], 3000.0, places=2) # the záväzok + self.assertAlmostEqual(rows["s052"], 0.0, places=2) # and NOT an asset + self.assertAlmostEqual(rows["s001"], rows["s079"], places=2) + + def test_every_two_sided_prefix_is_derived_from_the_form(self): + """The gated set is read off the definitions, not maintained by hand. + + Each of these is an account the osnova genuinely puts on either side by + the sign of its balance — 341-347 daňové účty, 336 sociálne poistenie, + 316 čistá hodnota zákazky, 373 deriváty, 398 spojovací účet, 481 + odložená daň. If this list changes, the tlačivo transcription changed + with it, and that is worth looking at rather than re-pinning. + """ + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "date_from": "2026-01-01", "date_to": "2026-12-31", + }) + self.assertEqual( + st._cssk_two_sided_prefixes(), + {"341", "342", "343", "345", "346", "347", + "316000", "316100", "336000", "373000", "373100", + "398000", "398100", "481000"}, + ) + + def test_an_account_no_row_claims_is_reported(self): + """The failure a balanced sheet cannot show you. + + UZPODv14's rows name specific chart codes, so a company whose chart + puts receivables somewhere the mapping never heard of loses that money + from the statement entirely — and the statement still foots, still + balances, and looks right. Nothing detects it from inside; it has to + be reported. + """ + stray = self.env["account.account"].with_company(self.company).create({ + "name": "Nezaradený účet", "code": "899123", + "account_type": "asset_current", + "company_ids": [(6, 0, [self.company.id])], + }) + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": stray.id, "debit": 4321.0}), + (0, 0, {"account_id": self.acc_rev.id, "credit": 4321.0}), + ], + }) + move.action_post() + st = self.env["cssk.fs.statement"].create({ + "company_id": self.company.id, "version_id": self.version.id, + "date_from": "2026-01-01", "date_to": "2026-12-31", + }) + st.action_compute_lines() + self.assertEqual(st.unmapped_count, 1) + self.assertIn("899123", st.unmapped_note) + self.assertEqual(st.unmapped_line_ids.account_ids, stray) + self.assertEqual(st.action_view_unmapped()["res_id"], st.id) + + +@tagged("post_install", "-at_install") +class TestSkFsSchemaPins(TransactionCase): + """The bundled schema must match data/SCHEMA_VERSION. + + Not a check that our copy is CURRENT — nothing local can answer that, and + UZPODv14 is the worst case for trying: the file stamps itself only + "Suvaha" and carries no revision, so a silently revised copy and a stale + one are byte-indistinguishable from inside. What this checks is the weaker + thing still worth having — that the bundled bytes are the ones somebody + verified against the published copy on a stated date, so replacing the + schema is a deliberate two-file change. + + This module is exactly why the check exists: until 19.0.1.1.0 it shipped + two HAND-WRITTEN schemas (uzsuv/uzvzs) whose root elements FS SR has never + published, and every export validated against them cleanly. + """ + + def _pins(self, data): + import os + pins = {} + with open(os.path.join(data, "SCHEMA_VERSION"), encoding="utf-8") as fh: + for raw in fh: + parts = raw.split() + if len(parts) == 3 and parts[0].endswith(".xsd"): + pins[parts[0]] = (parts[1], int(parts[2])) + return pins + + def _data_dir(self): + import os + here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + return os.path.join(here, "data") + + def test_bundled_schemas_match_the_pinned_digests(self): + import hashlib + import os + data = self._data_dir() + pins = self._pins(data) + self.assertTrue(pins, "SCHEMA_VERSION lists no schemas") + for name, (digest, size) in pins.items(): + path = os.path.join(data, name) + self.assertTrue(os.path.exists(path), "%s is pinned but missing" % name) + blob = open(path, "rb").read() + self.assertEqual(len(blob), size, "%s size moved" % name) + self.assertEqual( + hashlib.md5(blob).hexdigest(), digest, + "%s does not match its pin — if this is a deliberate refresh, " + "update data/SCHEMA_VERSION and re-run the export tests" % name) + + def test_every_bundled_schema_is_pinned(self): + """A schema added without a pin is the gap this file exists to close.""" + import os + data = self._data_dir() + on_disk = {f for f in os.listdir(data) if f.endswith(".xsd")} + self.assertEqual(on_disk, set(self._pins(data)), + "unpinned schema(s): %s" + % sorted(on_disk - set(self._pins(data)))) + + +@tagged("post_install", "-at_install") +class TestUzpodFootprint(AccountTestInvoicingCommon): + """The reverse drill must answer for BOTH columns of a row. + + A posting on an oprávky account feeds a row's KOREKCIA column, and until + now the footprint read only the base formula and answered "this feeds + nothing". 51 UZPODv14 rows carry a correction formula; 073000 appears in + that version once as a correction and never as a base, so software + depreciation — an entirely ordinary posting — reported no row at all. + """ + + @classmethod + @AccountTestInvoicingCommon.setup_country("sk") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.write({"vat": "SK2023456787", "city": "Bratislava"}) + cls.journal = cls.env["account.journal"].search( + [("type", "=", "general"), ("company_id", "=", cls.company.id)], + limit=1) + + def _account(self, code): + acc = self.env["account.account"].with_company(self.company).search( + [("code", "=", code), ("company_ids", "in", self.company.id)], + limit=1) + if not acc: + self.skipTest("%s is not in this chart" % code) + return acc + + def _post(self, debit_code, credit_code, amount=500.0): + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": self._account(debit_code).id, + "debit": amount}), + (0, 0, {"account_id": self._account(credit_code).id, + "credit": amount}), + ], + }) + move.action_post() + return move + + def test_a_korekcia_account_reports_the_row_it_feeds(self): + """073000 feeds r005's korekcia and must say so.""" + move = self._post("551000", "073000") + line = move.line_ids.filtered( + lambda l: l.account_id.code == "073000") + footprint = line._cssk_statutory_footprint() + codes = [fp["code"] for fp in footprint] + self.assertIn( + "s005", codes, + "oprávky k softvéru feed r005; the footprint read only the base " + "formula and answered nothing: %s" % codes) + korekcia = [fp for fp in footprint if fp["code"] == "s005"] + self.assertTrue( + any("korekcia" in fp["name"] for fp in korekcia), + "and it must say WHICH column, because brutto and korekcia are " + "different things on the filed form: %s" + % [fp["name"] for fp in korekcia]) + + def test_a_base_account_still_reports_its_own_row(self): + """The control: 013000 feeds r005's brutto and is not mislabelled.""" + move = self._post("013000", "321000") + line = move.line_ids.filtered( + lambda l: l.account_id.code == "013000") + rows = [fp for fp in line._cssk_statutory_footprint() + if fp["code"] == "s005"] + self.assertTrue(rows, "013000 feeds r005") + self.assertFalse(any("korekcia" in fp["name"] for fp in rows), + "and it is the BRUTTO side") + + def test_the_footprint_honours_the_claimed_set(self): + """It must not over-report a residual the evaluator would never absorb. + + The footprint used to reduce every token to its digits, so a residual + ``NNX`` became the bare prefix ``NN`` and claimed every account of the + group — including ones another row names outright. Driving it through + the same matcher the statement computes with is what fixes it, and + this asserts the two now agree about one account. + """ + version = self.env.ref("l10n_sk_fs.uzpod_v14") + claimed = version._cssk_claimed_codes() + self.assertIsNotNone(claimed, "UZPODv14 absorbs analytics") + matcher = self.env["cssk.fs.statement"] + # 311110 is routed to a row of its own, so the 311000 main token must + # NOT absorb it — forward and reverse must give the same answer. + self.assertFalse( + matcher._cssk_code_matches("311110", "311000", claimed), + "an explicitly-routed analytic is not absorbed by its main") + move = self._post("311110", "602000") + line = move.line_ids.filtered( + lambda l: l.account_id.code == "311110") + codes = {fp["code"] for fp in line._cssk_statutory_footprint()} + self.assertIn("s043", codes, "311110 is r043's own analytic") + + def test_a_tagged_account_reports_ONE_row_not_the_whole_family(self): + """Úč POD 2 splits IX/X/XI/N by account TAG, and so must the drill. + + `665&IX_1`, `665&IX_2` and `665!IX_1!IX_2` share one account code and + differ only by tag, so reducing a token to its digits reported a + posting on a 665 account as feeding all three — including the residual + "ostatné" leaf it definitively does not reach. + + Unlike a two-sided account, a single posting DOES decide this: the tag + is on the account. The information is there, so the answer should use + it. + """ + tag = self.env.ref("l10n_sk_fs.account_tag_pl_IX_1", + raise_if_not_found=False) + if not tag: + self.skipTest("the IX_1 split tag is not installed") + account = self.env["account.account"].with_company(self.company).create({ + "name": "Výnosy z CP — prepojené", "code": "665100", + "account_type": "income", + "company_ids": [(6, 0, [self.company.id])], + "tag_ids": [(6, 0, tag.ids)], + }) + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": account.id, "credit": 700.0}), + (0, 0, {"account_id": self._account("221000").id, + "debit": 700.0}), + ], + }) + move.action_post() + line = move.line_ids.filtered(lambda l: l.account_id == account) + codes = {fp["code"] for fp in line._cssk_statutory_footprint()} + self.assertIn("v32", codes, "IX.1 is the row this account feeds") + self.assertNotIn("v33", codes, "IX.2 is a different tag") + self.assertNotIn( + "v34", codes, + "and the residual 'ostatné' leaf EXCLUDES a tagged account") + + def test_an_untagged_account_reaches_the_ostatne_leaf(self): + """The other side of the same filter: `665!IX_1!IX_2`. + + An account of the family carrying neither tag is exactly what the + residual leaf is for, and it must not be reported against the two + tagged rows. + """ + if not self.env.ref("l10n_sk_fs.account_tag_pl_IX_1", + raise_if_not_found=False): + self.skipTest("the IX split tags are not installed") + account = self.env["account.account"].with_company(self.company).create({ + "name": "Výnosy z CP — ostatné", "code": "665900", + "account_type": "income", + "company_ids": [(6, 0, [self.company.id])], + }) + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, + "date": "2026-06-15", + "line_ids": [ + (0, 0, {"account_id": account.id, "credit": 300.0}), + (0, 0, {"account_id": self._account("221000").id, + "debit": 300.0}), + ], + }) + move.action_post() + line = move.line_ids.filtered(lambda l: l.account_id == account) + codes = {fp["code"] for fp in line._cssk_statutory_footprint()} + self.assertIn("v34", codes, "untagged is what 'ostatné' means") + self.assertNotIn("v32", codes) + self.assertNotIn("v33", codes) diff --git a/l10n_sk_fs/tests/test_row_partition.py b/l10n_sk_fs/tests/test_row_partition.py new file mode 100644 index 00000000..149f3799 --- /dev/null +++ b/l10n_sk_fs/tests/test_row_partition.py @@ -0,0 +1,125 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""Every account must reach EXACTLY ONE Súvaha leaf — the uniqueness half. + +Two checks already guard this statement and neither can see a double-mapping: + +* ``SUCET_BS`` asserts parent = Σ children. An account listed on two leaves is + also listed twice on their common ancestor, so the subtotals reconcile + perfectly while total assets are overstated. It validates internal + consistency, not correctness. +* ``BS_UNMAPPED`` asks "does this account reach a row?". An account on two rows + reaches one, so it is invisible there **by construction**. + +"Reaches exactly one" is a third question and needs its own test. It found +221100 sitting on both A.III.9 (bank accounts maturing over one year) and +B.V.2 (current bank accounts) — small in money terms, but the class of bug is +silent and permanent, and this is the only thing that looks for it. + +The coverage half of the partition — every account reaching AT LEAST one row — +is deliberately NOT asserted here. Several synthetics are known to reach none +(096, 395, 431, 461, 474, 325) pending a statutory decision, and runtime +``BS_UNMAPPED`` reports them against real data, which is where coverage is +better judged than against a demo chart. +""" +from odoo.tests.common import TransactionCase + +from odoo.addons.l10n_sk_fs.models.uzpod14_rows import ( + CLAIMED_ANALYTICS, + SUVAHA_AKTIVA, +) +from odoo.addons.l10n_sk_fs.models.uzpod14_kontroly import SUCET_BS + + +class TestRowPartition(TransactionCase): + """Static analysis of the row definitions — no ledger, no company.""" + + #: Accounts on more than one AKTÍVA leaf, with the reason each is tolerated. + #: Empty is the goal. An entry here is a KNOWN BUG awaiting a decision, not + #: an exemption — remove it when the row definitions are corrected, and the + #: test tightens automatically. + # 221100 "Zablokované bankové účty (> 1 rok)" sat on A.III.9 and on B.V.2 + # (current bank accounts) and inflated total assets by its balance until + # 19.0.1.5.0. Nothing is exempt now. + KNOWN_DOUBLE_MAPPED = set() + + @staticmethod + def _matches(code, prefix): + """The absorb-aware matcher, mirroring cssk.statutory.submission.mixin. + + Duplicated deliberately: this test exists to check the row DATA, so it + must not go through the code path whose behaviour it is validating. + """ + if len(prefix) == 6 and prefix.endswith("000"): + return code[:3] == prefix[:3] and code[:6] not in CLAIMED_ANALYTICS + return code.startswith(prefix) + + def _leaf_rows(self): + """AKTÍVA row numbers that are not a parent in any SUCET_BS relation.""" + parents = {parent for parent, _kids in SUCET_BS} + return [rn for rn in range(1, len(SUVAHA_AKTIVA) + 1) + if rn not in parents] + + def _placements(self, code, slot): + """[(row_number, times_counted)] for one account in one column.""" + out = [] + for rn in self._leaf_rows(): + formula = SUVAHA_AKTIVA[rn - 1][slot] or "" + hits = sum( + 1 for token in formula.split(",") + if token.strip() + and self._matches(code, token.strip().lstrip("-")) + ) + if hits: + out.append((rn, hits)) + return out + + def _chart_codes(self): + """Codes of the installed SK chart, minus what a Súvaha never carries.""" + accounts = self.env["account.account"].search([]) + return sorted({ + a.code for a in accounts + if a.code + and a.account_type != "off_balance" + and not a.code.startswith("7") # závierkové / podsúvahové + and a.code[:1] not in ("5", "6") # náklady / výnosy → VZS + }) + + def test_no_account_reaches_two_aktiva_leaves(self): + found = {} + for code in self._chart_codes(): + for slot, column in ((0, "brutto"), (2, "korekcia")): + placements = self._placements(code, slot) + if sum(times for _rn, times in placements) > 1: + found.setdefault(code, []).append((column, placements)) + + unexpected = {c: v for c, v in found.items() + if c not in self.KNOWN_DOUBLE_MAPPED} + self.assertFalse( + unexpected, + "Account(s) counted on more than one Súvaha leaf, so total assets " + "are overstated by their balance while SUCET_BS still reconciles:\n" + + "\n".join( + " %s -> %s" % (code, placements) + for code, placements in unexpected.items() + ), + ) + + def test_known_double_mapped_still_reproduces(self): + """The exemption list must not outlive the bug it documents. + + If a listed account stops being double-mapped, the fix has landed and + the entry has to go — otherwise the list quietly becomes a place where + real regressions can hide. + """ + for code in self.KNOWN_DOUBLE_MAPPED: + total = sum( + times + for slot in (0, 2) + for _rn, times in self._placements(code, slot) + ) + self.assertGreater( + total, 1, + "%s is no longer double-mapped — remove it from " + "KNOWN_DOUBLE_MAPPED so the partition test tightens." % code, + ) diff --git a/l10n_sk_fs/tests/test_uzpod.py b/l10n_sk_fs/tests/test_uzpod.py new file mode 100644 index 00000000..a749bbef --- /dev/null +++ b/l10n_sk_fs/tests/test_uzpod.py @@ -0,0 +1,315 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import base64 + +from lxml import etree + +from odoo import Command +from odoo.exceptions import UserError +from odoo.addons.account.tests.common import AccountTestInvoicingCommon +from odoo.tests import tagged + + +@tagged("post_install", "-at_install") +class TestUzpod(AccountTestInvoicingCommon): + @classmethod + @AccountTestInvoicingCommon.setup_country("sk") + def setUpClass(cls): + super().setUpClass() + cls.company = cls.company_data["company"] + cls.company.write({"vat": "SK2023456787", "city": "Bratislava", + # A real check digit: l10n_cssk_core validates IČO, and 12345678 + # does not add up — the fixture predated the check. + "company_registry": "31333532"}) + cls.journal = cls.env["account.journal"].search( + [("type", "=", "general"), ("company_id", "=", cls.company.id)], limit=1) + + def _acc(self, code): + acc = self.env["account.account"].search( + [("code", "=like", code + "%"), ("company_ids", "in", self.company.id)], + limit=1) + if not acc: + acc = self.env["account.account"].create({ + "name": code, "code": code + "000", "account_type": "asset_current", + "company_ids": [Command.set([self.company.id])]}) + return acc + + def test_uzpod_export_validates(self): + # Dr 012 1000, Cr 072 300 (oprávky), Cr 411 700 (equity) — balanced, + # no open P&L -> r004 net 700, r082 net 700, sheet reconciles. + dev, opr, eq = self._acc("012"), self._acc("072"), self._acc("411") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, "date": "2026-06-30", + "line_ids": [ + Command.create({"account_id": dev.id, "debit": 1000.0}), + Command.create({"account_id": opr.id, "credit": 300.0}), + Command.create({"account_id": eq.id, "credit": 700.0}), + ]}) + move.action_post() + + self.company.partner_id.nace_code = "62090" + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + uz.action_export_xml() + self.assertEqual(uz.state, "exported") + + root = etree.fromstring(base64.b64decode(uz.xml_attachment_id.datas)) + self.assertEqual(root.tag, "dokument") + suvaha = root.findall(".//ucPod1Suvaha/*") + vzs = root.findall(".//ucPod2VykazZS/*") + self.assertEqual(len(suvaha), 145) + self.assertEqual(len(vzs), 61) + + def srow(n): + return root.find(".//ucPod1Suvaha/r%03d" % n) + # r004 = Aktivované náklady na vývoj (012) - /072, 091A/ + r4 = srow(4) + self.assertEqual(r4.findtext("s1"), "1000") # brutto + self.assertEqual(r4.findtext("s2"), "300") # korekcia + self.assertEqual(r4.findtext("s3"), "700") # netto + # r001 SPOLU MAJETOK netto == r079 SPOLU VI A ZÁVÄZKY netto (reconciles) + self.assertEqual(srow(1).findtext("s3"), "700") + self.assertEqual(srow(79).findtext("s5"), "700") + + # header typUzavierky defaults: riadna + malá účtovná jednotka (the + # mandatory veľkostná trieda must be one-hot, never 0/0) + # SK NACE 62.09.0 from the company partner (it used to be 00.00.0) + nace = root.find(".//hlavicka/skNace") + self.assertEqual( + [nace.findtext(k) for k in ("k1", "k2", "k3")], ["62", "09", "0"]) + + tu = root.find(".//hlavicka/typUzavierky") + self.assertEqual(tu.findtext("riadna"), "1") + self.assertEqual(tu.findtext("mimoriadna"), "0") + self.assertEqual(tu.findtext("priebezna"), "0") + self.assertEqual(tu.findtext("mala"), "1") + self.assertEqual(tu.findtext("velka"), "0") + + def test_uzpod_size_class_velka(self): + """size_class='velka' emits velka=1 / mala=0 and stays XSD-valid.""" + dev, opr, eq = self._acc("012"), self._acc("072"), self._acc("411") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, "date": "2026-06-30", + "line_ids": [ + Command.create({"account_id": dev.id, "debit": 1000.0}), + Command.create({"account_id": opr.id, "credit": 300.0}), + Command.create({"account_id": eq.id, "credit": 700.0}), + ]}) + move.action_post() + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, "size_class": "velka", + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + uz.action_export_xml() # XSD validation runs inside + self.assertEqual(uz.state, "exported") + root = etree.fromstring(base64.b64decode(uz.xml_attachment_id.datas)) + tu = root.find(".//hlavicka/typUzavierky") + self.assertEqual(tu.findtext("velka"), "1") + self.assertEqual(tu.findtext("mala"), "0") + self.assertEqual(tu.findtext("riadna"), "1") + + def test_submit_lock_and_retention(self): + """Submitting the UZPODv14 freezes the filed XML and locks re-export; + reset keeps the filed copy.""" + dev, opr, eq = self._acc("012"), self._acc("072"), self._acc("411") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, "date": "2026-06-30", + "line_ids": [ + Command.create({"account_id": dev.id, "debit": 1000.0}), + Command.create({"account_id": opr.id, "credit": 300.0}), + Command.create({"account_id": eq.id, "credit": 700.0}), + ]}) + move.action_post() + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + uz.action_export_xml() + filed = uz.xml_attachment_id + self.assertTrue(filed) + + uz.submission_reference = "FS-UZ-2026-001" + uz.action_submit() + self.assertEqual(uz.state, "submitted") + self.assertEqual(uz.submitted_attachment_id, filed) + self.assertTrue(uz.submitted_date) + with self.assertRaises(UserError): + uz.action_export_xml() + uz.action_reset_to_draft() + self.assertEqual(uz.state, "draft") + self.assertEqual(uz.submitted_attachment_id, filed) + + def test_opravna_zaverka_amendment(self): + """Opravná účtovná závierka: submit a UZPODv14, raise an amendment — it + links back, is flagged Opravná, keeps the filed copy, and re-exports a + still-XSD-valid body (druh stays riadna) with the corrective marker in + the filing metadata (filename), not the schema body (which has none).""" + dev, opr, eq = self._acc("012"), self._acc("072"), self._acc("411") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, "date": "2026-06-30", + "line_ids": [ + Command.create({"account_id": dev.id, "debit": 1000.0}), + Command.create({"account_id": opr.id, "credit": 300.0}), + Command.create({"account_id": eq.id, "credit": 700.0}), + ]}) + move.action_post() + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + self.assertEqual(uz.submission_type, "radna") + uz.action_export_xml() + uz.submission_reference = "FS-UZ-2026-001" + uz.action_submit() + filed = uz.submitted_attachment_id + + action = uz.action_create_amendment() + amend = self.env["l10n.sk.uzpod"].browse(action["res_id"]) + self.assertEqual(amend.original_return_id, uz) + self.assertEqual(amend.submission_type, "opravna") + self.assertEqual(amend.state, "draft") + self.assertIn("opravná", amend.name) + self.assertIn(amend, uz.amendment_ids) + # original untouched: still submitted, filed copy preserved + self.assertEqual(uz.state, "submitted") + self.assertEqual(uz.submitted_attachment_id, filed) + + # amendment re-exports: action_export_xml validates against the XSD, so a + # pass proves the body stays schema-valid. The corrective marker is in the + # filename (submission-level); the body druh stays riadna with no + # corrective element (the schema has none). + amend.action_export_xml() + self.assertEqual(amend.state, "exported") + self.assertIn("-opravna", amend.xml_attachment_id.name) + root = etree.fromstring(base64.b64decode(amend.xml_attachment_id.datas)) + tu = root.find(".//hlavicka/typUzavierky") + self.assertEqual(tu.findtext("riadna"), "1") # body druh stays riadna + self.assertIsNone(tu.find("opravna")) # no corrective body element + + def test_uzpod_kontroly(self): + """The kontrolné pravidlá (content checks beyond the XSD) pass on valid + data and catch a deliberately broken bilančná rovnosť.""" + dev, opr, eq = self._acc("012"), self._acc("072"), self._acc("411") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, "date": "2026-06-30", + "line_ids": [ + Command.create({"account_id": dev.id, "debit": 1000.0}), + Command.create({"account_id": opr.id, "credit": 300.0}), + Command.create({"account_id": eq.id, "credit": 700.0}), + ]}) + move.action_post() + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + + xml_bytes = uz._build_xml() + # Valid, reconciling sheet -> no violations (bilancia, netto, súčty). + self.assertEqual( + uz.check_kontroly(xml_bytes), [], + "kontrolné pravidlá should pass on a reconciling výkaz") + + # Break AKTÍVA = PASÍVA (mutate r079 netto) -> BS_BALANCE must fire. + root = etree.fromstring(xml_bytes) + root.find(".//ucPod1Suvaha/r079/s5").text = "999" + broken = uz.check_kontroly(etree.tostring(root, encoding="UTF-8")) + self.assertIn("BS_BALANCE", [v["code"] for v in broken], + "kontroly must catch AKTÍVA != PASÍVA") + + # ------------------------------------------------------------------ + # One generator: l10n.sk.uzpod files what the Úč POD statement shows + # ------------------------------------------------------------------ + def _post_sample(self): + dev, opr, eq = self._acc("012"), self._acc("072"), self._acc("411") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, "date": "2026-06-30", + "line_ids": [ + Command.create({"account_id": dev.id, "debit": 1000.0}), + Command.create({"account_id": opr.id, "credit": 300.0}), + Command.create({"account_id": eq.id, "credit": 700.0}), + ]}) + move.action_post() + + def _body(self, xml_bytes): + root = etree.fromstring(xml_bytes) + return etree.tostring(root.find("telo")) + + def test_uzpod_and_statement_file_one_document(self): + """The filing record renders through the statement: same body, and + the header carries the filing record's own choices.""" + self._post_sample() + self.company.partner_id.nace_code = "62090" + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, "size_class": "velka", + "statement_nature": "mimoriadna", "poznamky_attached": True, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + filed = uz._build_xml() + statement = uz._l10n_sk_statement() + self.assertEqual(statement.version_id, + self.env.ref("l10n_sk_fs.uzpod_v14")) + self.assertEqual(self._body(filed), + self._body(statement._render_xml())) + h = etree.fromstring(filed).find("hlavicka") + self.assertEqual([h.findtext("skNace/k%d" % i) for i in (1, 2, 3)], + ["62", "09", "0"]) + self.assertEqual(h.findtext("typUzavierky/velka"), "1") + self.assertEqual(h.findtext("typUzavierky/mimoriadna"), "1") + self.assertEqual(h.findtext("prilozeneSucasti/poznamky"), "1") + # whole euros, totals carry brutto and korekcia + r001 = etree.fromstring(filed).find(".//ucPod1Suvaha/r001") + self.assertEqual([r001.findtext(s) for s in ("s1", "s2", "s3")], + ["1000", "300", "700"]) + + def test_an_override_on_the_statement_is_filed(self): + """What the accountant overrides on screen is what the filing record + files — the builder used to recompute every row from the ledger.""" + self._post_sample() + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + statement = uz._l10n_sk_statement() + row = statement.line_ids.filtered(lambda line: line.code == "s004") + self.assertAlmostEqual(row.current_value, 700.0, places=2) + row.write({"is_overridden": True, "manual_value": 650.0}) + filed = etree.fromstring(uz._build_xml()) + self.assertEqual(filed.findtext(".//ucPod1Suvaha/r004/s3"), "650") + + def test_statement_export_runs_the_kontroly(self): + """The statement's own Export XML runs the UZPODv14 rules: a sheet + that does not balance is refused, not filed.""" + self._post_sample() + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + statement = uz._l10n_sk_statement() + self.assertEqual(statement.check_kontroly(), []) + row = statement.line_ids.filtered(lambda line: line.code == "s004") + row.write({"is_overridden": True, "manual_value": 650.0}) + statement.action_compute_lines() + self.assertIn("BS_BALANCE", + [v["code"] for v in statement.check_kontroly()]) + with self.assertRaises(UserError): + statement.action_export_xml() + + def test_totals_are_the_sum_of_the_rounded_rows(self): + """Whole euros, totals summed from the ROUNDED rows: two rows of 0.60 + file 1 and 1 under a total of 2 — rounding the total on its own would + file 1 and fail the portal's sum check.""" + dev, sw, eq = self._acc("012"), self._acc("013"), self._acc("411") + move = self.env["account.move"].create({ + "move_type": "entry", "journal_id": self.journal.id, "date": "2026-06-30", + "line_ids": [ + Command.create({"account_id": dev.id, "debit": 0.6}), + Command.create({"account_id": sw.id, "debit": 0.6}), + Command.create({"account_id": eq.id, "credit": 1.2}), + ]}) + move.action_post() + uz = self.env["l10n.sk.uzpod"].create({ + "company_id": self.company.id, + "date_from": "2026-01-01", "date_to": "2026-12-31"}) + root = etree.fromstring(uz._build_xml()) + s3 = {r: int(root.findtext(".//ucPod1Suvaha/%s/s3" % r)) + for r in ("r003", "r004", "r005")} + self.assertEqual((s3["r004"], s3["r005"]), (1, 1)) + self.assertEqual(s3["r003"], 2) + codes = [v["code"] for v in uz.check_kontroly(etree.tostring(root))] + self.assertNotIn("BS_SUCET", codes) + self.assertNotIn("BS_NETTO", codes) diff --git a/l10n_sk_fs/tests/test_uzpod_one_mapping.py b/l10n_sk_fs/tests/test_uzpod_one_mapping.py new file mode 100644 index 00000000..de3920a0 --- /dev/null +++ b/l10n_sk_fs/tests/test_uzpod_one_mapping.py @@ -0,0 +1,575 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""One UZPODv14 mapping, checked from both ends. + +The module has two consumers of the same statutory form: ``l10n.sk.uzpod``, +which writes the filed XML straight from ``models/uzpod14_rows.py``, and a +``cssk.fs.statement`` version record, which renders the same form as a +drillable on-screen tree. Two mappings of one form is two chances to be wrong, +and the divergence is invisible from inside: the balance sheet still foots and +still balances, because A.VIII used to be a plug that absorbed whatever the +rest came to. It cost 47 of 114 súvaha rows before anybody compared them — +r043/r044/r045 each claimed the whole of 311-315, tripling the receivable. + +The version's leaf rows are therefore generated from the reference by +``tools/gen_uzpod_v14_version.py``, and these tests are what keeps them there. + +Coverage alone is not enough to prove the two agree, which is the point of +``test_each_aggregate_carries_the_same_weights_as_the_filed_row``: the same +SET of account codes can still produce a different number if a code is claimed +by two sibling rows, or claimed with a different sign. Weights per code, +summed over the transitive leaves, are what actually has to match. +""" + +import os +import re +from collections import Counter + +from odoo.tests import TransactionCase, tagged + +from odoo.addons.l10n_sk_fs.models.uzpod14_rows import ( + CLAIMED_ANALYTICS, SUVAHA_AKTIVA, SUVAHA_PASIVA, TWO_SIDED, VZS, +) + +# A.VIII is the current-year result and the one row deliberately NOT taken +# from the reference table, because the reference does not take it from there +# either: ``l10n.sk.uzpod`` overrides r100 with the MOVEMENT of triedy 5/6 so +# that r100 == VZS r61 even when last year's classes are not yet closed to +# účet 431. The version record says the same thing as a movement-basis leaf. +RESULT_ROW = "s100" +# r079 and r080 are likewise computed by the export rather than read off the +# table (r079 = r080 + r101 + r141; r080 = capital accounts + the result), and +# the version says both as aggregates. +EXPORT_OVERRIDES = {"s079", "s080"} + +# Rows where the FILED mapping's total does not equal the sum of its own +# components. Until 19.0.1.5.0 there were four (098000, 481000, 255100, +# 473100): the totals carried their own account lists and drifted from their +# leaves. They are now ``_SUM`` roll-ups of the leaves and cannot drift. +# +# What is left is not a defect: the filed r61 (and +# A.VIII with it) is literally ``-5,-6``, the WHOLE of classes 5 and 6, while +# the tree adds up the rows it shows. A class-5 account that no VZS row names +# therefore reaches the filed result and not the tree — which is the right way +# round for a filing, and exactly what the framework's unmapped-code +# diagnostic exists to surface on screen. Codes 500000/600000 stand for that +# residue in the synthetic chart these tests compare over. +KNOWN_FILED_GAPS = { + "500000": {"v61"}, + "600000": {"v61"}, +} + + +def _coverage_of_reference_row(code, atoms): + """What the filed export's row takes off the synthetic chart.""" + ref = _REFERENCE_ROWS[code] + cov = _coverage(ref[0], atoms) + # A cond's mode is a FILTER on which accounts of the range take part; its + # ``sign`` is the weight. Folding the mode into the weight would say that + # ``neg_each, -1`` and the version's own ``-341`` token disagree, when + # they are two spellings of the same thing: both multiply a credit balance + # by -1 and so report the payable positively. + for low, _high, _mode, sign in ref[1] or []: + cov.update({c: sign for c, _t in atoms if c.startswith(low)}) + if len(ref) > 2: + cov.update(_coverage(ref[2], atoms)) + for low, _high, _mode, sign in (ref[3] or []): + cov.update({c: sign for c, _t in atoms if c.startswith(low)}) + return Counter({k: v for k, v in cov.items() if v}) + + +_REFERENCE_ROWS = {} +for _i, _row in enumerate(SUVAHA_AKTIVA, start=1): + _REFERENCE_ROWS["s%03d" % _i] = _row +for _i, _row in enumerate(SUVAHA_PASIVA, start=1): + _REFERENCE_ROWS["s%03d" % (78 + _i)] = _row +for _i, _row in enumerate(VZS, start=1): + _REFERENCE_ROWS["v%02d" % _i] = _row + + +def _atoms(all_tokens): + """A synthetic chart fine enough to tell every token in play apart. + + The two mappings describe the same rows at different granularity: a total + says ``665`` where its components say ``665&IX_1``, ``665&IX_2`` and + ``665!IX_1!IX_2``, and ``51`` where they say ``501``..``507``. Comparing + the token strings calls those different when they are the same set of + money. So instead: invent one account per distinct token, tagged every way + any token asks about, and compare what each formula COVERS. + + Returns ``[(code, {tags})]``. + """ + tags = set() + for tok in all_tokens: + for _op, name in re.findall(r"([&!])([A-Za-z0-9_]+)", tok): + tags.add(name) + codes = {re.match(r"[0-9]*", tok).group(0) for tok in all_tokens} + codes = {c for c in codes if c} + out = [] + for code in sorted(codes): + # pad so a short prefix ('51') and a long one ('501000') can both be + # exercised, and give each tag its own account so an & and a ! token + # never collapse onto the same one. + base = (code + "000000")[:6] + out.append((base, frozenset())) + for tag in sorted(tags): + out.append((base + "#" + tag, frozenset([tag]))) + return out + + +def _covers(token, code, code_tags): + """Does one formula token take this synthetic account?""" + negated = token.startswith("-") + token = token.lstrip("-") + include, exclude = None, set() + match = re.match(r"([0-9]+)(.*)", token) + prefix, rest = (match.group(1), match.group(2)) if match else (token, "") + for op, name in re.findall(r"([&!])([A-Za-z0-9_]+)", rest): + if op == "&": + include = (include or set()) | {name} + else: + exclude.add(name) + if not code.startswith(prefix): + return 0 + if include is not None and not (code_tags & include): + return 0 + if code_tags & exclude: + return 0 + return -1 if negated else 1 + + +def _coverage(formula, atoms, negate=False): + """``{synthetic account: signed count}`` — what a formula actually takes.""" + out = Counter() + for tok in (formula or "").split(","): + tok = tok.strip() + if not tok: + continue + for code, code_tags in atoms: + weight = _covers(tok, code, code_tags) + if weight: + out[code] += -weight if negate else weight + return out + + +def _weights(formula, negate=False): + """``{account_code_prefix: signed count}`` for one formula. + + A Counter, not a set: an account claimed by two rows of the same subtree + is the failure this exists to catch, and set equality cannot see it. + """ + out = Counter() + for tok in (formula or "").split(","): + tok = tok.strip() + if not tok: + continue + sign = -1 if tok.startswith("-") else 1 + code = tok.lstrip("-") + # tag filters ride along with the code; they narrow WHICH accounts a + # prefix takes, so they belong in the key rather than being stripped. + out[code] += -sign if negate else sign + return out + + +def _cond_weights(conds): + """Range conds as the signed tokens the version stores them as. + + The reference keeps the 341-347 receivable/payable split out of the + formula string, in ``[low, high, mode, sign]`` terms; the version says the + same thing as a signed token that the evaluator's sign gate acts on. Both + have to be counted or the two look different when they are not. + """ + out = Counter() + for low, _high, mode, sign in conds or []: + if mode == "pos_each" and sign == 1: + out[low] += 1 + elif mode == "neg_each" and sign == -1: + out[low] -= 1 + elif mode == "neg_each" and sign == 1: + # r079 SPOLU carries this alongside its -1 twin over the same + # range, so the pair cancels; counted so it cancels here too. + out[low] += 1 + return out + + +def _all_tokens(): + """Every token either mapping mentions, for building the synthetic chart.""" + toks = set() + for rows, cols in ((SUVAHA_AKTIVA, (0, 2)), (SUVAHA_PASIVA, (0,)), + (VZS, (0,))): + for row in rows: + for col in cols: + for tok in (row[col] or "").split(","): + if tok.strip(): + toks.add(tok.strip().lstrip("-")) + for low, _high, _mode, _sign in (row[1] or []): + toks.add(low) + return toks + + +def _reference_weights(): + """``{row code: Counter}`` for every row of the filed export's tables.""" + ref = {} + for i, row in enumerate(SUVAHA_AKTIVA, start=1): + w = _weights(row[0]) + w.update(_cond_weights(row[1])) + # the korekcia column is subtracted from the gross, so its accounts + # enter the row's net weight negated relative to how it stores them + w.update(_weights(row[2])) + w.update(_cond_weights(row[3])) + ref["s%03d" % i] = w + for i, row in enumerate(SUVAHA_PASIVA, start=1): + w = _weights(row[0]) + w.update(_cond_weights(row[1])) + ref["s%03d" % (78 + i)] = w + for i, row in enumerate(VZS, start=1): + w = _weights(row[0]) + w.update(_cond_weights(row[1])) + ref["v%02d" % i] = w + return {k: Counter({c: n for c, n in v.items() if n}) for k, v in ref.items()} + + +@tagged("post_install", "-at_install") +class TestUzpodOneMapping(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.version = cls.env.ref("l10n_sk_fs.uzpod_v14") + cls.defs = {d.code: d for d in cls.version.line_def_ids} + cls.ref = _reference_weights() + cls.atoms = _atoms(_all_tokens()) + + def _leaf_coverage(self, code, seen=None): + """What a row takes off the synthetic chart: its own accounts, or its + leaves' if it is a total.""" + seen = seen if seen is not None else set() + self.assertNotIn(code, seen, "cycle through %s" % code) + seen = seen | {code} + ldef = self.defs[code] + if ldef.kind == "aggregate": + total = Counter() + for ref_code, sign in self._terms(ldef.aggregate_formula): + for key, val in self._leaf_coverage(ref_code, seen).items(): + total[key] += sign * val + return total + cov = _coverage(ldef.account_formula, self.atoms) + cov.update(_coverage(ldef.account_formula_correction, self.atoms, + negate=True)) + return cov + + @staticmethod + def _terms(formula): + """``[(code, +1/-1)]`` for an aggregate formula, honouring brackets. + + Only the shapes the form actually uses: sums, differences, and one + parenthesised group that a leading minus distributes over. + """ + out, sign, depth_sign = [], 1, 1 + for tok in re.findall(r"[A-Za-z_]\w*|[-+()]", formula or ""): + if tok == "+": + sign = 1 + elif tok == "-": + sign = -1 + elif tok == "(": + depth_sign = sign + sign = 1 + elif tok == ")": + depth_sign = 1 + sign = 1 + else: + out.append((tok, sign * depth_sign)) + sign = 1 + return out + + def test_every_leaf_formula_is_the_filed_export_s_formula(self): + """A leaf row must say exactly what the filed XML says for that row. + + Not "covers the same accounts" — the same string, so nobody can + hand-edit the data file into a second mapping again. Regenerate with + ``tools/gen_uzpod_v14_version.py`` if the reference table moves. + """ + mismatched = [] + for code, ldef in sorted(self.defs.items()): + if ldef.kind != "accounts" or code == RESULT_ROW: + continue + got = _weights(ldef.account_formula) + got.update(_weights(ldef.account_formula_correction, negate=True)) + if got != self.ref[code]: + mismatched.append(code) + self.assertFalse( + mismatched, + "leaf rows that no longer match uzpod14_rows.py: %s" % mismatched) + + def test_each_aggregate_carries_the_same_weights_as_the_filed_row(self): + """A total's leaves must add up to what the export computes directly. + + This is the check that account-code coverage alone cannot make: an + account claimed by two sibling rows is counted twice here and once + there, and set equality sees nothing. Comparing signed COUNTS over a + synthetic chart catches multiplicity and sign, and comparing accounts + rather than tokens stops a total that says ``665`` from looking + different to leaves that partition it into ``665&IX_1`` / + ``665&IX_2`` / ``665!IX_1!IX_2``. + + The exceptions are gaps in the FILED mapping, listed one by one in + KNOWN_FILED_GAPS with what each one costs. They are not ours to fix + quietly: changing that table changes filed figures. + """ + bad = {} + for code, ldef in sorted(self.defs.items()): + if ldef.kind != "aggregate" or code in EXPORT_OVERRIDES: + continue + got = self._leaf_coverage(code) + want = _coverage_of_reference_row(code, self.atoms) + diff = {k: (want.get(k, 0), got.get(k, 0)) + for k in set(want) | set(got) + if want.get(k, 0) != got.get(k, 0)} + expected = {a for a, rows in KNOWN_FILED_GAPS.items() + if code in rows} + unexplained = {k: v for k, v in diff.items() + if k.split("#")[0] not in expected} + if unexplained: + bad[code] = unexplained + self.assertFalse( + bad, "aggregate rows disagree with the filed export " + "(account: filed weight vs. tree weight): %s" % bad) + + def test_no_row_the_filed_export_reads_is_manual(self): + """A manual row shows 0 until somebody types a figure, while the filed + XML reads the same row off the ledger. s113 and v01 sat like that.""" + manual = sorted( + code for code, ldef in self.defs.items() + if ldef.kind == "manual" and code in self.ref + and any(self.ref[code].values())) + self.assertFalse( + manual, "manual rows the filed export computes: %s" % manual) + + def test_the_result_row_is_not_a_plug(self): + """A.VIII must be read off triedy 5/6, not derived from the sheet. + + As a plug (``s001 - the other passive rows``) it absorbs every mapping + error on the passive side and the sheet still balances, which is how + the 47 divergent rows went unnoticed. It also has to equal VZS r61, + which a plug only does by luck. + """ + result = self.defs[RESULT_ROW] + self.assertEqual(result.kind, "accounts") + self.assertEqual(result.account_formula, "-5,-6") + self.assertEqual(result.basis, "movement", + "the result is the period's movement, not a balance") + + def test_the_generator_reproduces_the_checked_in_file(self): + """The data file is generated; prove it still is. + + A hand edit that the two tests above happen not to catch — a renamed + row, a changed sequence — would otherwise drift away from the script + silently, and the next regeneration would revert it without warning. + """ + import subprocess + import sys + root = os.path.dirname(os.path.dirname(os.path.dirname( + os.path.abspath(__file__)))) + script = os.path.join(root, "tools", "gen_uzpod_v14_version.py") + data = os.path.join(root, "l10n_sk_fs", "data", + "cssk_uzpod_v14_version_data.xml") + if not os.path.exists(script): + self.skipTest("running from an installed copy without tools/") + before = open(data, encoding="utf-8").read() + try: + subprocess.run([sys.executable, script], cwd=root, check=True, + capture_output=True) + after = open(data, encoding="utf-8").read() + finally: + open(data, "w", encoding="utf-8").write(before) + self.assertEqual( + before, after, + "cssk_uzpod_v14_version_data.xml differs from what " + "tools/gen_uzpod_v14_version.py produces — re-run it") + + def test_the_screen_claims_the_same_analytics_as_the_filed_export(self): + """Both matchers must absorb analytics the same way, or they diverge. + + ``022000`` means "022 and its analytics, except those another row + names". Which analytics are "named" is the whole content of that rule, + and the two mappings compute it separately — the export in + ``uzpod14_rows._claimed_analytics``, the statement from the version's + own formulas. They agree only as long as both mean "six-character + codes that are not a main account". + """ + from odoo.addons.l10n_sk_fs.models.uzpod14_rows import CLAIMED_ANALYTICS + st = self.env["cssk.fs.statement"].new({ + "company_id": self.env.company.id, + "version_id": self.version.id, + }) + self.assertEqual(st._cssk_claimed_codes(), CLAIMED_ANALYTICS) + self.assertFalse( + [c for c in CLAIMED_ANALYTICS if c.endswith("000")], + "a main account must not claim against itself: 013000 would then " + "exclude the account coded 013000 and the row would report zero") + + def test_every_row_carries_the_tlacivo_s_own_caption(self): + """Labels come from the form, not from a transcription of it. + + The first pass took them from a plain pdftotext dump, where a two-line + caption bleeds into the row below — "Poskytnuté" sat on r009 and r010 + read "7. preddavky na dlhodobý nehmotný majetok". About a third of the + form was shifted like that, and a shifted caption is worse than a + missing one: it names the row above's account list while looking + perfectly ordinary. + """ + import os + + from odoo.tools.misc import file_path + + path = file_path("l10n_sk_fs/data/uzpod14_row_labels.tsv") + labels = {} + with open(path, encoding="utf-8") as fh: + for line in fh: + code, _, label = line.rstrip("\n").partition("\t") + if code and label: + labels[code] = label + self.assertEqual(len(labels), 206, os.path.basename(path)) + + wrong = {code: (ldef.name, labels.get(code)) + for code, ldef in sorted(self.defs.items()) + if ldef.name != labels.get(code)} + self.assertFalse( + wrong, "rows whose caption is not the tlačivo's " + "(row: shipped vs. tlačivo): %s" % wrong) + + def test_no_caption_carries_another_row_s_text(self): + """The signature of the leak, asserted directly. + + A bled caption shows up two ways: a row marker stranded inside the + text ("Pohľadávky z obchodné1.b. ho styku"), and page furniture that + is not a caption at all. + """ + stranded = {c: d.name for c, d in self.defs.items() + if re.search(r"[a-záäčďéíĺľňóôŕšťúýž]\d{1,2}\.[a-c]?\.?\s", + d.name)} + self.assertFalse(stranded, "a row marker inside a caption: %s" % stranded) + furniture = {c: d.name for c, d in self.defs.items() + if re.search(r"Korekcia|Brutto|Netto|MF SR|Strana \d|" + r"UZPODv14|Á Ä", d.name)} + self.assertFalse(furniture, "page furniture in a caption: %s" % furniture) + + +# Balance-sheet accounts of the l10n_sk chart that deliberately reach no +# Súvaha row. 431000 stands against the unclosed opening balance of triedy +# 5/6 and r100 takes the result from their movement instead (see +# ``l10n.sk.uzpod._cssk_unmapped_report``). 395000 is a vnútorné zúčtovanie +# between a company's own units: it nets to zero in a closed ledger, and a +# balance left on it is an error the unmapped diagnostic should show rather +# than a row should absorb. +NOT_REPORTED = {"395000", "431000"} + + +@tagged("post_install", "-at_install") +class TestUzpodChartCoverage(TransactionCase): + """The mapping against the chart it is used with, account by account. + + Matching the two mappings to each other proves they agree, not that + either is right: in 19.0.1.4.0 both named 16 codes the l10n_sk chart does + not have (091120 where the chart says 091200, 322300 for 322100, ...) and + let 34 of its balance-sheet accounts reach no row at all — 325000 Iné + záväzky, 461000 bank loans, 471200, 473100 issued bonds among them. The + sheet still balanced, because the leftover went nowhere on either side + only when it happened to net to zero. + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + import csv + + from odoo.tools.misc import file_path + + path = file_path("l10n_sk/data/template/account.account-sk.csv") + with open(path, encoding="utf-8") as fh: + cls.chart = {row["code"]: row["name"] for row in csv.DictReader(fh)} + cls.uzpod = cls.env["l10n.sk.uzpod"] + cls.leaves = {} + version = cls.env.ref("l10n_sk_fs.uzpod_v14") + leaf_codes = {d.code for d in version.line_def_ids + if d.kind == "accounts" and d.code.startswith("s")} + leaf_codes.discard(RESULT_ROW) + for code in leaf_codes: + row = _REFERENCE_ROWS[code] + cells = [(row[0], row[1], "")] + if len(row) > 2: + cells.append((row[2], row[3], "korekcia ")) + cls.leaves[code] = cells + + def _landing(self, account): + """``[row label]`` of every leaf cell that takes this account.""" + out = [] + for code, cells in sorted(self.leaves.items()): + for formula, conds, col in cells: + for tok in (formula or "").split(","): + _neg, prefix, _inc, _exc = self.uzpod._cssk_split_token(tok, {}) + if prefix and self.uzpod._cssk_code_matches( + account, prefix, CLAIMED_ANALYTICS): + out.append(col + code) + if any(low <= account < high for low, high, _m, _s in conds): + out.append(col + code) + return out + + def test_every_balance_sheet_account_reaches_a_row(self): + missing = {code: name for code, name in sorted(self.chart.items()) + if code[:1] in "01234" and code not in NOT_REPORTED + and not self._landing(code)} + self.assertFalse(missing, "l10n_sk accounts on no Súvaha row: %s" % missing) + + def test_no_row_names_an_account_the_chart_does_not_have(self): + dead = {} + for code, cells in sorted(self.leaves.items()): + for formula, _conds, col in cells: + for tok in (formula or "").split(","): + _neg, prefix, _inc, _exc = self.uzpod._cssk_split_token(tok, {}) + if prefix and not any( + self.uzpod._cssk_code_matches(a, prefix, CLAIMED_ANALYTICS) + for a in self.chart): + dead.setdefault(col + code, []).append(tok.strip()) + self.assertFalse(dead, "tokens matching no l10n_sk account: %s" % dead) + + def test_an_account_lands_on_one_row_or_is_gated(self): + """Two rows taking one balance double it, unless the balance sign + decides between them (``TWO_SIDED``, and the 341-347 conds).""" + doubled = {} + for account in sorted(self.chart): + if account[:1] not in "01234": + continue + rows = self._landing(account) + if len(rows) > 1 and account not in TWO_SIDED and not ( + "341" <= account < "348"): + doubled[account] = rows + self.assertFalse(doubled, "accounts on two rows at once: %s" % doubled) + + def test_the_export_gates_the_same_accounts_as_the_screen(self): + st = self.env["cssk.fs.statement"].new({ + "company_id": self.env.company.id, + "version_id": self.env.ref("l10n_sk_fs.uzpod_v14").id, + }) + screen = set(st._cssk_two_sided_prefixes()) + # the screen also gates the 341-347 cond tokens the generator emits + self.assertEqual(screen - {"341", "342", "343", "345", "346", "347"}, + set(TWO_SIDED)) + + def test_a_deferred_tax_asset_is_filed_on_the_asset_side_only(self): + balances = {"481000": 100.0} + asset = SUVAHA_AKTIVA[52 - 1] + liability = SUVAHA_PASIVA[117 - 79] + self.assertEqual(self.uzpod._eval(asset[0], asset[1], balances), 100.0) + self.assertEqual(self.uzpod._eval(liability[0], liability[1], balances), 0.0) + balances = {"481000": -40.0} + self.assertEqual(self.uzpod._eval(asset[0], asset[1], balances), 0.0) + self.assertEqual(self.uzpod._eval(liability[0], liability[1], balances), 40.0) + + def test_an_allowance_reduces_the_netto(self): + """Korekcia is subtracted from brutto, so an opravná položka (a credit + balance) must enter it positive. 391336 and 391700 were carried + unsigned and raised r062/r051 by the allowance instead.""" + for row_no, account in ((62, "391336"), (51, "391700")): + row = SUVAHA_AKTIVA[row_no - 1] + korekcia = self.uzpod._eval(row[2], row[3], {account: -30.0}) + self.assertEqual(korekcia, 30.0, "r%03d / %s" % (row_no, account)) diff --git a/l10n_sk_fs/views/cssk_fs_statement_views.xml b/l10n_sk_fs/views/cssk_fs_statement_views.xml new file mode 100644 index 00000000..c5967b91 --- /dev/null +++ b/l10n_sk_fs/views/cssk_fs_statement_views.xml @@ -0,0 +1,20 @@ + + + + + cssk.fs.statement.form.uzpod + cssk.fs.statement + + + + + + + + + + + diff --git a/l10n_sk_fs/views/l10n_sk_poznamky_views.xml b/l10n_sk_fs/views/l10n_sk_poznamky_views.xml new file mode 100644 index 00000000..ae74b6c3 --- /dev/null +++ b/l10n_sk_fs/views/l10n_sk_poznamky_views.xml @@ -0,0 +1,170 @@ + + + + l10n.sk.poznamky.form + l10n.sk.poznamky + +
+
+
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + l10n.sk.poznamky.list + l10n.sk.poznamky + + + + + + + + + + + + Poznámky k závierke + l10n.sk.poznamky + list,form + + + + + + + l10n.sk.uzpod.form + l10n.sk.uzpod + +
+
+
+ +

+ + + + + + + + + + + + + + + + +
+ + +
+
+ + + l10n.sk.uzpod.list + l10n.sk.uzpod + + + + + + + + + + + + + + Účtovná závierka (UZPODv14) + l10n.sk.uzpod + list,form + + + +
diff --git a/l10n_sk_hr_payroll_hlasenie/CHANGELOG.rst b/l10n_sk_hr_payroll_annual_tax_report/CHANGELOG.rst similarity index 83% rename from l10n_sk_hr_payroll_hlasenie/CHANGELOG.rst rename to l10n_sk_hr_payroll_annual_tax_report/CHANGELOG.rst index f7fb9bea..34791e5a 100644 --- a/l10n_sk_hr_payroll_hlasenie/CHANGELOG.rst +++ b/l10n_sk_hr_payroll_annual_tax_report/CHANGELOG.rst @@ -1,6 +1,14 @@ Unreleased ~~~~~~~~~~ +* **Renamed from ``l10n_sk_hr_payroll_hlasenie`` to ``l10n_sk_hr_payroll_annual_tax_report``.** The technical + name is English now, like every other module here; the statutory form keeps its + Slovak name in the module title, in the models and in the data files, exactly as + ``kv_dph``, ``eldp`` and ``pvpoj`` keep theirs. No migration ships with this: on a + database that already has the old module installed, the old record stays behind + with no code and the new module installs fresh, so rename the module record + before upgrading if the old one holds data. + * Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: everything Data Dance authors is AGPL-3 except the modules that depend on Odoo Enterprise, which OEEL forbids from carrying it. What differs between modules diff --git a/l10n_sk_hr_payroll_annual_tax_report/LICENSE b/l10n_sk_hr_payroll_annual_tax_report/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/l10n_sk_hr_payroll_annual_tax_report/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/l10n_sk_hr_payroll_annual_tax_report/__init__.py b/l10n_sk_hr_payroll_annual_tax_report/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_sk_hr_payroll_annual_tax_report/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_sk_hr_payroll_hlasenie/__manifest__.py b/l10n_sk_hr_payroll_annual_tax_report/__manifest__.py similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/__manifest__.py rename to l10n_sk_hr_payroll_annual_tax_report/__manifest__.py diff --git a/l10n_sk_hr_payroll_hlasenie/data/hlasenie_version_data.xml b/l10n_sk_hr_payroll_annual_tax_report/data/hlasenie_version_data.xml similarity index 72% rename from l10n_sk_hr_payroll_hlasenie/data/hlasenie_version_data.xml rename to l10n_sk_hr_payroll_annual_tax_report/data/hlasenie_version_data.xml index d14964ba..aaf7317a 100644 --- a/l10n_sk_hr_payroll_hlasenie/data/hlasenie_version_data.xml +++ b/l10n_sk_hr_payroll_annual_tax_report/data/hlasenie_version_data.xml @@ -7,12 +7,12 @@ 2023-01-01 + ref="l10n_sk_hr_payroll_annual_tax_report.hlasenie_xml"/> dokument - l10n_sk_hr_payroll_hlasenie + l10n_sk_hr_payroll_annual_tax_report data/rh2023.xsd + file="l10n_sk_hr_payroll_annual_tax_report/data/rh2023.xsd"/> diff --git a/l10n_sk_hr_payroll_hlasenie/data/rh2023.xsd b/l10n_sk_hr_payroll_annual_tax_report/data/rh2023.xsd similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/data/rh2023.xsd rename to l10n_sk_hr_payroll_annual_tax_report/data/rh2023.xsd diff --git a/l10n_sk_hr_payroll_annual_tax_report/i18n/l10n_sk_hr_payroll_annual_tax_report.pot b/l10n_sk_hr_payroll_annual_tax_report/i18n/l10n_sk_hr_payroll_annual_tax_report.pot new file mode 100644 index 00000000..fdb8ca35 --- /dev/null +++ b/l10n_sk_hr_payroll_annual_tax_report/i18n/l10n_sk_hr_payroll_annual_tax_report.pot @@ -0,0 +1,392 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_hr_payroll_annual_tax_report +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-07-06 21:40+0000\n" +"PO-Revision-Date: 2026-07-06 21:40+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_res_company__l10n_sk_dic +msgid "" +"10-digit tax identification number assigned by the tax administrator — the " +" element of the monthly tax overview." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_ids +msgid "Activities" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_state +msgid "Activity State" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_type_icon +msgid "Activity Type Icon" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.actions.act_window,name:l10n_sk_hr_payroll_annual_tax_report.action_l10n_sk_hlasenie +msgid "Annual tax reconciliation statement" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model,name:l10n_sk_hr_payroll_annual_tax_report.model_res_company +msgid "Companies" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__company_id +msgid "Company" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#. odoo-python +#: code:addons/l10n_sk_hr_payroll_annual_tax_report/models/l10n_sk_hlasenie.py:0 +msgid "" +"Company '%(company)s' needs a 10-digit DIČ (tax identification number) for " +"the annual tax reconciliation statement element. Set 'DIČ' on the " +"company." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__country_id +msgid "Country" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__create_uid +msgid "Created by" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__create_date +msgid "Created on" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__currency_id +msgid "Currency" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_res_company__l10n_sk_dic +msgid "DIČ (tax identification number)" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__date_from +msgid "Date From" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__date_to +msgid "Date To" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__display_name +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_res_company__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#. odoo-python +#: code:addons/l10n_sk_hr_payroll_annual_tax_report/models/l10n_sk_hlasenie.py:0 +msgid "" +"Employee '%(name)s' needs a 9-10 digit birth number in the Identification " +"No. field for the annual tax reconciliation statement per-employee annex " +"(Part V)." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__line_ids +msgid "Employee declarations" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__lines_count +msgid "Employees" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__submitted_attachment_id +msgid "Filed XML" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__filed_history_ids +msgid "Filed history" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_annual_tax_report.view_l10n_sk_hlasenie_form +msgid "Generate" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__has_message +msgid "Has Message" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__id +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_res_company__id +msgid "ID" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_error +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__write_date +msgid "Last Updated on" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_annual_tax_report.view_l10n_sk_hlasenie_form +msgid "Mark Submitted" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_ids +msgid "Messages" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__month +msgid "Month" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__name +msgid "Name" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_summary +msgid "Next Activity Summary" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_type_id +msgid "Next Activity Type" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__payment_date +msgid "" +"Payday reported to the authority (SK denVyplaty). Defaults to the period " +"end." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__payment_date +msgid "Payment Date" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_annual_tax_report.view_l10n_sk_hlasenie_form +msgid "Reset to Draft" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_user_id +msgid "Responsible User" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.ui.menu,name:l10n_sk_hr_payroll_annual_tax_report.menu_l10n_sk_hlasenie +msgid "SK annual tax reconciliation statement" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model,name:l10n_sk_hr_payroll_annual_tax_report.model_l10n_sk_hlasenie +msgid "SK annual tax reconciliation statement (§ 39 ods. 9)" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_sms_error +msgid "SMS Delivery error" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__state +msgid "State" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__submission_reference +msgid "Submission Reference" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__submitted_date +msgid "Submitted Date" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__version_id +msgid "Version" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__xml_attachment_id +msgid "Xml Attachment" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__year +msgid "Year" +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_res_company__l10n_sk_dic +msgid "" +"10-digit tax identification number assigned by the tax administrator — the " +" element of the annual tax reconciliation statement." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.module.module,summary:base.module_l10n_sk_hr_payroll_annual_tax_report +msgid "" +"Annual Slovak employer income-tax report (Hlásenie o vyúčtovaní dane a o " +"úhrne príjmov zo závislej činnosti, § 39 ods. 9) for the Finančná správa — " +"aggregate wrap-up + full per-employee annex (Časť V), XML validated against " +"the official rh2023 schema. Engine-neutral (payroll or the hr_payroll " +"engine)." +msgstr "" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.module.module,shortdesc:base.module_l10n_sk_hr_payroll_annual_tax_report +msgid "Slovakia — Hlásenie o vyúčtovaní dane (annual)" +msgstr "" diff --git a/l10n_sk_hr_payroll_annual_tax_report/i18n/sk.po b/l10n_sk_hr_payroll_annual_tax_report/i18n/sk.po new file mode 100644 index 00000000..d03779f7 --- /dev/null +++ b/l10n_sk_hr_payroll_annual_tax_report/i18n/sk.po @@ -0,0 +1,414 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_hr_payroll_annual_tax_report +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-07-06 21:45+0000\n" +"PO-Revision-Date: 2026-07-06 21:45+0000\n" +"Last-Translator: \n" +"Language-Team: Slovak (https://www.transifex.com/)\n" +"Language: sk_SK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_res_company__l10n_sk_dic +msgid "" +"10-digit tax identification number assigned by the tax administrator — the " +" element of the monthly tax overview." +msgstr "" +"10-miestne daňové identifikačné číslo pridelené správcom dane — element " +" mesačného prehľadu." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_needaction +msgid "Action Needed" +msgstr "Potrebná akcia" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_ids +msgid "Activities" +msgstr "Aktivity" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Označenie aktivity výnimky" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_state +msgid "Activity State" +msgstr "Stav aktivity" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_type_icon +msgid "Activity Type Icon" +msgstr "Ikona typu aktivity" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.actions.act_window,name:l10n_sk_hr_payroll_annual_tax_report.action_l10n_sk_hlasenie +msgid "Annual tax reconciliation statement" +msgstr "Hlásenie o vyúčtovaní dane" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_attachment_count +msgid "Attachment Count" +msgstr "Počet príloh" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model,name:l10n_sk_hr_payroll_annual_tax_report.model_res_company +msgid "Companies" +msgstr "Spoločnosti" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__company_id +msgid "Company" +msgstr "Spoločnosť" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#. odoo-python +#: code:addons/l10n_sk_hr_payroll_annual_tax_report/models/l10n_sk_hlasenie.py:0 +msgid "" +"Company '%(company)s' needs a 10-digit DIČ (tax identification number) for " +"the annual tax reconciliation statement element. Set 'DIČ' on the " +"company." +msgstr "" +"Spoločnosť '%(company)s' potrebuje 10-miestne DIČ (daňové identifikačné " +"číslo) pre element hlásenia o vyúčtovaní dane. Nastavte 'DIČ' na " +"spoločnosti." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__country_id +msgid "Country" +msgstr "Krajina" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__create_uid +msgid "Created by" +msgstr "Vytvoril(a)" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__create_date +msgid "Created on" +msgstr "Vytvorené dňa" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__currency_id +msgid "Currency" +msgstr "Mena" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_res_company__l10n_sk_dic +msgid "DIČ (tax identification number)" +msgstr "DIČ (daňové identifikačné číslo)" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__date_from +msgid "Date From" +msgstr "Dátum od" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__date_to +msgid "Date To" +msgstr "Dátum do" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__display_name +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_res_company__display_name +msgid "Display Name" +msgstr "Zobrazovaný názov" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#. odoo-python +#: code:addons/l10n_sk_hr_payroll_annual_tax_report/models/l10n_sk_hlasenie.py:0 +msgid "" +"Employee '%(name)s' needs a 9-10 digit birth number in the Identification " +"No. field for the annual tax reconciliation statement per-employee annex " +"(Part V)." +msgstr "" +"Zamestnanec '%(name)s' potrebuje 9- až 10-miestne rodné číslo v poli " +"Identifikačné č. pre prílohu hlásenia o vyúčtovaní dane za zamestnancov " +"(Časť V)." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__line_ids +msgid "Employee declarations" +msgstr "Hlásenia zamestnancov" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__lines_count +msgid "Employees" +msgstr "Zamestnanci" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__submitted_attachment_id +msgid "Filed XML" +msgstr "Podané XML" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__filed_history_ids +msgid "Filed history" +msgstr "História podaní" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_follower_ids +msgid "Followers" +msgstr "Odberatelia" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_partner_ids +msgid "Followers (Partners)" +msgstr "Odberatelia (partneri)" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "Ikona písma font awesome, napr. fa-tasks" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_annual_tax_report.view_l10n_sk_hlasenie_form +msgid "Generate" +msgstr "Generovať" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__has_message +msgid "Has Message" +msgstr "Má správu" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__id +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_res_company__id +msgid "ID" +msgstr "ID" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_exception_icon +msgid "Icon" +msgstr "Ikona" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Ikona indikujúca výnimočnú aktivitu." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Ak označené, potom nové správy vyžadujú vašu pozornosť." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_error +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "Ak je začiarknuté, niektoré správy majú chybu doručenia." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_is_follower +msgid "Is Follower" +msgstr "Je odberateľ" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__write_uid +msgid "Last Updated by" +msgstr "Naposledy upravil(a)" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__write_date +msgid "Last Updated on" +msgstr "Naposledy upravené dňa" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_annual_tax_report.view_l10n_sk_hlasenie_form +msgid "Mark Submitted" +msgstr "Označiť ako odoslané" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_error +msgid "Message Delivery error" +msgstr "Chyba doručenia správy" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_ids +msgid "Messages" +msgstr "Správy" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__month +msgid "Month" +msgstr "Mesiac" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Termín mojej aktivity" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__name +msgid "Name" +msgstr "Názov" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Termín ďalšej aktivity" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_summary +msgid "Next Activity Summary" +msgstr "Zhrnutie ďalšej aktivity" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_type_id +msgid "Next Activity Type" +msgstr "Typ ďalšej aktivity" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_needaction_counter +msgid "Number of Actions" +msgstr "Počet akcií" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_error_counter +msgid "Number of errors" +msgstr "Počet chýb" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Počet správ vyžadujúcich akciu" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Počet správ s chybou doručenia" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__payment_date +msgid "" +"Payday reported to the authority (SK denVyplaty). Defaults to the period " +"end." +msgstr "" +"Dátum výplaty hlásený úradu (SK denVyplaty). Predvolene je to koniec " +"obdobia." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__payment_date +msgid "Payment Date" +msgstr "Dátum výplaty" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_annual_tax_report.view_l10n_sk_hlasenie_form +msgid "Reset to Draft" +msgstr "Vrátiť do konceptu" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_user_id +msgid "Responsible User" +msgstr "Zodpovedný užívateľ" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.ui.menu,name:l10n_sk_hr_payroll_annual_tax_report.menu_l10n_sk_hlasenie +msgid "SK annual tax reconciliation statement" +msgstr "SK hlásenie o vyúčtovaní dane" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model,name:l10n_sk_hr_payroll_annual_tax_report.model_l10n_sk_hlasenie +msgid "SK annual tax reconciliation statement (§ 39 ods. 9)" +msgstr "SK hlásenie o vyúčtovaní dane (§ 39 ods. 9)" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__message_has_sms_error +msgid "SMS Delivery error" +msgstr "Chyba doručenia SMS" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__state +msgid "State" +msgstr "Stav" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" +"Stav založený na aktivitách\n" +"Po termíne: termín vypršal\n" +"Dnešné: termín aktivity je dnes\n" +"Budúce: budúce aktivity." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__submission_reference +msgid "Submission Reference" +msgstr "Referencia podania" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__submitted_date +msgid "Submitted Date" +msgstr "Dátum odoslania" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "Typ výnimočnej aktivity v zázname." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__version_id +msgid "Version" +msgstr "Verzia" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__website_message_ids +msgid "Website Messages" +msgstr "Správy webstránok" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__website_message_ids +msgid "Website communication history" +msgstr "História komunikácie webstránok" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__xml_attachment_id +msgid "Xml Attachment" +msgstr "Príloha XML" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_annual_tax_report.field_l10n_sk_hlasenie__year +msgid "Year" +msgstr "Rok" + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.model.fields,help:l10n_sk_hr_payroll_annual_tax_report.field_res_company__l10n_sk_dic +msgid "" +"10-digit tax identification number assigned by the tax administrator — the " +" element of the annual tax reconciliation statement." +msgstr "" +"10-miestne daňové identifikačné číslo pridelené správcom dane — " +"prvok hlásenia o ročnom zúčtovaní dane." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.module.module,summary:base.module_l10n_sk_hr_payroll_annual_tax_report +msgid "" +"Annual Slovak employer income-tax report (Hlásenie o vyúčtovaní dane a o " +"úhrne príjmov zo závislej činnosti, § 39 ods. 9) for the Finančná správa — " +"aggregate wrap-up + full per-employee annex (Časť V), XML validated against " +"the official rh2023 schema. Engine-neutral (payroll or the hr_payroll " +"engine)." +msgstr "" +"Ročné hlásenie zamestnávateľa o vyúčtovaní dane a o úhrne príjmov zo " +"závislej činnosti (§ 39 ods. 9) pre Finančnú správu — súhrnné " +"zúčtovanie + úplná príloha za jednotlivých zamestnancov (Časť V), " +"XML validované voči oficiálnej schéme rh2023. Nezávislé od mzdového " +"enginu (payroll alebo engine hr_payroll)." + +#. module: l10n_sk_hr_payroll_annual_tax_report +#: model:ir.module.module,shortdesc:base.module_l10n_sk_hr_payroll_annual_tax_report +msgid "Slovakia — Hlásenie o vyúčtovaní dane (annual)" +msgstr "Slovensko — Hlásenie o vyúčtovaní dane (ročné)" \ No newline at end of file diff --git a/l10n_sk_hr_payroll_hlasenie/models/__init__.py b/l10n_sk_hr_payroll_annual_tax_report/models/__init__.py similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/models/__init__.py rename to l10n_sk_hr_payroll_annual_tax_report/models/__init__.py diff --git a/l10n_sk_hr_payroll_hlasenie/models/l10n_sk_hlasenie.py b/l10n_sk_hr_payroll_annual_tax_report/models/l10n_sk_hlasenie.py similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/models/l10n_sk_hlasenie.py rename to l10n_sk_hr_payroll_annual_tax_report/models/l10n_sk_hlasenie.py diff --git a/l10n_sk_hr_payroll_hlasenie/readme/DESCRIPTION.rst b/l10n_sk_hr_payroll_annual_tax_report/readme/DESCRIPTION.rst similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/readme/DESCRIPTION.rst rename to l10n_sk_hr_payroll_annual_tax_report/readme/DESCRIPTION.rst diff --git a/l10n_sk_hr_payroll_hlasenie/readme/ROADMAP.rst b/l10n_sk_hr_payroll_annual_tax_report/readme/ROADMAP.rst similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/readme/ROADMAP.rst rename to l10n_sk_hr_payroll_annual_tax_report/readme/ROADMAP.rst diff --git a/l10n_sk_hr_payroll_hlasenie/report/hlasenie_templates.xml b/l10n_sk_hr_payroll_annual_tax_report/report/hlasenie_templates.xml similarity index 97% rename from l10n_sk_hr_payroll_hlasenie/report/hlasenie_templates.xml rename to l10n_sk_hr_payroll_annual_tax_report/report/hlasenie_templates.xml index 58022fe5..43b26787 100644 --- a/l10n_sk_hr_payroll_hlasenie/report/hlasenie_templates.xml +++ b/l10n_sk_hr_payroll_annual_tax_report/report/hlasenie_templates.xml @@ -149,12 +149,12 @@ - + - + diff --git a/l10n_sk_hr_payroll_hlasenie/security/ir.model.access.csv b/l10n_sk_hr_payroll_annual_tax_report/security/ir.model.access.csv similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/security/ir.model.access.csv rename to l10n_sk_hr_payroll_annual_tax_report/security/ir.model.access.csv diff --git a/l10n_sk_hr_payroll_hlasenie/tests/__init__.py b/l10n_sk_hr_payroll_annual_tax_report/tests/__init__.py similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/tests/__init__.py rename to l10n_sk_hr_payroll_annual_tax_report/tests/__init__.py diff --git a/l10n_sk_hr_payroll_hlasenie/tests/test_hlasenie.py b/l10n_sk_hr_payroll_annual_tax_report/tests/test_hlasenie.py similarity index 98% rename from l10n_sk_hr_payroll_hlasenie/tests/test_hlasenie.py rename to l10n_sk_hr_payroll_annual_tax_report/tests/test_hlasenie.py index 1fdde292..3063cbad 100644 --- a/l10n_sk_hr_payroll_hlasenie/tests/test_hlasenie.py +++ b/l10n_sk_hr_payroll_annual_tax_report/tests/test_hlasenie.py @@ -149,7 +149,7 @@ def test_full_year_generate_and_validate(self): xml_bytes = base64.b64decode(decl.xml_attachment_id.datas) schema = etree.XMLSchema(etree.parse( - file_path("l10n_sk_hr_payroll_hlasenie/data/rh2023.xsd"))) + file_path("l10n_sk_hr_payroll_annual_tax_report/data/rh2023.xsd"))) root = etree.fromstring(xml_bytes) schema.assertValid(root) # strict XSD validation @@ -196,7 +196,7 @@ def test_odd_employee_count_pads_with_empty_column(self): xml_bytes = base64.b64decode(decl.xml_attachment_id.datas) schema = etree.XMLSchema(etree.parse( - file_path("l10n_sk_hr_payroll_hlasenie/data/rh2023.xsd"))) + file_path("l10n_sk_hr_payroll_annual_tax_report/data/rh2023.xsd"))) root = etree.fromstring(xml_bytes) schema.assertValid(root) self.assertEqual(root.findtext("hlavicka/pocetZamC5"), "1") diff --git a/l10n_sk_hr_payroll_hlasenie/views/l10n_sk_hlasenie_views.xml b/l10n_sk_hr_payroll_annual_tax_report/views/l10n_sk_hlasenie_views.xml similarity index 100% rename from l10n_sk_hr_payroll_hlasenie/views/l10n_sk_hlasenie_views.xml rename to l10n_sk_hr_payroll_annual_tax_report/views/l10n_sk_hlasenie_views.xml diff --git a/l10n_sk_hr_payroll_hlasenie/i18n/l10n_sk_hr_payroll_hlasenie.pot b/l10n_sk_hr_payroll_hlasenie/i18n/l10n_sk_hr_payroll_hlasenie.pot deleted file mode 100644 index 56bd3e03..00000000 --- a/l10n_sk_hr_payroll_hlasenie/i18n/l10n_sk_hr_payroll_hlasenie.pot +++ /dev/null @@ -1,392 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * l10n_sk_hr_payroll_hlasenie -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 19.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-06 21:40+0000\n" -"PO-Revision-Date: 2026-07-06 21:40+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_res_company__l10n_sk_dic -msgid "" -"10-digit tax identification number assigned by the tax administrator — the " -" element of the monthly tax overview." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_needaction -msgid "Action Needed" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_ids -msgid "Activities" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_exception_decoration -msgid "Activity Exception Decoration" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_state -msgid "Activity State" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_type_icon -msgid "Activity Type Icon" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.actions.act_window,name:l10n_sk_hr_payroll_hlasenie.action_l10n_sk_hlasenie -msgid "Annual tax reconciliation statement" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_attachment_count -msgid "Attachment Count" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model,name:l10n_sk_hr_payroll_hlasenie.model_res_company -msgid "Companies" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__company_id -msgid "Company" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#. odoo-python -#: code:addons/l10n_sk_hr_payroll_hlasenie/models/l10n_sk_hlasenie.py:0 -msgid "" -"Company '%(company)s' needs a 10-digit DIČ (tax identification number) for " -"the annual tax reconciliation statement element. Set 'DIČ' on the " -"company." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__country_id -msgid "Country" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__create_uid -msgid "Created by" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__create_date -msgid "Created on" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__currency_id -msgid "Currency" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_res_company__l10n_sk_dic -msgid "DIČ (tax identification number)" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__date_from -msgid "Date From" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__date_to -msgid "Date To" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__display_name -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_res_company__display_name -msgid "Display Name" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#. odoo-python -#: code:addons/l10n_sk_hr_payroll_hlasenie/models/l10n_sk_hlasenie.py:0 -msgid "" -"Employee '%(name)s' needs a 9-10 digit birth number in the Identification " -"No. field for the annual tax reconciliation statement per-employee annex " -"(Part V)." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__line_ids -msgid "Employee declarations" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__lines_count -msgid "Employees" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__submitted_attachment_id -msgid "Filed XML" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__filed_history_ids -msgid "Filed history" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_follower_ids -msgid "Followers" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_partner_ids -msgid "Followers (Partners)" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_type_icon -msgid "Font awesome icon e.g. fa-tasks" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_hlasenie.view_l10n_sk_hlasenie_form -msgid "Generate" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__has_message -msgid "Has Message" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__id -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_res_company__id -msgid "ID" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_exception_icon -msgid "Icon" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_exception_icon -msgid "Icon to indicate an exception activity." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_needaction -msgid "If checked, new messages require your attention." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_error -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_sms_error -msgid "If checked, some messages have a delivery error." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_is_follower -msgid "Is Follower" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__write_date -msgid "Last Updated on" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_hlasenie.view_l10n_sk_hlasenie_form -msgid "Mark Submitted" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_error -msgid "Message Delivery error" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_ids -msgid "Messages" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__month -msgid "Month" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__my_activity_date_deadline -msgid "My Activity Deadline" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__name -msgid "Name" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_date_deadline -msgid "Next Activity Deadline" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_summary -msgid "Next Activity Summary" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_type_id -msgid "Next Activity Type" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_needaction_counter -msgid "Number of Actions" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_error_counter -msgid "Number of errors" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_needaction_counter -msgid "Number of messages requiring action" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_error_counter -msgid "Number of messages with delivery error" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__payment_date -msgid "" -"Payday reported to the authority (SK denVyplaty). Defaults to the period " -"end." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__payment_date -msgid "Payment Date" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_hlasenie.view_l10n_sk_hlasenie_form -msgid "Reset to Draft" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_user_id -msgid "Responsible User" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.ui.menu,name:l10n_sk_hr_payroll_hlasenie.menu_l10n_sk_hlasenie -msgid "SK annual tax reconciliation statement" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model,name:l10n_sk_hr_payroll_hlasenie.model_l10n_sk_hlasenie -msgid "SK annual tax reconciliation statement (§ 39 ods. 9)" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_sms_error -msgid "SMS Delivery error" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__state -msgid "State" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_state -msgid "" -"Status based on activities\n" -"Overdue: Due date is already passed\n" -"Today: Activity date is today\n" -"Planned: Future activities." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__submission_reference -msgid "Submission Reference" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__submitted_date -msgid "Submitted Date" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_exception_decoration -msgid "Type of the exception activity on record." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__version_id -msgid "Version" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__website_message_ids -msgid "Website Messages" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__website_message_ids -msgid "Website communication history" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__xml_attachment_id -msgid "Xml Attachment" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__year -msgid "Year" -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_res_company__l10n_sk_dic -msgid "" -"10-digit tax identification number assigned by the tax administrator — the " -" element of the annual tax reconciliation statement." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.module.module,summary:base.module_l10n_sk_hr_payroll_hlasenie -msgid "" -"Annual Slovak employer income-tax report (Hlásenie o vyúčtovaní dane a o " -"úhrne príjmov zo závislej činnosti, § 39 ods. 9) for the Finančná správa — " -"aggregate wrap-up + full per-employee annex (Časť V), XML validated against " -"the official rh2023 schema. Engine-neutral (payroll or the hr_payroll " -"engine)." -msgstr "" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.module.module,shortdesc:base.module_l10n_sk_hr_payroll_hlasenie -msgid "Slovakia — Hlásenie o vyúčtovaní dane (annual)" -msgstr "" diff --git a/l10n_sk_hr_payroll_hlasenie/i18n/sk.po b/l10n_sk_hr_payroll_hlasenie/i18n/sk.po deleted file mode 100644 index 937a3dd1..00000000 --- a/l10n_sk_hr_payroll_hlasenie/i18n/sk.po +++ /dev/null @@ -1,414 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * l10n_sk_hr_payroll_hlasenie -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 19.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-06 21:45+0000\n" -"PO-Revision-Date: 2026-07-06 21:45+0000\n" -"Last-Translator: \n" -"Language-Team: Slovak (https://www.transifex.com/)\n" -"Language: sk_SK\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_res_company__l10n_sk_dic -msgid "" -"10-digit tax identification number assigned by the tax administrator — the " -" element of the monthly tax overview." -msgstr "" -"10-miestne daňové identifikačné číslo pridelené správcom dane — element " -" mesačného prehľadu." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_needaction -msgid "Action Needed" -msgstr "Potrebná akcia" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_ids -msgid "Activities" -msgstr "Aktivity" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_exception_decoration -msgid "Activity Exception Decoration" -msgstr "Označenie aktivity výnimky" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_state -msgid "Activity State" -msgstr "Stav aktivity" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_type_icon -msgid "Activity Type Icon" -msgstr "Ikona typu aktivity" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.actions.act_window,name:l10n_sk_hr_payroll_hlasenie.action_l10n_sk_hlasenie -msgid "Annual tax reconciliation statement" -msgstr "Hlásenie o vyúčtovaní dane" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_attachment_count -msgid "Attachment Count" -msgstr "Počet príloh" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model,name:l10n_sk_hr_payroll_hlasenie.model_res_company -msgid "Companies" -msgstr "Spoločnosti" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__company_id -msgid "Company" -msgstr "Spoločnosť" - -#. module: l10n_sk_hr_payroll_hlasenie -#. odoo-python -#: code:addons/l10n_sk_hr_payroll_hlasenie/models/l10n_sk_hlasenie.py:0 -msgid "" -"Company '%(company)s' needs a 10-digit DIČ (tax identification number) for " -"the annual tax reconciliation statement element. Set 'DIČ' on the " -"company." -msgstr "" -"Spoločnosť '%(company)s' potrebuje 10-miestne DIČ (daňové identifikačné " -"číslo) pre element hlásenia o vyúčtovaní dane. Nastavte 'DIČ' na " -"spoločnosti." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__country_id -msgid "Country" -msgstr "Krajina" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__create_uid -msgid "Created by" -msgstr "Vytvoril(a)" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__create_date -msgid "Created on" -msgstr "Vytvorené dňa" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__currency_id -msgid "Currency" -msgstr "Mena" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_res_company__l10n_sk_dic -msgid "DIČ (tax identification number)" -msgstr "DIČ (daňové identifikačné číslo)" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__date_from -msgid "Date From" -msgstr "Dátum od" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__date_to -msgid "Date To" -msgstr "Dátum do" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__display_name -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_res_company__display_name -msgid "Display Name" -msgstr "Zobrazovaný názov" - -#. module: l10n_sk_hr_payroll_hlasenie -#. odoo-python -#: code:addons/l10n_sk_hr_payroll_hlasenie/models/l10n_sk_hlasenie.py:0 -msgid "" -"Employee '%(name)s' needs a 9-10 digit birth number in the Identification " -"No. field for the annual tax reconciliation statement per-employee annex " -"(Part V)." -msgstr "" -"Zamestnanec '%(name)s' potrebuje 9- až 10-miestne rodné číslo v poli " -"Identifikačné č. pre prílohu hlásenia o vyúčtovaní dane za zamestnancov " -"(Časť V)." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__line_ids -msgid "Employee declarations" -msgstr "Hlásenia zamestnancov" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__lines_count -msgid "Employees" -msgstr "Zamestnanci" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__submitted_attachment_id -msgid "Filed XML" -msgstr "Podané XML" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__filed_history_ids -msgid "Filed history" -msgstr "História podaní" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_follower_ids -msgid "Followers" -msgstr "Odberatelia" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_partner_ids -msgid "Followers (Partners)" -msgstr "Odberatelia (partneri)" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_type_icon -msgid "Font awesome icon e.g. fa-tasks" -msgstr "Ikona písma font awesome, napr. fa-tasks" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_hlasenie.view_l10n_sk_hlasenie_form -msgid "Generate" -msgstr "Generovať" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__has_message -msgid "Has Message" -msgstr "Má správu" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__id -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_res_company__id -msgid "ID" -msgstr "ID" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_exception_icon -msgid "Icon" -msgstr "Ikona" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_exception_icon -msgid "Icon to indicate an exception activity." -msgstr "Ikona indikujúca výnimočnú aktivitu." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_needaction -msgid "If checked, new messages require your attention." -msgstr "Ak označené, potom nové správy vyžadujú vašu pozornosť." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_error -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_sms_error -msgid "If checked, some messages have a delivery error." -msgstr "Ak je začiarknuté, niektoré správy majú chybu doručenia." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_is_follower -msgid "Is Follower" -msgstr "Je odberateľ" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__write_uid -msgid "Last Updated by" -msgstr "Naposledy upravil(a)" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__write_date -msgid "Last Updated on" -msgstr "Naposledy upravené dňa" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_hlasenie.view_l10n_sk_hlasenie_form -msgid "Mark Submitted" -msgstr "Označiť ako odoslané" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_error -msgid "Message Delivery error" -msgstr "Chyba doručenia správy" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_ids -msgid "Messages" -msgstr "Správy" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__month -msgid "Month" -msgstr "Mesiac" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__my_activity_date_deadline -msgid "My Activity Deadline" -msgstr "Termín mojej aktivity" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__name -msgid "Name" -msgstr "Názov" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_date_deadline -msgid "Next Activity Deadline" -msgstr "Termín ďalšej aktivity" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_summary -msgid "Next Activity Summary" -msgstr "Zhrnutie ďalšej aktivity" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_type_id -msgid "Next Activity Type" -msgstr "Typ ďalšej aktivity" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_needaction_counter -msgid "Number of Actions" -msgstr "Počet akcií" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_error_counter -msgid "Number of errors" -msgstr "Počet chýb" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_needaction_counter -msgid "Number of messages requiring action" -msgstr "Počet správ vyžadujúcich akciu" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_error_counter -msgid "Number of messages with delivery error" -msgstr "Počet správ s chybou doručenia" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__payment_date -msgid "" -"Payday reported to the authority (SK denVyplaty). Defaults to the period " -"end." -msgstr "" -"Dátum výplaty hlásený úradu (SK denVyplaty). Predvolene je to koniec " -"obdobia." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__payment_date -msgid "Payment Date" -msgstr "Dátum výplaty" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_hlasenie.view_l10n_sk_hlasenie_form -msgid "Reset to Draft" -msgstr "Vrátiť do konceptu" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_user_id -msgid "Responsible User" -msgstr "Zodpovedný užívateľ" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.ui.menu,name:l10n_sk_hr_payroll_hlasenie.menu_l10n_sk_hlasenie -msgid "SK annual tax reconciliation statement" -msgstr "SK hlásenie o vyúčtovaní dane" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model,name:l10n_sk_hr_payroll_hlasenie.model_l10n_sk_hlasenie -msgid "SK annual tax reconciliation statement (§ 39 ods. 9)" -msgstr "SK hlásenie o vyúčtovaní dane (§ 39 ods. 9)" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__message_has_sms_error -msgid "SMS Delivery error" -msgstr "Chyba doručenia SMS" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__state -msgid "State" -msgstr "Stav" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_state -msgid "" -"Status based on activities\n" -"Overdue: Due date is already passed\n" -"Today: Activity date is today\n" -"Planned: Future activities." -msgstr "" -"Stav založený na aktivitách\n" -"Po termíne: termín vypršal\n" -"Dnešné: termín aktivity je dnes\n" -"Budúce: budúce aktivity." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__submission_reference -msgid "Submission Reference" -msgstr "Referencia podania" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__submitted_date -msgid "Submitted Date" -msgstr "Dátum odoslania" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__activity_exception_decoration -msgid "Type of the exception activity on record." -msgstr "Typ výnimočnej aktivity v zázname." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__version_id -msgid "Version" -msgstr "Verzia" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__website_message_ids -msgid "Website Messages" -msgstr "Správy webstránok" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__website_message_ids -msgid "Website communication history" -msgstr "História komunikácie webstránok" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__xml_attachment_id -msgid "Xml Attachment" -msgstr "Príloha XML" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_hlasenie.field_l10n_sk_hlasenie__year -msgid "Year" -msgstr "Rok" - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.model.fields,help:l10n_sk_hr_payroll_hlasenie.field_res_company__l10n_sk_dic -msgid "" -"10-digit tax identification number assigned by the tax administrator — the " -" element of the annual tax reconciliation statement." -msgstr "" -"10-miestne daňové identifikačné číslo pridelené správcom dane — " -"prvok hlásenia o ročnom zúčtovaní dane." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.module.module,summary:base.module_l10n_sk_hr_payroll_hlasenie -msgid "" -"Annual Slovak employer income-tax report (Hlásenie o vyúčtovaní dane a o " -"úhrne príjmov zo závislej činnosti, § 39 ods. 9) for the Finančná správa — " -"aggregate wrap-up + full per-employee annex (Časť V), XML validated against " -"the official rh2023 schema. Engine-neutral (payroll or the hr_payroll " -"engine)." -msgstr "" -"Ročné hlásenie zamestnávateľa o vyúčtovaní dane a o úhrne príjmov zo " -"závislej činnosti (§ 39 ods. 9) pre Finančnú správu — súhrnné " -"zúčtovanie + úplná príloha za jednotlivých zamestnancov (Časť V), " -"XML validované voči oficiálnej schéme rh2023. Nezávislé od mzdového " -"enginu (payroll alebo engine hr_payroll)." - -#. module: l10n_sk_hr_payroll_hlasenie -#: model:ir.module.module,shortdesc:base.module_l10n_sk_hr_payroll_hlasenie -msgid "Slovakia — Hlásenie o vyúčtovaní dane (annual)" -msgstr "Slovensko — Hlásenie o vyúčtovaní dane (ročné)" \ No newline at end of file diff --git a/l10n_sk_hr_payroll_prehlad/CHANGELOG.rst b/l10n_sk_hr_payroll_monthly_tax_overview/CHANGELOG.rst similarity index 83% rename from l10n_sk_hr_payroll_prehlad/CHANGELOG.rst rename to l10n_sk_hr_payroll_monthly_tax_overview/CHANGELOG.rst index 6580dc08..302f331f 100644 --- a/l10n_sk_hr_payroll_prehlad/CHANGELOG.rst +++ b/l10n_sk_hr_payroll_monthly_tax_overview/CHANGELOG.rst @@ -5,6 +5,14 @@ Changelog Unreleased ========== +* **Renamed from ``l10n_sk_hr_payroll_prehlad`` to ``l10n_sk_hr_payroll_monthly_tax_overview``.** The technical + name is English now, like every other module here; the statutory form keeps its + Slovak name in the module title, in the models and in the data files, exactly as + ``kv_dph``, ``eldp`` and ``pvpoj`` keep theirs. No migration ships with this: on a + database that already has the old module installed, the old record stays behind + with no code and the new module installs fresh, so rename the module record + before upgrading if the old one holds data. + * Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: everything Data Dance authors is AGPL-3 except the modules that depend on Odoo Enterprise, which OEEL forbids from carrying it. What differs between modules diff --git a/l10n_sk_hr_payroll_monthly_tax_overview/LICENSE b/l10n_sk_hr_payroll_monthly_tax_overview/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/l10n_sk_hr_payroll_monthly_tax_overview/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/l10n_sk_hr_payroll_monthly_tax_overview/__init__.py b/l10n_sk_hr_payroll_monthly_tax_overview/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_sk_hr_payroll_monthly_tax_overview/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_sk_hr_payroll_prehlad/__manifest__.py b/l10n_sk_hr_payroll_monthly_tax_overview/__manifest__.py similarity index 100% rename from l10n_sk_hr_payroll_prehlad/__manifest__.py rename to l10n_sk_hr_payroll_monthly_tax_overview/__manifest__.py diff --git a/l10n_sk_hr_payroll_prehlad/data/prehlad2026.xsd b/l10n_sk_hr_payroll_monthly_tax_overview/data/prehlad2026.xsd similarity index 100% rename from l10n_sk_hr_payroll_prehlad/data/prehlad2026.xsd rename to l10n_sk_hr_payroll_monthly_tax_overview/data/prehlad2026.xsd diff --git a/l10n_sk_hr_payroll_prehlad/data/prehlad_version_data.xml b/l10n_sk_hr_payroll_monthly_tax_overview/data/prehlad_version_data.xml similarity index 70% rename from l10n_sk_hr_payroll_prehlad/data/prehlad_version_data.xml rename to l10n_sk_hr_payroll_monthly_tax_overview/data/prehlad_version_data.xml index 372faf46..6ec2196d 100644 --- a/l10n_sk_hr_payroll_prehlad/data/prehlad_version_data.xml +++ b/l10n_sk_hr_payroll_monthly_tax_overview/data/prehlad_version_data.xml @@ -7,12 +7,12 @@ 2026-01-01 + ref="l10n_sk_hr_payroll_monthly_tax_overview.prehlad_xml"/> dokument - l10n_sk_hr_payroll_prehlad + l10n_sk_hr_payroll_monthly_tax_overview data/prehlad2026.xsd + file="l10n_sk_hr_payroll_monthly_tax_overview/data/prehlad2026.xsd"/> diff --git a/l10n_sk_hr_payroll_monthly_tax_overview/i18n/l10n_sk_hr_payroll_monthly_tax_overview.pot b/l10n_sk_hr_payroll_monthly_tax_overview/i18n/l10n_sk_hr_payroll_monthly_tax_overview.pot new file mode 100644 index 00000000..d129b6ff --- /dev/null +++ b/l10n_sk_hr_payroll_monthly_tax_overview/i18n/l10n_sk_hr_payroll_monthly_tax_overview.pot @@ -0,0 +1,360 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_hr_payroll_monthly_tax_overview +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-07-06 21:40+0000\n" +"PO-Revision-Date: 2026-07-06 21:40+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_res_company__l10n_sk_dic +msgid "" +"10-digit tax identification number assigned by the tax administrator — the " +" element of the monthly tax overview." +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_needaction +msgid "Action Needed" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_ids +msgid "Activities" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_state +msgid "Activity State" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_type_icon +msgid "Activity Type Icon" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_attachment_count +msgid "Attachment Count" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model,name:l10n_sk_hr_payroll_monthly_tax_overview.model_res_company +msgid "Companies" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__company_id +msgid "Company" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#. odoo-python +#: code:addons/l10n_sk_hr_payroll_monthly_tax_overview/models/l10n_sk_prehlad.py:0 +msgid "" +"Company '%(company)s' needs a 10-digit DIČ (tax identification number) for " +"the monthly tax overview element. Set 'DIČ' on the company." +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__country_id +msgid "Country" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__create_uid +msgid "Created by" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__create_date +msgid "Created on" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__currency_id +msgid "Currency" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_res_company__l10n_sk_dic +msgid "DIČ (tax identification number)" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__date_from +msgid "Date From" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__date_to +msgid "Date To" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__display_name +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_res_company__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__line_ids +msgid "Employee declarations" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__lines_count +msgid "Employees" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__submitted_attachment_id +msgid "Filed XML" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__filed_history_ids +msgid "Filed history" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_follower_ids +msgid "Followers" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_partner_ids +msgid "Followers (Partners)" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_monthly_tax_overview.view_l10n_sk_prehlad_form +msgid "Generate" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__has_message +msgid "Has Message" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__id +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_res_company__id +msgid "ID" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_exception_icon +msgid "Icon" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_needaction +msgid "If checked, new messages require your attention." +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_error +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_is_follower +msgid "Is Follower" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__write_date +msgid "Last Updated on" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_monthly_tax_overview.view_l10n_sk_prehlad_form +msgid "Mark Submitted" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_error +msgid "Message Delivery error" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_ids +msgid "Messages" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__month +msgid "Month" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.actions.act_window,name:l10n_sk_hr_payroll_monthly_tax_overview.action_l10n_sk_prehlad +msgid "Monthly tax overview" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__name +msgid "Name" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_summary +msgid "Next Activity Summary" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_type_id +msgid "Next Activity Type" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_needaction_counter +msgid "Number of Actions" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_error_counter +msgid "Number of errors" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__payment_date +msgid "" +"Payday reported to the authority (SK denVyplaty). Defaults to the period " +"end." +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__payment_date +msgid "Payment Date" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_monthly_tax_overview.view_l10n_sk_prehlad_form +msgid "Reset to Draft" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_user_id +msgid "Responsible User" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model,name:l10n_sk_hr_payroll_monthly_tax_overview.model_l10n_sk_prehlad +msgid "SK monthly overview of withheld tax advances" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.ui.menu,name:l10n_sk_hr_payroll_monthly_tax_overview.menu_l10n_sk_prehlad +msgid "SK monthly tax overview" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_sms_error +msgid "SMS Delivery error" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__state +msgid "State" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__submission_reference +msgid "Submission Reference" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__submitted_date +msgid "Submitted Date" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__version_id +msgid "Version" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__website_message_ids +msgid "Website Messages" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__website_message_ids +msgid "Website communication history" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__xml_attachment_id +msgid "Xml Attachment" +msgstr "" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__year +msgid "Year" +msgstr "" diff --git a/l10n_sk_hr_payroll_monthly_tax_overview/i18n/sk.po b/l10n_sk_hr_payroll_monthly_tax_overview/i18n/sk.po new file mode 100644 index 00000000..2edd47a3 --- /dev/null +++ b/l10n_sk_hr_payroll_monthly_tax_overview/i18n/sk.po @@ -0,0 +1,371 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_hr_payroll_monthly_tax_overview +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 19.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-07-06 21:45+0000\n" +"PO-Revision-Date: 2026-07-06 21:45+0000\n" +"Last-Translator: \n" +"Language-Team: Slovak (https://www.transifex.com/)\n" +"Language: sk_SK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_res_company__l10n_sk_dic +msgid "" +"10-digit tax identification number assigned by the tax administrator — the " +" element of the monthly tax overview." +msgstr "" +"10-miestne daňové identifikačné číslo pridelené správcom dane — element " +" mesačného prehľadu." + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_needaction +msgid "Action Needed" +msgstr "Potrebná akcia" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_ids +msgid "Activities" +msgstr "Aktivity" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_exception_decoration +msgid "Activity Exception Decoration" +msgstr "Označenie aktivity výnimky" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_state +msgid "Activity State" +msgstr "Stav aktivity" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_type_icon +msgid "Activity Type Icon" +msgstr "Ikona typu aktivity" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_attachment_count +msgid "Attachment Count" +msgstr "Počet príloh" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model,name:l10n_sk_hr_payroll_monthly_tax_overview.model_res_company +msgid "Companies" +msgstr "Spoločnosti" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__company_id +msgid "Company" +msgstr "Spoločnosť" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#. odoo-python +#: code:addons/l10n_sk_hr_payroll_monthly_tax_overview/models/l10n_sk_prehlad.py:0 +msgid "" +"Company '%(company)s' needs a 10-digit DIČ (tax identification number) for " +"the monthly tax overview element. Set 'DIČ' on the company." +msgstr "" +"Spoločnosť '%(company)s' potrebuje 10-miestne DIČ (daňové identifikačné " +"číslo) pre element mesačného prehľadu. Nastavte 'DIČ' na spoločnosti." + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__country_id +msgid "Country" +msgstr "Krajina" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__create_uid +msgid "Created by" +msgstr "Vytvoril(a)" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__create_date +msgid "Created on" +msgstr "Vytvorené dňa" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__currency_id +msgid "Currency" +msgstr "Mena" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_res_company__l10n_sk_dic +msgid "DIČ (tax identification number)" +msgstr "DIČ (daňové identifikačné číslo)" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__date_from +msgid "Date From" +msgstr "Dátum od" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__date_to +msgid "Date To" +msgstr "Dátum do" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__display_name +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_res_company__display_name +msgid "Display Name" +msgstr "Zobrazovaný názov" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__line_ids +msgid "Employee declarations" +msgstr "Hlásenia zamestnancov" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__lines_count +msgid "Employees" +msgstr "Zamestnanci" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__submitted_attachment_id +msgid "Filed XML" +msgstr "Podané XML" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__filed_history_ids +msgid "Filed history" +msgstr "História podaní" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_follower_ids +msgid "Followers" +msgstr "Odberatelia" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_partner_ids +msgid "Followers (Partners)" +msgstr "Odberatelia (partneri)" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_type_icon +msgid "Font awesome icon e.g. fa-tasks" +msgstr "Ikona písma font awesome, napr. fa-tasks" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_monthly_tax_overview.view_l10n_sk_prehlad_form +msgid "Generate" +msgstr "Generovať" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__has_message +msgid "Has Message" +msgstr "Má správu" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__id +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_res_company__id +msgid "ID" +msgstr "ID" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_exception_icon +msgid "Icon" +msgstr "Ikona" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_exception_icon +msgid "Icon to indicate an exception activity." +msgstr "Ikona indikujúca výnimočnú aktivitu." + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_needaction +msgid "If checked, new messages require your attention." +msgstr "Ak označené, potom nové správy vyžadujú vašu pozornosť." + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_error +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_sms_error +msgid "If checked, some messages have a delivery error." +msgstr "Ak je začiarknuté, niektoré správy majú chybu doručenia." + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_is_follower +msgid "Is Follower" +msgstr "Je odberateľ" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__write_uid +msgid "Last Updated by" +msgstr "Naposledy upravil(a)" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__write_date +msgid "Last Updated on" +msgstr "Naposledy upravené dňa" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_monthly_tax_overview.view_l10n_sk_prehlad_form +msgid "Mark Submitted" +msgstr "Označiť ako odoslané" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_error +msgid "Message Delivery error" +msgstr "Chyba doručenia správy" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_ids +msgid "Messages" +msgstr "Správy" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__month +msgid "Month" +msgstr "Mesiac" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.actions.act_window,name:l10n_sk_hr_payroll_monthly_tax_overview.action_l10n_sk_prehlad +msgid "Monthly tax overview" +msgstr "Prehľad o zrazených preddavkoch na daň" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__my_activity_date_deadline +msgid "My Activity Deadline" +msgstr "Termín mojej aktivity" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__name +msgid "Name" +msgstr "Názov" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_date_deadline +msgid "Next Activity Deadline" +msgstr "Termín ďalšej aktivity" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_summary +msgid "Next Activity Summary" +msgstr "Zhrnutie ďalšej aktivity" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_type_id +msgid "Next Activity Type" +msgstr "Typ ďalšej aktivity" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_needaction_counter +msgid "Number of Actions" +msgstr "Počet akcií" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_error_counter +msgid "Number of errors" +msgstr "Počet chýb" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_needaction_counter +msgid "Number of messages requiring action" +msgstr "Počet správ vyžadujúcich akciu" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_error_counter +msgid "Number of messages with delivery error" +msgstr "Počet správ s chybou doručenia" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__payment_date +msgid "" +"Payday reported to the authority (SK denVyplaty). Defaults to the period " +"end." +msgstr "" +"Dátum výplaty hlásený úradu (SK denVyplaty). Predvolene je to koniec " +"obdobia." + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__payment_date +msgid "Payment Date" +msgstr "Dátum výplaty" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_monthly_tax_overview.view_l10n_sk_prehlad_form +msgid "Reset to Draft" +msgstr "Vrátiť do konceptu" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_user_id +msgid "Responsible User" +msgstr "Zodpovedný užívateľ" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model,name:l10n_sk_hr_payroll_monthly_tax_overview.model_l10n_sk_prehlad +msgid "SK monthly overview of withheld tax advances" +msgstr "SK prehľad o zrazených preddavkoch na daň" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.ui.menu,name:l10n_sk_hr_payroll_monthly_tax_overview.menu_l10n_sk_prehlad +msgid "SK monthly tax overview" +msgstr "SK prehľad o zrazených preddavkoch na daň" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__message_has_sms_error +msgid "SMS Delivery error" +msgstr "Chyba doručenia SMS" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__state +msgid "State" +msgstr "Stav" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_state +msgid "" +"Status based on activities\n" +"Overdue: Due date is already passed\n" +"Today: Activity date is today\n" +"Planned: Future activities." +msgstr "" +"Stav založený na aktivitách\n" +"Po termíne: termín vypršal\n" +"Dnešné: termín aktivity je dnes\n" +"Budúce: budúce aktivity." + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__submission_reference +msgid "Submission Reference" +msgstr "Referencia podania" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__submitted_date +msgid "Submitted Date" +msgstr "Dátum odoslania" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__activity_exception_decoration +msgid "Type of the exception activity on record." +msgstr "Typ výnimočnej aktivity v zázname." + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__version_id +msgid "Version" +msgstr "Verzia" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__website_message_ids +msgid "Website Messages" +msgstr "Správy webstránok" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,help:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__website_message_ids +msgid "Website communication history" +msgstr "História komunikácie webstránok" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__xml_attachment_id +msgid "Xml Attachment" +msgstr "Príloha XML" + +#. module: l10n_sk_hr_payroll_monthly_tax_overview +#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_monthly_tax_overview.field_l10n_sk_prehlad__year +msgid "Year" +msgstr "Rok" diff --git a/l10n_sk_hr_payroll_prehlad/models/__init__.py b/l10n_sk_hr_payroll_monthly_tax_overview/models/__init__.py similarity index 100% rename from l10n_sk_hr_payroll_prehlad/models/__init__.py rename to l10n_sk_hr_payroll_monthly_tax_overview/models/__init__.py diff --git a/l10n_sk_hr_payroll_prehlad/models/l10n_sk_prehlad.py b/l10n_sk_hr_payroll_monthly_tax_overview/models/l10n_sk_prehlad.py similarity index 100% rename from l10n_sk_hr_payroll_prehlad/models/l10n_sk_prehlad.py rename to l10n_sk_hr_payroll_monthly_tax_overview/models/l10n_sk_prehlad.py diff --git a/l10n_sk_hr_payroll_prehlad/readme/DESCRIPTION.rst b/l10n_sk_hr_payroll_monthly_tax_overview/readme/DESCRIPTION.rst similarity index 100% rename from l10n_sk_hr_payroll_prehlad/readme/DESCRIPTION.rst rename to l10n_sk_hr_payroll_monthly_tax_overview/readme/DESCRIPTION.rst diff --git a/l10n_sk_hr_payroll_prehlad/readme/ROADMAP.rst b/l10n_sk_hr_payroll_monthly_tax_overview/readme/ROADMAP.rst similarity index 96% rename from l10n_sk_hr_payroll_prehlad/readme/ROADMAP.rst rename to l10n_sk_hr_payroll_monthly_tax_overview/readme/ROADMAP.rst index 5c191cb2..54088e99 100644 --- a/l10n_sk_hr_payroll_prehlad/readme/ROADMAP.rst +++ b/l10n_sk_hr_payroll_monthly_tax_overview/readme/ROADMAP.rst @@ -19,7 +19,7 @@ Not covered ----------- * Only the monthly Prehľad. The annual wrap-up is the Hlásenie — - ``l10n_sk_hr_payroll_hlasenie``. + ``l10n_sk_hr_payroll_annual_tax_report``. * Line mapping is r00↔GROSS, r01↔the income-tax advance, r05↔CHILD_BONUS, r08 = r04 − r05. Employers with additional taxable components must confirm they land in GROSS. diff --git a/l10n_sk_hr_payroll_prehlad/report/prehlad_templates.xml b/l10n_sk_hr_payroll_monthly_tax_overview/report/prehlad_templates.xml similarity index 100% rename from l10n_sk_hr_payroll_prehlad/report/prehlad_templates.xml rename to l10n_sk_hr_payroll_monthly_tax_overview/report/prehlad_templates.xml diff --git a/l10n_sk_hr_payroll_prehlad/security/ir.model.access.csv b/l10n_sk_hr_payroll_monthly_tax_overview/security/ir.model.access.csv similarity index 100% rename from l10n_sk_hr_payroll_prehlad/security/ir.model.access.csv rename to l10n_sk_hr_payroll_monthly_tax_overview/security/ir.model.access.csv diff --git a/l10n_sk_hr_payroll_prehlad/tests/__init__.py b/l10n_sk_hr_payroll_monthly_tax_overview/tests/__init__.py similarity index 100% rename from l10n_sk_hr_payroll_prehlad/tests/__init__.py rename to l10n_sk_hr_payroll_monthly_tax_overview/tests/__init__.py diff --git a/l10n_sk_hr_payroll_prehlad/tests/test_prehlad.py b/l10n_sk_hr_payroll_monthly_tax_overview/tests/test_prehlad.py similarity index 99% rename from l10n_sk_hr_payroll_prehlad/tests/test_prehlad.py rename to l10n_sk_hr_payroll_monthly_tax_overview/tests/test_prehlad.py index 036da54b..478fee79 100644 --- a/l10n_sk_hr_payroll_prehlad/tests/test_prehlad.py +++ b/l10n_sk_hr_payroll_monthly_tax_overview/tests/test_prehlad.py @@ -128,7 +128,7 @@ def test_generate_and_validate(self): xml_bytes = base64.b64decode(decl.xml_attachment_id.datas) schema = etree.XMLSchema(etree.parse( - file_path("l10n_sk_hr_payroll_prehlad/data/prehlad2026.xsd"))) + file_path("l10n_sk_hr_payroll_monthly_tax_overview/data/prehlad2026.xsd"))) root = etree.fromstring(xml_bytes) schema.assertValid(root) diff --git a/l10n_sk_hr_payroll_prehlad/views/l10n_sk_prehlad_views.xml b/l10n_sk_hr_payroll_monthly_tax_overview/views/l10n_sk_prehlad_views.xml similarity index 100% rename from l10n_sk_hr_payroll_prehlad/views/l10n_sk_prehlad_views.xml rename to l10n_sk_hr_payroll_monthly_tax_overview/views/l10n_sk_prehlad_views.xml diff --git a/l10n_sk_hr_payroll_oca/readme/ROADMAP.rst b/l10n_sk_hr_payroll_oca/readme/ROADMAP.rst index 0e70ffca..c2292b18 100644 --- a/l10n_sk_hr_payroll_oca/readme/ROADMAP.rst +++ b/l10n_sk_hr_payroll_oca/readme/ROADMAP.rst @@ -17,7 +17,7 @@ module shipping the official XSD and validating its export against it: (Sociálna poisťovňa), and ``l10n_sk_hr_payroll_vpp`` for dohody. * ``l10n_sk_hr_payroll_health`` — monthly Mesačný výkaz preddavkov na poistné (dávka 514) for the health insurers. -* ``l10n_sk_hr_payroll_prehlad`` / ``l10n_sk_hr_payroll_hlasenie`` — the monthly +* ``l10n_sk_hr_payroll_monthly_tax_overview`` / ``l10n_sk_hr_payroll_annual_tax_report`` — the monthly Prehľad and the annual Hlásenie o vyúčtovaní dane (Finančná správa). * ``l10n_sk_hr_payroll_rlfo`` — RLFO/RLZEC registration events. * ``l10n_sk_hr_payroll_eldp`` — the annual ELDP pension record. diff --git a/l10n_sk_hr_payroll_parity/parity.py b/l10n_sk_hr_payroll_parity/parity.py index 57c6fe7c..a0eda2d6 100644 --- a/l10n_sk_hr_payroll_parity/parity.py +++ b/l10n_sk_hr_payroll_parity/parity.py @@ -381,7 +381,7 @@ "key": "hlasenie_2026", "doc": "Annual income-tax report. r00 and r01 are the yearly sums of " "the same base and advance the monthly Prehľad reports.", - "requires": ["l10n_sk_hr_payroll_hlasenie"], + "requires": ["l10n_sk_hr_payroll_annual_tax_report"], "model": "l10n.sk.hlasenie", "create": {"year": "2026"}, "expected": { @@ -422,7 +422,7 @@ "key": "prehlad_2026_03", "doc": "Monthly income-tax overview. r01 is the concept whose rule " "codes differ per engine, so it is the one most at risk.", - "requires": ["l10n_sk_hr_payroll_prehlad"], + "requires": ["l10n_sk_hr_payroll_monthly_tax_overview"], "model": "l10n.sk.prehlad", "create": {"year": "2026", "month": "3"}, "expected": { diff --git a/l10n_sk_hr_payroll_prehlad/i18n/l10n_sk_hr_payroll_prehlad.pot b/l10n_sk_hr_payroll_prehlad/i18n/l10n_sk_hr_payroll_prehlad.pot deleted file mode 100644 index cf0dd7e9..00000000 --- a/l10n_sk_hr_payroll_prehlad/i18n/l10n_sk_hr_payroll_prehlad.pot +++ /dev/null @@ -1,360 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * l10n_sk_hr_payroll_prehlad -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 19.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-06 21:40+0000\n" -"PO-Revision-Date: 2026-07-06 21:40+0000\n" -"Last-Translator: \n" -"Language-Team: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: \n" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_res_company__l10n_sk_dic -msgid "" -"10-digit tax identification number assigned by the tax administrator — the " -" element of the monthly tax overview." -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_needaction -msgid "Action Needed" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_ids -msgid "Activities" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_exception_decoration -msgid "Activity Exception Decoration" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_state -msgid "Activity State" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_type_icon -msgid "Activity Type Icon" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_attachment_count -msgid "Attachment Count" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model,name:l10n_sk_hr_payroll_prehlad.model_res_company -msgid "Companies" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__company_id -msgid "Company" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#. odoo-python -#: code:addons/l10n_sk_hr_payroll_prehlad/models/l10n_sk_prehlad.py:0 -msgid "" -"Company '%(company)s' needs a 10-digit DIČ (tax identification number) for " -"the monthly tax overview element. Set 'DIČ' on the company." -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__country_id -msgid "Country" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__create_uid -msgid "Created by" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__create_date -msgid "Created on" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__currency_id -msgid "Currency" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_res_company__l10n_sk_dic -msgid "DIČ (tax identification number)" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__date_from -msgid "Date From" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__date_to -msgid "Date To" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__display_name -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_res_company__display_name -msgid "Display Name" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__line_ids -msgid "Employee declarations" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__lines_count -msgid "Employees" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__submitted_attachment_id -msgid "Filed XML" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__filed_history_ids -msgid "Filed history" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_follower_ids -msgid "Followers" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_partner_ids -msgid "Followers (Partners)" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_type_icon -msgid "Font awesome icon e.g. fa-tasks" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_prehlad.view_l10n_sk_prehlad_form -msgid "Generate" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__has_message -msgid "Has Message" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__id -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_res_company__id -msgid "ID" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_exception_icon -msgid "Icon" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_exception_icon -msgid "Icon to indicate an exception activity." -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_needaction -msgid "If checked, new messages require your attention." -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_error -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_sms_error -msgid "If checked, some messages have a delivery error." -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_is_follower -msgid "Is Follower" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__write_uid -msgid "Last Updated by" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__write_date -msgid "Last Updated on" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_prehlad.view_l10n_sk_prehlad_form -msgid "Mark Submitted" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_error -msgid "Message Delivery error" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_ids -msgid "Messages" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__month -msgid "Month" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.actions.act_window,name:l10n_sk_hr_payroll_prehlad.action_l10n_sk_prehlad -msgid "Monthly tax overview" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__my_activity_date_deadline -msgid "My Activity Deadline" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__name -msgid "Name" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_date_deadline -msgid "Next Activity Deadline" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_summary -msgid "Next Activity Summary" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_type_id -msgid "Next Activity Type" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_needaction_counter -msgid "Number of Actions" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_error_counter -msgid "Number of errors" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_needaction_counter -msgid "Number of messages requiring action" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_error_counter -msgid "Number of messages with delivery error" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__payment_date -msgid "" -"Payday reported to the authority (SK denVyplaty). Defaults to the period " -"end." -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__payment_date -msgid "Payment Date" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_prehlad.view_l10n_sk_prehlad_form -msgid "Reset to Draft" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_user_id -msgid "Responsible User" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model,name:l10n_sk_hr_payroll_prehlad.model_l10n_sk_prehlad -msgid "SK monthly overview of withheld tax advances" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.ui.menu,name:l10n_sk_hr_payroll_prehlad.menu_l10n_sk_prehlad -msgid "SK monthly tax overview" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_sms_error -msgid "SMS Delivery error" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__state -msgid "State" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_state -msgid "" -"Status based on activities\n" -"Overdue: Due date is already passed\n" -"Today: Activity date is today\n" -"Planned: Future activities." -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__submission_reference -msgid "Submission Reference" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__submitted_date -msgid "Submitted Date" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_exception_decoration -msgid "Type of the exception activity on record." -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__version_id -msgid "Version" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__website_message_ids -msgid "Website Messages" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__website_message_ids -msgid "Website communication history" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__xml_attachment_id -msgid "Xml Attachment" -msgstr "" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__year -msgid "Year" -msgstr "" diff --git a/l10n_sk_hr_payroll_prehlad/i18n/sk.po b/l10n_sk_hr_payroll_prehlad/i18n/sk.po deleted file mode 100644 index 42e0a004..00000000 --- a/l10n_sk_hr_payroll_prehlad/i18n/sk.po +++ /dev/null @@ -1,371 +0,0 @@ -# Translation of Odoo Server. -# This file contains the translation of the following modules: -# * l10n_sk_hr_payroll_prehlad -# -msgid "" -msgstr "" -"Project-Id-Version: Odoo Server 19.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-07-06 21:45+0000\n" -"PO-Revision-Date: 2026-07-06 21:45+0000\n" -"Last-Translator: \n" -"Language-Team: Slovak (https://www.transifex.com/)\n" -"Language: sk_SK\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: \n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_res_company__l10n_sk_dic -msgid "" -"10-digit tax identification number assigned by the tax administrator — the " -" element of the monthly tax overview." -msgstr "" -"10-miestne daňové identifikačné číslo pridelené správcom dane — element " -" mesačného prehľadu." - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_needaction -msgid "Action Needed" -msgstr "Potrebná akcia" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_ids -msgid "Activities" -msgstr "Aktivity" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_exception_decoration -msgid "Activity Exception Decoration" -msgstr "Označenie aktivity výnimky" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_state -msgid "Activity State" -msgstr "Stav aktivity" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_type_icon -msgid "Activity Type Icon" -msgstr "Ikona typu aktivity" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_attachment_count -msgid "Attachment Count" -msgstr "Počet príloh" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model,name:l10n_sk_hr_payroll_prehlad.model_res_company -msgid "Companies" -msgstr "Spoločnosti" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__company_id -msgid "Company" -msgstr "Spoločnosť" - -#. module: l10n_sk_hr_payroll_prehlad -#. odoo-python -#: code:addons/l10n_sk_hr_payroll_prehlad/models/l10n_sk_prehlad.py:0 -msgid "" -"Company '%(company)s' needs a 10-digit DIČ (tax identification number) for " -"the monthly tax overview element. Set 'DIČ' on the company." -msgstr "" -"Spoločnosť '%(company)s' potrebuje 10-miestne DIČ (daňové identifikačné " -"číslo) pre element mesačného prehľadu. Nastavte 'DIČ' na spoločnosti." - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__country_id -msgid "Country" -msgstr "Krajina" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__create_uid -msgid "Created by" -msgstr "Vytvoril(a)" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__create_date -msgid "Created on" -msgstr "Vytvorené dňa" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__currency_id -msgid "Currency" -msgstr "Mena" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_res_company__l10n_sk_dic -msgid "DIČ (tax identification number)" -msgstr "DIČ (daňové identifikačné číslo)" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__date_from -msgid "Date From" -msgstr "Dátum od" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__date_to -msgid "Date To" -msgstr "Dátum do" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__display_name -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_res_company__display_name -msgid "Display Name" -msgstr "Zobrazovaný názov" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__line_ids -msgid "Employee declarations" -msgstr "Hlásenia zamestnancov" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__lines_count -msgid "Employees" -msgstr "Zamestnanci" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__submitted_attachment_id -msgid "Filed XML" -msgstr "Podané XML" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__filed_history_ids -msgid "Filed history" -msgstr "História podaní" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_follower_ids -msgid "Followers" -msgstr "Odberatelia" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_partner_ids -msgid "Followers (Partners)" -msgstr "Odberatelia (partneri)" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_type_icon -msgid "Font awesome icon e.g. fa-tasks" -msgstr "Ikona písma font awesome, napr. fa-tasks" - -#. module: l10n_sk_hr_payroll_prehlad -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_prehlad.view_l10n_sk_prehlad_form -msgid "Generate" -msgstr "Generovať" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__has_message -msgid "Has Message" -msgstr "Má správu" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__id -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_res_company__id -msgid "ID" -msgstr "ID" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_exception_icon -msgid "Icon" -msgstr "Ikona" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_exception_icon -msgid "Icon to indicate an exception activity." -msgstr "Ikona indikujúca výnimočnú aktivitu." - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_needaction -msgid "If checked, new messages require your attention." -msgstr "Ak označené, potom nové správy vyžadujú vašu pozornosť." - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_error -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_sms_error -msgid "If checked, some messages have a delivery error." -msgstr "Ak je začiarknuté, niektoré správy majú chybu doručenia." - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_is_follower -msgid "Is Follower" -msgstr "Je odberateľ" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__write_uid -msgid "Last Updated by" -msgstr "Naposledy upravil(a)" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__write_date -msgid "Last Updated on" -msgstr "Naposledy upravené dňa" - -#. module: l10n_sk_hr_payroll_prehlad -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_prehlad.view_l10n_sk_prehlad_form -msgid "Mark Submitted" -msgstr "Označiť ako odoslané" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_error -msgid "Message Delivery error" -msgstr "Chyba doručenia správy" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_ids -msgid "Messages" -msgstr "Správy" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__month -msgid "Month" -msgstr "Mesiac" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.actions.act_window,name:l10n_sk_hr_payroll_prehlad.action_l10n_sk_prehlad -msgid "Monthly tax overview" -msgstr "Prehľad o zrazených preddavkoch na daň" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__my_activity_date_deadline -msgid "My Activity Deadline" -msgstr "Termín mojej aktivity" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__name -msgid "Name" -msgstr "Názov" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_date_deadline -msgid "Next Activity Deadline" -msgstr "Termín ďalšej aktivity" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_summary -msgid "Next Activity Summary" -msgstr "Zhrnutie ďalšej aktivity" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_type_id -msgid "Next Activity Type" -msgstr "Typ ďalšej aktivity" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_needaction_counter -msgid "Number of Actions" -msgstr "Počet akcií" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_error_counter -msgid "Number of errors" -msgstr "Počet chýb" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_needaction_counter -msgid "Number of messages requiring action" -msgstr "Počet správ vyžadujúcich akciu" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_error_counter -msgid "Number of messages with delivery error" -msgstr "Počet správ s chybou doručenia" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__payment_date -msgid "" -"Payday reported to the authority (SK denVyplaty). Defaults to the period " -"end." -msgstr "" -"Dátum výplaty hlásený úradu (SK denVyplaty). Predvolene je to koniec " -"obdobia." - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__payment_date -msgid "Payment Date" -msgstr "Dátum výplaty" - -#. module: l10n_sk_hr_payroll_prehlad -#: model_terms:ir.ui.view,arch_db:l10n_sk_hr_payroll_prehlad.view_l10n_sk_prehlad_form -msgid "Reset to Draft" -msgstr "Vrátiť do konceptu" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_user_id -msgid "Responsible User" -msgstr "Zodpovedný užívateľ" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model,name:l10n_sk_hr_payroll_prehlad.model_l10n_sk_prehlad -msgid "SK monthly overview of withheld tax advances" -msgstr "SK prehľad o zrazených preddavkoch na daň" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.ui.menu,name:l10n_sk_hr_payroll_prehlad.menu_l10n_sk_prehlad -msgid "SK monthly tax overview" -msgstr "SK prehľad o zrazených preddavkoch na daň" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__message_has_sms_error -msgid "SMS Delivery error" -msgstr "Chyba doručenia SMS" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__state -msgid "State" -msgstr "Stav" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_state -msgid "" -"Status based on activities\n" -"Overdue: Due date is already passed\n" -"Today: Activity date is today\n" -"Planned: Future activities." -msgstr "" -"Stav založený na aktivitách\n" -"Po termíne: termín vypršal\n" -"Dnešné: termín aktivity je dnes\n" -"Budúce: budúce aktivity." - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__submission_reference -msgid "Submission Reference" -msgstr "Referencia podania" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__submitted_date -msgid "Submitted Date" -msgstr "Dátum odoslania" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__activity_exception_decoration -msgid "Type of the exception activity on record." -msgstr "Typ výnimočnej aktivity v zázname." - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__version_id -msgid "Version" -msgstr "Verzia" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__website_message_ids -msgid "Website Messages" -msgstr "Správy webstránok" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,help:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__website_message_ids -msgid "Website communication history" -msgstr "História komunikácie webstránok" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__xml_attachment_id -msgid "Xml Attachment" -msgstr "Príloha XML" - -#. module: l10n_sk_hr_payroll_prehlad -#: model:ir.model.fields,field_description:l10n_sk_hr_payroll_prehlad.field_l10n_sk_prehlad__year -msgid "Year" -msgstr "Rok" diff --git a/l10n_sk_intrastat/CHANGELOG.rst b/l10n_sk_intrastat/CHANGELOG.rst index 7d6afbbd..3c5e3ddb 100644 --- a/l10n_sk_intrastat/CHANGELOG.rst +++ b/l10n_sk_intrastat/CHANGELOG.rst @@ -8,6 +8,25 @@ Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Cha [Unreleased] ------------ +[19.0.1.1.0] — 2026-10-03 +------------------------- + +Fixed +~~~~~ + +- **Supplementary units are filed by their official code.** OCA names eight + units differently from the Eurostat codes (``items`` for ``p/st``, + ``1000 kWh`` for ``1 000 kWh``, …), and the adapter filed the name. + ``SUCode`` now carries the code, spelled as Odoo Enterprise's + ``account_intrastat`` spells it, so both editions file the same unit. +- **Mode of transport and delivery terms are filed.** A full declaration + carries ``modeOfTransportCode`` and ``TODCode`` on every item. The engine + has both, and this adapter wrote neither. +- The nature of transaction now reaches INSTAT as its A and B codes, via + ``l10n_cssk_intrastat_base`` 19.0.1.1.0. It used to be one A code, ``11``. +- The description said the shared renderer is LGPL-3. It is AGPL-3, and the + Enterprise variant reuses it under dual licensing. + [19.0.1.0.1] — 2026-09-13 ------------------------- diff --git a/l10n_sk_intrastat/README.rst b/l10n_sk_intrastat/README.rst index 0f6384fc..b99fc73f 100644 --- a/l10n_sk_intrastat/README.rst +++ b/l10n_sk_intrastat/README.rst @@ -11,9 +11,10 @@ declaration lines; this module hands those lines to the shared ``l10n_cssk_intrastat_base`` INSTAT renderer to produce the fileable XML. This is the Community / OCA adapter. It is licensed **AGPL-3** because it -depends on (subclasses) the AGPL OCA engine. The shared INSTAT renderer itself -is LGPL-3, so the Enterprise variant — which rides Odoo EE -``account_intrastat`` instead — can reuse the renderer without inheriting AGPL. +depends on (subclasses) the AGPL OCA engine. The shared INSTAT renderer is +AGPL-3 too; the Enterprise variant ``l10n_sk_intrastat_ee`` — which rides Odoo +EE ``account_intrastat`` instead — reuses it because Data Dance s.r.o. owns it +outright and dual-licenses it. Features ======== @@ -24,7 +25,7 @@ Features shared ``l10n_cssk_intrastat_base`` renderer. * Arrivals / dispatches grouped declaration lines computed by the underlying OCA engine. -* LGPL-3 INSTAT renderer is shared with the EE variant so both adapters emit the +* The INSTAT renderer is shared with the EE variant so both adapters emit the identical statutory XML. Usage diff --git a/l10n_sk_intrastat/__manifest__.py b/l10n_sk_intrastat/__manifest__.py index c2cb349e..db6c09b7 100644 --- a/l10n_sk_intrastat/__manifest__.py +++ b/l10n_sk_intrastat/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Slovakia — INTRASTAT-SK (CE / OCA)", "summary": "INTRASTAT-SK INSTAT XML on the OCA intrastat_product engine " - "(CE-clean). EE variant rides Odoo EE account_intrastat.", + "(CE-clean). EE variant: l10n_sk_intrastat_ee.", "description": """ INTRASTAT-SK — Community / OCA adapter ====================================== @@ -13,12 +13,14 @@ INSTAT (instat62) XML via ``l10n_cssk_intrastat_base``. AGPL-3 because it depends on (subclasses) the AGPL OCA engine. The shared -INSTAT renderer is LGPL-3 so the EE variant (on ``account_intrastat``) can reuse -it without inheriting AGPL. +INSTAT renderer is AGPL-3 as well; the Enterprise variant +(``l10n_sk_intrastat_ee``, on ``account_intrastat``) reuses it because Data +Dance s.r.o. is its sole owner and dual-licenses it, not because of a weaker +licence. """, "author": "Data Dance s.r.o.", "website": "https://www.datadance.eu", - "version": "19.0.1.0.1", + "version": "19.0.1.1.0", "category": "Accounting/Localizations", "license": "AGPL-3", "depends": [ diff --git a/l10n_sk_intrastat/models/intrastat_product_declaration.py b/l10n_sk_intrastat/models/intrastat_product_declaration.py index 4e8a5064..da3179a4 100644 --- a/l10n_sk_intrastat/models/intrastat_product_declaration.py +++ b/l10n_sk_intrastat/models/intrastat_product_declaration.py @@ -4,12 +4,28 @@ Subclasses OCA ``intrastat.product.declaration`` and implements the country XML hook ``_generate_xml`` by mapping the engine's computed declaration lines onto -the edition-neutral INSTAT builder in ``l10n_cssk_intrastat_base`` (LGPL). The +the edition-neutral INSTAT builder in ``l10n_cssk_intrastat_base``. The engine (gather invoices → computation lines → grouped declaration lines) is all OCA; we add only the SK Štatistický úrad / Finančná správa INSTAT output. """ from odoo import fields, models +#: OCA names eight units differently from the Eurostat supplementary-unit +#: codes ("items" for p/st, "1000 kWh" for 1 000 kWh, …), and INSTAT wants the +#: code. The spellings are Odoo Enterprise's ``account_intrastat`` list, so +#: both adapters file the same SUCode; every other OCA unit is already named +#: by its code. Keyed by xmlid, not name: names are editable. +_SU_CODE_BY_XMLID = { + "intrastat_product.intrastat_unit_pce": "p/st", + "intrastat_product.intrastat_unit_100pce": "100 p/st", + "intrastat_product.intrastat_unit_1000pce": "1 000 p/st", + "intrastat_product.intrastat_unit_l_alc_100_pct": "l alc. 100 %", + "intrastat_product.intrastat_unit_kg_90_pct_sdt": "kg 90 % sdt", + "intrastat_product.intrastat_unit_1000_kWh": "1 000 kWh", + "intrastat_product.intrastat_unit_1000m3": "1 000 m3", + "intrastat_product.intrastat_unit_gi_FS": "gi F / S", +} + class IntrastatProductDeclaration(models.Model): _inherit = "intrastat.product.declaration" @@ -41,14 +57,26 @@ def _generate_xml(self): hs = dl.hs_code_id lines.append({ "cn8": (hs.local_code or hs.hs_code or "").replace(" ", ""), - "su_code": dl.intrastat_unit_id.name or "", + "su_code": self._sk_su_code(dl.intrastat_unit_id), "ms_cons_dest": dl.src_dest_country_code, "country_of_origin": dl.product_origin_country_code, "net_mass": dl.weight, "quantity_in_su": dl.suppl_unit_qty, "invoiced_amount": dl.amount_company_currency, "nature_a": dl.transaction_code, + # modeOfTransportCode and TODCode belong on every item of a full + # declaration (FS SR: "len pre declarationTypeCode = 1"); the + # engine has both and this used to file neither. + "transport_mode": dl.transport_id.code or "", + "delivery_terms": dl.incoterm_id.code or "", "region": dl.region_code, "partner_vat": dl.vat, }) return builder.build_instat_xml(header, lines) + + def _sk_su_code(self, unit): + """The official supplementary-unit code INSTAT carries, not OCA's name.""" + if not unit: + return "" + xmlid = unit.get_external_id().get(unit.id, "") + return _SU_CODE_BY_XMLID.get(xmlid) or unit.name diff --git a/l10n_sk_intrastat/static/description/index.html b/l10n_sk_intrastat/static/description/index.html index 272af97f..bdc3760f 100644 --- a/l10n_sk_intrastat/static/description/index.html +++ b/l10n_sk_intrastat/static/description/index.html @@ -14,8 +14,9 @@

Official Finančná správa INTRAST INTRASTAT-SK INSTAT (instat62) XML via the shared l10n_cssk_intrastat_base renderer.

It is the Community / OCA adapter, licensed AGPL-3 because it subclasses the AGPL OCA - engine. The shared INSTAT renderer is LGPL-3, so the Enterprise variant — which rides Odoo - EE account_intrastat instead — can reuse it without inheriting AGPL.

+ engine. The shared INSTAT renderer is AGPL-3 too; the Enterprise variant — which rides Odoo + EE account_intrastat instead — reuses it because Data Dance owns it + outright and dual-licenses it.

@@ -30,7 +31,7 @@

Key features

  • Renders the official FS SR INTRASTAT-SK INSTAT (instat62) XML through the shared l10n_cssk_intrastat_base renderer.
  • Arrivals / dispatches declaration lines computed by the underlying OCA engine.
  • -
  • The LGPL-3 INSTAT renderer is shared with the EE variant, so both adapters emit the +
  • The INSTAT renderer is shared with the EE variant, so both adapters emit the identical statutory XML.
  • diff --git a/l10n_sk_intrastat/static/description/index_cz.html b/l10n_sk_intrastat/static/description/index_cz.html index a3840425..79e0f71d 100644 --- a/l10n_sk_intrastat/static/description/index_cz.html +++ b/l10n_sk_intrastat/static/description/index_cz.html @@ -14,9 +14,9 @@

    Oficiální XML INTRASTAT-SK INSTAT INTRASTAT-SK INSTAT (instat62) slovenské Finanční správy přes sdílený renderer l10n_cssk_intrastat_base.

    Jde o adaptér pro Community / OCA, licencovaný pod AGPL-3, protože dědí z AGPL - enginu OCA. Sdílený renderer INSTAT je pod LGPL-3, takže varianta pro Enterprise — která - místo toho jede na Odoo EE account_intrastat — jej může znovu použít bez - přejímání AGPL.

    + enginu OCA. Sdílený renderer INSTAT je také pod AGPL-3; varianta pro Enterprise — která + místo toho jede na Odoo EE account_intrastat — jej používá, protože jej + Data Dance výhradně vlastní a poskytuje pod dvojí licencí.

    @@ -31,7 +31,7 @@

    Klíčové funkce

  • Vykresluje oficiální XML INTRASTAT-SK INSTAT (instat62) FS SR přes sdílený renderer l10n_cssk_intrastat_base.
  • Řádky výkazu pro přijetí / odeslání zboží počítané spodním enginem OCA.
  • -
  • Renderer INSTAT pod LGPL-3 je sdílený s variantou pro EE, takže oba adaptéry vydávají +
  • Renderer INSTAT je sdílený s variantou pro EE, takže oba adaptéry vydávají totožné zákonné XML.
  • diff --git a/l10n_sk_intrastat/static/description/index_sk.html b/l10n_sk_intrastat/static/description/index_sk.html index e8c39206..d24e8154 100644 --- a/l10n_sk_intrastat/static/description/index_sk.html +++ b/l10n_sk_intrastat/static/description/index_sk.html @@ -14,9 +14,9 @@

    Oficiálne XML INTRASTAT-SK INSTAT INTRASTAT-SK INSTAT (instat62) slovenskej Finančnej správy cez spoločný renderer l10n_cssk_intrastat_base.

    Ide o adaptér pre Community / OCA, licencovaný pod AGPL-3, pretože dedí z AGPL - enginu OCA. Spoločný renderer INSTAT je pod LGPL-3, takže variant pre Enterprise — ktorý - namiesto toho beží na Odoo EE account_intrastat — ho môže znovu použiť bez - preberania AGPL.

    + enginu OCA. Spoločný renderer INSTAT je tiež pod AGPL-3; variant pre Enterprise — ktorý + namiesto toho beží na Odoo EE account_intrastat — ho používa, pretože ho + Data Dance výlučne vlastní a poskytuje pod dvojitou licenciou.

    @@ -31,7 +31,7 @@

    Kľúčové funkcie

  • Vykresľuje oficiálne XML INTRASTAT-SK INSTAT (instat62) FS SR cez spoločný renderer l10n_cssk_intrastat_base.
  • Riadky výkazu pre prijatie / odoslanie tovaru počítané spodným enginom OCA.
  • -
  • Renderer INSTAT pod LGPL-3 je spoločný s variantom pre EE, takže oba adaptéry vydávajú +
  • Renderer INSTAT je spoločný s variantom pre EE, takže oba adaptéry vydávajú totožné zákonné XML.
  • diff --git a/l10n_sk_intrastat/tests/__init__.py b/l10n_sk_intrastat/tests/__init__.py new file mode 100644 index 00000000..b8f65ea6 --- /dev/null +++ b/l10n_sk_intrastat/tests/__init__.py @@ -0,0 +1 @@ +from . import test_sk_intrastat diff --git a/l10n_sk_intrastat/tests/test_sk_intrastat.py b/l10n_sk_intrastat/tests/test_sk_intrastat.py new file mode 100644 index 00000000..35b43276 --- /dev/null +++ b/l10n_sk_intrastat/tests/test_sk_intrastat.py @@ -0,0 +1,26 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3 — see the LICENSE file. +from odoo.tests import TransactionCase, tagged + +from odoo.addons.l10n_sk_intrastat.models.intrastat_product_declaration import ( + _SU_CODE_BY_XMLID, +) + + +@tagged("post_install", "-at_install") +class TestSkIntrastat(TransactionCase): + + def test_supplementary_units_file_the_eurostat_code(self): + """OCA's unit NAME is not always the code: "items" is p/st.""" + decl = self.env["intrastat.product.declaration"] + for xmlid, code in _SU_CODE_BY_XMLID.items(): + self.assertEqual(decl._sk_su_code(self.env.ref(xmlid)), code, xmlid) + # the rest are named by their code already + self.assertEqual( + decl._sk_su_code(self.env.ref("intrastat_product.intrastat_unit_m2")), "m2") + self.assertEqual(decl._sk_su_code(self.env["intrastat.unit"]), "") + + def test_every_mapped_unit_exists(self): + """A renamed or dropped OCA xmlid would silently fall back to the name.""" + for xmlid in _SU_CODE_BY_XMLID: + self.assertTrue(self.env.ref(xmlid, raise_if_not_found=False), xmlid) diff --git a/l10n_sk_kv_dph/CHANGELOG.rst b/l10n_sk_kv_dph/CHANGELOG.rst index daf68069..b636388e 100644 --- a/l10n_sk_kv_dph/CHANGELOG.rst +++ b/l10n_sk_kv_dph/CHANGELOG.rst @@ -8,6 +8,113 @@ Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Cha [Unreleased] ------------ +[19.0.2.3.0] — 2026-10-04 +------------------------- + +Changed +~~~~~~~ + +- **Odpočítaná daň (``O`` / ``OR``) is taken from the tax's repartition**, on + B.1, B.2, C.2 and the B.3 aggregates. It used to be the whole tax. A tax set + up to deduct 50 % (a vehicle under the 2026 rule) now files half in ``O``, + matching row 21 of the DP DPH. A self-assessed tax with no right to deduct + files 0.00. A § 50 koeficient set on the tax works the same way. The + deductible *share* is applied to ``D``, so a fully deductible tax still + files ``O`` equal to ``D`` to the cent, and ``O`` never exceeds ``D``. The + year-end koeficient true-up remains a manual override on the row. Asked for + by a customer's accountant, who wanted it read from the tax setting rather than + hard-coded. + + ⚠️ **Recomputing a period that is already filed now gives a different ``O``** + wherever a partially deductible tax was used, and a dodatočný výkaz built on + it will report the difference. + +Fixed +~~~~~ + +- **A credit note to a customer who is not a taxable person goes to D.2**, not + C.1, and reduces the D.2 totals. C.1 corrects what A.1/A.2 reported, and such + a supply was never there. It uses the same test as the supply itself. + Confirmed by a customer's accountant. +- **A vendor credit note's Source Document is read as the original invoice + number** (C.2 ``FO``) when the credit note is not linked to the invoice and + the Original document number field is empty. Some agendas require that field + before posting. A purchase order name there is skipped, so the export still + asks for the real number. Sales credit notes still do not read it, because + there it holds the sales order number. + +[19.0.2.2.2] — 2026-10-04 +------------------------- + +Fixed +~~~~~ + +- **A bare country code is "no VAT number" in a row's identity.** The form + leaves the counterparty's IČ DPH optional and free-format where there is + none (a third-country supplier on B.1), so ``US`` and an empty attribute + file the same thing; Odoo and migrated sources each write either, depending + on the partner. Such rows no longer read as two one-sided differences, and + a dodatočný baseline keyed either way still matches. On the i6 agenda, + with the importer's aggregate folding and a filed-side-only normalisation + as the baseline, agreeing rows went from 21 330 to 22 639. + + +[19.0.2.2.1] — 2026-10-04 +------------------------- + +Fixed +~~~~~ + +- **A KV DPH row is identified by the reference the form carries**, whitespace + removed (``_kv_identity_ref``). A filed statement can only hold that form + (``\S{1,32}``), so a comparison keyed on the stored spelling reported every + spaced reference as two one-sided rows with the money agreeing — 3 694 on one + migrated i6 agenda. A chained dodatočný baseline snapshotted before this + still matches (``_kv_identity_key``). +- **B.3 of the vzor that ran to 31. 3. 2016 is exported.** The section was + computed (it is listed on ``kvdph_version_2014``) but missing from + ``_collect_sections_by_code``, which the XML template, the comparison and + the materialising of a migrated statement all read — so every KV DPH for + 2014-01 … 2016-03 was exported without its simplified invoices, and a + migrated i6 agenda could place none of its 2 047 filed B.3 rows. New + ``sk_section_b3_ids`` and a B.3 tab, shown only when it has rows. The test + that claimed to cover it created no receipt; it now does. + +Carry-over to 18.0 +~~~~~~~~~~~~~~~~~~ + +- B.3 in ``_collect_sections_by_code``; the whitespace-free row identity + (19.0.2.2.1, 2026-10-04). + +[19.0.2.2.0] — 2026-10-03 +------------------------- + +Added +~~~~~ + +- **The A.2 commodity code is checked against § 69 ods. 12.** Písm. f) takes + chapters 10 and 12 of the Spoločný colný sadzobník, písm. g) chapter 72 and + headings 7301, 7308 and 7314. A code typed on the product is refused outside + them. A code reused from the product's HS / intrastat classification cannot + be refused on the product, so the export names it instead: that is the + realistic failure, a phone (8517) flagged písm. f) filed in a cereals row, + and FS SR accepts any four digits. Checked at chapter level on purpose, not + against a list of codes, which would refuse a heading the tariff adds. + Raised from a customer's accountant review. + +[19.0.2.1.1] — 2026-10-03 +------------------------- + +Fixed +~~~~~ + +- **The A.2 product fields had no label.** *Tovar podľa § 69 ods. 12 písm. f)/g)* + and the CN code were put straight into the product form's ``accounting`` + group, which becomes an outer group once another module adds a sub-group + there. An outer group renders a bare field as an unlabelled column slot, so + on a real database they could not be found. They now sit in a group of their + own, *Kontrolný výkaz DPH*. Reported from a customer's development database. + [19.0.2.1.0] — 2026-09-28 ------------------------- @@ -58,7 +165,7 @@ Fixed - **A private person from another EU state goes to D.2, not A.1.** A supply taxed at Slovak rates to a customer with neither an IČ DPH nor an IČO went to D.2 only when the customer was outside the EU. Confirmed by an - accountant (Atheo): "Fyzické osoby nepodnikatelia idú do D2", whatever the + accountant: "Fyzické osoby nepodnikatelia idú do D2", whatever the country — § 72 imposes no invoice obligation towards a private individual anywhere. An EU business recorded with its registry number stays in A.1. The upgrade recomputes the affected sales lines; recompute unfiled @@ -80,7 +187,7 @@ Fixed under that document's number and counterparty and dated by the payment. A payment that is un-reconciled within the period cancels out rather than filing +x and −x. The upgrade re-resolves the affected lines. - Reported by an external accountant (Atheo). + Reported by an external accountant. - **A 0 % purchase reached B.2.** B.2 and B.3 report the tax the recipient deducts, and a received supply bearing no tax (exempt supply, duty-only customs line, a 0 % purchase tax) deducts none — its B.2 row filed diff --git a/l10n_sk_kv_dph/__manifest__.py b/l10n_sk_kv_dph/__manifest__.py index ea55403f..5149fe89 100644 --- a/l10n_sk_kv_dph/__manifest__.py +++ b/l10n_sk_kv_dph/__manifest__.py @@ -1,6 +1,6 @@ { "name": "Slovakia — Kontrolný výkaz DPH (KV DPH)", - "version": "19.0.2.1.0", + "version": "19.0.2.3.0", "summary": "Slovak VAT control statement (Kontrolný výkaz DPH) — sections " "A.1–D.2 incl. D.1, with FS SR XML export. Built on the shared " "l10n_cssk_kv_kh_base framework.", diff --git a/l10n_sk_kv_dph/models/account_move_line.py b/l10n_sk_kv_dph/models/account_move_line.py index 9b96016b..c9d8c904 100644 --- a/l10n_sk_kv_dph/models/account_move_line.py +++ b/l10n_sk_kv_dph/models/account_move_line.py @@ -151,6 +151,19 @@ def _cssk_resolve_section_code(self): # question will want to know the population is not uniform.) if is_outbound and not self._cssk_bears_vat(): return False + # A credit note to a customer who is not a taxable person corrects + # a D.2 supply, so it is reported in D.2 — the aggregate takes it + # with its negative sign. C.1 corrects what A.1 / A.2 reported, + # and a supply to a private individual was never there. Same test + # as the supply itself below, deliberately. + # + # Confirmed by a customer's accountant (2026-10-03): "Dobropis k + # vystavenej faktúre, ktorá patrila pôvodne do D2, patrí tiež do + # D2." Not for a reverse charge: § 69 ods. 12 needs a taxable + # customer by definition, and its corrections stay in C.1. + if (is_outbound and not is_reverse_charge + and not self._cssk_customer_is_taxable_person()): + return "D.2" return "C.1" if is_outbound else "C.2" if is_outbound: @@ -228,7 +241,7 @@ def _cssk_resolve_section_code(self): # A.1 — which is what the MRP agenda's accountant filed for a # Czech private customer taxed at Slovak rates (2024-09, 2025-08). # Asked directly on 2026-09-21, a second Slovak accountant - # (Atheo) answered without qualification: "Fyzické osoby + # answered without qualification: "Fyzické osoby # nepodnikatelia idú do D2." That is also the statutory reading: # § 72 obliges an invoice towards a zdaniteľná osoba or a # právnická osoba, and a private individual in Brno is neither, @@ -351,3 +364,40 @@ def _cssk_is_eu_counterparty(self): return False eu = self.env.ref("base.europe", raise_if_not_found=False) return bool(eu) and country in eu.country_ids + + def _cssk_deductible_share(self): + """``(deductible, charged)`` legs of this base line's tax, unsigned. + + Both from ONE ``compute_all``, so their ratio is exact: a full + deduction is 1 whatever the rounding or the currency, and the share of + a partial one is what its repartition says. ``charged`` is every leg + with a positive factor (the deductible ones plus those onto the + expense); ``deductible`` the ones the VAT closing settles. + + The refund repartition is chosen from the DOCUMENT, not the move: a + cash-basis entry is an ``entry`` even when it settles a credit note. + """ + self.ensure_one() + taxes = self._cssk_taxes_for_amounts() + if not taxes: + return 0.0, 0.0 + document = self.move_id._cssk_vat_document() + res = taxes.compute_all( + self.balance, + currency=self.company_id.currency_id, + quantity=1.0, + partner=self.partner_id, + is_refund=document.move_type in ("out_refund", "in_refund"), + handle_price_include=False, + ) + Rep = self.env["account.tax.repartition.line"] + deductible = charged = 0.0 + for tax_res in res["taxes"]: + rep = Rep.browse(tax_res.get("tax_repartition_line_id")) + if not rep or rep.factor_percent <= 0: + continue + charged += abs(tax_res["amount"]) + if rep.use_in_tax_closing: + deductible += abs(tax_res["amount"]) + return deductible, charged + diff --git a/l10n_sk_kv_dph/models/cssk_control_statement.py b/l10n_sk_kv_dph/models/cssk_control_statement.py index 7b391b35..6892b4e2 100644 --- a/l10n_sk_kv_dph/models/cssk_control_statement.py +++ b/l10n_sk_kv_dph/models/cssk_control_statement.py @@ -3,6 +3,8 @@ from odoo import _, fields, models from odoo.exceptions import UserError +from .product import kv_cn_code_in_scope, kv_cn_code_scope_label + # Master-data preflight scope — derived from the OFFICIAL kv_dph_2025.xsd + # the QWeb template (report/l10n_sk_kv_dph_templates.xml): # * counterparty IČ DPH (Odb/Dod): REQUIRED by the form on A.2, B.2 and @@ -70,6 +72,13 @@ class CSSKControlStatement(models.Model): sk_section_b2_ids = fields.One2many( "l10n.sk.kv.dph.section.b2", "statement_id" ) + # The single B.3 of the vzor that ran to 31. 3. 2016. Declared from the + # start as a section of that version, but never listed here, so the + # export, the comparison and a migrated statement all read it as absent: + # every 2014-01 … 2016-03 KV DPH was exported WITHOUT its B.3 rows. + sk_section_b3_ids = fields.One2many( + "l10n.sk.kv.dph.section.b3", "statement_id" + ) sk_section_b31_ids = fields.One2many( "l10n.sk.kv.dph.section.b31", "statement_id" ) @@ -89,6 +98,20 @@ class CSSKControlStatement(models.Model): "l10n.sk.kv.dph.section.d2", "statement_id" ) + def _kv_identity_key(self, identity): + """Whitespace removed from the text of a Slovak row identity, so a + chained dodatočný baseline snapshotted before the identity dropped it + (``_kv_identity_ref``) still matches the row it describes.""" + if self.country_id.code != "SK": + return super()._kv_identity_key(identity) + key = [re.sub(r"\s+", "", v) if isinstance(v, str) else v + for v in identity] + # And a bare country code as no number, as ``_kv_identity`` reads it. + if key and isinstance(key[0], str) and ( + key[0] == "0" or re.fullmatch(r"[A-Za-z]{2}", key[0])): + key[0] = "" + return tuple(key) + def _collect_sections_by_code(self): self.ensure_one() if self.country_id.code == "SK": @@ -97,6 +120,7 @@ def _collect_sections_by_code(self): "A.2": self.sk_section_a2_ids, "B.1": self.sk_section_b1_ids, "B.2": self.sk_section_b2_ids, + "B.3": self.sk_section_b3_ids, "B.3.1": self.sk_section_b31_ids, "B.3.2": self.sk_section_b32_ids, "C.1": self.sk_section_c1_ids, @@ -211,7 +235,9 @@ def _cssk_preflight_export(self): "%(sec)s: %(n)d row(s) without the number of the " "invoice being corrected — the credit note is not " "linked to it; enter it on the credit note (Other Info " - "→ Original document number) and recompute: %(refs)s", + "→ Original document number; on a vendor credit note " + "the Source Document is read too) and recompute: " + "%(refs)s", sec=code, n=len(bad), refs=self._kv_offenders_detail(bad))) # A.2 goods under § 69 ods. 12 písm. f) to i): the form wants the @@ -228,6 +254,24 @@ def _cssk_preflight_export(self): "product (KV DPH commodity code, or its intrastat/HS " "code): %(refs)s", n=len(bad), refs=self._kv_offenders_detail(bad))) + # The code that gets filed is often not one anybody typed for KV + # DPH: it falls back to the product's HS / intrastat code, so a + # phone (8517) flagged písm. f) would be filed in a cereals row. + # FS SR accepts any four digits; the statute does not. + bad = [r for r in a2 if r.rc_goods in ("f", "g") and r.goods_code + and not kv_cn_code_in_scope(r.rc_goods, r.goods_code)] + if bad: + problems.append(_( + "A.2: %(n)d row(s) whose commodity code is outside the " + "chapters § 69 ods. 12 names for its goods (písm. f: " + "%(f)s; písm. g: %(g)s) — correct the product's KV DPH " + "commodity code or its HS code, or its § 69 ods. 12 " + "category: %(refs)s", + n=len(bad), f=kv_cn_code_scope_label("f"), + g=kv_cn_code_scope_label("g"), + refs=", ".join("%s (%s)" % (self._kv_row_label(r), r.goods_code) + for r in bad[:5]) + + (", … (+%d)" % (len(bad) - 5) if len(bad) > 5 else ""))) bad = [r for r in a2 if r.rc_goods and not r.uom_code] if bad: problems.append(_( diff --git a/l10n_sk_kv_dph/models/l10n_sk_kv_dph_sections.py b/l10n_sk_kv_dph/models/l10n_sk_kv_dph_sections.py index e328a4f9..4ad2d5c7 100644 --- a/l10n_sk_kv_dph/models/l10n_sk_kv_dph_sections.py +++ b/l10n_sk_kv_dph/models/l10n_sk_kv_dph_sections.py @@ -1,3 +1,5 @@ +import re + from odoo import api, fields, models from odoo.addons.l10n_cssk_kv_kh_base.models.cssk_control_statement_mixins import ( @@ -30,6 +32,37 @@ class L10nSKKvDphDetailMixin(models.AbstractModel): _inherit = "cssk.control.statement.section.mixin" _description = "SK KV DPH detail section (mixin)" + def _kv_identity(self): + """A bare country code is "no VAT number", on either side. + + Where a supplier has no IČ DPH — a third-country one on B.1, mostly — + the form leaves ``Dod`` optional and free-format, so ``US`` and an + empty attribute file the same thing. Sources and Odoo both write + either, depending on the partner record: on a migrated i6 agenda the + same US supplier read ``US`` on one side and nothing on the other for + some rows and the reverse for others, and every such row reported as + two one-sided differences. + """ + identity = super()._kv_identity() + vat = (identity[0] or "").strip() + if vat == "0" or re.fullmatch(r"[A-Za-z]{2}", vat): + identity = ("",) + identity[1:] + return identity + + def _kv_identity_ref(self): + """The reference as the FORM carries it, whitespace removed. + + A filed KV DPH can only hold the stripped reference (``\\S{1,32}``, + and the export writes ``entry_ref_xml``), so a comparison against a + filed or migrated statement keyed on the stored ``FV 1/06/2024`` never + met the ``FV1/06/2024`` the tax office received: 3 694 rows of a + migrated i6 agenda read as pairs of one-sided differences with the + money agreeing. Two references that differ only by whitespace are one + row on this form anyway. The stored spelling is untouched. + """ + self.ensure_one() + return self._cssk_ref_for_xml(self.entry_ref) + # Received-document sections (B.1, B.2, C.2) must report the SUPPLIER's # poradové číslo faktúry (move.ref), not our internal move name — for the # correction sections that applies to the original-document column too. @@ -94,6 +127,15 @@ def _cssk_row_vals(self, statement, group): # original, which is what the template used to fall back to. original = move._cssk_control_original() manual = move.cssk_control_original_ref or False + if self._kv_inbound and not manual: + # A received credit note's "Source Document" is where the + # supplier's original invoice number is commonly typed, and on + # some agendas it is mandatory before posting — one customer's requires + # it on every SK vendor credit note. Read last: a link or the + # dedicated field stays authoritative where filled. Not on the + # sales side, where invoice_origin is the SALES ORDER number and + # would be filed as the corrected invoice. + manual = self._cssk_origin_as_invoice_number(move) or False if self._kv_inbound: entry_ref = move.ref or move.name entry_ref_original = ( @@ -129,26 +171,70 @@ def _cssk_row_vals(self, statement, group): ) return vals + def _cssk_origin_as_invoice_number(self, move): + """``invoice_origin`` when it can be the supplier's invoice number. + + A vendor credit note raised from a purchase order carries the ORDER + name there ("P00012", or several, comma-separated). That is not the + corrected invoice, and filing it as FO would pass the export's check + that a number is present. So an origin naming a purchase order is not + read, and the export asks for the real number instead.""" + origin = (move.invoice_origin or "").strip() + if not origin or "purchase.order" not in self.env: + return origin + names = [n.strip() for n in origin.split(",") if n.strip()] + if self.env["purchase.order"].sudo().search_count( + [("name", "in", names), ("company_id", "=", move.company_id.id)], + limit=1): + return "" + return origin + def _cssk_deducted_amount(self, statement, group): """Odpočítaná daň — attribute ``O`` (``OR`` on C.2). Only the received sections (B.1 / B.2 / C.2) have one; A.1 / A.2 / C.1 are output tax with nothing to deduct, and the field stays 0.00 there. - The FULL tax, which is what the XML has always filed — the template - read ``tax_amount`` directly — so no filed figure moves. The row now - simply states what it files instead of holding 0.00 while filing - something else; the summary sections (B.3*) already did this. - - Full deduction is an ASSERTION, and the right one for a company whose - supplies are all taxable. A company applying the § 50 koeficient - deducts a fraction, and neither this row nor the filing can derive it: - the coefficient is a fact about the COMPANY and its year, not about - the document. Until it is modelled such a company overrides the row — + The part of the tax that the TAX ITSELF deducts: the legs of its + repartition that the VAT closing settles. A full deduction is the + whole tax, as before. A partial one is what the return deducts — + a vehicle at 50 % puts half on 343 and half on the expense, and only + that half is on row 21 of the DP DPH, so only that half belongs in + ``O``. A tax with no right to deduct (self-assessed with no deduction + leg) deducts 0.00. A § 50 koeficient set on the tax the same way is + honoured the same way. + + Confirmed by a customer's accountant (2026-10-03), who + asked for exactly this: "tak ako do daňového priznania berieš ako + uplatnenú daň na riadku 21 len 50 % z hodnoty DPH, tak aj do + kontrolného výkazu do stĺpca Odpočítaná daň musí ísť len tých 50 % … + brala by som to z nastavenia dane" — from the tax setting, not a + hard-coded ratio. It used to file the full tax whatever the tax said. + + The year-end koeficient true-up stays a manual override: ``deducted_amount`` is in ``_KV_OVERRIDE_FIELDS``, so the override survives a recompute and reaches the XML. """ - return group["tax"] + return self._cssk_deducted_of(group["lines"], group["tax"]) + + @api.model + def _cssk_deducted_of(self, lines, tax): + """``tax`` (as D files it) times the deductible share of ``lines``. + + Applied to D rather than recomputed per line, so a fully deductible + tax files ``O`` equal to ``D`` to the cent — a per-line recomputation + drifts on a foreign-currency or many-line document — and ``O`` can + never exceed ``D``. + """ + deductible = charged = 0.0 + for line in lines: + d, c = line._cssk_deductible_share() + deductible += d + charged += c + if not charged: + return 0.0 + currency = self.env.company.currency_id + return currency.round(tax * min(deductible / charged, 1.0)) class L10nSKKvDphSectionA1(models.Model): @@ -393,7 +479,11 @@ def _cssk_b3_lines(self, statement): @api.model def _cssk_b3_totals(self, statement): - """Return ``(grand_base, grand_tax, by_supplier)`` over the B.3 set.""" + """Return ``(grand_base, grand_tax, by_supplier)`` over the B.3 set. + + Each supplier also carries ``share`` — the deductible and charged legs + of its taxes, as on the detail rows (fuel on a pokladničný doklad is + exactly the 50 % case); :meth:`_cssk_b3_deducted` applies it.""" grand_base = grand_tax = 0.0 by_supplier = {} for line in self._cssk_b3_lines(statement): @@ -412,10 +502,27 @@ def _cssk_b3_totals(self, statement): } sup["base"] += base sup["tax"] += tax + sup.setdefault("share", [0.0, 0.0]) + d, c = line._cssk_deductible_share() + sup["share"][0] += d + sup["share"][1] += c sup["moves"].add(line.move_id.id) sup["lines"] |= line return grand_base, grand_tax, by_supplier + @api.model + def _cssk_b3_deducted(self, by_supplier): + return sum(self._cssk_sup_deducted(sup) for sup in by_supplier.values()) + + @api.model + def _cssk_sup_deducted(self, sup): + """A supplier's tax times its deductible share (see ``_cssk_deducted_of``).""" + deductible, charged = sup.get("share", (0.0, 0.0)) + if not charged: + return 0.0 + return self.env.company.currency_id.round( + sup["tax"] * min(deductible / charged, 1.0)) + class L10nSKKvDphSectionB3(models.Model): """B.3 — the single aggregate of the vintage that ran to 31. 3. 2016. @@ -452,7 +559,7 @@ def _populate_for_statement(self, statement, section_code): "statement_id": statement.id, "total_tax_base": grand_base, "total_tax_amount": grand_tax, - "total_deducted_amount": grand_tax, + "total_deducted_amount": self._cssk_b3_deducted(by_supplier), "row_count": len(moves), # An aggregate names no document, so the documents behind # it are only reachable from here — the one way to check @@ -484,7 +591,7 @@ def _populate_for_statement(self, statement, section_code): "statement_id": statement.id, "total_tax_base": grand_base, "total_tax_amount": grand_tax, - "total_deducted_amount": grand_tax, + "total_deducted_amount": self._cssk_b3_deducted(by_supplier), "row_count": len(moves), # An aggregate names no document, so the documents behind # it are only reachable from here — the one way to check @@ -516,7 +623,7 @@ def _populate_for_statement(self, statement, section_code): "partner_vat": sup["partner"].vat or "", "total_tax_base": sup["base"], "total_tax_amount": sup["tax"], - "total_deducted_amount": sup["tax"], + "total_deducted_amount": self._cssk_sup_deducted(sup), "row_count": len(sup["moves"]), "source_move_line_ids": [(6, 0, sup["lines"].ids)], } diff --git a/l10n_sk_kv_dph/models/product.py b/l10n_sk_kv_dph/models/product.py index 0bf1275f..cd773f45 100644 --- a/l10n_sk_kv_dph/models/product.py +++ b/l10n_sk_kv_dph/models/product.py @@ -1,6 +1,7 @@ import re -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError # § 69 ods. 12 zákona o DPH — the domestic reverse-charge goods for which # oddiel A.2 carries more than the base. Read off the vzor's own schema @@ -16,6 +17,29 @@ RC_GOODS_WITH_CODE = ("f", "g") RC_GOODS_KIND = {"h": "MT", "i": "IO"} +# What § 69 ods. 12 bounds the SCS code to. The statute names CHAPTERS (and for +# písm. g) three headings), not codes, so this is checked at chapter level: a +# list of codes would refuse a heading the Spoločný colný sadzobník adds until +# the next release. The schema cannot catch a wrong one either — KodScsType is +# \d{4} with no enumeration. +RC_GOODS_CHAPTERS = {"f": ("10", "12"), "g": ("72",)} +RC_GOODS_HEADINGS = {"g": ("7301", "7308", "7314")} + + +def kv_cn_code_in_scope(goods, code): + """Does a 4-digit SCS code fall where § 69 ods. 12 písm. ``goods`` puts it?""" + if goods not in RC_GOODS_WITH_CODE or not code: + return True + return (code[:2] in RC_GOODS_CHAPTERS.get(goods, ()) + or code in RC_GOODS_HEADINGS.get(goods, ())) + + +def kv_cn_code_scope_label(goods): + """The statute's own bound, for messages: "kapitoly 10, 12" etc.""" + parts = ["kapitola %s" % c for c in RC_GOODS_CHAPTERS.get(goods, ())] + parts += RC_GOODS_HEADINGS.get(goods, ()) + return ", ".join(parts) + class ProductTemplate(models.Model): _inherit = "product.template" @@ -34,10 +58,32 @@ class ProductTemplate(models.Model): string="Číselný kód tovaru (KV DPH)", size=4, help="First four digits of the Spoločný colný sadzobník code, reported " - "in A.2 for § 69 ods. 12 písm. f) and g). Leave empty to take it from " - "the product's intrastat / HS code when one is installed.", + "in A.2 for § 69 ods. 12 písm. f) and g). The statute bounds it: " + "písm. f) to chapters 10 and 12, písm. g) to chapter 72 and headings " + "7301, 7308 and 7314. Leave empty to reuse the product's customs " + "classification (HS / intrastat code) — that code is checked against " + "the same chapters when the kontrolný výkaz is exported.", ) + @api.constrains("l10n_sk_kv_rc_goods", "l10n_sk_kv_cn_code") + def _check_l10n_sk_kv_cn_code(self): + for product in self: + code = product.l10n_sk_kv_cn_code + if not code: + continue + if not (len(code) == 4 and code.isdigit()): + raise ValidationError(_( + "%(product)s: the KV DPH commodity code must be four " + "digits, not \"%(code)s\".", + product=product.display_name, code=code)) + goods = product.l10n_sk_kv_rc_goods + if not kv_cn_code_in_scope(goods, code): + raise ValidationError(_( + "%(product)s: commodity code %(code)s is outside § 69 " + "ods. 12 písm. %(goods)s) (%(scope)s).", + product=product.display_name, code=code, goods=goods, + scope=kv_cn_code_scope_label(goods))) + class ProductProduct(models.Model): _inherit = "product.product" diff --git a/l10n_sk_kv_dph/tests/test_kv_dph.py b/l10n_sk_kv_dph/tests/test_kv_dph.py index 8fac3262..d79f8d9e 100644 --- a/l10n_sk_kv_dph/tests/test_kv_dph.py +++ b/l10n_sk_kv_dph/tests/test_kv_dph.py @@ -1,10 +1,11 @@ import base64 +from unittest.mock import patch from lxml import etree from odoo import Command from odoo.addons.account.tests.common import AccountTestInvoicingCommon -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError from odoo.tests import tagged from odoo.tests.common import TransactionCase @@ -311,6 +312,39 @@ def test_a2_without_the_commodity_code_is_named_not_filed(self): with self.assertRaisesRegex(UserError, "commodity code"): st.action_export_xml() + def test_a2_commodity_code_is_bounded_by_the_statute(self): + """§ 69 ods. 12 names chapters: f) 10 and 12, g) 72 + 7301/7308/7314.""" + Product = self.env["product.product"] + for goods, code in (("f", "1001"), ("f", "1205"), ("g", "7208"), + ("g", "7301"), ("g", "7308"), ("g", "7314")): + Product.create({"name": code, "l10n_sk_kv_rc_goods": goods, + "l10n_sk_kv_cn_code": code}) + for goods, code in (("f", "8517"), ("f", "7208"), ("g", "1001"), + ("g", "7302"), ("g", "7310"), ("g", "abcd")): + with self.assertRaises(ValidationError, msg=(goods, code)): + Product.create({"name": code, "l10n_sk_kv_rc_goods": goods, + "l10n_sk_kv_cn_code": code}) + # changing the category re-checks a code typed earlier + product = Product.create({"name": "Kukurica", "l10n_sk_kv_rc_goods": "f", + "l10n_sk_kv_cn_code": "1005"}) + with self.assertRaises(ValidationError): + product.l10n_sk_kv_rc_goods = "g" + + def test_a2_inherited_commodity_code_outside_the_chapters_is_not_filed(self): + """The realistic failure: the product's HS code, reused as the fallback, + is not one anybody chose for KV DPH — a phone flagged písm. f).""" + grain = self.env["product.product"].create({ + "name": "Pšenica", "l10n_sk_kv_rc_goods": "f"}) + self._rc_goods_invoice( + [(grain, 2.0, self.env.ref("uom.product_uom_ton"), 300.0)]) + with patch.object(type(grain), "_l10n_sk_kv_cn_code", + lambda product: "8517"): + st = self._make_statement() + st.action_compute_lines() + self.assertEqual(st.sk_section_a2_ids.goods_code, "8517") + with self.assertRaisesRegex(UserError, r"outside the chapters.*\(8517\)"): + st.action_export_xml() + def test_a2_other_69_12_supplies_carry_only_the_base(self): """Scrap, construction work: no category, no goods attributes.""" service = self.env["product.product"].create({"name": "Stavebné práce"}) @@ -355,12 +389,22 @@ def test_the_2014_vzor_renders_b3_and_never_b31(self): aggregate. Asserted on the rendered XML rather than on the row, because the section a vzor does not have is exactly the kind of error that an optional attribute would let through silently.""" + receipt = self.init_invoice( + "in_invoice", partner=self.partner_a, invoice_date="2015-06-05", + amounts=[100.0], taxes=self.tax_purchase, post=False) + receipt.l10n_sk_kv_is_simplified = True + receipt.action_post() version = self.env.ref("l10n_sk_kv_dph.kvdph_version_2014") st = self._statement_for(version, "2015-06-01", "2015-06-30") st.action_compute_lines() + # ⚠️ This test used to create no receipt, so it passed while B.3 was + # never written at all: the section was computed and then left out of + # ``_collect_sections_by_code``, which the export reads. + self.assertEqual(len(st._collect_sections_by_code()["B.3"]), 1) st.action_export_xml() xml = etree.fromstring(base64.b64decode(st.xml_attachment_id.datas)) tags = {etree.QName(el).localname for el in xml.iter()} + self.assertIn("B3", tags, "the simplified receipt must be filed") self.assertNotIn("B31", tags, "the B.3 split did not exist until 2016") self.assertNotIn("B32", tags) @@ -634,7 +678,7 @@ def test_an_eu_customer_without_a_vat_number_is_not_an_intra_eu_supply(self): two in A.1; that filing is the outlier. """ czech = self.env["res.partner"].create({ - "name": "Romana bez IC DPH", + "name": "Jana bez IC DPH", "country_id": self.env.ref("base.cz").id, }) invoice = self.init_invoice( @@ -999,6 +1043,56 @@ def test_a_rated_correction_still_reaches_C1(self): "an ordinary rated credit note is exactly what C.1 reports", ) + def test_a_row_is_identified_by_the_reference_the_form_carries(self): + """A filed statement holds the reference stripped of whitespace, so a + comparison keyed on the stored spelling never met it: 3 694 rows of a + migrated i6 agenda read as two one-sided differences each.""" + row = self.env["l10n.sk.kv.dph.section.b1"].new({ + "partner_vat": "PL8942520778", "entry_ref": "FV 1/06/2024"}) + filed = self.env["l10n.sk.kv.dph.section.b1"].new({ + "partner_vat": "PL8942520778", "entry_ref": "FV1/06/2024"}) + self.assertEqual(row._kv_identity(), filed._kv_identity()) + self.assertEqual(row.entry_ref, "FV 1/06/2024", + "the stored spelling is untouched") + + def test_a_bare_country_code_is_no_vat_number(self): + """`US` and nothing file the same optional `Dod`; a row must not read + as two one-sided differences because one side wrote the placeholder.""" + B1 = self.env["l10n.sk.kv.dph.section.b1"] + self.assertEqual( + B1.new({"partner_vat": "US", "entry_ref": "MC22931247"})._kv_identity(), + B1.new({"partner_vat": False, "entry_ref": "MC22931247"})._kv_identity()) + self.assertNotEqual( + B1.new({"partner_vat": "CZ25856294", "entry_ref": "X"})._kv_identity(), + B1.new({"partner_vat": False, "entry_ref": "X"})._kv_identity(), + "a real number is still part of the identity") + + def test_an_older_baseline_with_spaces_still_matches(self): + """A chained dodatočný baseline snapshotted before the identity dropped + whitespace must still name the same row, or an unchanged document + would be stornoed and re-added.""" + key = self._make_statement()._kv_identity_key + self.assertEqual(key(["PL8942520778", "FV 1/06/2024", 20.0]), + key(("PL8942520778", "FV1/06/2024", 20.0))) + + def test_a_recompute_clears_rows_of_a_section_the_version_lacks(self): + """B.3 rows computed under the 2014 vzor must not survive a recompute + under a later one: the clear used to walk only the CURRENT version's + sections, and B.3 is now exported whenever the statement holds it.""" + receipt = self.init_invoice( + "in_invoice", partner=self.partner_a, invoice_date="2015-06-05", + amounts=[100.0], taxes=self.tax_purchase, post=False) + receipt.l10n_sk_kv_is_simplified = True + receipt.action_post() + st = self._statement_for( + self.env.ref("l10n_sk_kv_dph.kvdph_version_2014"), + "2015-06-01", "2015-06-30") + st.action_compute_lines() + self.assertTrue(st.sk_section_b3_ids) + st.version_id = self.env.ref("l10n_sk_kv_dph.kvdph_version_2016") + st.action_compute_lines() + self.assertFalse(st.sk_section_b3_ids) + def test_dodatocny_delta_kopr(self): """A dodatočný KV reports only the delta vs the original, tagged kód opravy: a forgotten invoice appears once with KOpr=2; unchanged rows are @@ -1633,6 +1727,189 @@ def test_a_linked_original_wins_over_the_typed_number(self): st.action_compute_lines() self.assertEqual(self._c1_fo(st), [invoice.name.replace(" ", "")]) + # --- O from the tax repartition (accountant review, 2026-10-03) ---------------- + def _partial_tax(self, base_tax, deductible_pct, name): + """``base_tax`` with ``deductible_pct`` of its VAT on the VAT account + and the rest onto the expense, as a vehicle at 50 % is set up.""" + vat_leg = base_tax.invoice_repartition_line_ids.filtered( + lambda r: r.repartition_type == "tax" and r.factor_percent > 0)[:1] + + def legs(): + out = [Command.create({"repartition_type": "base"}), + Command.create({"repartition_type": "tax", + "factor_percent": deductible_pct, + "account_id": vat_leg.account_id.id, + "tag_ids": [Command.set(vat_leg.tag_ids.ids)]})] + if deductible_pct < 100: + out.append(Command.create({"repartition_type": "tax", + "factor_percent": 100 - deductible_pct, + "use_in_tax_closing": False})) + return out + return base_tax.copy({ + "name": name, + "invoice_repartition_line_ids": [Command.clear()] + legs(), + "refund_repartition_line_ids": [Command.clear()] + legs(), + }) + + def test_a_half_deduction_files_half_in_O(self): + """"do stĺpca Odpočítaná daň musí ísť len tých 50 %" — from the tax.""" + half = self._partial_tax(self.tax_purchase, 50, "23 % 50/50") + self.init_invoice("in_invoice", partner=self.partner_a, + invoice_date="2026-06-12", amounts=[1000.0], + taxes=half, post=True) + st = self._make_statement() + st.action_compute_lines() + b2 = st.sk_section_b2_ids + self.assertEqual(len(b2), 1) + self.assertAlmostEqual(b2.tax_amount, 10.0 * half.amount, 2, + msg="D is the whole tax charged") + self.assertAlmostEqual(b2.deducted_amount, 5.0 * half.amount, 2, + msg="O is the half the return deducts") + st.action_export_xml() + root = etree.fromstring(base64.b64decode(st.xml_attachment_id.datas)) + row = [el for el in root.iter() if etree.QName(el).localname == "B2"][0] + self.assertEqual(row.get("O"), "%.2f" % (5.0 * half.amount)) + + def test_a_self_assessed_tax_without_deduction_files_zero_O(self): + """"Purchase EU bez nároku na odpočet": 100 % payable, 0 % deductible. + B.1 still reports the tax, and O is 0.00.""" + rc = self.env["account.tax"].search([ + ("company_id", "=", self.company.id), + ("type_tax_use", "=", "purchase"), + ("cssk_control_is_reverse_charge", "=", True), + ("amount", ">", 0)], limit=1) + self.assertTrue(rc, "fixture: the SK chart ships a self-assessed purchase tax") + + def legs(repartition): + out = [Command.create({"repartition_type": "base"})] + for leg in repartition.filtered(lambda r: r.repartition_type == "tax"): + vals = {"repartition_type": "tax", + "factor_percent": leg.factor_percent, + "account_id": leg.account_id.id, + "tag_ids": [Command.set(leg.tag_ids.ids)]} + if leg.factor_percent > 0: # the deduction leg: onto the expense + vals.update(account_id=False, use_in_tax_closing=False, + tag_ids=[Command.clear()]) + out.append(Command.create(vals)) + return out + no_deduction = rc.copy({ + "name": "Purchase EU bez nároku na odpočet", + "invoice_repartition_line_ids": [Command.clear()] + legs(rc.invoice_repartition_line_ids), + "refund_repartition_line_ids": [Command.clear()] + legs(rc.refund_repartition_line_ids), + }) + self.init_invoice("in_invoice", partner=self.partner_a, + invoice_date="2026-06-12", amounts=[1000.0], + taxes=no_deduction, post=True) + st = self._make_statement() + st.action_compute_lines() + b1 = st.sk_section_b1_ids + self.assertEqual(len(b1), 1) + self.assertAlmostEqual(b1.tax_amount, 10.0 * rc.amount, 2, + msg="D: the tax is still computed and payable") + self.assertAlmostEqual(b1.deducted_amount, 0.0, 2, msg="O: nothing deducted") + + def test_a_full_self_assessed_deduction_is_unchanged(self): + rc = self.env["account.tax"].search([ + ("company_id", "=", self.company.id), + ("type_tax_use", "=", "purchase"), + ("cssk_control_is_reverse_charge", "=", True), + ("amount", ">", 0)], limit=1) + self.init_invoice("in_invoice", partner=self.partner_a, + invoice_date="2026-06-12", amounts=[1000.0], + taxes=rc, post=True) + st = self._make_statement() + st.action_compute_lines() + b1 = st.sk_section_b1_ids + self.assertAlmostEqual(b1.deducted_amount, b1.tax_amount, 2) + self.assertTrue(b1.tax_amount) + + def test_a_half_deduction_on_receipts_reaches_b31(self): + half = self._partial_tax(self.tax_purchase, 50, "PHM 50/50") + bill = self.init_invoice("in_invoice", partner=self.partner_a, + invoice_date="2026-06-05", amounts=[100.0], + taxes=half, post=False) + bill.l10n_sk_kv_is_simplified = True + bill.action_post() + st = self._make_statement() + st.action_compute_lines() + b31 = st.sk_section_b31_ids + self.assertAlmostEqual(b31.total_tax_amount, half.amount, 2) + self.assertAlmostEqual(b31.total_deducted_amount, half.amount / 2, 2) + + # --- a credit note to a D.2 supply stays in D.2 -------------------- + def test_a_credit_note_to_a_private_individual_reduces_d2(self): + """"Dobropis k vystavenej faktúre, ktorá patrila pôvodne do D2, patrí + tiež do D2." It used to go to C.1, which A.1 never fed.""" + person = self.env["res.partner"].create({"name": "Súkromná osoba"}) + invoice = self.init_invoice( + "out_invoice", partner=person, invoice_date="2026-06-05", + amounts=[1000.0], taxes=self.tax_sale, post=True) + credit = self.init_invoice( + "out_refund", partner=person, invoice_date="2026-06-19", + amounts=[300.0], taxes=self.tax_sale, post=False) + credit.reversed_entry_id = invoice + credit.action_post() + self.assertEqual( + credit.line_ids.filtered("tax_ids")[:1].cssk_control_section_code, "D.2") + st = self._make_statement() + st.action_compute_lines() + self.assertFalse(st._collect_sections_by_code().get("C.1"), + "nothing in C.1") + d2 = st.sk_section_d2_ids + self.assertAlmostEqual(sum(d2.mapped("total_tax_base")), 700.0, 2) + self.assertAlmostEqual(sum(d2.mapped("total_tax_amount")), + 7.0 * self.rate_s, 2) + st.action_export_xml() # and it still files + + def test_a_credit_note_to_a_business_is_still_c1(self): + credit = self._credit(cssk_control_original_ref="FV2026/0042") + self.assertEqual( + credit.line_ids.filtered("tax_ids")[:1].cssk_control_section_code, "C.1") + + # --- C.2 original from the vendor credit note's Source Document ---- + def _vendor_credit(self, **vals): + credit = self.init_invoice( + "in_refund", partner=self.partner_a, invoice_date="2026-06-19", + amounts=[200.0], taxes=self.tax_purchase) + credit.write(dict(ref="DOB-77", **vals)) + credit.action_post() + return credit + + def _c2_original(self): + st = self._make_statement() + st.action_compute_lines() + return st._collect_sections_by_code()["C.2"].entry_ref_original + + def test_a_vendor_credit_note_reads_its_source_document(self): + self._vendor_credit(invoice_origin="DF-2026-0315") + self.assertEqual(self._c2_original(), "DF-2026-0315") + + def test_the_typed_original_wins_over_the_source_document(self): + self._vendor_credit(invoice_origin="DF-2026-0315", + cssk_control_original_ref="DF-2026-0316") + self.assertEqual(self._c2_original(), "DF-2026-0316") + + def test_a_purchase_order_name_is_not_read_as_the_invoice(self): + """A vendor credit note raised from a PO carries the ORDER there.""" + if "purchase.order" not in self.env: + self.skipTest("purchase not installed") + order = self.env["purchase.order"].create({ + "partner_id": self.partner_a.id, "company_id": self.company.id}) + self._vendor_credit(invoice_origin="%s, X" % order.name) + st = self._make_statement() + st.action_compute_lines() + with self.assertRaisesRegex(UserError, "invoice being corrected"): + st.action_export_xml() + + def test_a_sales_credit_note_does_not_read_the_sales_order(self): + """invoice_origin on the sales side is the order number, not the + invoice: it must not be filed as FO.""" + self._credit(invoice_origin="S00042") + st = self._make_statement() + st.action_compute_lines() + with self.assertRaisesRegex(UserError, "invoice being corrected"): + st.action_export_xml() + @tagged("post_install", "-at_install") diff --git a/l10n_sk_kv_dph/views/cssk_control_statement_views.xml b/l10n_sk_kv_dph/views/cssk_control_statement_views.xml index 32628dcf..6743224f 100644 --- a/l10n_sk_kv_dph/views/cssk_control_statement_views.xml +++ b/l10n_sk_kv_dph/views/cssk_control_statement_views.xml @@ -79,6 +79,22 @@
    + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + vrp2.receipt.search + vrp2.receipt + + + + + + + + + + + + + + + + + + VRP2 Receipts + vrp2.receipt + list,form + {'create': False} + +

    No VRP2 receipts yet

    +

    Fiscal receipts are created when you fiscalize a customer invoice + in VRP2 (from the invoice form or the invoice-list mass action).

    +
    +
    + + + + diff --git a/l10n_sk_vrp2_base/CHANGELOG.rst b/l10n_sk_vrp2_base/CHANGELOG.rst new file mode 100644 index 00000000..8778a984 --- /dev/null +++ b/l10n_sk_vrp2_base/CHANGELOG.rst @@ -0,0 +1,180 @@ +========= +Changelog +========= + +All notable changes to **l10n_sk_vrp2_base** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +[19.0.2.4.0] — 2026-10-06 +------------------------- + +Added +~~~~~ + +- ``vrp2_register_version`` and ``vrp2_vat_payer`` on the credentials + mixin, cached from the dashboard (``cashRegister.version``, + ``organization.vatPayer``), and ``_vrp2_valid_receipt_header()``, which + refreshes them and returns the ``vatPayer`` / ``version`` every + ``/v5/receipt/create/valid`` body must carry. All three captured valid + receipts sent the dashboard's version, not the clock. +- ``vrp2_round2`` / ``vrp2_round4`` / ``vrp2_round5``: the web app's + ``zaokruhli2/4/5``, including its two oddities — an amount under 5 cents + rounds AWAY from zero to ±0.05, and a half rounds towards +∞ (JavaScript + ``Math.round``), so −1.025 → −1.00. +- ``vrp2.client._get_receipt_data`` (``GET /v4/receipt/getdata``). + +Fixed +~~~~~ + +- ``_get_receipt_list`` sent a GET; the web app POSTs a paging/filter/ordering + body to ``/v4/receipt/receiptlist``. + +Changed +~~~~~~~ + +- ``crpOs`` reports app version 3.1.9, which the 2026-10-06 capture shows the + web app now sends (was 3.1.3). The checksum algorithm and compact JSON were + re-verified byte-for-byte against that capture's three POSTs. + +[19.0.2.3.4] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- **Code translations that Odoo was never loading.** An entry whose references + are ``code:addons/...`` is treated as a Python translation only if it carries + the extracted comment ``#. odoo-python`` — ``_load_python_translations`` + filters on exactly that and never on the reference. Without it an entry can + name the right ``.py``, carry a correct msgstr, pass ``msgfmt --check``, and + be silently ignored for ever. This module's hand-added entries were in that + state. Repaired by ``tools/fix_po_code_comments.py``, which is also the CI + check; the offline exporter now emits the comment itself. + +[19.0.2.3.3] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- **The model's own name was never exported.** ``_description`` is what + labels a record's type in breadcrumbs and the technical model list, and + ``tools/i18n_export_offline.py`` emitted no ``model:ir.model,name:`` line + at all — the entries already in the catalogues had come from an older + DATABASE export. So every model added since had no entry and its name + could not be translated. The exporter now emits it, and the missing + entries are merged and filled. + +[19.0.2.3.2] — 2026-09-13 +------------------------- + +Fixed +~~~~~ + +- **A field label was being INVENTED, not missing.** ``fields.Float("Night + work %", ...)`` passes ``string`` positionally, and + ``tools/i18n_export_offline.py`` read it from keyword arguments only — so it + fell back to deriving a label from the field name and wrote "Noc Pct" into + the catalogue where Odoo's own export writes "Night work %". Every + translation keyed to the derived form was therefore keyed to a msgid the + runtime never looks up: present, valid, and dead. +- The exporter now reads the positional slot, which differs per field type + (``Many2one`` puts ``comodel_name`` first, ``One2many`` two arguments, + ``Many2many`` four, ``Selection`` its selection). Positional ``selection`` + lists are extracted too, which is where the stupne-náročnosti and VRP2 + receipt-state labels had been going missing entirely. +- Catalogues regenerated against the corrected msgids and re-translated. + +[19.0.2.3.1] — 2026-09-13 +------------------------- + +Added +~~~~~ + +- **Slovak catalogue — the module had none.** Generated with + ``tools/i18n_export_offline.py`` (no database is available here) and verified + back through Odoo's own ``PoFileReader``, so every entry resolves to the + record it belongs to rather than importing as code strings only. + +Changed +~~~~~~~ + +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +[19.0.2.3.0] +------------ + +- **Fixed request/checksum body consistency.** The request body is now + serialized **once** as compact JSON (``_compact_json``) and sent as those exact + bytes via ``data=``; ``crpChecksum`` is computed over the same string. Before, + the body went out through requests' ``json=`` (which inserts ``", "``/``": "`` + spaces) while the checksum was computed over compact JSON — so the checksummed + bytes did not match the transmitted body. ``_crp_checksum`` now takes the + already-serialized body string. +- Removed the guessed ``_create_storno`` client method (``POST + /v5/receipt/create/storno`` never existed). A storno is an ordinary + ``/v5/receipt/create/valid`` receipt — see l10n_sk_vrp2_account. + +[19.0.2.2.0] +------------ + +- Added ``vrp2_round_5c`` ("Round to 0.05 €") on the credentials mixin, mirroring + the VRP2 web app toggle "Zaokrúhľovať na 5 centov". **Confirmed against a + capture (settings-rounding HAR): this toggle is purely client-side** — turning + it off/on sends nothing to the server (the two ``setprofile`` saves were + byte-identical with no rounding field, and it appears in no API + request/response). So it is a local per-register preference, never synced; the + fiscal effect is carried per-receipt via ``useRounding``/``roundingAmount``. +- Added ``vrp2.client._create_storno`` (POST ``/v5/receipt/create/storno``) for + cancelling a prior receipt. **[VERIFY LIVE]**: endpoint + payload are a best + guess pending a real storno capture. + +[19.0.2.1.0] +------------ + +- Added computed ``vrp2_status`` (Not configured / Disconnected / Connected) on + the credentials mixin, shown as a badge in the Accounting VRP2 settings. + Login button now hidden once a session token exists. + +[19.0.2.0.0] +------------ + +- Introduced ``vrp2.credentials.mixin`` (AbstractModel) holding the VRP2 + credentials + session + dashboard fields and the login/logout helpers. + ``res.company`` now inherits it (= the "company default register" used by + invoice payments); ``pos.config`` inherits it too for per-till registers. +- ``vrp2.client`` methods now take a generic ``holder`` (any record with the + mixin) instead of a ``company`` — one login = one cash register (DKP), per + the captured login/dashboard response. + +[19.0.1.1.1] +------------ + +- Login success notification is now sticky and richer (business name, DKP, + access role) so it is not missed as a fleeting toast. + +[19.0.1.1.0] +------------ + +- Moved the VRP2 VAT-rate↔tax mapping out to ``pos_vrp2`` (only POS uses it; + invoice-payment receipts carry no VAT breakdown). Removed from base: + ``vrp2_tax_0..23`` + ``vrp2_vatlist_json`` fields, the ``_vrp2_vat_*`` helpers, + the "Tax Mapping" settings block, and the VAT-list fetch in login. Base is + now pure connection/session/dashboard. + +[19.0.1.0.0] +------------ + +- Initial release. Communication layer extracted from ``pos_vrp2``: + + - ``vrp2.client`` API client with crpChecksum request signing + (verified byte-for-byte against captured VRP2 traffic). + - VRP2 credentials, session state and cached dashboard on ``res.company``. + - VRP2 VAT-rate ↔ Odoo sales-tax mapping helpers. + - Login / Logout actions surfaced in the Accounting settings. diff --git a/l10n_sk_vrp2_base/__init__.py b/l10n_sk_vrp2_base/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/l10n_sk_vrp2_base/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/l10n_sk_vrp2_base/__manifest__.py b/l10n_sk_vrp2_base/__manifest__.py new file mode 100644 index 00000000..a396eadd --- /dev/null +++ b/l10n_sk_vrp2_base/__manifest__.py @@ -0,0 +1,32 @@ +{ + "name": "Slovak VRP2 - Base / Connection", + "version": "19.0.2.4.0", + "author": "Data Dance s.r.o.", + "category": "Accounting/Localizations", + "summary": "Connection layer for the Slovak Virtual Cash Register (VRP 2)", + "description": """ +Base module for integrating Odoo with the Slovak Financial Administration's +Virtuálna registračná pokladnica 2 (VRP 2) REST API. + +Provides the shared communication layer: +- ``vrp2.client`` API client with crpChecksum request signing +- VRP2 credentials, session and dashboard cache on the company +- VRP2 VAT-rate ↔ Odoo sales-tax mapping +- Login / Logout actions in the Accounting settings + +Functional modules (invoice payments, POS receipts, ...) depend on this +module and reuse ``vrp2.client``. + """, + "license": "AGPL-3", + "depends": [ + "account", + ], + "external_dependencies": { + "python": ["requests"], + }, + "data": [ + "views/res_config_settings_views.xml", + ], + "installable": True, + "application": False, +} diff --git a/l10n_sk_vrp2_base/i18n/l10n_sk_vrp2_base.pot b/l10n_sk_vrp2_base/i18n/l10n_sk_vrp2_base.pot new file mode 100644 index 00000000..3db9c0fa --- /dev/null +++ b/l10n_sk_vrp2_base/i18n/l10n_sk_vrp2_base.pot @@ -0,0 +1,291 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_vrp2_base +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-06 12:16+0000\n" +"PO-Revision-Date: 2026-10-06 12:16+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_status__connected +msgid "Connected" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_status +msgid "" +"Connected = a VRP2 session token is held (the token may still expire server-" +"side; it is refreshed automatically on the next call)." +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_credentials.py:0 +msgid "" +"Connected as %(business)s\n" +"DKP: %(dkp)s\n" +"Access: %(role)s" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.module.module,summary:base.module_l10n_sk_vrp2_base +msgid "Connection layer for the Slovak Virtual Cash Register (VRP 2)" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Credentials for the VRP2 API (vcrp.financnasprava.sk)" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_dic +msgid "DIČ" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_status__disconnected +msgid "Disconnected" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Information fetched from VRP2 after login" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_ic_dph +msgid "IČ DPH" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_ico +msgid "IČO" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_dkp +msgid "Kód pokladnice (DKP)" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_token +msgid "" +"Live VRP2 session token — anyone holding it can act on the cash register, so" +" only administrators may read it." +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_credentials.py:0 +msgid "Logged out from VRP2." +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Login" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_login +msgid "Login (kód pokladnice / kód podnikateľa) from Finančná správa SR." +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Logout" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_round_5c +msgid "" +"Mirrors the VRP2 web app toggle 'Zaokrúhľovať na 5 centov'. When on, paid totals are rounded to the nearest 5 cents and the difference is reported in the receipt's roundingAmount/useRounding.\n" +"This is a CLIENT-SIDE preference in the VRP2 web app (confirmed: toggling it sends nothing to the server and never appears in any API request/response), so it is configured here per register." +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_status__not_configured +msgid "Not configured" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_password +msgid "Password for VRP2 authentication." +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_role__read +msgid "Read" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_round_5c +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Round to 0.05 €" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.module.module,shortdesc:base.module_l10n_sk_vrp2_base +msgid "Slovak VRP2 - Base / Connection" +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "The 'requests' Python package is required for VRP2." +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_vat_payer +msgid "" +"The register's organization.vatPayer, as VRP2 reports it. Sent as vatPayer " +"on every sale receipt." +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_credentials.py:0 +msgid "VRP2" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "VRP2 - Slovak Virtual Cash Register" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_business_name +msgid "VRP2 Business Name" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "VRP2 Cash Register Info" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "VRP2 Connection" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model,name:l10n_sk_vrp2_base.model_vrp2_credentials_mixin +msgid "VRP2 Credentials & Session" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_last_login +msgid "VRP2 Last Login" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_login +msgid "VRP2 Login" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_password +msgid "VRP2 Password" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model,name:l10n_sk_vrp2_base.model_vrp2_client +msgid "VRP2 REST API Client" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_register_version +msgid "VRP2 Register Version" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_return_value +msgid "VRP2 Return Value" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_role +msgid "VRP2 Role" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_status +msgid "VRP2 Status" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_token +msgid "VRP2 Token" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_vat_payer +msgid "VRP2 VAT Payer" +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "VRP2 credentials are not configured on holder '%s'." +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "VRP2 error: %s" +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "VRP2 login failed: %s" +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "VRP2 login response missing token." +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "" +"VRP2 register setting 'Zaokrúhľovať na 5 centov'. When on, paid totals are " +"rounded to the nearest 5 cents before fiscalization." +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_credentials.py:0 +msgid "VRP2 — Login successful" +msgstr "" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "" +"VRP2: Unsynchronised receipt or location. The system will attempt to re-sync" +" automatically. Error code: -100" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_role__write +msgid "Write" +msgstr "" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_register_version +msgid "" +"cashRegister.version from the dashboard: a millisecond stamp of the " +"register's configuration, which moves whenever its settings are changed in " +"the portal. The web app sends it as the version of every valid receipt " +"(/v5/receipt/create/valid)." +msgstr "" diff --git a/l10n_sk_vrp2_base/i18n/sk.po b/l10n_sk_vrp2_base/i18n/sk.po new file mode 100644 index 00000000..cb23fdb0 --- /dev/null +++ b/l10n_sk_vrp2_base/i18n/sk.po @@ -0,0 +1,313 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_sk_vrp2_base +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-06 12:16+0000\n" +"PO-Revision-Date: 2026-10-06 12:16+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: sk_SK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : n%1!=0 ? 2: 3);\n" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_status__connected +msgid "Connected" +msgstr "Pripojené" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_status +msgid "" +"Connected = a VRP2 session token is held (the token may still expire server-" +"side; it is refreshed automatically on the next call)." +msgstr "" +"Pripojené = je držaný token relácie VRP2 (token môže na strane servera " +"expirovať; pri ďalšom volaní sa obnoví automaticky)." + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_credentials.py:0 +msgid "" +"Connected as %(business)s\n" +"DKP: %(dkp)s\n" +"Access: %(role)s" +msgstr "" +"Prihlásený ako %(business)s\n" +"DKP: %(dkp)s\n" +"Prístup: %(role)s" + +#. module: l10n_sk_vrp2_base +#: model:ir.module.module,summary:base.module_l10n_sk_vrp2_base +msgid "Connection layer for the Slovak Virtual Cash Register (VRP 2)" +msgstr "Pripojovacia vrstva pre virtuálnu registračnú pokladnicu (VRP 2)" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Credentials for the VRP2 API (vcrp.financnasprava.sk)" +msgstr "Prihlasovacie údaje pre API VRP2 (vcrp.financnasprava.sk)" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_dic +msgid "DIČ" +msgstr "DIČ" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_status__disconnected +msgid "Disconnected" +msgstr "Odpojené" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Information fetched from VRP2 after login" +msgstr "Údaje načítané z VRP2 po prihlásení" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_ic_dph +msgid "IČ DPH" +msgstr "IČ DPH" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_ico +msgid "IČO" +msgstr "IČO" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_dkp +msgid "Kód pokladnice (DKP)" +msgstr "Kód pokladnice (DKP)" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_token +msgid "" +"Live VRP2 session token — anyone holding it can act on the cash register, so" +" only administrators may read it." +msgstr "" +"Živý token relácie VRP2 — kto ho má, môže konať v mene pokladnice, preto ho " +"smú čítať len správcovia." + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_credentials.py:0 +msgid "Logged out from VRP2." +msgstr "Odhlásené z VRP2." + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Login" +msgstr "Prihlásenie" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_login +msgid "Login (kód pokladnice / kód podnikateľa) from Finančná správa SR." +msgstr "" +"Prihlasovacie meno (kód pokladnice / kód podnikateľa) od Finančnej správy " +"SR." + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Logout" +msgstr "Odhlásiť" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_round_5c +msgid "" +"Mirrors the VRP2 web app toggle 'Zaokrúhľovať na 5 centov'. When on, paid totals are rounded to the nearest 5 cents and the difference is reported in the receipt's roundingAmount/useRounding.\n" +"This is a CLIENT-SIDE preference in the VRP2 web app (confirmed: toggling it sends nothing to the server and never appears in any API request/response), so it is configured here per register." +msgstr "" +"Zodpovedá prepínaču „Zaokrúhľovať na 5 centov“ vo webovej aplikácii VRP2. Ak je zapnutý, uhrádzané sumy sa zaokrúhľujú na 5 centov a rozdiel sa uvádza v roundingAmount/useRounding dokladu.\n" +"Ide o nastavenie na STRANE KLIENTA webovej aplikácie VRP2 (overené: jeho prepnutie neodošle na server nič a v žiadnej požiadavke/odpovedi API sa neobjaví), preto sa nastavuje tu pre každú pokladnicu." + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_status__not_configured +msgid "Not configured" +msgstr "Nenastavené" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_password +msgid "Password for VRP2 authentication." +msgstr "Heslo na overenie vo VRP2." + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_role__read +msgid "Read" +msgstr "Čítanie" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_round_5c +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "Round to 0.05 €" +msgstr "Zaokrúhľovať na 0,05 €" + +#. module: l10n_sk_vrp2_base +#: model:ir.module.module,shortdesc:base.module_l10n_sk_vrp2_base +msgid "Slovak VRP2 - Base / Connection" +msgstr "Slovenská VRP2 — základ / pripojenie" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "The 'requests' Python package is required for VRP2." +msgstr "Pre VRP2 je potrebný Python balík „requests“." + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_vat_payer +msgid "" +"The register's organization.vatPayer, as VRP2 reports it. Sent as vatPayer " +"on every sale receipt." +msgstr "" +"Hodnota organization.vatPayer pokladnice, ako ju hlási VRP2. Odosiela sa ako" +" vatPayer na každom pokladničnom doklade." + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_credentials.py:0 +msgid "VRP2" +msgstr "VRP2" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "VRP2 - Slovak Virtual Cash Register" +msgstr "VRP2 — slovenská virtuálna registračná pokladnica" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_business_name +msgid "VRP2 Business Name" +msgstr "Obchodné meno vo VRP2" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "VRP2 Cash Register Info" +msgstr "Údaje o pokladnici VRP2" + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "VRP2 Connection" +msgstr "Pripojenie VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model,name:l10n_sk_vrp2_base.model_vrp2_credentials_mixin +msgid "VRP2 Credentials & Session" +msgstr "Prihlasovacie údaje a relácia VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_last_login +msgid "VRP2 Last Login" +msgstr "Posledné prihlásenie do VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_login +msgid "VRP2 Login" +msgstr "Prihlasovacie meno VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_password +msgid "VRP2 Password" +msgstr "Heslo VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model,name:l10n_sk_vrp2_base.model_vrp2_client +msgid "VRP2 REST API Client" +msgstr "Klient REST API VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_register_version +msgid "VRP2 Register Version" +msgstr "Verzia pokladnice VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_return_value +msgid "VRP2 Return Value" +msgstr "Návratová hodnota VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_role +msgid "VRP2 Role" +msgstr "Rola VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_status +msgid "VRP2 Status" +msgstr "Stav VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_token +msgid "VRP2 Token" +msgstr "Token VRP2" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,field_description:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_vat_payer +msgid "VRP2 VAT Payer" +msgstr "Platiteľ DPH podľa VRP2" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "VRP2 credentials are not configured on holder '%s'." +msgstr "Na nositeľovi „%s“ nie sú nastavené prihlasovacie údaje VRP2." + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "VRP2 error: %s" +msgstr "Chyba VRP2: %s" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "VRP2 login failed: %s" +msgstr "Prihlásenie do VRP2 zlyhalo: %s" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "VRP2 login response missing token." +msgstr "Odpoveď na prihlásenie do VRP2 neobsahuje token." + +#. module: l10n_sk_vrp2_base +#: model_terms:ir.ui.view,arch_db:l10n_sk_vrp2_base.res_config_settings_view_form_vrp2_base +msgid "" +"VRP2 register setting 'Zaokrúhľovať na 5 centov'. When on, paid totals are " +"rounded to the nearest 5 cents before fiscalization." +msgstr "" +"Nastavenie pokladnice VRP2 „Zaokrúhľovať na 5 centov“. Ak je zapnuté, " +"uhrádzané sumy sa pred evidovaním zaokrúhlia na 5 centov." + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_credentials.py:0 +msgid "VRP2 — Login successful" +msgstr "VRP2 — prihlásenie úspešné" + +#. module: l10n_sk_vrp2_base +#. odoo-python +#: code:addons/l10n_sk_vrp2_base/models/vrp2_client.py:0 +msgid "" +"VRP2: Unsynchronised receipt or location. The system will attempt to re-sync" +" automatically. Error code: -100" +msgstr "" +"VRP2: nesynchronizovaný doklad alebo prevádzka. Systém sa pokúsi o opätovnú " +"synchronizáciu automaticky. Kód chyby: -100" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields.selection,name:l10n_sk_vrp2_base.selection__vrp2_credentials_mixin__vrp2_role__write +msgid "Write" +msgstr "Zápis" + +#. module: l10n_sk_vrp2_base +#: model:ir.model.fields,help:l10n_sk_vrp2_base.field_vrp2_credentials_mixin__vrp2_register_version +msgid "" +"cashRegister.version from the dashboard: a millisecond stamp of the " +"register's configuration, which moves whenever its settings are changed in " +"the portal. The web app sends it as the version of every valid receipt " +"(/v5/receipt/create/valid)." +msgstr "" +"cashRegister.version z prehľadu: časová pečiatka (v milisekundách) " +"konfigurácie pokladnice, ktorá sa mení pri každej zmene jej nastavení na " +"portáli. Webová aplikácia ju odosiela ako version každého platného dokladu " +"(/v5/receipt/create/valid)." diff --git a/l10n_sk_vrp2_base/models/__init__.py b/l10n_sk_vrp2_base/models/__init__.py new file mode 100644 index 00000000..59a24aa0 --- /dev/null +++ b/l10n_sk_vrp2_base/models/__init__.py @@ -0,0 +1,4 @@ +from . import vrp2_credentials +from . import vrp2_client +from . import res_company +from . import res_config_settings diff --git a/l10n_sk_vrp2_base/models/res_company.py b/l10n_sk_vrp2_base/models/res_company.py new file mode 100644 index 00000000..d6941c29 --- /dev/null +++ b/l10n_sk_vrp2_base/models/res_company.py @@ -0,0 +1,8 @@ +from odoo import models + + +class ResCompany(models.Model): + # The company carries VRP2 credentials/session via the mixin and acts as + # the "company default register" used by invoice-payment fiscalization. + _name = "res.company" + _inherit = ["res.company", "vrp2.credentials.mixin"] diff --git a/l10n_sk_vrp2_base/models/res_config_settings.py b/l10n_sk_vrp2_base/models/res_config_settings.py new file mode 100644 index 00000000..baf26418 --- /dev/null +++ b/l10n_sk_vrp2_base/models/res_config_settings.py @@ -0,0 +1,35 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + # The company is the "default VRP2 register" (used by invoice payments). + # Per-till credentials live on pos.config (see pos_vrp2). + + vrp2_login = fields.Char(related="company_id.vrp2_login", readonly=False) + vrp2_password = fields.Char( + related="company_id.vrp2_password", readonly=False + ) + + # Read-only display + vrp2_dkp = fields.Char(related="company_id.vrp2_dkp") + vrp2_business_name = fields.Char(related="company_id.vrp2_business_name") + vrp2_dic = fields.Char(related="company_id.vrp2_dic") + vrp2_ico = fields.Char(related="company_id.vrp2_ico") + vrp2_ic_dph = fields.Char(related="company_id.vrp2_ic_dph") + vrp2_role = fields.Selection(related="company_id.vrp2_role") + vrp2_last_login = fields.Datetime(related="company_id.vrp2_last_login") + vrp2_token = fields.Char( + related="company_id.vrp2_token", groups="base.group_system" + ) + vrp2_status = fields.Selection(related="company_id.vrp2_status") + vrp2_round_5c = fields.Boolean( + related="company_id.vrp2_round_5c", readonly=False + ) + + def action_vrp2_login(self): + return self.company_id.action_vrp2_login() + + def action_vrp2_logout(self): + return self.company_id.action_vrp2_logout() diff --git a/l10n_sk_vrp2_base/models/vrp2_client.py b/l10n_sk_vrp2_base/models/vrp2_client.py new file mode 100644 index 00000000..889d1789 --- /dev/null +++ b/l10n_sk_vrp2_base/models/vrp2_client.py @@ -0,0 +1,495 @@ +import base64 +import hashlib +import json +import logging +import time +from decimal import ROUND_FLOOR, ROUND_HALF_UP, Decimal + +from odoo import _, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +REQUEST_TIMEOUT = 30 +VRP2_APP_VERSION = "3.1.9" + +VRP2_BASE_URL = "https://vcrp.financnasprava.sk/crp/api" +VRP2_BASE_URL_MRP = "https://mcrp.financnasprava.sk/crp/api" + + +def vrp2_round2(amount): + """Round half up to 2 decimals, like the web app's ``zaokruhli2``.""" + return float( + Decimal(str(amount)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) + ) + + +def vrp2_round4(amount): + """Round half up to 4 decimals, like the web app's ``zaokruhli4``.""" + return float( + Decimal(str(amount)).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP) + ) + + +def vrp2_round5(amount): + """Round a CASH amount to 0.05 €, like the web app's ``zaokruhli5``. + + Faithful to app.js, including its edge case: a non-zero amount smaller + than 5 cents rounds AWAY from zero to ±0.05 instead of to 0. + """ + if not amount: + return 0.0 + if 0 < amount < 0.05: + return 0.05 + if -0.05 < amount < 0: + return -0.05 + # Math.round(20 * e) / 20: JavaScript rounds a half towards +infinity, + # so -0.025 goes to -0.00 where ROUND_HALF_UP would give -0.05. + twentieths = (Decimal(str(amount)) * 20 + Decimal("0.5")).quantize( + Decimal("1"), rounding=ROUND_FLOOR + ) + return vrp2_round2(twentieths / 20) + + +def _compact_json(data): + """Serialize ``data`` exactly like the VRP2 web client's ``JSON.stringify``. + + ``separators=(",", ":")`` drops all whitespace and ``ensure_ascii=False`` + keeps UTF-8 characters intact. The request body MUST be sent as these exact + bytes, because ``crpChecksum`` is computed over them — any reserialization + (e.g. requests' ``json=`` default, which inserts spaces) would break it. + """ + return json.dumps(data, separators=(",", ":"), ensure_ascii=False) + + +def _crp_checksum(body_str, token): + """Compute the crpChecksum header value for an already-serialized body. + + Algorithm (deobfuscated from the VRP2 web client, verified byte-for-byte + against captured traffic): + 1. Transform token: for each char take ``chr(ord(c) >> 4)``. + 2. SHA-256 of ``body_str + transformed_token`` (UTF-8). + 3. Base64 of the digest. + + ``body_str`` must be the compact JSON produced by :func:`_compact_json` and + must be the exact string sent as the request body. + """ + transformed = "".join(chr(ord(c) >> 4) for c in token) + raw = (body_str + transformed).encode("utf-8") + digest = hashlib.sha256(raw).digest() + return base64.b64encode(digest).decode("ascii") + + +class Vrp2Client(models.AbstractModel): + _name = "vrp2.client" + _description = "VRP2 REST API Client" + + # ------------------------------------------------------------------ + # Session management + # ------------------------------------------------------------------ + + def _login(self, holder): + """Authenticate with VRP2 and store the session on holder. + + POST /v1/security/autentify/vrp -> full auth response dict. + """ + # sudo: credentials/token are admin-only fields, but any user allowed + # to trigger a fiscalization must be able to open the session. + holder_sudo = holder.sudo() + if not holder_sudo.vrp2_login or not holder_sudo.vrp2_password: + raise UserError( + _("VRP2 credentials are not configured on holder '%s'.") + % holder.display_name + ) + + data = { + "login": holder_sudo.vrp2_login.strip(), + "password": holder_sudo.vrp2_password, + } + resp = self._request_raw( + "POST", + "/v1/security/autentify/vrp", + json_data=data, + token=None, + ) + if resp.status_code != 200: + body = resp.text[:500] + _logger.warning("VRP2 login failed %s: %s", resp.status_code, body) + error_desc = "" + try: + error_desc = resp.json().get("errorDescription", "") + except Exception: + pass + raise UserError( + _("VRP2 login failed: %s") % (error_desc or resp.reason) + ) + + auth = resp.json() + token = auth.get("token") + if not token: + raise UserError(_("VRP2 login response missing token.")) + + user_data = auth.get("user", {}) + roles = user_data.get("roles", []) + role = "write" if "ROLE_02_05" in roles else "read" + return_value = auth.get("returnValue", 0) + + holder.sudo().write({ + "vrp2_token": token, + "vrp2_role": role, + "vrp2_return_value": return_value, + "vrp2_last_login": time.strftime("%Y-%m-%d %H:%M:%S"), + }) + + _logger.info( + "VRP2 login OK for holder %s, role=%s, returnValue=%s", + holder.display_name, + role, + return_value, + ) + return auth + + def _ensure_session(self, holder): + """Ensure we have a valid VRP2 token. Login if needed.""" + if not holder.sudo().vrp2_token: + self._login(holder) + return holder.sudo().vrp2_token + + def _refresh_token(self, holder): + """POST /v1/security/token/refresh to keep the session alive.""" + token = holder.sudo().vrp2_token + if not token: + return self._login(holder) + + try: + self._request( + holder, "POST", "/v1/security/token/refresh", json_data={} + ) + except Exception: + _logger.info("VRP2 token refresh failed, re-logging in.") + return self._login(holder) + + def _logout(self, holder): + """POST /v1/security/signout.""" + if not holder.sudo().vrp2_token: + return + try: + self._request( + holder, "POST", "/v1/security/signout", json_data={} + ) + except Exception: + _logger.debug("VRP2 logout error (ignored).", exc_info=True) + holder.sudo().write({ + "vrp2_token": False, + "vrp2_role": False, + }) + + # ------------------------------------------------------------------ + # High-level request helpers + # ------------------------------------------------------------------ + + def _request( + self, holder, method, path, json_data=None, params=None, raw=False + ): + """Authenticated VRP2 API request. Re-logs in once on a 401.""" + token = self._ensure_session(holder) + resp = self._request_raw( + method, path, json_data=json_data, params=params, token=token + ) + + if resp.status_code == 401: + _logger.info("VRP2 401 on %s %s — re-authenticating.", method, path) + self._login(holder) + token = holder.sudo().vrp2_token + resp = self._request_raw( + method, path, json_data=json_data, params=params, token=token + ) + + if resp.status_code >= 400: + self._handle_error(resp, method, path) + + if raw: + return resp + if resp.status_code == 204 or not resp.content: + return {} + return resp.json() + + def _get(self, holder, path, params=None, raw=False): + return self._request(holder, "GET", path, params=params, raw=raw) + + def _post(self, holder, path, json_data=None, raw=False): + return self._request( + holder, "POST", path, json_data=json_data, raw=raw + ) + + # ------------------------------------------------------------------ + # Low-level HTTP + # ------------------------------------------------------------------ + + def _request_raw(self, method, path, json_data=None, params=None, token=None): + """Send a raw HTTP request to the VRP2 API. Returns requests.Response.""" + try: + import requests + except ImportError as exc: + raise UserError( + _("The 'requests' Python package is required for VRP2.") + ) from exc + import platform + + url = f"{VRP2_BASE_URL}{path}" + headers = { + "crpDate": str(int(time.time() * 1000)), + "crpOs": f"vrp2 {VRP2_APP_VERSION.ljust(8)} {platform.system()}", + "crpOsVersion": platform.release(), + "crpBrowser": "OdooERP", + "crpBrowserVersion": "19.0", + } + + # Serialize the body ONCE, compact, and send those exact bytes. The + # crpChecksum is computed over this same string, so we must not let + # requests reserialize it (its json= default inserts spaces). + body_bytes = None + if json_data is not None: + body_str = _compact_json(json_data) + body_bytes = body_str.encode("utf-8") + headers["Content-Type"] = "application/json" + + if token: + headers["crpToken"] = token + if method.upper() == "POST" and body_bytes is not None: + headers["crpChecksum"] = _crp_checksum(body_str, token) + + resp = requests.request( + method, + url, + data=body_bytes, + params=params, + headers=headers, + timeout=REQUEST_TIMEOUT, + ) + return resp + + # ------------------------------------------------------------------ + # Error handling + # ------------------------------------------------------------------ + + def _handle_error(self, resp, method, path): + """Raise UserError with a meaningful message from a VRP2 error response.""" + body = resp.text[:500] + _logger.warning( + "VRP2 %s %s → %s: %s", method, path, resp.status_code, body + ) + + error_desc = "" + error_code = "" + try: + data = resp.json() + error_desc = data.get("errorDescription", "") + error_code = data.get("errorCode", "") + except Exception: + pass + + msg = error_desc or resp.reason or f"HTTP {resp.status_code}" + if error_code: + msg = f"{msg} (Kód chyby: {error_code})" + + if resp.status_code == 800 and error_desc == "EErrorCode.SYNC_ERROR": + raise UserError( + _( + "VRP2: Unsynchronised receipt or location. " + "The system will attempt to re-sync automatically. " + "Error code: -100" + ) + ) + + raise UserError(_("VRP2 error: %s") % msg) + + # ------------------------------------------------------------------ + # Dashboard / system data + # ------------------------------------------------------------------ + + def _get_dashboard(self, holder): + """GET /v2/user/getdashboard — cash register + business info.""" + return self._get(holder, "/v2/user/getdashboard") + + def _get_vat_list(self, holder): + """GET /v1/vat/vatlist — available VAT rates.""" + return self._get(holder, "/v1/vat/vatlist") + + def _get_params(self, holder): + """POST /v1/system/getparams — system parameters.""" + return self._post( + holder, + "/v1/system/getparams", + json_data={ + "paramNames": [ + "ALLOWED_CHARS_IN_SERVICE_NAME", + "DEFAULT_CURRENCY", + "DEFAULT_LANGUAGE_OVERRIDE", + "MAX_IMPORT_ITEMS", + "REPORTS_OVERVIEW_REFRESH_INTERVAL", + "SUPPORTED_LANGUAGES", + ], + }, + ) + + def _get_exchange_rates(self, holder): + """GET /v1/system/exchangerates — exchange rates.""" + return self._get(holder, "/v1/system/exchangerates") + + # ------------------------------------------------------------------ + # Service (Product) endpoints + # ------------------------------------------------------------------ + + def _get_service_list(self, holder): + """GET /v1/service/getservicelist — all products/services.""" + return self._get(holder, "/v1/service/getservicelist") + + def _get_service_data(self, holder, service_code): + """GET /v1/service/getservicedata/ — single product detail.""" + return self._get( + holder, f"/v1/service/getservicedata/{service_code}" + ) + + def _add_service(self, holder, service_data): + """POST /v1/service/addservice — create a new product/service.""" + return self._post( + holder, "/v1/service/addservice", json_data=service_data + ) + + def _set_service_data(self, holder, service_data): + """POST /v1/service/setservicedata — update existing product/service.""" + return self._post( + holder, "/v1/service/setservicedata", json_data=service_data + ) + + def _set_services(self, holder, services_data): + """POST /v1/service/setservices — bulk update services.""" + return self._post( + holder, "/v1/service/setservices", json_data=services_data + ) + + def _add_service_correction(self, holder, correction_data): + """POST /v1/service/addservicecorrection — deactivate/correct service.""" + return self._post( + holder, + "/v1/service/addservicecorrection", + json_data=correction_data, + ) + + def _get_unique_service_list(self, holder): + """GET /v1/service/getuniqueservicelist.""" + return self._get(holder, "/v1/service/getuniqueservicelist") + + def _import_services(self, holder, import_data): + """POST /v1/service/importservices — bulk import.""" + return self._post( + holder, "/v1/service/importservices", json_data=import_data + ) + + # ------------------------------------------------------------------ + # Service Category endpoints + # ------------------------------------------------------------------ + + def _get_category_list(self, holder): + """GET /v1/service/category/list — all categories.""" + return self._get(holder, "/v1/service/category/list") + + def _set_category(self, holder, category_data): + """POST /v1/service/category/set — create/update/delete categories.""" + return self._post( + holder, "/v1/service/category/set", json_data=category_data + ) + + def _get_color_list(self, holder): + """GET /v1/service/color/list — available category colors.""" + return self._get(holder, "/v1/service/color/list") + + # ------------------------------------------------------------------ + # Receipt endpoints + # ------------------------------------------------------------------ + + def _create_receipt_valid(self, holder, receipt_data): + """POST /v5/receipt/create/valid.""" + return self._post( + holder, "/v5/receipt/create/valid", json_data=receipt_data + ) + + def _create_receipt_invalid(self, holder, receipt_data): + """POST /v5/receipt/create/invalid.""" + return self._post( + holder, "/v5/receipt/create/invalid", json_data=receipt_data + ) + + def _create_deposit(self, holder, amount): + """POST /v5/receipt/create/deposit.""" + return self._post( + holder, + "/v5/receipt/create/deposit", + json_data={"priceWithVat": amount}, + ) + + def _create_withdraw(self, holder, amount): + """POST /v5/receipt/create/withdraw.""" + return self._post( + holder, + "/v5/receipt/create/withdraw", + json_data={"priceWithVat": -abs(amount)}, + ) + + def _create_invoice(self, holder, invoice_data): + """POST /v5/receipt/create/invoice — fiscal receipt for an invoice payment. + + ``invoice_data`` is the full request body, e.g. ``{"dto": {...}}``. + """ + return self._post( + holder, "/v5/receipt/create/invoice", json_data=invoice_data + ) + + def _get_receipt_data(self, holder, server_id): + """GET /v4/receipt/getdata?id= — one receipt's full detail. + + Returns ``{"dto": {...}}`` with the items as VRP2 printed them + (``service.name`` for catalogue items, ``itemName`` for refunds). + """ + return self._get( + holder, "/v4/receipt/getdata", params={"id": server_id} + ) + + def _get_receipt_list(self, holder, filter_criteria=None, start=0, + count=10): + """POST /v4/receipt/receiptlist — paged receipt search. + + A POST with a paging/filter/ordering body (captured 2026-10-06); the + GET this used to send is not what the web app does. + """ + criteria = { + "createDateFrom": None, + "createDateTo": None, + "issueDateFrom": None, + "issueDateTo": None, + "priceVatFrom": None, + "priceVatTo": None, + "receiptId": None, + "receiptNumber": None, + "receiptTypeName": None, + "isChit": None, + } + criteria.update(filter_criteria or {}) + return self._post(holder, "/v4/receipt/receiptlist", json_data={ + "paging": {"resultSetStart": start, "resultSetCount": count}, + "filterCriteria": criteria, + "ordering": {"orderColumn": "createDate", "orderDirection": "desc"}, + }) + + def _send_receipt_email(self, holder, receipt_id, email): + """POST /v5/receipt/send — email a receipt.""" + return self._post( + holder, + "/v5/receipt/send", + json_data={"dto": {"id": receipt_id, "emailAddress": email}}, + ) + + def _sync(self, holder): + """GET /v5/receipt/sync — re-sync an unsynchronised receipt.""" + return self._get(holder, "/v5/receipt/sync") diff --git a/l10n_sk_vrp2_base/models/vrp2_credentials.py b/l10n_sk_vrp2_base/models/vrp2_credentials.py new file mode 100644 index 00000000..68f99b34 --- /dev/null +++ b/l10n_sk_vrp2_base/models/vrp2_credentials.py @@ -0,0 +1,230 @@ +import time + +from odoo import _, api, fields, models + + +class Vrp2CredentialsMixin(models.AbstractModel): + """Shared VRP2 credentials + session state. + + One VRP2 login is bound to exactly one cash register (DKP) — see the login + response (unitId / cashRegisterId) and the dashboard (single dkp). So this + mixin represents "one VRP2 cash register" and is inherited by every model + that needs to authenticate independently: + + - ``res.company`` — the company default register (used by invoice payments) + - ``pos.config`` — each physical till authenticates as its own register + + ``vrp2.client`` accepts any record inheriting this mixin as its "holder". + """ + + _name = "vrp2.credentials.mixin" + _description = "VRP2 Credentials & Session" + + # ------------------------------------------------------------------ + # Credentials + # ------------------------------------------------------------------ + vrp2_login = fields.Char( + string="VRP2 Login", + help="Login (kód pokladnice / kód podnikateľa) from Finančná správa SR.", + ) + vrp2_password = fields.Char( + string="VRP2 Password", + groups="base.group_system", + help="Password for VRP2 authentication.", + ) + + # ------------------------------------------------------------------ + # Session state (written by vrp2.client) + # ------------------------------------------------------------------ + vrp2_token = fields.Char( + string="VRP2 Token", + readonly=True, + copy=False, + groups="base.group_system", + help="Live VRP2 session token — anyone holding it can act on the " + "cash register, so only administrators may read it.", + ) + vrp2_role = fields.Selection( + [("read", "Read"), ("write", "Write")], + string="VRP2 Role", + readonly=True, + copy=False, + ) + vrp2_return_value = fields.Integer( + string="VRP2 Return Value", readonly=True, copy=False + ) + vrp2_last_login = fields.Datetime( + string="VRP2 Last Login", readonly=True, copy=False + ) + + # ------------------------------------------------------------------ + # Cached dashboard / cash register info + # ------------------------------------------------------------------ + vrp2_dkp = fields.Char( + string="Kód pokladnice (DKP)", readonly=True, copy=False + ) + vrp2_business_name = fields.Char( + string="VRP2 Business Name", readonly=True, copy=False + ) + vrp2_dic = fields.Char(string="DIČ", readonly=True, copy=False) + vrp2_ico = fields.Char(string="IČO", readonly=True, copy=False) + vrp2_ic_dph = fields.Char(string="IČ DPH", readonly=True, copy=False) + vrp2_vat_payer = fields.Boolean( + string="VRP2 VAT Payer", + readonly=True, + copy=False, + help="The register's organization.vatPayer, as VRP2 reports it. " + "Sent as vatPayer on every sale receipt.", + ) + vrp2_register_version = fields.Char( + string="VRP2 Register Version", + readonly=True, + copy=False, + help="cashRegister.version from the dashboard: a millisecond stamp " + "of the register's configuration, which moves whenever its settings " + "are changed in the portal. The web app sends it as the version of " + "every valid receipt (/v5/receipt/create/valid).", + ) + + # ------------------------------------------------------------------ + # Fiscal rounding ("Zaokrúhľovať na 5 centov") + # ------------------------------------------------------------------ + vrp2_round_5c = fields.Boolean( + string="Round to 0.05 €", + default=True, + copy=False, + help="Mirrors the VRP2 web app toggle 'Zaokrúhľovať na 5 centov'. When " + "on, paid totals are rounded to the nearest 5 cents and the difference " + "is reported in the receipt's roundingAmount/useRounding.\n" + "This is a CLIENT-SIDE preference in the VRP2 web app (confirmed: " + "toggling it sends nothing to the server and never appears in any API " + "request/response), so it is configured here per register.", + ) + + # ------------------------------------------------------------------ + # Connection status + # ------------------------------------------------------------------ + vrp2_status = fields.Selection( + [ + ("not_configured", "Not configured"), + ("disconnected", "Disconnected"), + ("connected", "Connected"), + ], + string="VRP2 Status", + compute="_compute_vrp2_status", + help="Connected = a VRP2 session token is held (the token may still " + "expire server-side; it is refreshed automatically on the next call).", + ) + + @api.depends("vrp2_login", "vrp2_token") + def _compute_vrp2_status(self): + for record in self: + # sudo: vrp2_token is admin-only, but the (boolean) connection + # status itself is not sensitive. + if not record.vrp2_login: + record.vrp2_status = "not_configured" + elif record.sudo().vrp2_token: + record.vrp2_status = "connected" + else: + record.vrp2_status = "disconnected" + + # ------------------------------------------------------------------ + # Session helpers + # ------------------------------------------------------------------ + + def _vrp2_authenticate(self): + """Login and cache this register's dashboard (cash register + business).""" + self.ensure_one() + self.env["vrp2.client"]._login(self) + self._vrp2_refresh_register() + return self + + def _vrp2_refresh_register(self): + """Fetch this register's dashboard and cache its profile. + + Returns the ``cashRegister`` dict. Called at login and again before + every valid receipt, because the register version it carries changes + whenever the register is reconfigured in the portal, and a receipt + must carry the current one. + """ + self.ensure_one() + dashboard = self.env["vrp2.client"]._get_dashboard(self) + cr_data = dashboard.get("cashRegister") or {} + org_data = cr_data.get("organization") or {} + # Note: the 5-cent rounding toggle is a client-side preference in the + # VRP2 web app and is NOT part of the register profile/dashboard, so it + # is configured locally on the holder (vrp2_round_5c) and never synced. + version = cr_data.get("version") + self.sudo().write({ + "vrp2_dkp": cr_data.get("dkp"), + "vrp2_business_name": org_data.get("name"), + "vrp2_dic": org_data.get("dic"), + "vrp2_ico": org_data.get("ico"), + "vrp2_ic_dph": org_data.get("icDph"), + "vrp2_vat_payer": bool(org_data.get("vatPayer")), + "vrp2_register_version": str(version) if version else False, + }) + return cr_data + + def _vrp2_valid_receipt_header(self): + """The register-level part of a /v5/receipt/create/valid body. + + Verified against three captures (2026-07-15 invoice storno, + 2026-10-06 sale and sale storno) and the web app's builder: + ``vatPayer`` is ``cashRegister.organization.vatPayer`` and + ``version`` is ``cashRegister.version`` — NOT the current time, + which is what the invoice-payment endpoint takes instead. + """ + self.ensure_one() + self._vrp2_refresh_register() + version = self.vrp2_register_version + return { + "vatPayer": self.vrp2_vat_payer, + "version": int(version) if version else int(time.time() * 1000), + } + + def _vrp2_logout_session(self): + self.ensure_one() + self.env["vrp2.client"]._logout(self) + + def _vrp2_login_notification(self): + self.ensure_one() + role_label = dict( + self._fields["vrp2_role"].selection + ).get(self.vrp2_role, self.vrp2_role or "") + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "title": _("VRP2 — Login successful"), + "message": _( + "Connected as %(business)s\nDKP: %(dkp)s\nAccess: %(role)s", + business=self.vrp2_business_name or self.vrp2_login, + dkp=self.vrp2_dkp or "-", + role=role_label, + ), + "type": "success", + "sticky": True, + }, + } + + # ------------------------------------------------------------------ + # Button actions (available on any holder, e.g. pos.config) + # ------------------------------------------------------------------ + + def action_vrp2_login(self): + self._vrp2_authenticate() + return self._vrp2_login_notification() + + def action_vrp2_logout(self): + self._vrp2_logout_session() + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "title": _("VRP2"), + "message": _("Logged out from VRP2."), + "type": "info", + "sticky": False, + }, + } diff --git a/l10n_sk_vrp2_base/tests/__init__.py b/l10n_sk_vrp2_base/tests/__init__.py new file mode 100644 index 00000000..100ac6ec --- /dev/null +++ b/l10n_sk_vrp2_base/tests/__init__.py @@ -0,0 +1 @@ +from . import test_vrp2_base diff --git a/l10n_sk_vrp2_base/tests/test_vrp2_base.py b/l10n_sk_vrp2_base/tests/test_vrp2_base.py new file mode 100644 index 00000000..eab73b25 --- /dev/null +++ b/l10n_sk_vrp2_base/tests/test_vrp2_base.py @@ -0,0 +1,90 @@ +from unittest.mock import patch + +from odoo.exceptions import AccessError +from odoo.tests import TransactionCase, tagged +from odoo.tests.common import new_test_user + +from odoo.addons.l10n_sk_vrp2_base.models.vrp2_client import ( + _compact_json, + _crp_checksum, + vrp2_round5, +) + + +@tagged("post_install", "-at_install") +class TestVrp2Base(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.company = cls.env.company + cls.company.vrp2_login = "88812345678900001" + cls.company.vrp2_token = "SECRET-TOKEN" + cls.user = new_test_user( + cls.env, login="vrp2_plain_user", groups="base.group_user" + ) + + # ---- pure helpers ---------------------------------------------------- + + def test_crp_checksum_stable(self): + # Frozen vector (compact JSON + shifted token + SHA-256 + base64), + # matching the deobfuscated VRP2 web-client algorithm. + self.assertEqual( + _crp_checksum( + _compact_json( + {"priceWithVat": 12.3, "invoiceNumber": "FA-001"} + ), + "AbC123xyz", + ), + "cxfypeuih01qF21DJ7uJxcAQqvE6XyGA73KiYUl268Y=", + ) + + def test_round5_mirrors_the_web_app(self): + """zaokruhli5 from app.js, including its two oddities: an amount + under 5 cents rounds AWAY from zero, and a half rounds towards + +infinity (JavaScript Math.round), so −1.025 → −1.00.""" + cases = { + 18.48: 18.5, 18.47: 18.45, 8.48: 8.5, 0: 0.0, 0.01: 0.05, + -0.01: -0.05, 1.025: 1.05, -1.025: -1.0, -18.48: -18.5, + } + for amount, expected in cases.items(): + self.assertEqual(vrp2_round5(amount), expected, amount) + + def test_valid_receipt_header_comes_from_the_dashboard(self): + dashboard = {"cashRegister": { + "dkp": "99920201234560002", "version": 1662177617338, + "organization": {"name": "X", "vatPayer": False}, + }} + with patch.object( + type(self.env["vrp2.client"]), "_get_dashboard", + return_value=dashboard, + ): + header = self.company._vrp2_valid_receipt_header() + self.assertEqual( + header, {"vatPayer": False, "version": 1662177617338} + ) + self.assertEqual(self.company.vrp2_register_version, "1662177617338") + + # ---- session-token protection (groups=base.group_system) -------------- + + def test_token_not_readable_by_internal_users(self): + """A live VRP2 session token must be admin-only.""" + with self.assertRaises(AccessError): + self.company.with_user(self.user).vrp2_token # noqa: B018 + + def test_status_still_computes_for_internal_users(self): + """The (non-sensitive) connection status stays visible to everyone + even though it derives from the protected token.""" + self.assertEqual( + self.company.with_user(self.user).vrp2_status, "connected" + ) + + def test_client_session_works_for_internal_users(self): + """The client reads the token with elevated rights, so a normal user + allowed to fiscalize can still open/reuse the session.""" + client = self.env["vrp2.client"].with_user(self.user) + with patch.object( + type(self.env["vrp2.client"]), "_request_raw", + side_effect=AssertionError("token present — no network expected"), + ): + token = client._ensure_session(self.company.with_user(self.user)) + self.assertEqual(token, "SECRET-TOKEN") diff --git a/l10n_sk_vrp2_base/views/res_config_settings_views.xml b/l10n_sk_vrp2_base/views/res_config_settings_views.xml new file mode 100644 index 00000000..a0b4977a --- /dev/null +++ b/l10n_sk_vrp2_base/views/res_config_settings_views.xml @@ -0,0 +1,79 @@ + + + + + res.config.settings.form.vrp2.base + res.config.settings + + + + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + + + +
    +
    +
    +
    + +
    diff --git a/partner_autocomplete_ares_cz/CHANGELOG.rst b/partner_autocomplete_ares_cz/CHANGELOG.rst index 28518750..9f583def 100644 --- a/partner_autocomplete_ares_cz/CHANGELOG.rst +++ b/partner_autocomplete_ares_cz/CHANGELOG.rst @@ -8,6 +8,16 @@ Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Cha [Unreleased] ------------ +[19.0.1.2.1] — 2026-10-07 +------------------------- + +Fixed +~~~~~ + +- The summary and README linked the retired ``wwwinfo.mfcr.cz``; ARES lives at + https://ares.gov.cz/, which is also what the module has been calling. The + README's licence badge said "Other proprietary" for an AGPL-3 module. + [19.0.1.2.0] — 2026-09-29 ------------------------- diff --git a/partner_autocomplete_ares_cz/README.rst b/partner_autocomplete_ares_cz/README.rst index b83b7af7..393081a0 100644 --- a/partner_autocomplete_ares_cz/README.rst +++ b/partner_autocomplete_ares_cz/README.rst @@ -8,11 +8,11 @@ Partner Autocomplete Ares CZ .. |badge1| image:: https://raster.shields.io/badge/license-AGPL--3-blue.png - :alt: License: Other proprietary + :alt: License: AGPL-3 |badge1| -| Completes Partner information using ARES from https://wwwinfo.mfcr.cz/ +| Completes Partner information from ARES (https://ares.gov.cz/) | | Availiable countries diff --git a/partner_autocomplete_ares_cz/__manifest__.py b/partner_autocomplete_ares_cz/__manifest__.py index 22048fb5..9c173c94 100644 --- a/partner_autocomplete_ares_cz/__manifest__.py +++ b/partner_autocomplete_ares_cz/__manifest__.py @@ -6,10 +6,10 @@ "license": "AGPL-3", "author": "Data Dance s.r.o.", "website": "https://www.datadance.eu", - "version": "19.0.1.2.0", + "version": "19.0.1.2.1", "post_init_hook": "post_init_hook", "summary": """ -Completes Partner information using ARES from https://wwwinfo.mfcr.cz/ +Completes Partner information from ARES (https://ares.gov.cz/) """, "depends": ["partner_autocomplete_dispatcher"], "data": [ diff --git a/partner_autocomplete_ares_cz/static/tests/tours/ares_clear_tour.js b/partner_autocomplete_ares_cz/static/tests/tours/ares_clear_tour.js index 971ea298..14319f60 100644 --- a/partner_autocomplete_ares_cz/static/tests/tours/ares_clear_tour.js +++ b/partner_autocomplete_ares_cz/static/tests/tours/ares_clear_tour.js @@ -20,11 +20,11 @@ registry.category("web_tour.tours").add("partner_autocomplete_ares_clear", { { content: "Type the company name", trigger: '.o_field_widget[name="name"] input', - run: "edit Durwen", + run: "edit Vzorová", }, { content: "Pick the ARES suggestion", - trigger: '.o-autocomplete--dropdown-item:contains("Durwen CZ s.r.o.")', + trigger: '.o-autocomplete--dropdown-item:contains("Vzorová CZ s.r.o.")', run: "click", }, { diff --git a/partner_autocomplete_ares_cz/tests/test_clear_tour.py b/partner_autocomplete_ares_cz/tests/test_clear_tour.py index 2af64712..0e29eb6d 100644 --- a/partner_autocomplete_ares_cz/tests/test_clear_tour.py +++ b/partner_autocomplete_ares_cz/tests/test_clear_tour.py @@ -27,9 +27,9 @@ def test_a_cleared_one2many_is_saved_cleared(self): }) old_contact = company.child_ids provider_class = type(self.env[ARES]) - suggestion = {"name": "Durwen CZ s.r.o.", "partner_gid": "12345678", "duns": "12345678"} + suggestion = {"name": "Vzorová CZ s.r.o.", "partner_gid": "12345678", "duns": "12345678"} enriched = { - "name": "Durwen CZ s.r.o.", + "name": "Vzorová CZ s.r.o.", "vat": "CZ12345678", "partner_gid": "12345678", "child_ids": [ diff --git a/partner_autocomplete_dispatcher/CHANGELOG.rst b/partner_autocomplete_dispatcher/CHANGELOG.rst index 0760a69e..79f5243e 100644 --- a/partner_autocomplete_dispatcher/CHANGELOG.rst +++ b/partner_autocomplete_dispatcher/CHANGELOG.rst @@ -25,7 +25,7 @@ Carry-over to 18.0 ~~~~~~~~~~~~~~~~~~ - **Country dispatch (19.0.1.1.0, 2026-09-27).** 18.0 still dispatches by the - company's provider only. 18.0 is what DURWEN runs, so port with its upgrade + company's provider only. 18.0 is what a customer runs, so port with its upgrade rehearsal (see the 18.0.2.0.0 notes) rather than straight to production. Fixed diff --git a/partner_autocomplete_dispatcher/__manifest__.py b/partner_autocomplete_dispatcher/__manifest__.py index 694f652b..aaf56ef2 100644 --- a/partner_autocomplete_dispatcher/__manifest__.py +++ b/partner_autocomplete_dispatcher/__manifest__.py @@ -7,10 +7,8 @@ "author": "Data Dance s.r.o.", "website": "https://www.datadance.eu", "version": "19.0.1.1.0", - "summary": """ -Technical module serving as dispatcher for other modules -Enables assigning these dependent modules to individual companies - """, + "summary": "Technical dispatcher for the partner-autocomplete providers: assigns each dependent " + "provider module to the companies that should use it.", "depends": ["partner_autocomplete", "web"], "data": [ "views/res_config_settings_views.xml", diff --git a/partner_autocomplete_dispatcher/tests/test_lookup_key.py b/partner_autocomplete_dispatcher/tests/test_lookup_key.py index c895c015..f158d8ca 100644 --- a/partner_autocomplete_dispatcher/tests/test_lookup_key.py +++ b/partner_autocomplete_dispatcher/tests/test_lookup_key.py @@ -27,18 +27,18 @@ class TestAutocompleteLookupKey(TransactionCase): def setUpClass(cls): super().setUpClass() cls.company = cls.env["res.partner"].create({ - "name": "Zlaté Zrnko s.r.o.", + "name": "Testovacia s.r.o.", "is_company": True, - "company_registry": "50022814", + "company_registry": "12345679", }) def test_a_company_is_looked_up_by_its_own_ico(self): - self.assertEqual(self.company._autocomplete_lookup_key(), "50022814") + self.assertEqual(self.company._autocomplete_lookup_key(), "12345679") def test_a_contact_inside_a_company_is_not(self): """The reported bug. - The contact really does carry 50022814 — core copies it down as a + The contact really does carry 12345679 — core copies it down as a commercial field — but it is the COMPANY's number, not the person's. Looking it up would fetch the company and overwrite the person's name, street and VAT with it. @@ -87,8 +87,8 @@ def test_a_partner_with_nothing_to_search_by_is_skipped(self): def test_the_key_is_stripped(self): """It is concatenated into a signing hash and posted as a form value; a stray space changes the hash and the register rejects the call.""" - self.company.company_registry = " 50022814 " - self.assertEqual(self.company._autocomplete_lookup_key(), "50022814") + self.company.company_registry = " 12345679 " + self.assertEqual(self.company._autocomplete_lookup_key(), "12345679") class TestImportEnrichTrigger(TransactionCase): @@ -114,7 +114,7 @@ def test_saving_a_company_does_not_look_it_up(self): self.env["res.partner"].create({ "name": "Typed by hand s.r.o.", "is_company": True, - "company_registry": "50022814", + "company_registry": "12345679", }) self.assertFalse(calls) @@ -123,6 +123,6 @@ def test_an_import_column_does(self): with spy: partner = self.env["res.partner"].create({ "name": "Imported s.r.o.", - "import_enrich_company": "50022814", + "import_enrich_company": "12345679", }) self.assertEqual(calls, [partner]) diff --git a/partner_autocomplete_orsf_sk/CHANGELOG.rst b/partner_autocomplete_orsf_sk/CHANGELOG.rst index eeaf13fa..2cf7a475 100644 --- a/partner_autocomplete_orsf_sk/CHANGELOG.rst +++ b/partner_autocomplete_orsf_sk/CHANGELOG.rst @@ -8,6 +8,33 @@ Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Cha [Unreleased] ------------ +[19.0.1.4.0] — 2026-10-03 +------------------------- + +Added +~~~~~ + +- **A DIČ-only backfill**, ``action_orsf_fill_missing_dic`` on ``res.partner``. + Fills ``l10n_sk_dic`` where it is empty, from ``POST /lookup/batch``, and + writes nothing else. + + Deliberately separate from ``action_orsf_bulk_refresh``, which rewrites + identity wholesale — name, address, IČ DPH, status. That is right when you + want the register's current view of a contact and wrong when you want one + missing number added to a validated dataset: a migration build whose trial + balance ties to its source should not have partner names rewritten as a side + effect of needing a DIČ. + + It exists because the Slovak Peppol participant identifier is ``0245:`` + and ``l10n_sk_ubl_bis3`` deliberately never derives the DIČ from the VAT + number, so every Slovak trading partner needs the number on file before it + can be e-invoiced. On one real agenda that was 1204 partners, 1165 of them + with an IČO to resolve from — about a dozen batch calls. + + Writes only into an empty field, so it is safe to re-run and never overrules a + hand-entered value. Reports what it did split by reason: filled, not in the + register, in the register but with no DIČ published, and no usable IČO. + [19.0.1.3.0] — 2026-09-29 ------------------------- diff --git a/partner_autocomplete_orsf_sk/__manifest__.py b/partner_autocomplete_orsf_sk/__manifest__.py index 8c2d60ba..01ea1654 100644 --- a/partner_autocomplete_orsf_sk/__manifest__.py +++ b/partner_autocomplete_orsf_sk/__manifest__.py @@ -6,7 +6,7 @@ "license": "AGPL-3", "author": "Data Dance s.r.o.", "website": "https://www.datadance.eu", - "version": "19.0.1.3.0", + "version": "19.0.1.4.0", "summary": """ Completes Partner information from the Slovak state registers via https://orsf.sk """, diff --git a/partner_autocomplete_orsf_sk/models/partner_autocomplete_provider.py b/partner_autocomplete_orsf_sk/models/partner_autocomplete_provider.py index c6c9be01..60186dcb 100644 --- a/partner_autocomplete_orsf_sk/models/partner_autocomplete_provider.py +++ b/partner_autocomplete_orsf_sk/models/partner_autocomplete_provider.py @@ -438,19 +438,23 @@ def _orsf_lookup_batch(self, icos): twenty contacts and then silently returns nothing, which is what makes this worth a separate path. - It carries **far** less than the full record — in fact it is an - existence check and nothing more. A probe of - ``POST https://api.orsf.sk/v1/lookup/batch`` on 2026-10-01 returned - exactly ``{"found": ..., "ico": ...}`` per entry: no ``taxId``, no - ``vatRegistration``, no register office or číslo zápisu, no activities, - filings or history, and **not even a name**. - - So this cannot back a field refresh. Anything that needs a value — the - DIČ for a Peppol participant id, say — has to go through the full - record at 30 requests a minute, one IČO at a time. The earlier wording - here listed what batch omits and so read as though everything unlisted - was present, which made a ~1 200-partner DIČ backfill look like a dozen - requests when it is closer to forty minutes of polling. + It carries **less** than the full record: no ``vatRegistration``, no + register office or číslo zápisu, no activities, filings or history. The + caller is expected to know it is trading depth for reach. + + It DOES carry identity, which is the point — verified against + ``POST https://api.orsf.sk/v1/lookup/batch`` on 2026-10-03: + ``ico``, ``dic``, ``icDph``, ``name``, ``legalForm``, ``status``, + ``establishedOn`` / ``dissolvedOn``, ``address``, ``isVatPayer``, + ``register``, ``nace``, ``fetchedAt``. So it is enough to backfill a DIČ + across a whole partner base in a dozen calls. + + An IČO the register does not hold comes back as the two-key shape + ``{"ico": "...", "found": false}`` — no ``name``, no ``dic``. Worth + knowing because it is easy to mistake for the endpoint returning + nothing useful at all: probe with an IČO that is absent and every field + you were looking for is missing. ``action_orsf_bulk_refresh`` skips on + a missing ``name`` for exactly this reason. Returns ``{ico: record}``, omitting the ones ORSF does not have. """ diff --git a/partner_autocomplete_orsf_sk/models/res_partner.py b/partner_autocomplete_orsf_sk/models/res_partner.py index 8b6ef15e..6116fc9a 100644 --- a/partner_autocomplete_orsf_sk/models/res_partner.py +++ b/partner_autocomplete_orsf_sk/models/res_partner.py @@ -68,6 +68,87 @@ def action_orsf_bulk_refresh(self): self._orsf_report_bulk_refresh(len(updated), missing, len(skipped)) return True + def action_orsf_fill_missing_dic(self): + """Fill ``l10n_sk_dic`` where it is empty, and change nothing else. + + Separate from :meth:`action_orsf_bulk_refresh` on purpose. That one + rewrites identity wholesale — name, address, IČ DPH, status — which is + right when you want the register's current view of a contact and wrong + when you want one missing number added to a validated dataset. A + migration build whose trial balance ties to the source should not have + its partner names rewritten as a side effect of needing a DIČ. + + Why this exists at all: since the Peppol participant identifier for + Slovakia is ``0245:`` and the DIČ is deliberately never derived + from the VAT number, every Slovak trading partner needs the number + recorded before it can be e-invoiced. On a real agenda that was 1204 + partners, of which 1165 had an IČO to resolve from. + + Writes only where the field is empty, so it is safe to re-run and never + overrules a value somebody entered by hand. + """ + provider = self.env.get("partner.autocomplete.provider.orsf_sk") + if provider is None or "l10n_sk_dic" not in self._fields: + return True + + by_ico = {} + no_ico = self.browse() + for partner in self: + if (partner.l10n_sk_dic or "").strip(): + continue + ico = provider._orsf_normalise_ico( + partner.company_registry or partner.partner_gid + ) + if not ico: + no_ico |= partner + continue + by_ico.setdefault(ico, self.browse()) + by_ico[ico] |= partner + + records = provider._orsf_lookup_batch(list(by_ico)) if by_ico else {} + + filled = self.browse() + not_in_register = absent_dic = 0 + for ico, partners in by_ico.items(): + record = records.get(ico) + if not record or not record.get("name"): + # {"ico": ..., "found": false} is the not-found shape. + not_in_register += 1 + continue + dic = (record.get("taxId") or record.get("dic") or "").strip() + if not dic: + # In the register, but with no DIČ published for it. + absent_dic += 1 + continue + for partner in partners: + try: + partner.l10n_sk_dic = dic + filled |= partner + except Exception as err: # noqa: BLE001 - one bad row must not + # abort the rest of the run. + _logger.warning( + "ORSF DIČ backfill failed for %s: %s", + partner.display_name, err, + ) + + _logger.info( + "ORSF DIČ backfill: %s filled, %s not in the register, " + "%s with no DIČ published, %s without a usable IČO.", + len(filled), not_in_register, absent_dic, len(no_ico), + ) + self.env.user._bus_send("simple_notification", { + "type": "success" if filled else "warning", + "title": _("ORSF DIČ backfill"), + "message": _( + "%(filled)s filled, %(noreg)s not in the register, " + "%(nodic)s with no DIČ published, %(noico)s without a usable " + "IČO. Only the DIČ was written; nothing else was touched.", + filled=len(filled), noreg=not_in_register, + nodic=absent_dic, noico=len(no_ico), + ), + }) + return True + def _orsf_report_bulk_refresh(self, updated, missing, skipped): """Say what happened. A silent bulk action is indistinguishable from a broken one, which is exactly how the rate-limited version looked.""" diff --git a/partner_autocomplete_orsf_sk/tests/__init__.py b/partner_autocomplete_orsf_sk/tests/__init__.py index 7575ac74..99a5a181 100644 --- a/partner_autocomplete_orsf_sk/tests/__init__.py +++ b/partner_autocomplete_orsf_sk/tests/__init__.py @@ -1,3 +1,4 @@ # Copyright 2026 Data Dance s.r.o. # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). from . import test_orsf_sk +from . import test_dic_backfill diff --git a/partner_autocomplete_orsf_sk/tests/test_dic_backfill.py b/partner_autocomplete_orsf_sk/tests/test_dic_backfill.py new file mode 100644 index 00000000..907675af --- /dev/null +++ b/partner_autocomplete_orsf_sk/tests/test_dic_backfill.py @@ -0,0 +1,79 @@ +# Copyright 2026 Data Dance s.r.o. +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +"""The DIČ backfill writes one field and leaves everything else alone.""" + +from unittest.mock import patch + +from odoo.tests import TransactionCase, tagged + +PROVIDER = "odoo.addons.partner_autocomplete_orsf_sk.models" \ + ".partner_autocomplete_provider.PartnerAutocompleteProviderOrsfSk" + + +@tagged("post_install", "-at_install") +class TestOrsfDicBackfill(TransactionCase): + + def setUp(self): + super().setUp() + if "l10n_sk_dic" not in self.env["res.partner"]._fields: + self.skipTest("l10n_sk_base not installed") + self.sk = self.env.ref("base.sk") + + def _partner(self, **vals): + return self.env["res.partner"].create( + {"name": "Test s.r.o.", "country_id": self.sk.id, **vals}) + + def test_it_fills_an_empty_dic_and_touches_nothing_else(self): + partner = self._partner( + name="Pôvodný názov s.r.o.", company_registry="00681300", + street="Stará 1") + record = { + "ico": "00681300", "dic": "2020318256", "icDph": "SK2020318256", + "name": "Odvoz a likvidácia odpadu a.s.", "status": "aktívna", + "address": {"street": "Ivanská cesta 22", "city": "Bratislava"}, + } + with patch(f"{PROVIDER}._orsf_lookup_batch", + return_value={"00681300": record}): + partner.action_orsf_fill_missing_dic() + self.assertEqual(partner.l10n_sk_dic, "2020318256") + # the point of the separate action: identity is NOT rewritten + self.assertEqual(partner.name, "Pôvodný názov s.r.o.") + self.assertEqual(partner.street, "Stará 1") + self.assertFalse(partner.vat) + + def test_a_hand_entered_dic_is_never_overruled(self): + partner = self._partner( + company_registry="00681300", l10n_sk_dic="9999999999") + with patch(f"{PROVIDER}._orsf_lookup_batch") as batch: + partner.action_orsf_fill_missing_dic() + batch.assert_not_called() # nothing to ask about + self.assertEqual(partner.l10n_sk_dic, "9999999999") + + def test_the_not_found_shape_is_not_mistaken_for_a_record(self): + """{"ico": ..., "found": false} is how absence is reported. Reading it + as a record is what produced a wrong commit (b85340c).""" + partner = self._partner(company_registry="36197514") + with patch(f"{PROVIDER}._orsf_lookup_batch", + return_value={"36197514": {"ico": "36197514", + "found": False}}): + partner.action_orsf_fill_missing_dic() + self.assertFalse(partner.l10n_sk_dic) + + def test_a_partner_with_no_ico_is_skipped_not_crashed(self): + partner = self._partner(company_registry=False) + with patch(f"{PROVIDER}._orsf_lookup_batch") as batch: + partner.action_orsf_fill_missing_dic() + batch.assert_not_called() + self.assertFalse(partner.l10n_sk_dic) + + def test_two_partners_sharing_an_ico_both_get_it(self): + a = self._partner(name="A s.r.o.", company_registry="00681300") + b = self._partner(name="A s.r.o. (duplikát)", + company_registry="00681300") + with patch(f"{PROVIDER}._orsf_lookup_batch", + return_value={"00681300": {"ico": "00681300", + "name": "A s.r.o.", + "dic": "2020318256"}}): + (a | b).action_orsf_fill_missing_dic() + self.assertEqual(a.l10n_sk_dic, "2020318256") + self.assertEqual(b.l10n_sk_dic, "2020318256") diff --git a/partner_autocomplete_orsf_sk/tests/test_orsf_sk.py b/partner_autocomplete_orsf_sk/tests/test_orsf_sk.py index aa123fa5..d4c6a55c 100644 --- a/partner_autocomplete_orsf_sk/tests/test_orsf_sk.py +++ b/partner_autocomplete_orsf_sk/tests/test_orsf_sk.py @@ -531,7 +531,7 @@ def setUpClass(cls): {"name": "Stary nazov a", "company_registry": "31333532"} ) cls.b = cls.env["res.partner"].create( - {"name": "Stary nazov b", "company_registry": "00695599"} + {"name": "Stary nazov b", "company_registry": "00690007"} ) cls.no_ico = cls.env["res.partner"].create({"name": "Bez ICO"}) @@ -554,7 +554,7 @@ def test_batch_splits_at_a_hundred_and_drops_misses(self): def test_bulk_refresh_writes_identity_and_reports(self): records = { "31333532": dict(LOOKUP, name="ESET, spol. s r.o."), - "00695599": dict(LOOKUP, ico="00695599", name="ELCOM s.r.o.", + "00690007": dict(LOOKUP, ico="00690007", name="Príklad B s.r.o.", icDph="SK2020517895"), } with patch( @@ -564,9 +564,9 @@ def test_bulk_refresh_writes_identity_and_reports(self): ) as bus: (self.a | self.b | self.no_ico).action_orsf_bulk_refresh() # One call, and the partner with no IČO never reaches the register. - self.assertEqual(sorted(batch.call_args.args[0]), ["00695599", "31333532"]) + self.assertEqual(sorted(batch.call_args.args[0]), ["00690007", "31333532"]) self.assertEqual(self.a.name, "ESET, spol. s r.o.") - self.assertEqual(self.b.name, "ELCOM s.r.o.") + self.assertEqual(self.b.name, "Príklad B s.r.o.") self.assertEqual(self.no_ico.name, "Bez ICO") self.assertIn("without a usable IČO", bus.call_args.args[1]["message"]) diff --git a/pos_vrp2/CHANGELOG.rst b/pos_vrp2/CHANGELOG.rst new file mode 100644 index 00000000..e6d07bda --- /dev/null +++ b/pos_vrp2/CHANGELOG.rst @@ -0,0 +1,241 @@ +========= +Changelog +========= + +All notable changes to **pos_vrp2** are documented here. +Versioning follows the Odoo manifest (``19.0.x.y.z``); format follows Keep a Changelog. + +[Unreleased] +------------ + +[19.0.5.0.0] — 2026-10-06 +------------------------- + +Sale and storno receipts verified against a capture of the official web app +(``docs/VRP2/vrp2.financnasprava.sk-sales_storno.har``: a two-item sale, then +its storno) and against the app's own receipt builder in that capture. The +test suite replays both, and compares the bytes that are sent, not just the +values. + +Fixed +~~~~~ + +- **The sale receipt body was a guess and would not have been accepted.** + Items carried ``name`` / ``priceWithVat`` as a line total / ``vatId``. VRP2 + takes ``priceWithVat`` as the UNIT price incl. VAT, ``discount`` as an + absolute per-unit amount, ``quantity``, ``type: POSITIVE``, the catalogue + ``serviceCode`` (VRP2 prints the catalogue name) and ``vatRate`` as a + fraction (``0.19``), not the VAT-list id. +- **``version`` is the register's ``cashRegister.version`` from the + dashboard, not the clock**, and ``vatPayer`` is + ``organization.vatPayer``. In all three captured valid receipts the version + equalled the dashboard's (``1662177617338``, a 2022 stamp); only the + invoice-payment endpoint takes the current time. +- **Rounding mirrors the web app.** Only the CASH share is rounded to 0.05 €; + the top-level ``priceWithVat`` is the item total plus that rounding, and + VRP2 derives the printed ZAOKRÚHLENIE from it (the request has no + ``roundingAmount``). A negative receipt with return items is never rounded: + the captured storno refunds −18.48 against a sale paid 18.50, exactly as + here. +- The body serializes like ``JSON.stringify`` (``1``, not ``1.0``); the + checksum is computed over those bytes. + +Added +~~~~~ + +- **Refunds are VRP2 returns.** A POS refund line becomes a ``REFUND`` item + referencing the original order's ``receiptId``, named exactly as VRP2 + printed it on the original receipt (taken from the response's + ``dataBase64`` and stored on the line as ``vrp2_item_name``; the captured + name had a trailing space). Refunding a whole order is VRP2's storno of the + sale, settled by one ``EXPENSE`` payment. A return that references no + fiscalized order is refused. +- **Automatic fiscalization** (``vrp2_auto_fiscalize``, on by default) when + the till syncs a paid order. It runs AFTER the sync transaction commits: + ``sync_from_ui`` saves a batch of orders in one transaction, and a receipt + issued inside it would outlive a rollback of the batch and be issued again + on re-sync. A VRP2 failure never blocks the sale; the order is marked + "VRP2 Error" (with a warning to check the portal after a timeout) and can + be fiscalized again. The order row is locked ``NOWAIT`` during the call so + an automatic and a manual attempt cannot both send. +- **Invoiced POS orders** are fiscalized as "Úhrada faktúry" + (``/create/invoice``, no items), as ``l10n_sk_vrp2_account`` does — an + itemized sale receipt would report the invoice's VAT a second time. +- ``vrp2_payment_type`` on POS payment methods (Hotovosť, Platobná karta, + Poukážky, Stravné poukážky, Iné), defaulted from the journal; a customer- + account method has none and its orders are refused. +- List: "Fiscalize in VRP2" mass action (failures recorded per order, never + rolling back the others), VRP2 status/number columns, "VRP2 Error" and + "Not Fiscalized in VRP2" filters. The till form shows the auto-fiscalize + and 5-cent rounding settings. +- **Returned deposit packaging** ("vrátené obaly"). A product marked + ``vrp2_returnable_packaging`` and taken back with no sale to refer to + becomes a ``NEGATIVE`` item, which, unlike ``REFUND``, keeps its + ``serviceCode`` (web app ``parseToAPI``). A negative receipt holding one is + not rounded, the same rule as for returns. +- **Odoo's cash rounding must match VRP2's**, enforced: with "Round to + 0.05 €" on, the till needs Odoo cash rounding of 0.05 "Nearest", cash + only; with it off, Odoo must not round. Enabling VRP2 sets it from an + existing 0.05 rounding method, and the till form shows it. Otherwise Odoo + records 18.48 where VRP2 records 18.50 and the cash count never agrees + with the register's report. +- **The VRP2 receipt at the till.** Right after validation — before the + automatic print — the till asks the server for the order's VRP2 receipt + (``pos.order.vrp2_receipt_for_ui``) and prints its fiscal block on the + ticket: POKLADNIČNÝ DOKLAD č. 0019, Kód pokladnice, ID dokladu, OKP and + the QR of the receiptId, as on the official receipt. "VRP2 doklad (PDF)" + on the receipt screen opens the official PDF. A receipt that failed is + announced to the cashier and the ticket says "Doklad nebol zaevidovaný vo + VRP2" instead of carrying a fiscal block. The call fiscalizes only an order + the post-commit step never reached, and never retries an error by itself: + after a timeout VRP2 may already hold the receipt. Reprints from the order + history use the loaded fields. Covered by two browser tours. +- The POS no longer loads ``vrp2_pdf``, ``vrp2_qr_content`` or the request / + response JSON with every order of its history. +- Slovak catalogue (the module had none), including the till's own strings + and the five of the existing credentials widget, which the offline + exporter had never extracted from ``static/``. + +Changed +~~~~~~~ + +- ``vrp2_qr_content`` is relabelled "VRP2 Receipt Data". It holds + ``dataBase64`` — the whole receipt as base64 JSON — and was never the QR: + the QR on every official receipt (four decoded) encodes only the + ``receiptId``. +- Relicensed to **AGPL-3**. Part of a repository-wide move to a single licence: + everything Data Dance authors is AGPL-3 except the modules that depend on Odoo + Enterprise, which OEEL forbids from carrying it. What differs between modules + is now whether they are published, not how they are licensed. + +Known limits +~~~~~~~~~~~~ + +- Whether Odoo's own ticket carrying the fiscal block may stand in for the + VRP2 document is a legal question this module does not answer; the + official PDF is one click away on the receipt screen and on the order. +- Vouchers (``VOUCHER_EXCHANGE``), advance deductions (``DEDUCTED_ADVANCE``), + foreign currency and "v mene iného predávajúceho" items are not mapped, + deliberately: each waits for a capture of the real request. +- Upgrading does not check existing tills. One with VRP2 enabled and no + matching Odoo cash rounding keeps working until its rounding or VRP2 + settings are next saved, and is then asked to fix it. + +[19.0.4.6.0] +------------ + +- Login/Logout on the POS form are now OWL widgets that run the server action + then reload the record in place, so the VRP2 status badge and button + visibility update immediately (no dialog close, no manual refresh). + +[19.0.4.5.0] +------------ + +- Hide "Load Credentials from Company" once the till is connected (a session + token exists) — it's only useful before login. + +[19.0.4.4.0] +------------ + +- Per-till VRP2 status badge (via mixin ``vrp2_status``) on the POS form; Login + button hidden when already logged in (a token exists), Logout shown only + when logged in. + +[19.0.4.3.0] +------------ + +- "Load Credentials from Company" is now a client-side OWL widget: it fills + the login/password into the form in memory (no save, no reload), so the POS + quick-config dialog no longer closes. Backed by a ``vrp2_company_credentials`` + model method. Adds a ``web.assets_backend`` bundle. + +[19.0.4.2.0] +------------ + +- "Load from Company" → renamed "Load Credentials from Company"; it now + soft-reloads the form so the copied login/password actually appear. +- Login button is hidden until both VRP2 login and password are filled; + Logout stays hidden until logged in. +- Tooltips added to Load / Push to VRP2 / Pull from VRP2 explaining each. +- Constraint: a VRP2 login can be used by only one POS (one login = one + physical cash register). + +[19.0.4.1.0] +------------ + +- Added a "Load from Company" button on the POS VRP2 section that prefills the + till's VRP2 login/password from the company default register (the till still + logs in itself to obtain its own session). + +[19.0.4.0.0] +------------ + +- **Per-till VRP2 registers.** Each ``pos.config`` is now its own VRP2 cash + register: it inherits ``vrp2.credentials.mixin``, so credentials/session/DKP + live on the till. Login/Logout, Push/Pull and the dashboard now sit on the + POS form (Settings ▸ Edit), and a till's sales fiscalize on its own + register (``pos.order`` uses ``config_id``). Logging into a till also caches the + national VAT list on the company for the tax mapping. +- Catalog push/pull (``_vrp2_push``/``_vrp2_pull``) now take the till as the + session holder; VAT mapping is still derived from the company. +- Company-level POS settings keep only the VAT mapping (credentials moved to + the tills; the company stays the invoice "default register"). +- KNOWN LIMIT: ``product.template.vrp2_code`` is a single field, so the same + product synced to multiple tills shares one code — correct for one register + per product; true multi-till catalog needs per-(product,till) codes (future). + +[19.0.3.2.0] +------------ + +- Took ownership of the VRP2 VAT-rate↔tax mapping (moved here from + ``l10n_sk_vrp2_base``, where it didn't belong): ``vrp2_tax_0..23`` + + ``vrp2_vatlist_json`` on ``res.company``, the ``_vrp2_vat_*`` helpers, the + settings tax-mapping fields, and an ``action_vrp2_login`` override that + caches the VRP2 VAT list after login. + +[19.0.3.1.0] +------------ + +- Carried the **verified** create/invoice envelope into the POS sale-receipt + scaffolding: flat body (no ``dto`` wrapper), ``version`` ms-epoch, ``payments`` + array ``{type,sum,currency,exchangeRate,amount}``, ``roundingAmount``/``useRounding``. +- Response mapping aligned with the verified format: ``receiptNumber``, + ``receiptId`` (UUID), ``okp``, official ``pdfBase64`` stored as attachment, + ``dataBase64`` QR payload; removed PKP. New ``vrp2_receipt_uuid``, ``vrp2_pdf`` + fields and a "Print VRP2 Receipt" action on ``pos.order``. +- **[VERIFY LIVE]** remaining: only the create/valid ``items[]`` shape (needs a + POS sale-receipt capture); the request/response envelope is now confirmed. + +[19.0.3.0.0] +------------ + +- POS order fiscalization scaffolding (server-side): + + - ``pos.order`` gains VRP2 fiscal fields (status, receipt number, OKP/PKP, + QR, raw request/response) and a manager-only "Fiscalize in VRP2" action + that calls ``/v5/receipt/create/valid`` and stores the result. + - **[VERIFY LIVE]** ``pos.order._vrp2_build_receipt_dto()`` / + ``_vrp2_apply_response()`` — sale-receipt payload + response field names are + not in the captured HAR; confirm against a real capture or the VRP2 spec. + - Auto-fiscalization at payment time and printing fiscal data on the POS + receipt (OWL frontend) are deferred until the receipt DTO is verified. + +- Catalog sync correctness fixes (HAR-independent): + + - Gross↔net price now uses the tax engine (``compute_all``) instead of a + naive percentage; pull stores the correct net ``list_price``. + - Warns when the VRP2 service list is truncated (no pagination yet). + +[19.0.2.0.0] +------------ + +- **Structural:** communication layer (vrp2.client, VRP2 credentials/session, + VAT mapping, Login/Logout) extracted into the new ``l10n_sk_vrp2_base`` + module. ``pos_vrp2`` now depends on it and keeps only the POS-specific + category/product catalog sync. No functional change to POS behaviour. + +[19.0.1.0.0] +------------ + +- Initial release: POS ↔ VRP2 catalog (category/product) push & pull, + credentials UI, vrp2.client API client. diff --git a/pos_vrp2/__init__.py b/pos_vrp2/__init__.py new file mode 100644 index 00000000..0650744f --- /dev/null +++ b/pos_vrp2/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_vrp2/__manifest__.py b/pos_vrp2/__manifest__.py new file mode 100644 index 00000000..c234206c --- /dev/null +++ b/pos_vrp2/__manifest__.py @@ -0,0 +1,51 @@ +{ + "name": "POS VRP2 Integration", + "version": "19.0.5.0.0", + "author": "Data Dance s.r.o.", + "category": "Point of Sale", + "summary": "Integrate Odoo POS with Slovak Virtual Cash Register (VRP 2)", + "description": """ +Integrates Odoo Point of Sale with the Slovak Financial Administration's +Virtual Cash Register 2 (Virtuálna registračná pokladnica 2). + +Calls the VRP2 REST API at vcrp.financnasprava.sk to: + +- Authenticate and manage sessions +- Synchronise products and categories +- Issue the fiscal receipt of every paid POS order automatically (or + manually), itemized with the till's VRP2 service codes +- Fiscalize refunds as VRP2 returns ("vrátenie tovaru") against the + original receipt — VRP2's storno of a sale +- Invoiced POS orders as invoice payments ("Úhrada faktúry") + """, + "license": "AGPL-3", + "depends": [ + "point_of_sale", + "l10n_sk_vrp2_base", + ], + "external_dependencies": { + "python": ["requests"], + }, + "data": [ + "views/res_config_settings_views.xml", + "views/pos_config_views.xml", + "views/pos_category_views.xml", + "views/product_views.xml", + "views/pos_order_views.xml", + "views/pos_payment_method_views.xml", + ], + "assets": { + "point_of_sale._assets_pos": [ + "pos_vrp2/static/src/pos/**/*", + ], + "web.assets_tests": [ + "pos_vrp2/static/tests/tours/**/*", + ], + "web.assets_backend": [ + "pos_vrp2/static/src/js/vrp2_load_credentials.js", + "pos_vrp2/static/src/xml/vrp2_load_credentials.xml", + ], + }, + "installable": True, + "application": False, +} diff --git a/pos_vrp2/i18n/pos_vrp2.pot b/pos_vrp2/i18n/pos_vrp2.pot new file mode 100644 index 00000000..6588ddcc --- /dev/null +++ b/pos_vrp2/i18n/pos_vrp2.pot @@ -0,0 +1,789 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_vrp2 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-06 20:54+0000\n" +"PO-Revision-Date: 2026-10-06 20:54+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "%(done)s order(s) fiscalized, %(failed)s failed, %(skipped)s skipped." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"%(error)s\n" +"\n" +"If the failure was a timeout, VRP2 may already hold this receipt: check the register's receipt list in the VRP2 portal before fiscalizing again." +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "(" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid ")" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_returnable_packaging +msgid "" +"A deposit-bearing package (zálohovaný obal). Taking it back is a negative " +"quantity WITHOUT a sale to refer to, which VRP2 records as 'vrátené obaly' " +"(item type NEGATIVE) rather than as a return of goods. Push it to the VRP2 " +"catalogue like any other product." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"Categories: %d created, %d updated, %d deleted.\n" +"Products: %d created, %d updated, %d deactivated." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__travel_agencies +msgid "Cestovné kancelárie" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/xml/vrp2_load_credentials.xml:0 +msgid "" +"Copy the VRP2 login and password from the company default register into this" +" till." +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.js:0 +msgid "Could not read the VRP2 receipt of this order from the server." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_category__vrp2_order +msgid "Display order in VRP2 (set by server)." +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "Doklad nebol zaevidovaný vo VRP2" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_config__vrp2_enabled +msgid "Enable VRP2" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_order__vrp2_state__error +msgid "Error" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_config__vrp2_auto_fiscalize +msgid "Fiscalize Automatically" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_order_tree_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_pos_form_vrp2 +msgid "Fiscalize in VRP2" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_config__vrp2_enabled +msgid "" +"Fiscalize this till's sales in the Slovak Virtual Cash Register. Set the " +"till's own VRP2 credentials below." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_order__vrp2_state__fiscalized +msgid "Fiscalized" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__cash +msgid "Hotovosť" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_payment_method__vrp2_payment_type +msgid "" +"How payments by this method are reported on the VRP2 receipt. Defaults from " +"the journal: cash → Hotovosť, bank → Platobná karta. Empty means the method " +"is not a cash-register tender (a customer account), and an order paid by it " +"cannot be fiscalized." +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "ID dokladu:" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "" +"Import this cash register's catalog from VRP2 into Odoo: create/update POS " +"categories and products (name, price, VAT, barcode) from what currently " +"exists on the register." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.module.module,summary:base.module_pos_vrp2 +msgid "Integrate Odoo POS with Slovak Virtual Cash Register (VRP 2)" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_code +msgid "Internal code assigned by VRP2 when the product is created." +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.product_template_form_view_vrp2 +msgid "Internal note (max 1000 chars)" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_note +msgid "Internal note for VRP2 (max 1000 chars, not printed)." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__other +msgid "Iné" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_config__vrp2_auto_fiscalize +msgid "" +"Issue the VRP2 receipt as soon as the till syncs a paid order. A failure " +"never blocks the sale: the order is marked 'VRP2 Error' and can be " +"fiscalized again from its form." +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "Kód pokladnice:" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Line '%(line)s' returns goods without referencing the order they were sold " +"on. VRP2 accepts a return only against the original receipt: refund it from " +"that order in the POS. (Returned deposit packaging is different: mark the " +"product 'VRP2 Returnable Packaging'.)" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/xml/vrp2_load_credentials.xml:0 +msgid "Load Credentials from Company" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/js/vrp2_load_credentials.js:0 +msgid "Loaded credentials from the company. Click Login to connect this till." +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/js/vrp2_load_credentials.js:0 +msgid "Logged in to VRP2." +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/js/vrp2_load_credentials.js:0 +msgid "Logged out from VRP2." +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/xml/vrp2_load_credentials.xml:0 +msgid "Login" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/xml/vrp2_load_credentials.xml:0 +msgid "Logout" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.res_config_settings_view_form_vrp2 +msgid "" +"Map VRP2 VAT rates to Odoo sales taxes. Each till's own VRP2 credentials are" +" set on the Point of Sale (under Settings ▸ Edit), where you also log in and" +" sync its catalog." +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "" +"Must round to 0.05 'Nearest' on cash only, as VRP2 does, or Odoo's drawer " +"drifts from the register." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "No VRP2 PDF is stored for this order." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_order__vrp2_state__not_fiscalized +msgid "Not Fiscalized" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_order_filter_vrp2 +msgid "Not Fiscalized in VRP2" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_okp +msgid "OKP" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "OKP:" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "Odoo Cash Rounding" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s was invoiced, so VRP2 holds an invoice payment for it, not a" +" sale. Cancel that payment receipt rather than returning items against it." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s was paid %(paid)s but its VRP2 receipt totals %(total)s; the" +" difference is too large to be tax rounding." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s was paid more by non-cash means (%(non_cash)s) than it costs" +" (%(total)s) and also in cash; that cannot be put on a receipt." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s was paid more by non-cash means than it costs and also in " +"cash; that cannot be put on a receipt." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s, which this refund returns goods from, has no VRP2 receipt " +"to reference. Fiscalize it first." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "Order %s has no lines to fiscalize." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "Order %s is already fiscalized in VRP2." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %s is an invoiced refund. A credit note is not fiscalized as an " +"invoice payment; cancel the original payment receipt instead." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "Order %s must be paid before it can be fiscalized." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order line '%(line)s' carries taxes VRP2 cannot express: a receipt item has " +"exactly one percentage VAT rate." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order line '%(line)s' has VAT %(rate)s %%, which is not a VRP2 VAT rate " +"(%(allowed)s)." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__tax_exemption +msgid "Oslobodené od dane" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "Overte doklad pomocou QR kódu" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "POKLADNIČNÝ DOKLAD č." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.module.module,shortdesc:base.module_pos_vrp2 +msgid "POS VRP2 Integration" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Packaging '%(product)s' is not in this till's VRP2 catalogue (it has no VRP2" +" service code). Push the catalogue to VRP2 first." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Payment method '%(method)s' has no VRP2 payment type. A sale settled on a " +"customer account is not a cash-register sale; set the type on the payment " +"method if it is one." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__card +msgid "Platobná karta" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"Point of Sale '%(pos)s' does not round cash in VRP2, so Odoo must not round " +"it either: disable Cash Rounding or enable 'Round to 0.05 €'." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"Point of Sale '%(pos)s' rounds cash to 0.05 € in VRP2, so its Odoo cash " +"rounding must do the same: enable Cash Rounding with a 0.05 'Nearest' " +"rounding method (Add a rounding line) and 'Only apply rounding on cash'." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__voucher +msgid "Poukážky" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__used_goods +msgid "Použitý tovar" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__tax_obligation_transfer +msgid "Prenesenie daňovej povinnosti" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_pos_form_vrp2 +msgid "Print VRP2 Receipt" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Product '%(product)s' is not in this till's VRP2 catalogue (it has no VRP2 " +"service code). Push the catalogue to VRP2 first." +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "Pull from VRP2" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "Pulled %d categories and %d products from VRP2." +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "Push to VRP2" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "QR" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "" +"Send this till's catalog to VRP2: create/update its product categories and " +"POS products (with mapped VAT) on this cash register, and deactivate items " +"no longer in Odoo. Odoo is the source of truth." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_category__vrp2_id +msgid "Server-side category ID in VRP2 (categoryId)." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_category__vrp2_short_name +msgid "" +"Short name for VRP2 tabs (max ~3 chars). Auto-generated from name if not " +"set." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_favourite +msgid "Show in VRP2 favourites quick-access." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__gastro +msgid "Stravné poukážky" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_synced +msgid "Synced to VRP2" +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.js:0 +msgid "The VRP2 receipt was NOT issued: %s" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"The company has no VRP2 credentials. Set them in Accounting ▸ Settings ▸ " +"VRP2 first." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_order_line__vrp2_item_name +msgid "" +"The name VRP2 printed for this line on the fiscal receipt. A refund of the " +"line must repeat it verbatim." +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "" +"This till is its own VRP2 cash register. Enable it, enter the till's own " +"VRP2 login, then log in and sync its catalog." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__art_works +msgid "Umelecké diela" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_category_view_form_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.product_template_form_view_vrp2 +msgid "VRP2" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.res_config_settings_view_form_vrp2 +msgid "VRP2 - Slovak Virtual Cash Register" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_category__vrp2_id +msgid "VRP2 Category ID" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_order_filter_vrp2 +msgid "VRP2 Error" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_favourite +msgid "VRP2 Favourite" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "VRP2 Fiscal Receipts" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_created +msgid "VRP2 Fiscalized On" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order_line__vrp2_item_name +msgid "VRP2 Item Name" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_note +msgid "VRP2 Note" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_category__vrp2_order +msgid "VRP2 Order" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_payment_method__vrp2_payment_type +msgid "VRP2 Payment Type" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "VRP2 Pull Complete" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_qr_content +msgid "VRP2 Receipt Data" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_server_id +msgid "VRP2 Receipt ID" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_receipt_number +msgid "VRP2 Receipt Number" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_pdf +msgid "VRP2 Receipt PDF" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_receipt_uuid +msgid "VRP2 Receipt UUID" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_request_json +msgid "VRP2 Request" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_response_json +msgid "VRP2 Response" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_returnable_packaging +msgid "VRP2 Returnable Packaging" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_code +msgid "VRP2 Service Code" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_category__vrp2_short_name +msgid "VRP2 Short Name" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_special_regulation +msgid "VRP2 Special Regulation" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_state +msgid "VRP2 Status" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "VRP2 Sync Complete" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_0 +msgid "VRP2 Tax 0%" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_10 +msgid "VRP2 Tax 10%" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_19 +msgid "VRP2 Tax 19%" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_20 +msgid "VRP2 Tax 20%" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_23 +msgid "VRP2 Tax 23%" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_5 +msgid "VRP2 Tax 5%" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.res_config_settings_view_form_vrp2 +msgid "VRP2 Tax Mapping" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_vatlist_json +msgid "VRP2 VAT List (cached)" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "VRP2 credentials are not configured on till '%s'." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_order__vrp2_qr_content +msgid "" +"VRP2 dataBase64: the whole receipt as base64 JSON (items as VRP2 named them," +" VAT summary, payments, rounding). NOT the QR code, which encodes only the " +"receiptId." +msgstr "" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "VRP2 doklad (PDF)" +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "VRP2 is not enabled for Point of Sale '%s'." +msgstr "" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"VRP2 login '%(login)s' is already used by Point of Sale '%(pos)s'. Each VRP2" +" cash register can belong to only one POS." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_order__vrp2_receipt_uuid +msgid "" +"VRP2 receiptId (V-…). This is exactly what the QR code on the official " +"receipt encodes." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_special_regulation +msgid "Verbal information required on receipt for 0%% VAT items." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_error_message +msgid "Vrp2 Error Message" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_pdf_filename +msgid "Vrp2 Pdf Filename" +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_synced +msgid "Whether this product exists on the VRP2 server." +msgstr "" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__collective_subjects_and_antiques +msgid "Zberateľské predmety a starožitnosti" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_category_view_form_vrp2 +msgid "e.g. FD" +msgstr "" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "— DKP" +msgstr "" diff --git a/pos_vrp2/i18n/sk.po b/pos_vrp2/i18n/sk.po new file mode 100644 index 00000000..6d865b1d --- /dev/null +++ b/pos_vrp2/i18n/sk.po @@ -0,0 +1,876 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_vrp2 +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-06 20:54+0000\n" +"PO-Revision-Date: 2026-10-06 20:54+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: sk_SK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : n%1!=0 ? 2: 3);\n" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "%(done)s order(s) fiscalized, %(failed)s failed, %(skipped)s skipped." +msgstr "" +"Zaevidované objednávky: %(done)s, neúspešné: %(failed)s, preskočené: " +"%(skipped)s." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"%(error)s\n" +"\n" +"If the failure was a timeout, VRP2 may already hold this receipt: check the register's receipt list in the VRP2 portal before fiscalizing again." +msgstr "" +"%(error)s\n" +"\n" +"Ak išlo o vypršanie časového limitu, VRP2 už tento doklad môže mať: pred opätovným evidovaním skontrolujte zoznam dokladov pokladnice na portáli VRP2." + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "(" +msgstr "(" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid ")" +msgstr ")" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_returnable_packaging +msgid "" +"A deposit-bearing package (zálohovaný obal). Taking it back is a negative " +"quantity WITHOUT a sale to refer to, which VRP2 records as 'vrátené obaly' " +"(item type NEGATIVE) rather than as a return of goods. Push it to the VRP2 " +"catalogue like any other product." +msgstr "" +"Zálohovaný obal. Jeho prevzatie späť je záporné množstvo BEZ predaja, na " +"ktorý by sa odkazovalo; VRP2 ho eviduje ako „vrátené obaly“ (typ položky " +"NEGATIVE), nie ako vrátenie tovaru. Odošlite ho do katalógu VRP2 ako každý " +"iný produkt." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"Categories: %d created, %d updated, %d deleted.\n" +"Products: %d created, %d updated, %d deactivated." +msgstr "" +"Kategórie: vytvorené %d, aktualizované %d, zmazané %d.\n" +"Produkty: vytvorené %d, aktualizované %d, deaktivované %d." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__travel_agencies +msgid "Cestovné kancelárie" +msgstr "Cestovné kancelárie" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/xml/vrp2_load_credentials.xml:0 +msgid "" +"Copy the VRP2 login and password from the company default register into this" +" till." +msgstr "" +"Skopírovať prihlasovacie meno a heslo VRP2 z predvolenej pokladnice " +"spoločnosti do tejto pokladne." + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.js:0 +msgid "Could not read the VRP2 receipt of this order from the server." +msgstr "Doklad VRP2 k tejto objednávke sa nepodarilo načítať zo servera." + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_category__vrp2_order +msgid "Display order in VRP2 (set by server)." +msgstr "Poradie zobrazenia vo VRP2 (nastavuje server)." + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "Doklad nebol zaevidovaný vo VRP2" +msgstr "Doklad nebol zaevidovaný vo VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_config__vrp2_enabled +msgid "Enable VRP2" +msgstr "Zapnúť VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_order__vrp2_state__error +msgid "Error" +msgstr "Chyba" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_config__vrp2_auto_fiscalize +msgid "Fiscalize Automatically" +msgstr "Evidovať automaticky" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_order_tree_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_pos_form_vrp2 +msgid "Fiscalize in VRP2" +msgstr "Zaevidovať vo VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_config__vrp2_enabled +msgid "" +"Fiscalize this till's sales in the Slovak Virtual Cash Register. Set the " +"till's own VRP2 credentials below." +msgstr "" +"Evidovať tržby tejto pokladne vo Virtuálnej registračnej pokladnici. Nižšie " +"zadajte vlastné prihlasovacie údaje VRP2 tejto pokladne." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_order__vrp2_state__fiscalized +msgid "Fiscalized" +msgstr "Zaevidované" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__cash +msgid "Hotovosť" +msgstr "Hotovosť" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_payment_method__vrp2_payment_type +msgid "" +"How payments by this method are reported on the VRP2 receipt. Defaults from " +"the journal: cash → Hotovosť, bank → Platobná karta. Empty means the method " +"is not a cash-register tender (a customer account), and an order paid by it " +"cannot be fiscalized." +msgstr "" +"Ako sa platby touto metódou uvádzajú na doklade VRP2. Predvolená hodnota " +"podľa denníka: hotovosť → Hotovosť, banka → Platobná karta. Prázdne znamená," +" že metóda nie je platidlom registračnej pokladnice (účet zákazníka) a " +"objednávku ňou uhradenú nemožno zaevidovať." + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "ID dokladu:" +msgstr "ID dokladu:" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "" +"Import this cash register's catalog from VRP2 into Odoo: create/update POS " +"categories and products (name, price, VAT, barcode) from what currently " +"exists on the register." +msgstr "" +"Importovať katalóg tejto pokladnice z VRP2 do Odoo: vytvoriť/aktualizovať " +"kategórie a produkty POS (názov, cena, DPH, čiarový kód) podľa aktuálneho " +"stavu na pokladnici." + +#. module: pos_vrp2 +#: model:ir.module.module,summary:base.module_pos_vrp2 +msgid "Integrate Odoo POS with Slovak Virtual Cash Register (VRP 2)" +msgstr "Prepojenie Odoo POS s Virtuálnou registračnou pokladnicou (VRP 2)" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_code +msgid "Internal code assigned by VRP2 when the product is created." +msgstr "Interný kód, ktorý VRP2 pridelí pri vytvorení produktu." + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.product_template_form_view_vrp2 +msgid "Internal note (max 1000 chars)" +msgstr "Interná poznámka (max. 1000 znakov)" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_note +msgid "Internal note for VRP2 (max 1000 chars, not printed)." +msgstr "Interná poznámka pre VRP2 (max. 1000 znakov, netlačí sa)." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__other +msgid "Iné" +msgstr "Iné" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_config__vrp2_auto_fiscalize +msgid "" +"Issue the VRP2 receipt as soon as the till syncs a paid order. A failure " +"never blocks the sale: the order is marked 'VRP2 Error' and can be " +"fiscalized again from its form." +msgstr "" +"Vystaviť doklad VRP2 hneď, ako pokladňa zosynchronizuje zaplatenú " +"objednávku. Chyba nikdy nezablokuje predaj: objednávka sa označí „Chyba " +"VRP2“ a možno ju zaevidovať znova z jej formulára." + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "Kód pokladnice:" +msgstr "Kód pokladnice:" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Line '%(line)s' returns goods without referencing the order they were sold " +"on. VRP2 accepts a return only against the original receipt: refund it from " +"that order in the POS. (Returned deposit packaging is different: mark the " +"product 'VRP2 Returnable Packaging'.)" +msgstr "" +"Riadok „%(line)s“ vracia tovar bez odkazu na objednávku, na ktorej bol " +"predaný. VRP2 prijme vrátenie tovaru len voči pôvodnému dokladu: vráťte ho z" +" tejto objednávky v POS. (Vrátené zálohované obaly sú iný prípad: označte " +"produkt ako „Zálohovaný obal VRP2“.)" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/xml/vrp2_load_credentials.xml:0 +msgid "Load Credentials from Company" +msgstr "Načítať prihlasovacie údaje zo spoločnosti" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/js/vrp2_load_credentials.js:0 +msgid "Loaded credentials from the company. Click Login to connect this till." +msgstr "" +"Prihlasovacie údaje boli načítané zo spoločnosti. Kliknutím na Prihlásiť " +"pripojíte túto pokladňu." + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/js/vrp2_load_credentials.js:0 +msgid "Logged in to VRP2." +msgstr "Prihlásené do VRP2." + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/js/vrp2_load_credentials.js:0 +msgid "Logged out from VRP2." +msgstr "Odhlásené z VRP2." + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/xml/vrp2_load_credentials.xml:0 +msgid "Login" +msgstr "Prihlásiť" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/xml/vrp2_load_credentials.xml:0 +msgid "Logout" +msgstr "Odhlásiť" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.res_config_settings_view_form_vrp2 +msgid "" +"Map VRP2 VAT rates to Odoo sales taxes. Each till's own VRP2 credentials are" +" set on the Point of Sale (under Settings ▸ Edit), where you also log in and" +" sync its catalog." +msgstr "" +"Priraďte sadzby DPH VRP2 k predajným daniam Odoo. Vlastné prihlasovacie " +"údaje VRP2 každej pokladne sa zadávajú na mieste predaja (Nastavenia ▸ " +"Upraviť), kde sa aj prihlasuje a synchronizuje jej katalóg." + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "" +"Must round to 0.05 'Nearest' on cash only, as VRP2 does, or Odoo's drawer " +"drifts from the register." +msgstr "" +"Musí zaokrúhľovať na 0,05 „Najbližšie“ len pri hotovosti, rovnako ako VRP2, " +"inak sa stav pokladne v Odoo rozíde s pokladnicou." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "No VRP2 PDF is stored for this order." +msgstr "Pre túto objednávku nie je uložené PDF z VRP2." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_order__vrp2_state__not_fiscalized +msgid "Not Fiscalized" +msgstr "Nezaevidované" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_order_filter_vrp2 +msgid "Not Fiscalized in VRP2" +msgstr "Nezaevidované vo VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_okp +msgid "OKP" +msgstr "OKP" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "OKP:" +msgstr "OKP:" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "Odoo Cash Rounding" +msgstr "Zaokrúhľovanie hotovosti v Odoo" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s was invoiced, so VRP2 holds an invoice payment for it, not a" +" sale. Cancel that payment receipt rather than returning items against it." +msgstr "" +"Objednávka %(order)s bola vyfakturovaná, takže VRP2 k nej eviduje úhradu " +"faktúry, nie predaj. Namiesto vrátenia položiek voči nej stornujte tento " +"doklad o úhrade." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s was paid %(paid)s but its VRP2 receipt totals %(total)s; the" +" difference is too large to be tax rounding." +msgstr "" +"Objednávka %(order)s bola uhradená sumou %(paid)s, ale jej doklad VRP2 je na" +" %(total)s; rozdiel je príliš veľký na to, aby šlo o zaokrúhlenie dane." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s was paid more by non-cash means (%(non_cash)s) than it costs" +" (%(total)s) and also in cash; that cannot be put on a receipt." +msgstr "" +"Objednávka %(order)s bola bezhotovostne uhradená viac (%(non_cash)s), než je" +" jej suma (%(total)s), a zároveň aj v hotovosti; takýto doklad nemožno " +"vystaviť." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s was paid more by non-cash means than it costs and also in " +"cash; that cannot be put on a receipt." +msgstr "" +"Objednávka %(order)s bola bezhotovostne uhradená viac, než je jej suma, a " +"zároveň aj v hotovosti; takýto doklad nemožno vystaviť." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %(order)s, which this refund returns goods from, has no VRP2 receipt " +"to reference. Fiscalize it first." +msgstr "" +"Objednávka %(order)s, z ktorej sa tovar vracia, nemá doklad VRP2, na ktorý " +"by sa dalo odkázať. Najprv ju zaevidujte." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "Order %s has no lines to fiscalize." +msgstr "Objednávka %s nemá žiadne riadky na zaevidovanie." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "Order %s is already fiscalized in VRP2." +msgstr "Objednávka %s je už vo VRP2 zaevidovaná." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order %s is an invoiced refund. A credit note is not fiscalized as an " +"invoice payment; cancel the original payment receipt instead." +msgstr "" +"Objednávka %s je vyfakturované vrátenie. Dobropis sa neeviduje ako úhrada " +"faktúry; namiesto toho stornujte pôvodný doklad o úhrade." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "Order %s must be paid before it can be fiscalized." +msgstr "Objednávka %s musí byť pred zaevidovaním zaplatená." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order line '%(line)s' carries taxes VRP2 cannot express: a receipt item has " +"exactly one percentage VAT rate." +msgstr "" +"Riadok objednávky „%(line)s“ obsahuje dane, ktoré VRP2 nevie vyjadriť: " +"položka dokladu má práve jednu percentuálnu sadzbu DPH." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Order line '%(line)s' has VAT %(rate)s %%, which is not a VRP2 VAT rate " +"(%(allowed)s)." +msgstr "" +"Riadok objednávky „%(line)s“ má DPH %(rate)s %%, čo nie je sadzba DPH vo " +"VRP2 (%(allowed)s)." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__tax_exemption +msgid "Oslobodené od dane" +msgstr "Oslobodené od dane" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "Overte doklad pomocou QR kódu" +msgstr "Overte doklad pomocou QR kódu" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "POKLADNIČNÝ DOKLAD č." +msgstr "POKLADNIČNÝ DOKLAD č." + +#. module: pos_vrp2 +#: model:ir.module.module,shortdesc:base.module_pos_vrp2 +msgid "POS VRP2 Integration" +msgstr "Prepojenie POS s VRP2" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Packaging '%(product)s' is not in this till's VRP2 catalogue (it has no VRP2" +" service code). Push the catalogue to VRP2 first." +msgstr "" +"Obal „%(product)s“ nie je v katalógu VRP2 tejto pokladne (nemá kód služby " +"VRP2). Najprv odošlite katalóg do VRP2." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Payment method '%(method)s' has no VRP2 payment type. A sale settled on a " +"customer account is not a cash-register sale; set the type on the payment " +"method if it is one." +msgstr "" +"Platobná metóda „%(method)s“ nemá typ platidla VRP2. Predaj uhradený na účet" +" zákazníka nie je tržbou registračnej pokladnice; ak ňou je, nastavte typ na" +" platobnej metóde." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__card +msgid "Platobná karta" +msgstr "Platobná karta" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"Point of Sale '%(pos)s' does not round cash in VRP2, so Odoo must not round " +"it either: disable Cash Rounding or enable 'Round to 0.05 €'." +msgstr "" +"Miesto predaja „%(pos)s“ nezaokrúhľuje hotovosť vo VRP2, preto ju nesmie " +"zaokrúhľovať ani Odoo: vypnite zaokrúhľovanie hotovosti alebo zapnite " +"„Zaokrúhľovať na 0,05 €“." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"Point of Sale '%(pos)s' rounds cash to 0.05 € in VRP2, so its Odoo cash " +"rounding must do the same: enable Cash Rounding with a 0.05 'Nearest' " +"rounding method (Add a rounding line) and 'Only apply rounding on cash'." +msgstr "" +"Miesto predaja „%(pos)s“ zaokrúhľuje hotovosť vo VRP2 na 0,05 €, preto musí " +"rovnako zaokrúhľovať aj Odoo: zapnite zaokrúhľovanie hotovosti so spôsobom " +"0,05 „Najbližšie“ („Pridať riadok zaokrúhlenia“) a „Only apply rounding on " +"cash“." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__voucher +msgid "Poukážky" +msgstr "Poukážky" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__used_goods +msgid "Použitý tovar" +msgstr "Použitý tovar" + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__tax_obligation_transfer +msgid "Prenesenie daňovej povinnosti" +msgstr "Prenesenie daňovej povinnosti" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_pos_form_vrp2 +msgid "Print VRP2 Receipt" +msgstr "Tlačiť doklad VRP2" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "" +"Product '%(product)s' is not in this till's VRP2 catalogue (it has no VRP2 " +"service code). Push the catalogue to VRP2 first." +msgstr "" +"Produkt „%(product)s“ nie je v katalógu VRP2 tejto pokladne (nemá kód služby" +" VRP2). Najprv odošlite katalóg do VRP2." + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "Pull from VRP2" +msgstr "Načítať z VRP2" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "Pulled %d categories and %d products from VRP2." +msgstr "Z VRP2 sa načítalo %d kategórií a %d produktov." + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "Push to VRP2" +msgstr "Odoslať do VRP2" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "QR" +msgstr "QR" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "" +"Send this till's catalog to VRP2: create/update its product categories and " +"POS products (with mapped VAT) on this cash register, and deactivate items " +"no longer in Odoo. Odoo is the source of truth." +msgstr "" +"Odoslať katalóg tejto pokladne do VRP2: vytvoriť/aktualizovať jej kategórie " +"a produkty POS (s priradenou DPH) na tejto pokladnici a deaktivovať položky," +" ktoré už v Odoo nie sú. Rozhodujúce sú údaje v Odoo." + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_category__vrp2_id +msgid "Server-side category ID in VRP2 (categoryId)." +msgstr "ID kategórie na serveri VRP2 (categoryId)." + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_category__vrp2_short_name +msgid "" +"Short name for VRP2 tabs (max ~3 chars). Auto-generated from name if not " +"set." +msgstr "" +"Krátky názov pre záložky VRP2 (max. ~3 znaky). Ak nie je zadaný, vytvorí sa " +"z názvu." + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_favourite +msgid "Show in VRP2 favourites quick-access." +msgstr "Zobraziť v obľúbených položkách VRP2." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__pos_payment_method__vrp2_payment_type__gastro +msgid "Stravné poukážky" +msgstr "Stravné poukážky" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_synced +msgid "Synced to VRP2" +msgstr "Zosynchronizované s VRP2" + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.js:0 +msgid "The VRP2 receipt was NOT issued: %s" +msgstr "Doklad VRP2 NEBOL vystavený: %s" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"The company has no VRP2 credentials. Set them in Accounting ▸ Settings ▸ " +"VRP2 first." +msgstr "" +"Spoločnosť nemá prihlasovacie údaje VRP2. Najprv ich zadajte v Účtovníctvo ▸" +" Nastavenia ▸ VRP2." + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_order_line__vrp2_item_name +msgid "" +"The name VRP2 printed for this line on the fiscal receipt. A refund of the " +"line must repeat it verbatim." +msgstr "" +"Názov, ktorý VRP2 vytlačilo pre tento riadok na pokladničnom doklade. " +"Vrátenie tohto riadku ho musí zopakovať doslovne." + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "" +"This till is its own VRP2 cash register. Enable it, enter the till's own " +"VRP2 login, then log in and sync its catalog." +msgstr "" +"Táto pokladňa je samostatnou pokladnicou VRP2. Zapnite ju, zadajte jej " +"vlastné prihlasovacie údaje VRP2, potom sa prihláste a zosynchronizujte jej " +"katalóg." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__art_works +msgid "Umelecké diela" +msgstr "Umelecké diela" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_category_view_form_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.product_template_form_view_vrp2 +msgid "VRP2" +msgstr "VRP2" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.res_config_settings_view_form_vrp2 +msgid "VRP2 - Slovak Virtual Cash Register" +msgstr "VRP2 – Virtuálna registračná pokladnica" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_category__vrp2_id +msgid "VRP2 Category ID" +msgstr "ID kategórie VRP2" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.view_pos_order_filter_vrp2 +msgid "VRP2 Error" +msgstr "Chyba VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_favourite +msgid "VRP2 Favourite" +msgstr "Obľúbené vo VRP2" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "VRP2 Fiscal Receipts" +msgstr "Pokladničné doklady VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_created +msgid "VRP2 Fiscalized On" +msgstr "Zaevidované vo VRP2 dňa" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order_line__vrp2_item_name +msgid "VRP2 Item Name" +msgstr "Názov položky vo VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_note +msgid "VRP2 Note" +msgstr "Poznámka VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_category__vrp2_order +msgid "VRP2 Order" +msgstr "Poradie vo VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_payment_method__vrp2_payment_type +msgid "VRP2 Payment Type" +msgstr "Typ platidla VRP2" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "VRP2 Pull Complete" +msgstr "Načítanie z VRP2 dokončené" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_qr_content +msgid "VRP2 Receipt Data" +msgstr "Údaje dokladu VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_server_id +msgid "VRP2 Receipt ID" +msgstr "ID dokladu VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_receipt_number +msgid "VRP2 Receipt Number" +msgstr "Číslo dokladu VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_pdf +msgid "VRP2 Receipt PDF" +msgstr "PDF dokladu VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_receipt_uuid +msgid "VRP2 Receipt UUID" +msgstr "UUID dokladu VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_request_json +msgid "VRP2 Request" +msgstr "Požiadavka VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_response_json +msgid "VRP2 Response" +msgstr "Odpoveď VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_returnable_packaging +msgid "VRP2 Returnable Packaging" +msgstr "Zálohovaný obal VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_code +msgid "VRP2 Service Code" +msgstr "Kód služby VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_category__vrp2_short_name +msgid "VRP2 Short Name" +msgstr "Krátky názov VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_product_template__vrp2_special_regulation +msgid "VRP2 Special Regulation" +msgstr "Osobitná úprava VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_state +msgid "VRP2 Status" +msgstr "Stav VRP2" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "VRP2 Sync Complete" +msgstr "Synchronizácia s VRP2 dokončená" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_0 +msgid "VRP2 Tax 0%" +msgstr "Daň VRP2 0 %" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_10 +msgid "VRP2 Tax 10%" +msgstr "Daň VRP2 10 %" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_19 +msgid "VRP2 Tax 19%" +msgstr "Daň VRP2 19 %" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_20 +msgid "VRP2 Tax 20%" +msgstr "Daň VRP2 20 %" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_23 +msgid "VRP2 Tax 23%" +msgstr "Daň VRP2 23 %" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_tax_5 +msgid "VRP2 Tax 5%" +msgstr "Daň VRP2 5 %" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.res_config_settings_view_form_vrp2 +msgid "VRP2 Tax Mapping" +msgstr "Priradenie daní VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_res_company__vrp2_vatlist_json +msgid "VRP2 VAT List (cached)" +msgstr "Zoznam sadzieb DPH VRP2 (vyrovnávacia pamäť)" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "VRP2 credentials are not configured on till '%s'." +msgstr "Na pokladni „%s“ nie sú nastavené prihlasovacie údaje VRP2." + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_order__vrp2_qr_content +msgid "" +"VRP2 dataBase64: the whole receipt as base64 JSON (items as VRP2 named them," +" VAT summary, payments, rounding). NOT the QR code, which encodes only the " +"receiptId." +msgstr "" +"dataBase64 z VRP2: celý doklad ako JSON v base64 (položky s názvami podľa " +"VRP2, rekapitulácia DPH, platby, zaokrúhlenie). NIE JE to obsah QR kódu, ten" +" obsahuje len receiptId." + +#. module: pos_vrp2 +#. odoo-javascript +#: code:addons/pos_vrp2/static/src/pos/vrp2_receipt.xml:0 +msgid "VRP2 doklad (PDF)" +msgstr "VRP2 doklad (PDF)" + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_order.py:0 +msgid "VRP2 is not enabled for Point of Sale '%s'." +msgstr "VRP2 nie je zapnuté pre miesto predaja „%s“." + +#. module: pos_vrp2 +#. odoo-python +#: code:addons/pos_vrp2/models/pos_config.py:0 +msgid "" +"VRP2 login '%(login)s' is already used by Point of Sale '%(pos)s'. Each VRP2" +" cash register can belong to only one POS." +msgstr "" +"Prihlasovacie meno VRP2 „%(login)s“ už používa miesto predaja „%(pos)s“. " +"Každá pokladnica VRP2 môže patriť len jednému POS." + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_pos_order__vrp2_receipt_uuid +msgid "" +"VRP2 receiptId (V-…). This is exactly what the QR code on the official " +"receipt encodes." +msgstr "" +"receiptId z VRP2 (V-…). Presne toto obsahuje QR kód na oficiálnom doklade." + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_special_regulation +msgid "Verbal information required on receipt for 0%% VAT items." +msgstr "Slovná informácia povinná na doklade pri položkách s 0 %% DPH." + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_error_message +msgid "Vrp2 Error Message" +msgstr "Chybová správa VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,field_description:pos_vrp2.field_pos_order__vrp2_pdf_filename +msgid "Vrp2 Pdf Filename" +msgstr "Názov súboru PDF VRP2" + +#. module: pos_vrp2 +#: model:ir.model.fields,help:pos_vrp2.field_product_template__vrp2_synced +msgid "Whether this product exists on the VRP2 server." +msgstr "Či tento produkt existuje na serveri VRP2." + +#. module: pos_vrp2 +#: model:ir.model.fields.selection,name:pos_vrp2.selection__product_template__vrp2_special_regulation__collective_subjects_and_antiques +msgid "Zberateľské predmety a starožitnosti" +msgstr "Zberateľské predmety a starožitnosti" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_category_view_form_vrp2 +msgid "e.g. FD" +msgstr "napr. FD" + +#. module: pos_vrp2 +#: model_terms:ir.ui.view,arch_db:pos_vrp2.pos_config_view_form_vrp2 +msgid "— DKP" +msgstr "— DKP" diff --git a/pos_vrp2/models/__init__.py b/pos_vrp2/models/__init__.py new file mode 100644 index 00000000..4f948939 --- /dev/null +++ b/pos_vrp2/models/__init__.py @@ -0,0 +1,7 @@ +from . import res_company +from . import res_config_settings +from . import pos_config +from . import pos_category +from . import product +from . import pos_order +from . import pos_payment_method diff --git a/pos_vrp2/models/pos_category.py b/pos_vrp2/models/pos_category.py new file mode 100644 index 00000000..2925c842 --- /dev/null +++ b/pos_vrp2/models/pos_category.py @@ -0,0 +1,241 @@ +import logging + +from odoo import _, api, fields, models + +_logger = logging.getLogger(__name__) + +# Odoo ColorList indices and their actual hex values: +# 0: No color +# 1: Red #ee2d2d +# 2: Orange #dc8534 +# 3: Yellow #e8bb1d +# 4: Cyan #5794dd +# 5: Purple #9f628f +# 6: Almond #db8865 +# 7: Teal #41a9a2 +# 8: Blue #304be0 +# 9: Raspberry #ee2f8a +# 10: Green #61c36e +# 11: Violet #9872e6 + +# Odoo pos.category.color (0-11) → VRP2 color ID (50-74) +ODOO_TO_VRP2_COLOR = { + 0: 66, # no color → #c1c1c1 grey + 1: 50, # red → #eb1a1a red + 2: 51, # orange → #f7ba16 gold + 3: 52, # yellow → #f5ec00 yellow + 4: 55, # cyan → #23dde5 cyan + 5: 59, # purple → #712bc1 purple + 6: 63, # almond → #e3c295 tan + 7: 65, # teal → #2bd9a4 teal + 8: 57, # blue → #0089ff blue + 9: 60, # raspberry→ #d800ff magenta + 10: 54, # green → #21b232 green + 11: 68, # violet → #abade2 lavender +} + +# Reverse: VRP2 color ID → Odoo color index (best match) +VRP2_TO_ODOO_COLOR = { + 50: 1, # #eb1a1a → red + 51: 2, # #f7ba16 → orange + 52: 3, # #f5ec00 → yellow + 53: 10, # #b5e063 → green + 54: 10, # #21b232 → green + 55: 4, # #23dde5 → cyan + 56: 4, # #10b9e2 → cyan + 57: 8, # #0089ff → blue + 58: 8, # #075291 → blue + 59: 5, # #712bc1 → purple + 60: 9, # #d800ff → raspberry + 61: 9, # #a5177f → raspberry + 62: 0, # #000000 → no color + 63: 6, # #e3c295 → almond + 64: 10, # #778019 → green (olive) + 65: 7, # #2bd9a4 → teal + 66: 0, # #c1c1c1 → no color + 67: 4, # #cef0ff → cyan + 68: 11, # #abade2 → violet + 69: 6, # #e0c9c9 → almond + 70: 3, # #e0d225 → yellow + 71: 7, # #4d979a → teal + 72: 9, # #f8e1f3 → raspberry + 73: 11, # #ebe5fc → violet + 74: 3, # #fefee7 → yellow +} + + +class PosCategory(models.Model): + _inherit = "pos.category" + + vrp2_id = fields.Integer( + string="VRP2 Category ID", + readonly=True, + copy=False, + help="Server-side category ID in VRP2 (categoryId).", + ) + vrp2_short_name = fields.Char( + string="VRP2 Short Name", + help="Short name for VRP2 tabs (max ~3 chars). " + "Auto-generated from name if not set.", + ) + vrp2_order = fields.Integer( + string="VRP2 Order", + readonly=True, + copy=False, + help="Display order in VRP2 (set by server).", + ) + + # ------------------------------------------------------------------ + # Pull: VRP2 → Odoo + # ------------------------------------------------------------------ + + @api.model + def _vrp2_pull(self, register): + """Pull all categories from VRP2 and upsert as top-level pos.category. + + Returns the count of categories synced. + """ + client = self.env["vrp2.client"] + company = register.company_id + resp = client._get_category_list(register) + categories = resp.get("categories", []) + + synced = 0 + for cat_data in categories: + vrp2_id = cat_data.get("id") + if not vrp2_id: + continue + + existing = self.search([("vrp2_id", "=", vrp2_id)], limit=1) + + color_data = cat_data.get("color", {}) + vrp2_color_id = color_data.get("id") or 66 + odoo_color = VRP2_TO_ODOO_COLOR.get(vrp2_color_id, 0) + + vals = { + "name": cat_data.get("name", ""), + "vrp2_id": vrp2_id, + "vrp2_short_name": cat_data.get("shortName") or False, + "vrp2_order": cat_data.get("order", 0), + "color": odoo_color, + "parent_id": False, + } + + if existing: + existing.write(vals) + else: + self.create(vals) + synced += 1 + + _logger.info( + "VRP2 category pull for %s: %d categories", + company.display_name, + synced, + ) + return synced + + # ------------------------------------------------------------------ + # Push: Odoo → VRP2 (bulk — single API call) + # ------------------------------------------------------------------ + + @api.model + def _vrp2_push(self, register): + """Push all top-level POS categories to VRP2 in one bulk call. + + All top-level pos.category records are synced. If vrp2_short_name + is not set, it is auto-generated from the first 2 chars of the name. + + Color is derived from Odoo's pos.category.color field. + + Deletes from VRP2 any category that exists there but is no + longer in the intended set. + + Returns (created, updated, deleted) counts. + """ + client = self.env["vrp2.client"] + company = register.company_id + + # 1. All top-level categories + intended = self.search([("parent_id", "=", False)]) + + # Auto-fill vrp2_short_name where missing + for cat in intended: + if not cat.vrp2_short_name: + name = cat.name or "" + cat.vrp2_short_name = name[:2].upper() or "XX" + + # 2. Current VRP2 state + resp = client._get_category_list(register) + remote_cats = resp.get("categories", []) + remote_ids = {c["id"] for c in remote_cats if c.get("id")} + + # 3. Build setCategories + intended_vrp2_ids = set() + set_categories = [] + for order, cat in enumerate(intended, start=1): + vrp2_color = ODOO_TO_VRP2_COLOR.get(cat.color or 0, 66) + entry = { + "order": order, + "name": cat.name, + "shortName": cat.vrp2_short_name, + "colorId": vrp2_color, + } + if cat.vrp2_id: + entry["categoryId"] = cat.vrp2_id + intended_vrp2_ids.add(cat.vrp2_id) + set_categories.append(entry) + + # 4. Build deleteCategories — remote IDs not in intended set + delete_categories = [ + {"categoryId": rid} + for rid in remote_ids + if rid not in intended_vrp2_ids + ] + + if not set_categories and not delete_categories: + return 0, 0, 0 + + data = { + "setCategories": set_categories, + "deleteCategories": delete_categories, + } + resp = client._set_category(register, data) + + # 5. Update local vrp2_id from response + created = 0 + updated = 0 + resp_cats = resp.get("categories", []) if resp else [] + resp_by_name = {} + for rc in resp_cats: + resp_by_name.setdefault(rc.get("name"), rc) + + for cat in intended: + if cat.vrp2_id: + for rc in resp_cats: + if rc.get("id") == cat.vrp2_id: + cat.vrp2_order = rc.get("order", 0) + break + updated += 1 + else: + rc = resp_by_name.get(cat.name) + if rc and rc.get("id"): + cat.vrp2_id = rc["id"] + cat.vrp2_order = rc.get("order", 0) + created += 1 + + deleted = len(delete_categories) + + if deleted: + orphaned = self.search([ + ("vrp2_id", "in", [d["categoryId"] for d in delete_categories]), + ]) + orphaned.write({"vrp2_id": False, "vrp2_order": False}) + + _logger.info( + "VRP2 category push for %s: %d created, %d updated, %d deleted", + company.display_name, + created, + updated, + deleted, + ) + return created, updated, deleted diff --git a/pos_vrp2/models/pos_config.py b/pos_vrp2/models/pos_config.py new file mode 100644 index 00000000..9f0846a2 --- /dev/null +++ b/pos_vrp2/models/pos_config.py @@ -0,0 +1,196 @@ +import json + +from odoo import _, api, fields, models +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_compare + +# VRP2 rounds the cash share to 0.05 € half-up (zaokruhli5). Odoo's own cash +# rounding must do the same, or the drawer drifts from the register by the +# rounding difference on every cash sale (18.48 in Odoo, 18.50 in VRP2). +VRP2_CASH_ROUNDING = 0.05 + + +class PosConfig(models.Model): + # Each POS till is its own VRP2 cash register: it carries its own + # credentials/session via the mixin and fiscalizes its sales on it. + _name = "pos.config" + _inherit = ["pos.config", "vrp2.credentials.mixin"] + + vrp2_enabled = fields.Boolean( + string="Enable VRP2", + help="Fiscalize this till's sales in the Slovak Virtual Cash " + "Register. Set the till's own VRP2 credentials below.", + ) + vrp2_auto_fiscalize = fields.Boolean( + string="Fiscalize Automatically", + default=True, + help="Issue the VRP2 receipt as soon as the till syncs a paid order. " + "A failure never blocks the sale: the order is marked 'VRP2 Error' " + "and can be fiscalized again from its form.", + ) + + def _vrp2_odoo_rounds_like_vrp2(self): + """Odoo's cash rounding is exactly VRP2's: 0.05, nearest, cash only.""" + self.ensure_one() + method = self.rounding_method + return bool( + self.cash_rounding + and self.only_round_cash_method + and method + and method.rounding_method == "HALF-UP" + and not float_compare(method.rounding, VRP2_CASH_ROUNDING, + precision_digits=6) + ) + + @api.model + def _vrp2_find_cash_rounding(self): + return self.env["account.cash.rounding"].search([ + ("rounding", "=", VRP2_CASH_ROUNDING), + ("rounding_method", "=", "HALF-UP"), + ("strategy", "=", "add_invoice_line"), + ], limit=1) + + @api.onchange("vrp2_enabled", "vrp2_round_5c") + def _onchange_vrp2_cash_rounding(self): + """Set Odoo's cash rounding to match the register's.""" + for config in self.filtered("vrp2_enabled"): + if config.vrp2_round_5c: + config.cash_rounding = True + config.only_round_cash_method = True + if not config._vrp2_odoo_rounds_like_vrp2(): + config.rounding_method = ( + config._vrp2_find_cash_rounding() + or config.rounding_method + ) + else: + config.cash_rounding = False + + @api.constrains( + "vrp2_enabled", "vrp2_round_5c", "cash_rounding", "rounding_method", + "only_round_cash_method", + ) + def _check_vrp2_cash_rounding(self): + """Odoo must round cash exactly when, and exactly as, VRP2 does. + + Rounding in VRP2 but not in Odoo leaves Odoo's drawer 2 cents short + of the register on an 18.48 sale; rounding in Odoo but not in VRP2 + does the reverse. Neither raises anywhere else; the cash count just + never agrees with the register's report. + """ + for config in self.filtered("vrp2_enabled"): + if config.vrp2_round_5c and not config._vrp2_odoo_rounds_like_vrp2(): + raise ValidationError(_( + "Point of Sale '%(pos)s' rounds cash to 0.05 € in VRP2, " + "so its Odoo cash rounding must do the same: enable Cash " + "Rounding with a 0.05 'Nearest' rounding method (Add a " + "rounding line) and 'Only apply rounding on cash'.", + pos=config.display_name, + )) + if not config.vrp2_round_5c and config.cash_rounding: + raise ValidationError(_( + "Point of Sale '%(pos)s' does not round cash in VRP2, so " + "Odoo must not round it either: disable Cash Rounding or " + "enable 'Round to 0.05 €'.", + pos=config.display_name, + )) + + def action_vrp2_login(self): + """Authenticate this till and cache the (national) VAT list on the + company for the tax mapping used by catalog sync / receipts.""" + res = super().action_vrp2_login() + vat_resp = self.env["vrp2.client"]._get_vat_list(self) + self.company_id.sudo().write({ + "vrp2_vatlist_json": json.dumps(vat_resp.get("results", [])), + }) + return res + + @api.model + def vrp2_company_credentials(self, company_id=False): + """Return the company default register's VRP2 credentials. + + Used by the "Load Credentials from Company" form widget, which applies + them to the record in memory (no save / no dialog reload). + """ + company = ( + self.env["res.company"].browse(company_id) + if company_id else self.env.company + ).sudo() + if not company.vrp2_login: + raise UserError(_( + "The company has no VRP2 credentials. Set them in " + "Accounting ▸ Settings ▸ VRP2 first." + )) + return { + "vrp2_login": company.vrp2_login, + "vrp2_password": company.vrp2_password or "", + } + + @api.constrains("vrp2_login") + def _check_vrp2_login_unique(self): + """A VRP2 login is one physical cash register — it can belong to only + one POS.""" + for config in self: + if not config.vrp2_login: + continue + duplicate = self.sudo().with_context(active_test=False).search([ + ("id", "!=", config.id), + ("vrp2_login", "=", config.vrp2_login), + ], limit=1) + if duplicate: + raise ValidationError(_( + "VRP2 login '%(login)s' is already used by Point of Sale " + "'%(pos)s'. Each VRP2 cash register can belong to only one " + "POS.", + login=config.vrp2_login, + pos=duplicate.display_name, + )) + + def _vrp2_check_ready(self): + self.ensure_one() + if not self.vrp2_login: + raise UserError( + _("VRP2 credentials are not configured on till '%s'.") + % self.display_name + ) + self.env["vrp2.client"]._ensure_session(self) + + def action_vrp2_sync_to(self): + """Push this till's catalog (categories + products) to its register.""" + self._vrp2_check_ready() + cat_created, cat_updated, cat_deleted = ( + self.env["pos.category"]._vrp2_push(self) + ) + prod_created, prod_updated, prod_deactivated = ( + self.env["product.template"]._vrp2_push(self) + ) + msg = _( + "Categories: %d created, %d updated, %d deleted.\n" + "Products: %d created, %d updated, %d deactivated." + ) % ( + cat_created, cat_updated, cat_deleted, + prod_created, prod_updated, prod_deactivated, + ) + return self._vrp2_notify(_("VRP2 Sync Complete"), msg, sticky=True) + + def action_vrp2_pull(self): + """Pull this till's catalog from its register into Odoo.""" + self._vrp2_check_ready() + cat_count = self.env["pos.category"]._vrp2_pull(self) + prod_count = self.env["product.template"]._vrp2_pull(self) + return self._vrp2_notify( + _("VRP2 Pull Complete"), + _("Pulled %d categories and %d products from VRP2.") + % (cat_count, prod_count), + ) + + def _vrp2_notify(self, title, message, sticky=False): + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "title": title, + "message": message, + "type": "success", + "sticky": sticky, + }, + } diff --git a/pos_vrp2/models/pos_order.py b/pos_vrp2/models/pos_order.py new file mode 100644 index 00000000..8c9f54fe --- /dev/null +++ b/pos_vrp2/models/pos_order.py @@ -0,0 +1,790 @@ +import base64 +import json +import logging +import time +from datetime import datetime, timezone + +import psycopg2 + +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.modules.registry import Registry +from odoo.tools import format_datetime + +from odoo.addons.l10n_sk_vrp2_base.models.vrp2_client import ( + vrp2_round2, + vrp2_round4, + vrp2_round5, +) + +_logger = logging.getLogger(__name__) + +# Odoo order states in which the sale has actually been settled. +_SETTLED_STATES = ("paid", "done") + + +def _js_numbers(value): + """Integral floats as ints, recursively, so the body serializes the way + the web app's ``JSON.stringify`` does (``1`` and ``0``, never ``1.0``). + """ + if isinstance(value, dict): + return {k: _js_numbers(v) for k, v in value.items()} + if isinstance(value, list): + return [_js_numbers(v) for v in value] + if isinstance(value, float) and value.is_integer(): + return int(value) + return value + + +class PosOrder(models.Model): + _inherit = "pos.order" + + vrp2_state = fields.Selection( + [ + ("not_fiscalized", "Not Fiscalized"), + ("fiscalized", "Fiscalized"), + ("error", "Error"), + ], + string="VRP2 Status", + default="not_fiscalized", + readonly=True, + copy=False, + index=True, + ) + vrp2_receipt_number = fields.Char( + string="VRP2 Receipt Number", readonly=True, copy=False + ) + vrp2_server_id = fields.Char( + string="VRP2 Receipt ID", readonly=True, copy=False + ) + vrp2_receipt_uuid = fields.Char( + string="VRP2 Receipt UUID", + readonly=True, + copy=False, + help="VRP2 receiptId (V-…). This is exactly what the QR code on the " + "official receipt encodes.", + ) + vrp2_created = fields.Datetime( + string="VRP2 Fiscalized On", readonly=True, copy=False + ) + vrp2_okp = fields.Char(string="OKP", readonly=True, copy=False) + vrp2_qr_content = fields.Char( + string="VRP2 Receipt Data", + readonly=True, + copy=False, + help="VRP2 dataBase64: the whole receipt as base64 JSON (items as " + "VRP2 named them, VAT summary, payments, rounding). NOT the QR code, " + "which encodes only the receiptId.", + ) + vrp2_pdf = fields.Binary( + string="VRP2 Receipt PDF", readonly=True, copy=False, attachment=True + ) + vrp2_pdf_filename = fields.Char(readonly=True, copy=False) + vrp2_request_json = fields.Text( + string="VRP2 Request", readonly=True, copy=False + ) + vrp2_response_json = fields.Text( + string="VRP2 Response", readonly=True, copy=False + ) + vrp2_error_message = fields.Text(readonly=True, copy=False) + + # ------------------------------------------------------------------ + # Actions + # ------------------------------------------------------------------ + + def action_vrp2_fiscalize(self): + """Fiscalize the selected orders in VRP2. + + One order: fail loudly. Several (list action): each receipt is an + external side effect a rollback cannot undo, so one failure must not + roll back the others — failures are recorded on their orders. + """ + if len(self) == 1: + self._vrp2_fiscalize() + return True + todo = self.filtered( + lambda o: o.vrp2_state != "fiscalized" + and o.state in _SETTLED_STATES + and o.config_id.vrp2_enabled + ) + for order in todo: + order._vrp2_fiscalize_or_record_error() + failed = todo.filtered(lambda o: o.vrp2_state == "error") + return { + "type": "ir.actions.client", + "tag": "display_notification", + "params": { + "title": _("VRP2"), + "message": _( + "%(done)s order(s) fiscalized, %(failed)s failed, " + "%(skipped)s skipped.", + done=len(todo) - len(failed), + failed=len(failed), + skipped=len(self) - len(todo), + ), + "type": "warning" if failed else "success", + "sticky": bool(failed), + }, + } + + def action_vrp2_print_receipt(self): + """Download the official VRP2 PDF receipt for this order.""" + self.ensure_one() + if not self.vrp2_pdf: + raise UserError(_("No VRP2 PDF is stored for this order.")) + return { + "type": "ir.actions.act_url", + "url": "/web/content/pos.order/%s/vrp2_pdf/%s?download=true" + % (self.id, self.vrp2_pdf_filename or "receipt.pdf"), + "target": "self", + } + + # ------------------------------------------------------------------ + # Automatic fiscalization when the POS syncs a paid order + # ------------------------------------------------------------------ + + def _process_saved_order(self, draft): + order_id = super()._process_saved_order(draft) + if ( + not draft + and self.state in _SETTLED_STATES + and self.config_id.vrp2_enabled + and self.config_id.vrp2_auto_fiscalize + and self.vrp2_state != "fiscalized" + ): + self._vrp2_fiscalize_after_commit() + return order_id + + def _vrp2_fiscalize_after_commit(self): + """Fiscalize once the order's own transaction has COMMITTED. + + ``sync_from_ui`` saves a whole batch of orders in one transaction. A + receipt issued from inside it would survive a later rollback (say, the + next order of the batch failing) while the order did not, and the + till's re-sync would then fiscalize the same sale a second time. After + the commit, the order exists for certain; the worst left is a crash + before the call, which leaves the order visibly "Not Fiscalized" + rather than fiscalized twice. + """ + self.ensure_one() + dbname = self.env.cr.dbname + uid = self.env.uid + context = dict(self.env.context) + order_id = self.id + + @self.env.cr.postcommit.add + def _fiscalize(): + with Registry(dbname).cursor() as cr: + env = api.Environment(cr, uid, context) + env["pos.order"].browse(order_id)._vrp2_fiscalize_or_record_error() + + def _vrp2_fiscalize_or_record_error(self, retry_on_concurrency=False): + """Fiscalize, but never let a VRP2 failure lose the sale itself. + + The order sync from the till must succeed even when the Financial + Administration is unreachable, so a failure is recorded on the order + (``vrp2_state = error``) instead of raised. The savepoint discards the + half-written request fields of the failed attempt. + + Another transaction fiscalizing the same order is NOT a failure: its + lock (``LockNotAvailable``) or its commit (``SerializationFailure`` + under REPEATABLE READ) means the receipt is, or is being, issued + there, and recording an error would overwrite it. Background callers + skip; ``retry_on_concurrency`` re-raises instead, so that Odoo replays + the request on a fresh snapshot and the caller reads the real result. + """ + self.ensure_one() + if self.vrp2_state == "fiscalized": + return + try: + with self.env.cr.savepoint(): + self._vrp2_fiscalize() + except ( + psycopg2.errors.LockNotAvailable, + psycopg2.errors.SerializationFailure, + ): + if retry_on_concurrency: + raise + _logger.info( + "VRP2: order %s is being fiscalized elsewhere; skipped.", + self.name, + ) + except Exception as exc: # noqa: BLE001 — recorded, never swallowed silently + _logger.exception( + "VRP2 fiscalization of POS order %s failed", self.name + ) + self.write({ + "vrp2_state": "error", + "vrp2_error_message": _( + "%(error)s\n\nIf the failure was a timeout, VRP2 may " + "already hold this receipt: check the register's receipt " + "list in the VRP2 portal before fiscalizing again.", + error=exc, + ), + }) + + # ------------------------------------------------------------------ + # POS frontend + # ------------------------------------------------------------------ + + # Audit payloads and the PDF stay on the server: the POS loads every + # pos.order field it is sent, for each order in its history. + _VRP2_NOT_FOR_POS = ( + "vrp2_pdf", "vrp2_qr_content", "vrp2_request_json", "vrp2_response_json", + ) + + @api.model + def _load_pos_data_read(self, records, config): + # bin_size: read the PDF's size, not the PDF, before dropping it. + rows = super()._load_pos_data_read( + records.with_context(bin_size=True), config + ) + for row in rows: + for key in self._VRP2_NOT_FOR_POS: + row.pop(key, None) + return rows + + def vrp2_receipt_for_ui(self): + """The order's VRP2 receipt as the till prints it. + + Called by the POS right after it validated the order. The receipt is + normally already issued — the post-commit fiscalization runs before + the sync request returns — so this only reads it. It fiscalizes when + that step never ran (``not_fiscalized``), but never retries an + ``error`` on its own: after a timeout VRP2 may already hold the + receipt, and only a person can check the portal. + """ + self.ensure_one() + config = self.config_id + if not config.vrp2_enabled: + return {"enabled": False} + if ( + self.vrp2_state == "not_fiscalized" + and config.vrp2_auto_fiscalize + and self.state in _SETTLED_STATES + ): + self._vrp2_fiscalize_or_record_error(retry_on_concurrency=True) + return self._vrp2_receipt_values() + + def _vrp2_receipt_values(self): + self.ensure_one() + return { + "enabled": True, + "state": self.vrp2_state, + "receipt_number": self.vrp2_receipt_number or False, + "receipt_uuid": self.vrp2_receipt_uuid or False, + "okp": self.vrp2_okp or False, + "dkp": self.config_id.vrp2_dkp or False, + "created": format_datetime(self.env, self.vrp2_created) + if self.vrp2_created else False, + "error": self.vrp2_error_message or False, + "has_pdf": bool(self.vrp2_pdf), + } + + # ------------------------------------------------------------------ + # Fiscalization + # ------------------------------------------------------------------ + + def _vrp2_fiscalize(self): + """Issue this order's VRP2 receipt on its till's own cash register. + + - an invoiced order → "Úhrada faktúry" (``/v5/receipt/create/invoice``, + no item breakdown: the invoice already carries the VAT); + - otherwise → an itemized sale receipt (``/v5/receipt/create/valid``), + where refund lines become ``REFUND`` items that reference the + original order's receipt — that is VRP2's storno of a sale. + + A VRP2 error raises so the transaction rolls back and the order is not + left half-fiscalized; the automatic path catches it (see + ``_vrp2_fiscalize_or_record_error``). + """ + self.ensure_one() + # Serialize concurrent attempts (automatic + a manager's click, two + # workers): lock the order and re-read its state, so only one of them + # can reach VRP2. NOWAIT: the loser fails at once instead of queueing + # behind a network call and then sending a second receipt. + self.env.cr.execute( + "SELECT id FROM pos_order WHERE id = %s FOR UPDATE NOWAIT", + (self.id,), + ) + self.invalidate_recordset(["vrp2_state"]) + till = self.config_id + if not till.vrp2_enabled: + raise UserError( + _("VRP2 is not enabled for Point of Sale '%s'.") + % till.display_name + ) + if not till.vrp2_login: + raise UserError( + _("VRP2 credentials are not configured on till '%s'.") + % till.display_name + ) + if self.vrp2_state == "fiscalized": + raise UserError( + _("Order %s is already fiscalized in VRP2.") % self.name + ) + if self.state not in _SETTLED_STATES: + raise UserError( + _("Order %s must be paid before it can be fiscalized.") + % self.name + ) + + client = self.env["vrp2.client"] + client._ensure_session(till) + + if self.account_move: + request_body = self._vrp2_build_invoice_dto() + self.vrp2_request_json = json.dumps( + request_body, ensure_ascii=False, indent=2 + ) + response = client._create_invoice(till, request_body) + else: + request_body = self._vrp2_build_receipt_dto() + self.vrp2_request_json = json.dumps( + request_body, ensure_ascii=False, indent=2 + ) + response = client._create_receipt_valid(till, request_body) + self._vrp2_apply_response(response) + _logger.info( + "VRP2 POS receipt %s created for order %s", + self.vrp2_receipt_number, + self.name, + ) + return True + + # ------------------------------------------------------------------ + # Item lines + # ------------------------------------------------------------------ + + def _vrp2_fiscal_lines(self): + """The order lines that become receipt items, in receipt order.""" + self.ensure_one() + return self.lines.filtered(lambda line: line.qty).sorted("id") + + def _vrp2_line_figures(self, line): + """Unit price, per-unit discount and VAT rate as VRP2 expects them. + + From the web app's ``parseToAPI`` (app.js): ``priceWithVat`` is the + UNIT price including VAT, ``discount`` is an ABSOLUTE per-unit amount + (a percentage discount is converted with ``zaokruhli4``) and VRP2 + computes the line total itself as ``zaokruhli2((unit − discount) × + quantity)``. Both stay positive here; the item type carries the sign. + """ + taxes = line.tax_ids_after_fiscal_position + vat_taxes = taxes.filtered(lambda t: t.amount_type == "percent") + if len(vat_taxes) > 1 or (taxes - vat_taxes): + raise UserError(_( + "Order line '%(line)s' carries taxes VRP2 cannot express: a " + "receipt item has exactly one percentage VAT rate.", + line=line.full_product_name or line.product_id.display_name, + )) + rate = vrp2_round4((vat_taxes.amount or 0.0) / 100.0) + allowed = { + vrp2_round4(entry.get("vatRate") or 0.0) + for entry in line.order_id.company_id._vrp2_vatlist() + } + if allowed and rate not in allowed: + raise UserError(_( + "Order line '%(line)s' has VAT %(rate)s %%, which is not a " + "VRP2 VAT rate (%(allowed)s).", + line=line.full_product_name or line.product_id.display_name, + rate=rate * 100, + allowed=", ".join( + "%g %%" % (r * 100) for r in sorted(allowed) + ), + )) + if vat_taxes: + unit = vat_taxes.compute_all( + line.price_unit, + currency=line.order_id.currency_id, + quantity=1.0, + product=line.product_id, + )["total_included"] + else: + unit = line.price_unit + unit = vrp2_round4(unit) + discount = vrp2_round4((line.discount or 0.0) / 100.0 * unit) + quantity = abs(line.qty) + total = vrp2_round2((unit - discount) * quantity) + return { + "unit": unit, + "discount": discount, + "quantity": quantity, + "rate": rate, + "total": total, + } + + def _vrp2_item(self, line): + """One receipt item plus its signed line total.""" + figures = self._vrp2_line_figures(line) + if line.qty > 0: + code = line.product_id.product_tmpl_id.vrp2_code + if not code: + raise UserError(_( + "Product '%(product)s' is not in this till's VRP2 " + "catalogue (it has no VRP2 service code). Push the " + "catalogue to VRP2 first.", + product=line.product_id.display_name, + )) + # Sale capture 2026-10-06: a POSITIVE item names its service by + # code only; VRP2 prints the catalogue name. + item = { + "priceWithVat": figures["unit"], + "discount": figures["discount"], + "quantity": figures["quantity"], + "type": "POSITIVE", + "serviceCode": code, + "vatRate": figures["rate"], + } + return item, figures["total"] + + original = line.refunded_orderline_id + template = line.product_id.product_tmpl_id + if not original and template.vrp2_returnable_packaging: + # "vrátené obaly": the web app's parseToAPI keeps the serviceCode + # for every type but REFUND/CORRECTION and negates the unit price + # for every type but POSITIVE. + if not template.vrp2_code: + raise UserError(_( + "Packaging '%(product)s' is not in this till's VRP2 " + "catalogue (it has no VRP2 service code). Push the " + "catalogue to VRP2 first.", + product=line.product_id.display_name, + )) + item = { + "priceWithVat": -figures["unit"], + "discount": figures["discount"], + "quantity": figures["quantity"], + "type": "NEGATIVE", + "serviceCode": template.vrp2_code, + "vatRate": figures["rate"], + } + return item, -figures["total"] + if not original: + raise UserError(_( + "Line '%(line)s' returns goods without referencing the order " + "they were sold on. VRP2 accepts a return only against the " + "original receipt: refund it from that order in the POS. " + "(Returned deposit packaging is different: mark the product " + "'VRP2 Returnable Packaging'.)", + line=line.full_product_name or line.product_id.display_name, + )) + original_order = original.order_id + if ( + original_order.vrp2_state != "fiscalized" + or not original_order.vrp2_receipt_uuid + ): + raise UserError(_( + "Order %(order)s, which this refund returns goods from, has " + "no VRP2 receipt to reference. Fiscalize it first.", + order=original_order.name, + )) + if original_order.account_move: + raise UserError(_( + "Order %(order)s was invoiced, so VRP2 holds an invoice " + "payment for it, not a sale. Cancel that payment receipt " + "rather than returning items against it.", + order=original_order.name, + )) + # Storno capture 2026-10-06: a REFUND item carries no serviceCode but + # the name VRP2 printed on the original receipt, verbatim (trailing + # space included), and the original receiptId. + item = { + "priceWithVat": -figures["unit"], + "discount": figures["discount"], + "quantity": figures["quantity"], + "type": "REFUND", + "vatRate": figures["rate"], + "referenceReceiptId": original_order.vrp2_receipt_uuid, + "receiptItemName": original.vrp2_item_name + or original.product_id.product_tmpl_id.name, + } + return item, -figures["total"] + + # ------------------------------------------------------------------ + # Payments + # ------------------------------------------------------------------ + + def _vrp2_payment_split(self): + """(non-cash payments by VRP2 type, has_cash) of this order. + + Change handed back (``is_change``) belongs to the cash payment and + is netted into it, so it is not a payment of its own here. + """ + self.ensure_one() + non_cash = {} + has_cash = False + for payment in self.payment_ids: + vrp2_type = payment.payment_method_id.vrp2_payment_type + if not vrp2_type: + raise UserError(_( + "Payment method '%(method)s' has no VRP2 payment type. A " + "sale settled on a customer account is not a cash-" + "register sale; set the type on the payment method if it " + "is one.", + method=payment.payment_method_id.display_name, + )) + if vrp2_type == "CASH": + has_cash = True + continue + non_cash[vrp2_type] = non_cash.get(vrp2_type, 0.0) + payment.amount + return non_cash, has_cash + + @staticmethod + def _vrp2_payment(vrp2_type, amount, currency): + amount = vrp2_round2(amount) + return { + "type": vrp2_type, + "sum": amount, + "currency": currency, + "exchangeRate": None, + "amount": amount, + } + + def _vrp2_build_receipt_dto(self): + """Build the body for POST /v5/receipt/create/valid. + + Verified against the 2026-10-06 sale + storno capture and the web + app's receipt builder: + + - ``vatPayer`` / ``version`` come from the register's dashboard; + - items: see ``_vrp2_item``; + - only the CASH share is rounded to 0.05 €: ``rounding = + zaokruhli5(total − non-cash) − (total − non-cash)``, the top-level + ``priceWithVat`` is ``total + rounding`` and VRP2 derives the printed + ZAOKRÚHLENIE from it (the request carries no ``roundingAmount``); + - a negative total is settled by one ``EXPENSE`` payment, and a + receipt containing refund items is never rounded + (``VYPNUTIE_ZAOKRUHLOVANIA_ZAPOR``) — hence the storno's −18.48 + against the sale's 18.50. + + The receipt total is computed the way VRP2 computes it, not taken + from Odoo: they can differ by a cent of tax rounding, and a non-cash + payment is then aligned to the receipt so that it balances. + """ + self.ensure_one() + till = self.config_id + currency = self.currency_id.name or "EUR" + header = till._vrp2_valid_receipt_header() + + items = [] + total = 0.0 + # The web app never rounds a negative receipt that holds a NEGATIVE, + # REFUND or CORRECTION item (nezaokruhliZaporne). + has_negative_item = False + lines = self._vrp2_fiscal_lines() + if not lines: + raise UserError(_("Order %s has no lines to fiscalize.") % self.name) + for line in lines: + item, line_total = self._vrp2_item(line) + items.append(item) + total += line_total + has_negative_item = has_negative_item or item["type"] in ( + "NEGATIVE", "REFUND", "CORRECTION", + ) + total = vrp2_round2(total) + + use_rounding = bool(till.vrp2_round_5c) + if total < 0 and has_negative_item: + use_rounding = False + + if total < 0: + price_with_vat = total + payments = [self._vrp2_payment("EXPENSE", total, currency)] + else: + non_cash, has_cash = self._vrp2_payment_split() + non_cash_total = vrp2_round2(sum(non_cash.values())) + if has_cash: + cash_due = vrp2_round2(total - non_cash_total) + if cash_due < 0: + raise UserError(_( + "Order %(order)s was paid more by non-cash means " + "(%(non_cash)s) than it costs (%(total)s) and also in " + "cash; that cannot be put on a receipt.", + order=self.name, non_cash=non_cash_total, total=total, + )) + cash = vrp2_round5(cash_due) if use_rounding else cash_due + price_with_vat = vrp2_round2(total + cash - cash_due) + else: + cash = 0.0 + price_with_vat = total + payments = [] + gap = vrp2_round2(price_with_vat - cash - non_cash_total) + if gap and non_cash: + if abs(gap) > 0.05: + raise UserError(_( + "Order %(order)s was paid %(paid)s but its VRP2 " + "receipt totals %(total)s; the difference is too " + "large to be tax rounding.", + order=self.name, + paid=non_cash_total + cash, + total=price_with_vat, + )) + largest = max(non_cash, key=lambda k: abs(non_cash[k])) + non_cash[largest] += gap + for vrp2_type, amount in non_cash.items(): + if vrp2_round2(amount): + payments.append( + self._vrp2_payment(vrp2_type, amount, currency) + ) + if cash: + payments.append(self._vrp2_payment("CASH", cash, currency)) + + return _js_numbers({ + "vatPayer": header["vatPayer"], + "items": items, + "payments": payments, + "priceWithVat": price_with_vat, + "version": header["version"], + "useRounding": use_rounding, + }) + + def _vrp2_build_invoice_dto(self): + """Build the body for POST /v5/receipt/create/invoice. + + An invoiced POS order is an invoice paid at the till, so it is + fiscalized as "Úhrada faktúry", exactly like + ``l10n_sk_vrp2_account`` does for a back-office invoice (verified + 2026-06-18): exact ``priceWithVat``, CASH rounded to 0.05 €, the + difference in ``roundingAmount``, ``version`` = the clock. + """ + self.ensure_one() + if self.amount_total < 0: + raise UserError(_( + "Order %s is an invoiced refund. A credit note is not " + "fiscalized as an invoice payment; cancel the original " + "payment receipt instead.", + ) % self.name) + till = self.config_id + currency = self.currency_id.name or "EUR" + total = vrp2_round2(self.amount_total) + use_rounding = bool(till.vrp2_round_5c) + non_cash, has_cash = self._vrp2_payment_split() + non_cash_total = vrp2_round2(sum(non_cash.values())) + payments = [ + self._vrp2_payment(vrp2_type, amount, currency) + for vrp2_type, amount in non_cash.items() + if vrp2_round2(amount) + ] + paid = non_cash_total + if has_cash: + cash_due = vrp2_round2(total - non_cash_total) + if cash_due < 0: + raise UserError(_( + "Order %(order)s was paid more by non-cash means than it " + "costs and also in cash; that cannot be put on a receipt.", + order=self.name, + )) + cash = vrp2_round5(cash_due) if use_rounding else cash_due + if cash: + payments.append(self._vrp2_payment("CASH", cash, currency)) + paid += cash + return _js_numbers({ + "version": int(time.time() * 1000), + "priceWithVat": total, + "payments": payments, + "invoiceNumber": self.account_move.name or "", + "roundingAmount": vrp2_round2(paid - total), + "useRounding": use_rounding, + }) + + # ------------------------------------------------------------------ + # Response + # ------------------------------------------------------------------ + + @staticmethod + def _vrp2_decode_receipt_data(data_b64): + """Decode dataBase64 into the receipt dto, or {} when unreadable.""" + if not data_b64: + return {} + try: + decoded = json.loads(base64.b64decode(data_b64)) + except (ValueError, TypeError): + return {} + return (decoded or {}).get("dto") or {} + + def _vrp2_apply_response(self, response): + """Store the VRP2 create response on the order. + + Response mapping follows the verified captures: ``receiptNumber`` / + ``receiptId`` / ``id`` / ``okp``, the official ``pdfBase64`` and the + ``dataBase64`` receipt dump. The item names VRP2 printed are copied + from that dump onto the order lines, because a later refund must + repeat them verbatim. + """ + self.ensure_one() + data = response or {} + + number = data.get("receiptNumber") + receipt_uuid = data.get("receiptId") + server_id = data.get("id") + + created = fields.Datetime.now() + created_ms = data.get("createDate") or data.get("issueDate") + if created_ms: + created = fields.Datetime.to_string( + datetime.fromtimestamp(created_ms / 1000.0, tz=timezone.utc) + ) + + vals = { + "vrp2_state": "fiscalized", + "vrp2_receipt_number": str(number) if number not in (None, "") else ( + receipt_uuid or False + ), + "vrp2_server_id": str(server_id) if server_id else False, + "vrp2_receipt_uuid": receipt_uuid or False, + "vrp2_okp": data.get("okp"), + "vrp2_qr_content": data.get("dataBase64"), + "vrp2_created": created, + "vrp2_error_message": False, + } + + pdf_b64 = data.get("pdfBase64") + if pdf_b64: + vals["vrp2_pdf"] = pdf_b64 + vals["vrp2_pdf_filename"] = "VRP2-%s.pdf" % ( + number or receipt_uuid or self.id + ) + + slim = {k: v for k, v in data.items() if k not in ("pdfBase64",)} + vals["vrp2_response_json"] = json.dumps(slim, ensure_ascii=False, indent=2) + + self.write(vals) + if not self.account_move: + self._vrp2_store_item_names(data.get("dataBase64")) + + def _vrp2_store_item_names(self, data_b64): + """Copy each item's printed name onto the order line it came from. + + VRP2 returns the items in request order, so they pair with + ``_vrp2_fiscal_lines`` by position. A catalogue item is named by + ``service.name``, a refund item by ``itemName``. + """ + items = self._vrp2_decode_receipt_data(data_b64).get("items") or [] + lines = self._vrp2_fiscal_lines() + if len(items) != len(lines): + if items: + _logger.warning( + "VRP2 receipt %s has %d items for %d lines of order %s; " + "item names not stored.", + self.vrp2_receipt_number, len(items), len(lines), self.name, + ) + return + for line, item in zip(lines, items): + name = (item.get("service") or {}).get("name") or item.get( + "itemName" + ) + if name: + line.vrp2_item_name = name + + +class PosOrderLine(models.Model): + _inherit = "pos.order.line" + + vrp2_item_name = fields.Char( + string="VRP2 Item Name", + readonly=True, + copy=False, + help="The name VRP2 printed for this line on the fiscal receipt. A " + "refund of the line must repeat it verbatim.", + ) diff --git a/pos_vrp2/models/pos_payment_method.py b/pos_vrp2/models/pos_payment_method.py new file mode 100644 index 00000000..d4ac166b --- /dev/null +++ b/pos_vrp2/models/pos_payment_method.py @@ -0,0 +1,39 @@ +from odoo import api, fields, models + +# The payment types the VRP2 web app offers (optionsTypPlatidla, app.js). +# KVERKOM (QR payment) is sent as OTHER by the app itself; EXPENSE is not a +# tender but the refund line of a negative receipt, so neither is listed. +VRP2_PAYMENT_TYPES = [ + ("CASH", "Hotovosť"), + ("CARD", "Platobná karta"), + ("VOUCHER", "Poukážky"), + ("GASTRO", "Stravné poukážky"), + ("OTHER", "Iné"), +] + + +class PosPaymentMethod(models.Model): + _inherit = "pos.payment.method" + + vrp2_payment_type = fields.Selection( + VRP2_PAYMENT_TYPES, + string="VRP2 Payment Type", + compute="_compute_vrp2_payment_type", + store=True, + readonly=False, + help="How payments by this method are reported on the VRP2 receipt. " + "Defaults from the journal: cash → Hotovosť, bank → Platobná karta. " + "Empty means the method is not a cash-register tender (a customer " + "account), and an order paid by it cannot be fiscalized.", + ) + + @api.depends("journal_id.type") + def _compute_vrp2_payment_type(self): + for method in self: + journal_type = method.journal_id.type + if journal_type == "cash": + method.vrp2_payment_type = "CASH" + elif journal_type == "bank": + method.vrp2_payment_type = "CARD" + else: + method.vrp2_payment_type = False diff --git a/pos_vrp2/models/product.py b/pos_vrp2/models/product.py new file mode 100644 index 00000000..3a8d62ca --- /dev/null +++ b/pos_vrp2/models/product.py @@ -0,0 +1,347 @@ +import logging + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + +VRP2_SPECIAL_REGULATIONS = [ + ("TAX_EXEMPTION", "Oslobodené od dane"), + ("TAX_OBLIGATION_TRANSFER", "Prenesenie daňovej povinnosti"), + ("TRAVEL_AGENCIES", "Cestovné kancelárie"), + ("ART_WORKS", "Umelecké diela"), + ("USED_GOODS", "Použitý tovar"), + ("COLLECTIVE_SUBJECTS_AND_ANTIQUES", "Zberateľské predmety a starožitnosti"), +] + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + vrp2_code = fields.Char( + string="VRP2 Service Code", + readonly=True, + copy=False, + help="Internal code assigned by VRP2 when the product is created.", + ) + vrp2_special_regulation = fields.Selection( + VRP2_SPECIAL_REGULATIONS, + string="VRP2 Special Regulation", + help="Verbal information required on receipt for 0%% VAT items.", + ) + vrp2_returnable_packaging = fields.Boolean( + string="VRP2 Returnable Packaging", + help="A deposit-bearing package (zálohovaný obal). Taking it back is " + "a negative quantity WITHOUT a sale to refer to, which VRP2 records " + "as 'vrátené obaly' (item type NEGATIVE) rather than as a return of " + "goods. Push it to the VRP2 catalogue like any other product.", + ) + vrp2_favourite = fields.Boolean( + string="VRP2 Favourite", + help="Show in VRP2 favourites quick-access.", + ) + vrp2_note = fields.Text( + string="VRP2 Note", + help="Internal note for VRP2 (max 1000 chars, not printed).", + ) + vrp2_synced = fields.Boolean( + string="Synced to VRP2", + readonly=True, + copy=False, + help="Whether this product exists on the VRP2 server.", + ) + + # ------------------------------------------------------------------ + # Pull: VRP2 → Odoo + # ------------------------------------------------------------------ + + @api.model + def _vrp2_pull(self, register): + """Pull all products from VRP2 and upsert onto product.template. + + Returns the count of products synced. + """ + client = self.env["vrp2.client"] + company = register.company_id + resp = client._get_service_list(register) + services = resp.get("results", []) + self._vrp2_warn_if_truncated(resp, "service") + + cat_map = {} + for cat in self.env["pos.category"].search([("vrp2_id", "!=", False)]): + cat_map[cat.vrp2_id] = cat.id + + vat_id_map = company._vrp2_vat_id_to_tax() + + synced = 0 + for svc in services: + code = svc.get("code") + if not code: + continue + + existing = self.search([("vrp2_code", "=", code)], limit=1) + + vat_data = svc.get("vat", {}) + vat_id = vat_data.get("id") + tax_id = vat_id_map.get(vat_id) + + pos_categ_id = False + cat_data = svc.get("category") + if cat_data and cat_data.get("id"): + pos_categ_id = cat_map.get(cat_data["id"]) + + # VRP2 stores gross (priceWithVat); Odoo's list_price is net. + gross = svc.get("priceWithVat") or 0.0 + tax = self.env["account.tax"].browse(tax_id) if tax_id else None + net = gross + if tax and gross and tax.amount_type == "percent": + net = gross / (1.0 + (tax.amount or 0.0) / 100.0) + + vals = { + "name": svc.get("name", ""), + "vrp2_code": code, + "list_price": round(net, 2), + "default_code": svc.get("codeCustom") or False, + "vrp2_special_regulation": svc.get("specialRegulation") or False, + "vrp2_favourite": bool(svc.get("favourite")), + "vrp2_synced": True, + "available_in_pos": True, + "sale_ok": True, + "type": "service", + } + + if tax_id: + vals["taxes_id"] = [(6, 0, [tax_id])] + if pos_categ_id: + vals["pos_categ_ids"] = [(4, pos_categ_id)] + + ean = svc.get("ean") or False + + if existing: + existing.write(vals) + # Barcode lives on product.product + if ean and existing.product_variant_ids: + existing.product_variant_ids[0].barcode = ean + else: + tmpl = self.create(vals) + if ean and tmpl.product_variant_ids: + tmpl.product_variant_ids[0].barcode = ean + synced += 1 + + _logger.info( + "VRP2 product pull for %s: %d products", + company.display_name, + synced, + ) + return synced + + # ------------------------------------------------------------------ + # Push: Odoo → VRP2 (per-product API calls) + # ------------------------------------------------------------------ + + @api.model + def _vrp2_push(self, register): + """Push all POS-available products to VRP2. + + Intended products = available_in_pos + has a tax mapped in VRP2. + Products in VRP2 but no longer intended get deactivated. + + Returns (created, updated, deactivated) counts. + """ + client = self.env["vrp2.client"] + company = register.company_id + tax_reverse = company._vrp2_tax_to_vat_id() + + # 1. Intended products: available_in_pos + has a mapped tax + all_pos = self.search([ + ("available_in_pos", "=", True), + ("sale_ok", "=", True), + ]) + intended = all_pos.filtered( + lambda p: any( + tax_reverse.get(t.id) is not None for t in p.taxes_id + ) + ) + + # 2. Current VRP2 codes + resp = client._get_service_list(register) + remote_products = resp.get("results", []) + self._vrp2_warn_if_truncated(resp, "service") + remote_codes = {p["code"] for p in remote_products if p.get("code")} + + # 3. Push intended products + intended_codes = set() + created = 0 + updated = 0 + + for product in intended: + vat_id = None + matched_tax = None + for tax in product.taxes_id: + vid = tax_reverse.get(tax.id) + if vid is not None: + vat_id = vid + matched_tax = tax + break + if vat_id is None: + continue + + if product.vrp2_code: + # Update + dto = product._to_vrp2_update_dto(vat_id, matched_tax) + client._set_service_data(register, {"dto": dto}) + product.vrp2_synced = True + intended_codes.add(product.vrp2_code) + updated += 1 + else: + # Create + dto = product._to_vrp2_create_dto(vat_id, matched_tax) + resp = client._add_service(register, {"dto": dto}) + if resp and resp.get("code"): + product.vrp2_code = resp["code"] + product.vrp2_synced = True + intended_codes.add(resp["code"]) + created += 1 + + # 4. Deactivate VRP2 products no longer in intended set + deactivated = 0 + codes_to_deactivate = remote_codes - intended_codes + + if codes_to_deactivate: + # We need vatId and priceWithVat for deactivation — get from remote data + remote_by_code = {p["code"]: p for p in remote_products} + for code in codes_to_deactivate: + rp = remote_by_code.get(code, {}) + vat_id = rp.get("vat", {}).get("id") + price = rp.get("priceWithVat", 0) + if not vat_id: + continue + dto = { + "code": code, + "vatId": vat_id, + "priceWithVat": price, + "invalid": True, + } + try: + client._set_service_data(register, {"dto": dto}) + deactivated += 1 + # Clear vrp2_synced on local record if it exists + local = self.search([("vrp2_code", "=", code)], limit=1) + if local: + local.vrp2_synced = False + except Exception: + _logger.warning( + "VRP2 failed to deactivate product code=%s", code, + exc_info=True, + ) + + _logger.info( + "VRP2 product push for %s: %d created, %d updated, %d deactivated", + company.display_name, + created, + updated, + deactivated, + ) + return created, updated, deactivated + + # ------------------------------------------------------------------ + # DTO builders + # ------------------------------------------------------------------ + + def _to_vrp2_create_dto(self, vat_id, tax): + """Build dto for POST /v1/service/addservice.""" + self.ensure_one() + price_with_vat = self._compute_price_with_vat(tax) + dto = { + "name": self.name, + "priceWithVat": price_with_vat, + "vatId": vat_id, + "favourite": self.vrp2_favourite, + } + barcode = self._vrp2_barcode() + if barcode: + dto["ean"] = barcode + if self.default_code: + dto["codeCustom"] = self.default_code + cat_id = self._resolve_vrp2_category_id() + if cat_id: + dto["categoryId"] = cat_id + if self.vrp2_note: + dto["note"] = self.vrp2_note[:1000] + return dto + + def _to_vrp2_update_dto(self, vat_id, tax): + """Build dto for POST /v1/service/setservicedata.""" + self.ensure_one() + price_with_vat = self._compute_price_with_vat(tax) + dto = { + "code": self.vrp2_code, + "priceWithVat": price_with_vat, + "vatId": vat_id, + "favourite": self.vrp2_favourite, + "invalid": False, + } + barcode = self._vrp2_barcode() + if barcode: + dto["ean"] = barcode + if self.default_code: + dto["codeCustom"] = self.default_code + cat_id = self._resolve_vrp2_category_id() + if cat_id: + dto["categoryId"] = cat_id + if self.vrp2_note: + dto["note"] = self.vrp2_note[:1000] + return dto + + def _compute_price_with_vat(self, tax): + """Compute gross price (with VAT) from Odoo's net list_price. + + Odoo stores list_price WITHOUT tax; VRP2 expects priceWithVat. We use + the tax engine (``compute_all``) so price-included taxes, fixed + amounts and Odoo rounding all behave consistently — not a naive + percentage multiplication. + """ + self.ensure_one() + net = self.list_price or 0.0 + if not net: + return None + result = tax.compute_all( + net, currency=self.currency_id, quantity=1.0, product=self + ) + return round(result["total_included"], 2) + + @api.model + def _vrp2_warn_if_truncated(self, resp, what): + """Log a warning if VRP2 returned fewer rows than the total count. + + Pagination of the service list is not implemented; surface it loudly + rather than silently syncing a partial catalog. + """ + total = resp.get("resultTotalCount") + count = resp.get("resultCount") + results = resp.get("results") or [] + n = count if count is not None else len(results) + if total is not None and n < total: + _logger.warning( + "VRP2 %s list truncated: received %s of %s rows; " + "pagination is not implemented.", + what, n, total, + ) + + def _vrp2_barcode(self): + """Get barcode from first product variant (barcode is on product.product).""" + self.ensure_one() + for variant in self.product_variant_ids: + if variant.barcode: + return variant.barcode + return None + + def _resolve_vrp2_category_id(self): + """Walk up to the top-level pos.category and return its vrp2_id.""" + for categ in self.pos_categ_ids: + root = categ + while root.parent_id: + root = root.parent_id + if root.vrp2_id: + return root.vrp2_id + return None diff --git a/pos_vrp2/models/res_company.py b/pos_vrp2/models/res_company.py new file mode 100644 index 00000000..05c41789 --- /dev/null +++ b/pos_vrp2/models/res_company.py @@ -0,0 +1,105 @@ +import json + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + # ------------------------------------------------------------------ + # Tax mapping: VRP2 vatId <-> Odoo tax + # + # Only POS needs this: the catalog sync pushes/pulls per-product VAT and + # POS sale receipts itemize VAT. Invoice-payment receipts carry no VAT + # breakdown, so l10n_sk_vrp2_account does not use any of this. + # ------------------------------------------------------------------ + + vrp2_tax_0 = fields.Many2one( + "account.tax", + string="VRP2 Tax 0%", + domain="[('type_tax_use', '=', 'sale'), ('company_id', '=', id)]", + ) + vrp2_tax_5 = fields.Many2one( + "account.tax", + string="VRP2 Tax 5%", + domain="[('type_tax_use', '=', 'sale'), ('company_id', '=', id)]", + ) + vrp2_tax_10 = fields.Many2one( + "account.tax", + string="VRP2 Tax 10%", + domain="[('type_tax_use', '=', 'sale'), ('company_id', '=', id)]", + ) + vrp2_tax_19 = fields.Many2one( + "account.tax", + string="VRP2 Tax 19%", + domain="[('type_tax_use', '=', 'sale'), ('company_id', '=', id)]", + ) + vrp2_tax_20 = fields.Many2one( + "account.tax", + string="VRP2 Tax 20%", + domain="[('type_tax_use', '=', 'sale'), ('company_id', '=', id)]", + ) + vrp2_tax_23 = fields.Many2one( + "account.tax", + string="VRP2 Tax 23%", + domain="[('type_tax_use', '=', 'sale'), ('company_id', '=', id)]", + ) + + vrp2_vatlist_json = fields.Text( + string="VRP2 VAT List (cached)", readonly=True, copy=False + ) + + # ------------------------------------------------------------------ + # VAT mapping helpers + # ------------------------------------------------------------------ + + def _vrp2_vatlist(self): + self.ensure_one() + if not self.vrp2_vatlist_json: + return [] + try: + return json.loads(self.vrp2_vatlist_json) + except (json.JSONDecodeError, TypeError): + return [] + + def _vrp2_vat_rate_to_tax_field(self): + """Return {vat_rate_pct_int: account.tax record}.""" + self.ensure_one() + result = {} + for pct in (0, 5, 10, 19, 20, 23): + tax = getattr(self, f"vrp2_tax_{pct}", None) + if tax: + result[pct] = tax + return result + + def _vrp2_vat_id_to_tax(self): + """Return {vrp2_vat_id: odoo_tax_id} for pulling from VRP2.""" + self.ensure_one() + rate_to_tax = self._vrp2_vat_rate_to_tax_field() + result = {} + for entry in self._vrp2_vatlist(): + vat_id = entry.get("id") + rate_pct = int(round(entry.get("vatRate", 0) * 100)) + tax = rate_to_tax.get(rate_pct) + if vat_id and tax: + result[vat_id] = tax.id + return result + + def _vrp2_tax_to_vat_id(self): + """Return {odoo_tax_id: vrp2_vat_id} for pushing to VRP2.""" + self.ensure_one() + rate_to_tax = self._vrp2_vat_rate_to_tax_field() + tax_id_to_rate = {tax.id: pct for pct, tax in rate_to_tax.items()} + + rate_to_vat_id = {} + for entry in self._vrp2_vatlist(): + rate_pct = int(round(entry.get("vatRate", 0) * 100)) + if rate_pct not in rate_to_vat_id: + rate_to_vat_id[rate_pct] = entry["id"] + + result = {} + for tax_id, rate_pct in tax_id_to_rate.items(): + vat_id = rate_to_vat_id.get(rate_pct) + if vat_id is not None: + result[tax_id] = vat_id + return result diff --git a/pos_vrp2/models/res_config_settings.py b/pos_vrp2/models/res_config_settings.py new file mode 100644 index 00000000..9bc6b2fd --- /dev/null +++ b/pos_vrp2/models/res_config_settings.py @@ -0,0 +1,25 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + # POS-only, company-level VAT mapping (VRP2 VAT rate -> Odoo sales tax). + # Per-till credentials, login and catalog sync now live on pos.config; the + # VAT list is cached when a till logs in. Invoice-payment credentials (the + # "company default register") are configured in the Accounting settings. + + vrp2_tax_0 = fields.Many2one(related="company_id.vrp2_tax_0", readonly=False) + vrp2_tax_5 = fields.Many2one(related="company_id.vrp2_tax_5", readonly=False) + vrp2_tax_10 = fields.Many2one( + related="company_id.vrp2_tax_10", readonly=False + ) + vrp2_tax_19 = fields.Many2one( + related="company_id.vrp2_tax_19", readonly=False + ) + vrp2_tax_20 = fields.Many2one( + related="company_id.vrp2_tax_20", readonly=False + ) + vrp2_tax_23 = fields.Many2one( + related="company_id.vrp2_tax_23", readonly=False + ) diff --git a/pos_vrp2/static/src/js/vrp2_load_credentials.js b/pos_vrp2/static/src/js/vrp2_load_credentials.js new file mode 100644 index 00000000..dcfbc935 --- /dev/null +++ b/pos_vrp2/static/src/js/vrp2_load_credentials.js @@ -0,0 +1,91 @@ +import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { useService } from "@web/core/utils/hooks"; +import { standardWidgetProps } from "@web/views/widgets/standard_widget_props"; +import { _t } from "@web/core/l10n/translation"; + +const viewWidgetRegistry = registry.category("view_widgets"); + +/** + * Fill this till's VRP2 login/password from the company default register. + * Updates the record in memory (no save, no reload) so the quick-config + * dialog stays open and the fields populate immediately. + */ +export class Vrp2LoadCredentials extends Component { + static template = "pos_vrp2.Vrp2LoadCredentials"; + static props = { ...standardWidgetProps }; + + setup() { + this.orm = useService("orm"); + this.notification = useService("notification"); + } + + async onClick() { + const record = this.props.record; + const companyId = record.data.company_id && record.data.company_id.id; + const creds = await this.orm.call( + "pos.config", + "vrp2_company_credentials", + [companyId || false] + ); + await record.update({ + vrp2_login: creds.vrp2_login, + vrp2_password: creds.vrp2_password, + }); + this.notification.add( + _t("Loaded credentials from the company. Click Login to connect this till."), + { type: "success" } + ); + } +} +viewWidgetRegistry.add("vrp2_load_credentials", { component: Vrp2LoadCredentials }); + +/** + * Base for VRP2 session actions (Login / Logout): persist pending edits, run + * the server method, then reload the record in place so the status badge and + * button visibility refresh without closing the dialog. + */ +class Vrp2SessionButton extends Component { + static props = { ...standardWidgetProps }; + + setup() { + this.orm = useService("orm"); + this.notification = useService("notification"); + } + + async _run(method, fallbackMessage) { + const record = this.props.record; + // Persist credentials so the server method sees them. + if (await record.isDirty()) { + const saved = await record.save(); + if (saved === false) { + return; + } + } + const result = await this.orm.call("pos.config", method, [[record.resId]]); + // Reload so vrp2_token / vrp2_status / dashboard refresh on the form. + await record.load(); + const params = (result && result.params) || {}; + this.notification.add(params.message || fallbackMessage, { + type: params.type || "success", + title: params.title, + sticky: !!params.sticky, + }); + } +} + +export class Vrp2LoginButton extends Vrp2SessionButton { + static template = "pos_vrp2.Vrp2LoginButton"; + onClick() { + return this._run("action_vrp2_login", _t("Logged in to VRP2.")); + } +} +viewWidgetRegistry.add("vrp2_login", { component: Vrp2LoginButton }); + +export class Vrp2LogoutButton extends Vrp2SessionButton { + static template = "pos_vrp2.Vrp2LogoutButton"; + onClick() { + return this._run("action_vrp2_logout", _t("Logged out from VRP2.")); + } +} +viewWidgetRegistry.add("vrp2_logout", { component: Vrp2LogoutButton }); diff --git a/pos_vrp2/static/src/pos/vrp2_receipt.js b/pos_vrp2/static/src/pos/vrp2_receipt.js new file mode 100644 index 00000000..31a2974f --- /dev/null +++ b/pos_vrp2/static/src/pos/vrp2_receipt.js @@ -0,0 +1,96 @@ +/** + * VRP2 fiscal receipt at the till. + * + * The server issues the VRP2 receipt after the order's sync transaction has + * committed, before that request returns. Right after validation (and before + * the automatic print) the till asks the server for the result and keeps it + * on the order's uiState, so the printed ticket carries the fiscal block + * and the receipt screen can open the official VRP2 PDF. + */ +import OrderPaymentValidation from "@point_of_sale/app/utils/order_payment_validation"; +import { OrderReceipt } from "@point_of_sale/app/screens/receipt_screen/receipt/order_receipt"; +import { ReceiptScreen } from "@point_of_sale/app/screens/receipt_screen/receipt_screen"; +import { generateQRCodeDataUrl } from "@point_of_sale/utils"; +import { patch } from "@web/core/utils/patch"; +import { _t } from "@web/core/l10n/translation"; + +/** VRP2 prints the receipt number with four digits: "č. 0019". */ +function padReceiptNumber(number) { + return number ? String(number).padStart(4, "0") : ""; +} + +export async function loadVrp2Receipt(pos, order) { + // An order that never reached the server (offline) has no numeric id + // and no receipt yet; it is fiscalized when it syncs. + if (!pos.config.vrp2_enabled || !order || typeof order.id !== "number") { + return; + } + try { + const info = await pos.data.call("pos.order", "vrp2_receipt_for_ui", [order.id]); + order.uiState.vrp2 = info; + if (info.enabled && info.state !== "fiscalized") { + pos.notification.add( + _t("The VRP2 receipt was NOT issued: %s", info.error || info.state), + { type: "danger", sticky: true } + ); + } + } catch { + pos.notification.add( + _t("Could not read the VRP2 receipt of this order from the server."), + { type: "warning", sticky: true } + ); + } +} + +patch(OrderPaymentValidation.prototype, { + async afterOrderValidation() { + // Before super: it triggers the automatic print. + await loadVrp2Receipt(this.pos, this.order); + return await super.afterOrderValidation(...arguments); + }, +}); + +patch(OrderReceipt.prototype, { + /** The fiscal block to print, or null when there is none (yet). */ + get vrp2Receipt() { + const order = this.order; + if (!order.config.vrp2_enabled) { + return null; + } + const info = order.uiState?.vrp2; + if (info) { + return info.state === "fiscalized" && info.receipt_uuid + ? { ...info, receipt_number: padReceiptNumber(info.receipt_number) } + : null; + } + // A reprint from the order history: the fields were loaded with it. + if (order.vrp2_state === "fiscalized" && order.vrp2_receipt_uuid) { + return { + receipt_number: padReceiptNumber(order.vrp2_receipt_number), + receipt_uuid: order.vrp2_receipt_uuid, + okp: order.vrp2_okp, + dkp: order.config.vrp2_dkp, + created: false, + }; + } + return null; + }, + get vrp2NotIssued() { + const info = this.order.uiState?.vrp2; + return Boolean(info && info.enabled && info.state !== "fiscalized"); + }, + /** The official receipt's QR encodes the receiptId and nothing else. */ + get vrp2QrCode() { + return generateQRCodeDataUrl(this.vrp2Receipt.receipt_uuid); + }, +}); + +patch(ReceiptScreen.prototype, { + get vrp2HasPdf() { + return Boolean(this.currentOrder?.uiState?.vrp2?.has_pdf); + }, + openVrp2Pdf() { + const order = this.currentOrder; + window.open(`/web/content/pos.order/${order.id}/vrp2_pdf/VRP2-${order.id}.pdf`, "_blank"); + }, +}); diff --git a/pos_vrp2/static/src/pos/vrp2_receipt.xml b/pos_vrp2/static/src/pos/vrp2_receipt.xml new file mode 100644 index 00000000..13f23c30 --- /dev/null +++ b/pos_vrp2/static/src/pos/vrp2_receipt.xml @@ -0,0 +1,33 @@ + + + + + + + +
    +
    POKLADNIČNÝ DOKLAD č.
    +
    Kód pokladnice:
    +
    +
    ID dokladu:
    +
    OKP:
    + QR +
    Overte doklad pomocou QR kódu
    +
    +
    + Doklad nebol zaevidovaný vo VRP2 +
    + + + + + + + + + + diff --git a/pos_vrp2/static/src/xml/vrp2_load_credentials.xml b/pos_vrp2/static/src/xml/vrp2_load_credentials.xml new file mode 100644 index 00000000..7273d89d --- /dev/null +++ b/pos_vrp2/static/src/xml/vrp2_load_credentials.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/pos_vrp2/static/tests/tours/vrp2_receipt_tour.js b/pos_vrp2/static/tests/tours/vrp2_receipt_tour.js new file mode 100644 index 00000000..9fd3c65d --- /dev/null +++ b/pos_vrp2/static/tests/tours/vrp2_receipt_tour.js @@ -0,0 +1,67 @@ +import * as Chrome from "@point_of_sale/../tests/pos/tours/utils/chrome_util"; +import * as Dialog from "@point_of_sale/../tests/generic_helpers/dialog_util"; +import * as ProductScreen from "@point_of_sale/../tests/pos/tours/utils/product_screen_util"; +import * as PaymentScreen from "@point_of_sale/../tests/pos/tours/utils/payment_screen_util"; +import * as ReceiptScreen from "@point_of_sale/../tests/pos/tours/utils/receipt_screen_util"; +import { registry } from "@web/core/registry"; + +registry.category("web_tour.tours").add("pos_vrp2_receipt_tour", { + steps: () => + [ + Chrome.startPoS(), + Dialog.confirm("Open Register"), + ProductScreen.addOrderline("Káva A 250g", "1"), + ProductScreen.addOrderline("Káva B 250g", "1"), + ProductScreen.clickPayButton(), + PaymentScreen.clickPaymentMethod("Cash"), + PaymentScreen.clickValidate(), + ReceiptScreen.isShown(), + { + content: "the ticket carries the VRP2 fiscal block", + trigger: ".pos-receipt-vrp2:contains('POKLADNIČNÝ DOKLAD č. 0019')", + }, + { + content: "with the receiptId the QR encodes", + trigger: ".pos-receipt-vrp2:contains('V-0000000000000000000000000000A019')", + }, + { + content: "and the register code", + trigger: ".pos-receipt-vrp2:contains('99920201234560002')", + }, + { + content: "the QR is drawn", + trigger: ".pos-receipt-vrp2 img.pos-receipt-vrp2-qr[src^='data:image']", + }, + { + content: "the official PDF can be opened", + trigger: ".receipt-screen button.vrp2-pdf", + }, + ReceiptScreen.clickNextOrder(), + ].flat(), +}); + +registry.category("web_tour.tours").add("pos_vrp2_receipt_failed_tour", { + steps: () => + [ + Chrome.startPoS(), + Dialog.confirm("Open Register"), + ProductScreen.addOrderline("Káva A 250g", "1"), + ProductScreen.clickPayButton(), + PaymentScreen.clickPaymentMethod("Cash"), + PaymentScreen.clickValidate(), + ReceiptScreen.isShown(), + { + content: "the cashier is told the receipt was not issued", + trigger: ".o_notification:contains('VRP2 receipt was NOT issued')", + }, + { + content: "and the ticket says so instead of faking a fiscal block", + trigger: ".pos-receipt-vrp2-missing", + }, + { + content: "no fiscal block", + trigger: "body:not(:has(.pos-receipt-vrp2))", + }, + ReceiptScreen.clickNextOrder(), + ].flat(), +}); diff --git a/pos_vrp2/tests/__init__.py b/pos_vrp2/tests/__init__.py new file mode 100644 index 00000000..881fd817 --- /dev/null +++ b/pos_vrp2/tests/__init__.py @@ -0,0 +1,2 @@ +from . import test_pos_vrp2 +from . import test_pos_vrp2_frontend diff --git a/pos_vrp2/tests/test_pos_vrp2.py b/pos_vrp2/tests/test_pos_vrp2.py new file mode 100644 index 00000000..8e31cccd --- /dev/null +++ b/pos_vrp2/tests/test_pos_vrp2.py @@ -0,0 +1,517 @@ +import base64 +import json +from contextlib import contextmanager +from unittest.mock import patch + +import psycopg2 + +from odoo.exceptions import UserError, ValidationError +from odoo.tests import Form, tagged + +from odoo.addons.l10n_sk_vrp2_base.models.vrp2_client import _compact_json +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +# The register profile of the 2026-10-06 capture (getdashboard, trimmed). +DASHBOARD = { + "returnValue": 0, + "cashRegister": { + "dkp": "99920201234560002", + "login": "1234567890", + "version": 1662177617338, + "organization": {"name": "Testovacia s.r.o.", "vatPayer": True}, + }, +} + +# GET /v1/vat/vatlist of the same capture. +VATLIST = [ + {"id": 2000, "vatRate": 0}, + {"id": 2050, "vatRate": 0.1}, + {"id": 2100, "vatRate": 0.2}, + {"id": 500, "vatRate": 0.05}, + {"id": 501, "vatRate": 0.19}, + {"id": 502, "vatRate": 0.23}, +] + +SALE_UUID = "V-0000000000000000000000000000A019" +CODE_A = "800000000000000000001" +CODE_B = "800000000000000000002" +# VRP2's catalogue name, trailing space and all: a refund must repeat it. +NAME_B = "Káva B 250g " + + +def _data_b64(items): + return base64.b64encode( + json.dumps({"returnValue": 0, "dto": {"items": items}}).encode() + ).decode() + + +# POST /v5/receipt/create/valid — the sale, byte for byte as captured. +SALE_REQUEST = { + "vatPayer": True, + "items": [ + {"priceWithVat": 8.99, "discount": 0, "quantity": 1, + "type": "POSITIVE", "serviceCode": CODE_A, "vatRate": 0.19}, + {"priceWithVat": 9.49, "discount": 0, "quantity": 1, + "type": "POSITIVE", "serviceCode": CODE_B, "vatRate": 0.19}, + ], + "payments": [{"type": "CASH", "sum": 18.5, "currency": "EUR", + "exchangeRate": None, "amount": 18.5}], + "priceWithVat": 18.5, + "version": 1662177617338, + "useRounding": True, +} +SALE_RESPONSE = { + "returnValue": 0, + "id": 1019, + "createDate": 1791285713461, + "receiptId": SALE_UUID, + "receiptNumber": 19, + "okp": "00000000-00000000-00000000-00000000-00000019", + "pdfBase64": base64.b64encode(b"%PDF-1.4 sale").decode(), + "dataBase64": _data_b64([ + {"unitPriceWithVat": 8.99, "quantity": 1, "type": "POSITIVE", + "service": {"name": "Káva A 250g"}, "itemName": None}, + {"unitPriceWithVat": 9.49, "quantity": 1, "type": "POSITIVE", + "service": {"name": NAME_B}, "itemName": None}, + ]), +} + +# The storno of that sale, byte for byte as captured: REFUND items, no +# rounding, EXPENSE for the exact item total (−18.48 against 18.50 paid). +STORNO_REQUEST = { + "vatPayer": True, + "items": [ + {"priceWithVat": -8.99, "discount": 0, "quantity": 1, + "type": "REFUND", "vatRate": 0.19, + "referenceReceiptId": SALE_UUID, + "receiptItemName": "Káva A 250g"}, + {"priceWithVat": -9.49, "discount": 0, "quantity": 1, + "type": "REFUND", "vatRate": 0.19, + "referenceReceiptId": SALE_UUID, + "receiptItemName": NAME_B}, + ], + "payments": [{"type": "EXPENSE", "sum": -18.48, "currency": "EUR", + "exchangeRate": None, "amount": -18.48}], + "priceWithVat": -18.48, + "version": 1662177617338, + "useRounding": False, +} +STORNO_RESPONSE = { + "returnValue": 0, + "id": 1020, + "createDate": 1791285827790, + "receiptId": "V-0000000000000000000000000000A020", + "receiptNumber": 20, + "okp": "00000000-00000000-00000000-00000000-00000020", +} + + +@tagged("post_install", "-at_install") +class TestPosVrp2(TestPoSCommon): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.config = cls.basic_config + cls.rounding_5c = cls.env["account.cash.rounding"].create({ + "name": "0.05 nearest", + "rounding": 0.05, + "rounding_method": "HALF-UP", + "strategy": "add_invoice_line", + "profit_account_id": cls.company_data["default_account_revenue"].id, + "loss_account_id": cls.company_data["default_account_expense"].id, + }) + cls.config.write({ + "vrp2_enabled": True, + "vrp2_auto_fiscalize": True, + "vrp2_login": "1234567890", + "vrp2_round_5c": True, + "cash_rounding": True, + "rounding_method": cls.rounding_5c.id, + "only_round_cash_method": True, + }) + cls.company.vrp2_vatlist_json = json.dumps(VATLIST) + cls.vat19 = cls.env["account.tax"].create({ + "name": "VAT 19 % incl.", + "amount": 19, + "amount_type": "percent", + "type_tax_use": "sale", + "price_include_override": "tax_included", + "company_id": cls.company.id, + }) + cls.coffee_a = cls.create_product( + "Káva A 250g", cls.categ_basic, 8.99, + tax_ids=cls.vat19.ids, + ) + cls.coffee_b = cls.create_product( + "Káva B 250g", cls.categ_basic, 9.49, + tax_ids=cls.vat19.ids, + ) + cls.coffee_a.product_tmpl_id.vrp2_code = CODE_A + cls.coffee_b.product_tmpl_id.vrp2_code = CODE_B + # A deposit bottle: outside VAT, so no tax and vatRate 0. + cls.bottle = cls.create_product("Záloha PET", cls.categ_basic, 0.15) + cls.bottle.product_tmpl_id.write({ + "vrp2_code": "800000000000000000015", + "vrp2_returnable_packaging": True, + }) + + def setUp(self): + super().setUp() + self.open_new_session() + self.calls = [] + + # ---- helpers --------------------------------------------------------- + + @contextmanager + def _vrp2(self, valid=None, invoice=None): + """Mock the network edge of vrp2.client, recording every body.""" + Client = type(self.env["vrp2.client"]) + + def recorder(kind, response): + def call(holder, body): + self.calls.append((kind, body)) + if isinstance(response, Exception): + raise response + return dict(response or {}) + return call + + with patch.object(Client, "_ensure_session", return_value="TOK"), \ + patch.object(Client, "_get_dashboard", return_value=DASHBOARD), \ + patch.object(Client, "_create_receipt_valid", + side_effect=recorder("valid", valid)), \ + patch.object(Client, "_create_invoice", + side_effect=recorder("invoice", invoice)): + yield + + def _sync(self, lines, payments=None, commit=True, **kwargs): + """Sync an order as the till does; ``commit`` then runs what the + commit of that request would trigger (the post-commit fiscalization). + """ + data = self.create_ui_order_data(lines, payments=payments, **kwargs) + result = self.env["pos.order"].sync_from_ui([data]) + order = self.env["pos.order"].browse( + [o["id"] for o in result["pos.order"]] + ).filtered(lambda o: o.uuid == data["uuid"]) + if commit: + with self.enter_registry_test_mode(): + self.env.cr.postcommit.run() + order.invalidate_recordset() + return order + + def _expected(self, body): + """The captured body, in this test company's currency.""" + body = json.loads(json.dumps(body)) + for payment in body["payments"]: + payment["currency"] = self.currency.name + return body + + def _sale(self): + # Odoo rounds the cash due exactly as VRP2 does, so the till takes + # the same 18.50 the register records. + with self._vrp2(valid=SALE_RESPONSE): + return self._sync( + [(self.coffee_a, 1), (self.coffee_b, 1)], + payments=[(self.cash_pm1, 18.50)], + ) + + # ---- the capture, replayed ------------------------------------------- + + def test_sale_matches_capture(self): + """A synced paid order is fiscalized with exactly the captured body.""" + order = self._sale() + self.assertEqual(order.state, "paid") + self.assertEqual(len(self.calls), 1) + kind, body = self.calls[0] + self.assertEqual(kind, "valid") + # Compare the BYTES that are sent (and checksummed), not just values: + # 1 and 1.0 are equal in Python but not on the wire. + self.assertEqual( + _compact_json(body), _compact_json(self._expected(SALE_REQUEST)) + ) + + self.assertEqual(order.vrp2_state, "fiscalized") + # Odoo's drawer and VRP2's receipt agree to the cent. + self.assertEqual(order.amount_paid, body["payments"][0]["amount"]) + self.assertEqual(order.vrp2_receipt_number, "19") + self.assertEqual(order.vrp2_receipt_uuid, SALE_UUID) + self.assertEqual(order.vrp2_server_id, "1019") + self.assertTrue(order.vrp2_pdf) + self.assertEqual( + order.lines.sorted("id").mapped("vrp2_item_name"), + ["Káva A 250g", NAME_B], + ) + + def test_refund_matches_storno_capture(self): + """Refunding the whole order is VRP2's storno: REFUND items against + the original receipt, the names VRP2 printed, no rounding.""" + sale = self._sale() + line_a, line_b = sale.lines.sorted("id") + self.calls.clear() + with self._vrp2(valid=STORNO_RESPONSE): + refund = self._sync( + [ + {"product": self.coffee_a, "quantity": -1, + "refunded_orderline_id": line_a.id}, + {"product": self.coffee_b, "quantity": -1, + "refunded_orderline_id": line_b.id}, + ], + payments=[(self.cash_pm1, -18.48)], + ) + self.assertEqual(len(self.calls), 1) + self.assertEqual( + _compact_json(self.calls[0][1]), + _compact_json(self._expected(STORNO_REQUEST)), + ) + self.assertEqual(refund.vrp2_state, "fiscalized") + self.assertEqual(refund.vrp2_receipt_number, "20") + + def test_nothing_reaches_vrp2_before_the_commit(self): + """A sync whose transaction never commits issues no receipt — so a + rolled-back batch cannot leave a receipt behind to be re-issued.""" + with self._vrp2(valid=SALE_RESPONSE): + order = self._sync([(self.coffee_a, 1)], + payments=[(self.cash_pm1, 8.99)], commit=False) + self.assertFalse(self.calls) + self.assertEqual(order.vrp2_state, "not_fiscalized") + with self.enter_registry_test_mode(): + self.env.cr.postcommit.run() + order.invalidate_recordset() + self.assertEqual(len(self.calls), 1) + self.assertEqual(order.vrp2_state, "fiscalized") + + def test_overpaid_by_card_with_cash_is_refused(self): + with self._vrp2(valid={}): + order = self._sync([(self.coffee_a, 1)], + payments=[(self.bank_pm1, 9.50), + (self.cash_pm1, -0.51)]) + self.assertEqual(order.vrp2_state, "error") + self.assertFalse(self.calls) + + # ---- payments and rounding ------------------------------------------ + + def test_only_the_cash_share_is_rounded(self): + with self._vrp2(valid=SALE_RESPONSE): + self._sync( + [(self.coffee_a, 1), (self.coffee_b, 1)], + payments=[(self.bank_pm1, 10.0), (self.cash_pm1, 8.50)], + ) + body = self.calls[0][1] + self.assertEqual(body["priceWithVat"], 18.5) + self.assertEqual( + [(p["type"], p["amount"]) for p in body["payments"]], + [("CARD", 10.0), ("CASH", 8.5)], + ) + + def test_card_only_is_not_rounded(self): + with self._vrp2(valid=SALE_RESPONSE): + self._sync( + [(self.coffee_a, 1), (self.coffee_b, 1)], + payments=[(self.bank_pm1, 18.48)], + ) + body = self.calls[0][1] + self.assertEqual(body["priceWithVat"], 18.48) + self.assertEqual( + [(p["type"], p["amount"]) for p in body["payments"]], + [("CARD", 18.48)], + ) + + def test_rounding_off_on_the_till(self): + self.config.write({"vrp2_round_5c": False, "cash_rounding": False}) + with self._vrp2(valid=SALE_RESPONSE): + self._sync([(self.coffee_a, 1), (self.coffee_b, 1)], + payments=[(self.cash_pm1, 18.48)]) + body = self.calls[0][1] + self.assertFalse(body["useRounding"]) + self.assertEqual(body["priceWithVat"], 18.48) + self.assertEqual(body["payments"][0]["amount"], 18.48) + + def test_quantity_and_discount_are_per_unit(self): + """priceWithVat is the unit price and discount an absolute amount + per unit (web app parseToAPI), never line totals.""" + with self._vrp2(valid={}): + self._sync([(self.coffee_a, 3, 10.0)], + payments=[(self.cash_pm1, 24.25)]) + item = self.calls[0][1]["items"][0] + self.assertEqual(item["priceWithVat"], 8.99) + self.assertEqual(item["quantity"], 3) + self.assertEqual(item["discount"], 0.899) + # VRP2's own line total: zaokruhli2((8.99 − 0.899) × 3) = 24.27, + # cash-rounded to 24.25. + self.assertEqual(self.calls[0][1]["priceWithVat"], 24.25) + + # ---- Odoo cash rounding must match the register's ------------------ + + def test_vrp2_rounding_requires_matching_odoo_rounding(self): + with self.assertRaisesRegex(ValidationError, "0.05"): + self.config.cash_rounding = False + with self.assertRaisesRegex(ValidationError, "0.05"): + self.config.rounding_method = self.env["account.cash.rounding"].create({ + "name": "0.10", "rounding": 0.10, "rounding_method": "HALF-UP", + "strategy": "add_invoice_line", + "profit_account_id": self.rounding_5c.profit_account_id.id, + "loss_account_id": self.rounding_5c.loss_account_id.id, + }) + with self.assertRaisesRegex(ValidationError, "0.05"): + self.config.only_round_cash_method = False + + def test_no_vrp2_rounding_forbids_odoo_rounding(self): + with self.assertRaisesRegex(ValidationError, "must not round"): + self.config.vrp2_round_5c = False + + def test_enabling_vrp2_sets_odoo_rounding(self): + config = self.env["pos.config"].create({"name": "Second till"}) + with Form(config) as form: + form.vrp2_enabled = True + self.assertTrue(config.cash_rounding) + self.assertTrue(config.only_round_cash_method) + self.assertEqual(config.rounding_method.rounding, 0.05) + self.assertEqual(config.rounding_method.rounding_method, "HALF-UP") + + # ---- returned packaging ("vrátené obaly") ------------------------------ + + def test_returned_packaging_is_a_negative_item(self): + """Bottles taken back with a sale: NEGATIVE items, which keep their + serviceCode (unlike REFUND); a positive total is still rounded.""" + with self._vrp2(valid={}): + order = self._sync([(self.coffee_a, 1), (self.bottle, -2)], + payments=[(self.cash_pm1, 8.70)]) + body = self.calls[0][1] + self.assertEqual(body["items"][1], { + "priceWithVat": -0.15, "discount": 0, "quantity": 2, + "type": "NEGATIVE", "serviceCode": "800000000000000000015", + "vatRate": 0, + }) + self.assertTrue(body["useRounding"]) + self.assertEqual(body["priceWithVat"], 8.7) + self.assertEqual(body["payments"][0]["amount"], 8.7) + self.assertEqual(order.vrp2_state, "fiscalized") + + def test_packaging_only_return_pays_out_unrounded(self): + with self._vrp2(valid={}): + self._sync([(self.bottle, -2)], payments=[(self.cash_pm1, -0.30)]) + body = self.calls[0][1] + self.assertFalse(body["useRounding"]) + self.assertEqual(body["priceWithVat"], -0.3) + self.assertEqual( + [(p["type"], p["amount"]) for p in body["payments"]], + [("EXPENSE", -0.3)], + ) + + def test_packaging_outside_the_catalogue_is_an_error(self): + self.bottle.product_tmpl_id.vrp2_code = False + with self._vrp2(valid={}): + order = self._sync([(self.bottle, -1)], + payments=[(self.cash_pm1, -0.15)]) + self.assertEqual(order.vrp2_state, "error") + self.assertIn("Packaging", order.vrp2_error_message) + self.assertFalse(self.calls) + + # ---- failure handling ------------------------------------------------ + + def test_vrp2_failure_never_blocks_the_sale(self): + with self._vrp2(valid=UserError("VRP2 error: boom")): + order = self._sync([(self.coffee_a, 1)], + payments=[(self.cash_pm1, 8.99)]) + self.assertEqual(order.state, "paid") + self.assertEqual(order.vrp2_state, "error") + self.assertIn("boom", order.vrp2_error_message) + # The savepoint discarded the failed attempt's request. + self.assertFalse(order.vrp2_request_json) + + # Retrying from the form succeeds and clears the error. + with self._vrp2(valid=SALE_RESPONSE): + order.action_vrp2_fiscalize() + self.assertEqual(order.vrp2_state, "fiscalized") + self.assertFalse(order.vrp2_error_message) + + def test_concurrent_fiscalization_is_not_an_error(self): + """Another transaction holding or having just fiscalized the order + must never be recorded as a VRP2 error over its receipt.""" + Order = type(self.env["pos.order"]) + with self._vrp2(valid=SALE_RESPONSE): + order = self._sync([(self.coffee_a, 1)], + payments=[(self.cash_pm1, 9.00)], commit=False) + for error in (psycopg2.errors.LockNotAvailable, + psycopg2.errors.SerializationFailure): + with patch.object(Order, "_vrp2_fiscalize", side_effect=error()): + # Background paths skip ... + order._vrp2_fiscalize_or_record_error() + self.assertEqual(order.vrp2_state, "not_fiscalized") + self.assertFalse(order.vrp2_error_message) + # ... the till's call re-raises, so Odoo replays the request + # on a fresh snapshot and the cashier sees the real result. + with self.assertRaises(error): + order.vrp2_receipt_for_ui() + + def test_fiscalized_order_is_never_refiscalized(self): + order = self._sale() + with patch.object(type(order), "_vrp2_fiscalize", + side_effect=AssertionError("must not be called")): + order._vrp2_fiscalize_or_record_error() + info = order.vrp2_receipt_for_ui() + self.assertEqual(order.vrp2_state, "fiscalized") + self.assertEqual(info["state"], "fiscalized") + self.assertEqual(info["receipt_uuid"], SALE_UUID) + self.assertEqual(info["dkp"], "99920201234560002") + + def test_pos_does_not_load_the_audit_payloads(self): + order = self._sale() + rows = self.env["pos.order"]._load_pos_data_read(order, self.config) + self.assertEqual(rows[0]["vrp2_receipt_uuid"], SALE_UUID) + for key in ("vrp2_pdf", "vrp2_qr_content", "vrp2_request_json", + "vrp2_response_json"): + self.assertNotIn(key, rows[0]) + + def test_product_outside_the_catalogue_is_an_error(self): + self.coffee_b.product_tmpl_id.vrp2_code = False + with self._vrp2(valid=SALE_RESPONSE): + order = self._sync([(self.coffee_b, 1)], + payments=[(self.cash_pm1, 9.49)]) + self.assertEqual(order.vrp2_state, "error") + self.assertIn("service code", order.vrp2_error_message) + self.assertFalse(self.calls, "nothing may reach VRP2") + + def test_unreferenced_return_is_refused(self): + with self._vrp2(valid={}): + order = self._sync([(self.coffee_a, -1)], + payments=[(self.cash_pm1, -8.99)]) + self.assertEqual(order.vrp2_state, "error") + self.assertIn("original receipt", order.vrp2_error_message) + self.assertFalse(self.calls) + + def test_customer_account_payment_is_refused(self): + self.assertFalse(self.pay_later_pm.vrp2_payment_type) + self.assertEqual(self.cash_pm1.vrp2_payment_type, "CASH") + self.assertEqual(self.bank_pm1.vrp2_payment_type, "CARD") + with self._vrp2(valid={}): + order = self._sync([(self.coffee_a, 1)], + payments=[(self.pay_later_pm, 8.99)], + customer=self.customer) + self.assertEqual(order.vrp2_state, "error") + self.assertFalse(self.calls) + + def test_auto_fiscalize_off(self): + self.config.vrp2_auto_fiscalize = False + with self._vrp2(valid=SALE_RESPONSE): + order = self._sync([(self.coffee_a, 1)], + payments=[(self.cash_pm1, 8.99)]) + self.assertEqual(order.vrp2_state, "not_fiscalized") + self.assertFalse(self.calls) + + # ---- invoiced orders ------------------------------------------------- + + def test_invoiced_order_is_an_invoice_payment(self): + with self._vrp2(invoice={"receiptId": "V-INV", "receiptNumber": 21}): + order = self._sync([(self.coffee_a, 1), (self.coffee_b, 1)], + payments=[(self.cash_pm1, 18.48)], + customer=self.customer, is_invoiced=True) + self.assertTrue(order.account_move) + self.assertEqual(len(self.calls), 1) + kind, body = self.calls[0] + self.assertEqual(kind, "invoice") + self.assertNotIn("items", body) + self.assertEqual(body["invoiceNumber"], order.account_move.name) + self.assertEqual(body["priceWithVat"], 18.48) + self.assertEqual(body["payments"][0]["amount"], 18.5) + self.assertEqual(body["roundingAmount"], 0.02) + self.assertEqual(order.vrp2_state, "fiscalized") diff --git a/pos_vrp2/tests/test_pos_vrp2_frontend.py b/pos_vrp2/tests/test_pos_vrp2_frontend.py new file mode 100644 index 00000000..7267b9c4 --- /dev/null +++ b/pos_vrp2/tests/test_pos_vrp2_frontend.py @@ -0,0 +1,97 @@ +import json +from contextlib import contextmanager +from unittest.mock import patch + +from odoo import Command +from odoo.exceptions import UserError +from odoo.tests import tagged + +from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon + +from .test_pos_vrp2 import ( + CODE_B, + CODE_A, + DASHBOARD, + SALE_RESPONSE, + VATLIST, +) + + +@tagged("post_install", "-at_install") +class TestPosVrp2Frontend(TestPointOfSaleHttpCommon): + """The till itself: what the cashier sees and prints after validating.""" + + @classmethod + def setUpClass(cls): + super().setUpClass() + company = cls.main_pos_config.company_id + company.vrp2_vatlist_json = json.dumps(VATLIST) + rounding = cls.env["account.cash.rounding"].create({ + "name": "0.05 nearest", + "rounding": 0.05, + "rounding_method": "HALF-UP", + "strategy": "add_invoice_line", + "profit_account_id": company.default_cash_difference_income_account_id.id, + "loss_account_id": company.default_cash_difference_expense_account_id.id, + }) + cls.main_pos_config.write({ + "vrp2_enabled": True, + "vrp2_login": "1234567890", + "vrp2_round_5c": True, + "cash_rounding": True, + "rounding_method": rounding.id, + "only_round_cash_method": True, + }) + cls.main_pos_config.sudo().vrp2_dkp = "99920201234560002" + vat19 = cls.env["account.tax"].create({ + "name": "VAT 19 % incl.", + "amount": 19, + "amount_type": "percent", + "type_tax_use": "sale", + "price_include_override": "tax_included", + "company_id": company.id, + }) + for name, price, code in ( + ("Káva A 250g", 8.99, CODE_A), + ("Káva B 250g", 9.49, CODE_B), + ): + cls.env["product.template"].create({ + "name": name, + "available_in_pos": True, + "list_price": price, + "taxes_id": [Command.set(vat19.ids)], + "pos_categ_ids": [Command.set(cls.pos_desk_misc_test.ids)], + "vrp2_code": code, + }) + + @contextmanager + def _vrp2(self, valid): + Client = type(self.env["vrp2.client"]) + + def create_valid(holder, body): + if isinstance(valid, Exception): + raise valid + return dict(valid) + + with patch.object(Client, "_ensure_session", return_value="TOK"), \ + patch.object(Client, "_get_dashboard", return_value=DASHBOARD), \ + patch.object(Client, "_create_receipt_valid", side_effect=create_valid): + yield + + def test_receipt_carries_the_vrp2_block(self): + with self._vrp2(SALE_RESPONSE): + self.start_pos_tour("pos_vrp2_receipt_tour") + order = self.env["pos.order"].search( + [("config_id", "=", self.main_pos_config.id)], limit=1 + ) + self.assertEqual(order.vrp2_state, "fiscalized") + self.assertEqual(order.vrp2_receipt_uuid, SALE_RESPONSE["receiptId"]) + + def test_failed_receipt_is_shown_not_hidden(self): + with self._vrp2(UserError("VRP2 error: boom")): + self.start_pos_tour("pos_vrp2_receipt_failed_tour") + order = self.env["pos.order"].search( + [("config_id", "=", self.main_pos_config.id)], limit=1 + ) + self.assertEqual(order.state, "paid") + self.assertEqual(order.vrp2_state, "error") diff --git a/pos_vrp2/views/pos_category_views.xml b/pos_vrp2/views/pos_category_views.xml new file mode 100644 index 00000000..f433a0dc --- /dev/null +++ b/pos_vrp2/views/pos_category_views.xml @@ -0,0 +1,24 @@ + + + + + pos.category.form.vrp2 + pos.category + + + + + + + + + + + + + + + + + diff --git a/pos_vrp2/views/pos_config_views.xml b/pos_vrp2/views/pos_config_views.xml new file mode 100644 index 00000000..1984add3 --- /dev/null +++ b/pos_vrp2/views/pos_config_views.xml @@ -0,0 +1,82 @@ + + + + + pos.config.form.vrp2 + pos.config + + + +
    + + +
    +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + + + +
    +
    + + — DKP + () +
    + +
    +
    +
    +
    +
    +
    + +
    diff --git a/pos_vrp2/views/pos_order_views.xml b/pos_vrp2/views/pos_order_views.xml new file mode 100644 index 00000000..ad04fafe --- /dev/null +++ b/pos_vrp2/views/pos_order_views.xml @@ -0,0 +1,82 @@ + + + + + pos.order.form.vrp2 + pos.order + + + +