Skip to content

[16.0][IMP][FIX] pms_l10n_es: add the INE tourist apartments occupancy survey (EOAP) and fix the hotel one for IRIA - #457

Open
davidpachecop wants to merge 12 commits into
OCA:16.0from
commitsun:david/ine-iria-surveys-oca
Open

davidpachecop wants to merge 12 commits into
OCA:16.0from
commitsun:david/ine-iria-surveys-oca

Conversation

@davidpachecop

@davidpachecop davidpachecop commented Sep 29, 2026 •

Copy link
Copy Markdown

The INE (Spanish National Statistics Institute) shut down its ARCE platform and moved the occupancy surveys to IRIA, where a questionnaire can no longer be edited once uploaded. Whatever is wrong in the file comes back as a rejection, days later, with a message that names neither the guest nor the setting behind it.

This PR adds the tourist apartments occupancy survey (EOAP), which the module did not build at all, and fixes the hotel one (EOH) for the rejection causes seen in production, reporting them while the file is being built instead of handing over a file the INE will reject.

Tourist apartments occupancy survey (EOAP)

An establishment listed as tourist apartments answers a different questionnaire from a hotel (root element APARTAMENTOS instead of ENCUESTA). Until now the module built the hotel one for everybody, so those establishments had no way to answer their survey from the PMS.

  • The survey is picked from the INE category of the property (new survey_type on pms.ine.tourism.type.category), so there is no new setting to decide. The 53 existing categories are seeded as hotel and the 5 apartment ones (0–4 keys) are added.
  • Apartment typology on pms.room (studio / 2-4 pax / 4-6 pax / other), inferred from the capacity of the room when left empty, so it works unconfigured.
  • INFORMANTE block on pms.property, mandatory in this survey.
  • Full builder: CABECERA, CABECERA_APARTAMENTOS, INFORMANTE, ALOJAMIENTO (shared with the hotel survey, extracted to a common helper), CAPACIDAD and OCUPACION per typology, PRECIOS and PERSONAL_OCUPADO.

A file built by this code for a real establishment was uploaded to IRIA and accepted by the INE, which issued the receipt.

Hotel occupancy survey (EOH)

Without changing the format establishments already upload successfully.

Configuration checks move to the property. ine_configuration_problems() returns everything that is missing at once instead of only the first thing, exposed as ine_ready / ine_blocking_reasons so the interface can list it. Before, each attempt produced one generic error and another download.

The period is always the natural month. The questionnaire asks for a week (hotels) or a fortnight (apartments), but the file must carry the whole month. A range was accepted as given, and it failed in both directions:

  • Reaching into the next month repeats a day number inside a RESIDENCIA, and the schema rejects it because the day is a key there.
  • Falling short of the month breaks the daily chain (XML_8) on the first day of the file, because the INE assumes no overnight stays the day before.

The period is now expanded to the month of the start date.

Extra beds are derived from the occupancy. Only the ones sold as a service were counted, and hardly any establishment configures that product. A baby in a cot counted as a traveller while the bed they slept in did not, and the INE rejected the file as soon as the guests of a night exceeded the declared seats. The survey methodology (5.10) is explicit that cots count as extra beds, and travellers (5.4) have no exception by age.

Two sources are now combined and the larger one wins: the extra beds sold as a service, and the guests registered beyond the places their room offers. Since the declared seats are already required to be at least the capacity of the rooms reported, the per-room figure always dominates the aggregate shortfall, which makes XML_12 unfailable by construction.

Country codes of the INE. The INE publishes its own country list next to the survey schemas. It follows ISO 3166-1 alpha-3 except where ISO has no code: Kosovo is KOS for them and has no alpha-3 in Odoo, so ID_PAIS came out empty and the schema rejected the file. A new ine_country_code on res.country covers the exceptions, and a country left without a code is now reported by name.

Formatting. Decimals with 2 positions and percentages normalised in whole hundredths, so the printed sum is exactly 100.00 by construction. Real UTF-8: the file declared ISO-8859-1 while encoding UTF-8. Province literal of the specification (max. 25 characters), seeded for the 52 provinces from the published annex: several Odoo names exceed it.

How it is organised

Building a file is three steps, each in its own place:

Step Where What it does
1. Is the property ready? models/pms_property.py Lists what is missing to configure, no XML involved
2. Build the XML wizards/wizard_ine.py Reads the occupancy and builds the questionnaire
3. Would the INE accept it? wizards/ine_content_checks.py The content rules, over the built file

Step 3 is plain code with no ORM: it takes the XML and returns the list of problems. Each rule carries the identifier the INE publishes it under (XML_0 … XML_17), reads without the wizard in front of it and is tested without creating records.

Reviewing this

Half of the diff is not code:

Lines What
.xsd fixtures 2,234 The 4 schemas as the INE publishes them. Not loaded at runtime: only the tests use them, to prove the file validates
Tests 564 34 tests
Data 218 Provinces and categories from the published annexes
Code ~1,300 What there is to review

Suggested order: the module docstring of wizards/wizard_ine.py (what the two surveys are, why two schema variants coexist, in what order a file is built), then wizards/ine_content_checks.py, then models/pms_property.py, then the two builders, then the tests.

Tests

34 tests, covering validation against the 4 schemas (published and versioned, hotel and apartments), exact percentage sum, rate and percentage consistency, period expansion in both directions, extra beds through both sources, country codes, the configuration checklist and each content rule.

Beyond the test suite, every correction was checked by regenerating the real files of two months for 125 properties across 50 databases, read-only, and comparing the result against the schemas and the content rules.

Notes

  • No new dependency: the only manifest change is the version bump.
  • No migration: the new fields are created by Odoo and the province and category data files update on their own. Existing categories become hotel, and a property without one falls back to hotel.
  • Translations are left to Weblate.
  • Behaviour change worth knowing: a property that used to download a file the INE would reject now gets an error saying what is missing. That is the point, but it makes pending configuration visible the first month.

Not in this PR

Submitting the questionnaire through the INE web service, so the portal does not have to be visited at all. IRIA requires no prior registration for it: the credential is the order number printed on the questionnaire, which this PR already keeps in the property, plus a single-use control code that is never stored.

Size

This is over the 500 line reference point of the AI policy, so to be explicit about what it is made of: 2,234 of the added lines are the four .xsd schema fixtures published by the INE, 564 the tests and 218 the reference data from the official annexes. The code is around 1,300 lines, of which roughly half is the new apartments survey.

It can be split into the apartments survey and the hotel fixes if a maintainer prefers, though both share the ALOJAMIENTO builder and the test fixtures, and several of the hotel fixes are what make the apartments file pass validation too. Happy to do it either way — say the word before reviewing and I will resubmit rather than have anyone read it twice.

Disclosure

Developed with AI assistance, declared with an Assisted-by: Claude Opus 5 trailer on every commit, per the OCA Generative AI / LLM policy. The work was directed, reviewed and tested by me against the INE specification and against real data, and I take full responsibility for it: ask about any line and I will answer for it.

… tourist apartments (EOAP)

The INE replaced the ARCE platform with IRIA. IRIA questionnaire schemas
are namespace-qualified, so the XML files generated so far are rejected
at upload time (XSD error on line 1: cannot find the declaration of the
root element). IRIA also removed the in-portal editing that ARCE allowed,
so the generated file must be exact.

Hotel survey (EOH) fixes:
- Qualify the root element with the IRIA namespace, configurable through
  the ir.config_parameter pms_l10n_es.ine_xml_namespace_hotel.
- Declare and encode the file as UTF-8 (it declared ISO-8859-1 while
  encoding UTF-8 bytes).
- Print DIAS_ABIERTO_MES_REFERENCIA zero-padded to 2 digits and omit the
  URL element when the property has no website (both rejected by the
  IRIA schema).
- Emit every decimal with exactly 2 digits and normalize the occupancy
  percentages in integer hundredths so the printed values add up to
  exactly 100.00: float artifacts have been a recurrent source of files
  rejected by the INE content validations.
- New INE province literal on res.country.state: the survey header
  expects the province literal of the INE specification (max. 25 chars)
  and several Odoo state names exceed it.

New tourist apartments survey (EOAP):
- New survey_type on the INE categories and seed data for the tourist
  apartments categories (0-4 keys). The wizard picks the questionnaire
  format from the property INE category.
- New apartment typology on rooms (studio / 2-4 pax / 4-6 pax / other),
  inferred from the room capacity when not set.
- New informant fields on the property (required block in EOAP).
- New XML builder with the EOAP blocks (CABECERA_APARTAMENTOS,
  INFORMANTE, CAPACIDAD, OCUPACION and PRECIOS per typology), sharing
  the guest movements and staff blocks with the hotel survey. The whole
  occupancy of each typology is reported under the normal rate with its
  ADR, which satisfies the INE content validations and has been accepted
  by the INE in a real submission.

Tests validate both generated files against the current IRIA schemas and
the exactness of the printed decimals and percentage sums.

(cherry picked from commit 1bd8f95)

Assisted-by: Claude Opus 5
…nexes

The INE publishes the survey annexes as XML files. Checking the seed data
against them:
- The province literal expected for province 48 is VIZCAKAIA (as published
  in the province annex), not VIZCAYA.
- The hotel type/category table includes one pair that was missing.

(cherry picked from commit 1703247)

Assisted-by: Claude Opus 5
The INE confirmed that the reference schema of each survey is the one they
publish, which declares no target namespace, and not the one the IRIA
application generates for the questionnaire. Both variants have been
accepted on upload so far (hotel files without namespace, an apartments
file with it), so the files are built after the published schema and the
qualified variant can be enabled per survey with a config parameter for
the questionnaires that need it.

This keeps the hotel file byte-compatible with what the establishments
have been uploading successfully.

(cherry picked from commit 03f7b5f)

Assisted-by: Claude Opus 5
Generating the file of every property with an INE category in the fleet
and checking it against the schema published by the INE and its official
content validations (XML_0..XML_17 of the web service documentation)
surfaced three cases that build a file the INE rejects:

- Properties whose phone has less than 9 digits: the schema requires
  between 9 and 13 characters in TELEFONO_1.
- Periods without guest movements: the schema requires at least one
  RESIDENCIA under ALOJAMIENTO, so an empty block cannot be uploaded.
- A client type reporting a rate with a zero percentage, or the other way
  around: the INE requires both to be consistent, on top of the
  percentages adding up to 100.

The first two now raise a message explaining what to fix instead of
building an invalid file, and the percentages are normalized keeping
their consistency with the rates.

The file must also carry the whole reference month even though the
questionnaire asks for a single week (hotels) or fortnight (apartments),
so a partial period is rejected up front.

(cherry picked from commit 82e911e)

Assisted-by: Claude Opus 5
The order number identifies the establishment in the INE questionnaire
and is fixed, so it is stored in the property and prefilled in the INE
wizard, where it can also be filled in for the first time. The control
code is deliberately not stored: it belongs to a single questionnaire
and is single use.

(cherry picked from commit c3672ab)

Assisted-by: Claude Opus 5
The establishment used to find out about a rejected file days later, on
the INE portal, with a message naming an XML element. Two changes move
that feedback to the moment the file is generated.

The reference period is now expanded to the natural month of the start
date instead of being rejected when it is not the whole month. A range
reaching into the next month was the worst case: it repeats day numbers
inside a place of residence, and the day is a key in the survey schema,
so the INE answered with a duplicate key error that says nothing to a
receptionist who only picked the wrong end date.

The content validations the INE runs after the schema are now applied to
the built file, and a failure names the day and the figures involved
instead of the XML element. Checked against 97 files generated from two
real fleets: the only two files reported are the two the INE would
reject, both for overnight stays exceeding the seats declared in the
property.

The empty month error now points at the check-in data, which is where
the survey is built from: several establishments with real occupancy and
no completed check-ins would otherwise report an empty month.

(cherry picked from commit 088e92e)

Assisted-by: Claude Opus 5
The configuration checks move from the wizard to the property, as
ine_configuration_problems(), and report every missing field at once
instead of only the first one. Two computed fields expose the result,
ine_ready and ine_blocking_reasons, so the interface can list what to
fix instead of offering a download that fails.

The seats check joins them: it used to live in the middle of the file
generation, and it is the most frequent blocker in the fleet, where
establishments have the seats unset while their rooms already offer
capacity.

Spanish translations for the new messages are included, since the app
shows them to the end user through the API. The help texts are left to
Weblate.

(cherry picked from commit 8f074a1)

Assisted-by: Claude Opus 5
The INE settings page now lists what is missing before the occupancy
survey can be built, instead of leaving the operator to find it out when
the download fails.

(cherry picked from commit 75b1478)

Assisted-by: Claude Opus 5
The INE counts as an extra bed every bed without a fixed character that
is not among the declared places, cots included (survey methodology,
5.10), and it counts every guest who stays the night as a traveller,
with no exception by age. The file, however, only reported the extra
beds sold as a service, and hardly any establishment of the fleet has an
extra bed product configured: a baby in a cot was counted as a traveller
while the bed they slept in was not, and the survey was rejected as soon
as the guests of a night exceeded the declared seats.

Extra beds are now also derived from the guests registered in a room
beyond the places it offers, and the larger of the two sources wins, so
establishments that do sell the service keep reporting it. The declared
seats are already required to be at least the capacity of the rooms
reported to the INE, which makes the derived figure enough for the
overnight stays to never exceed the seats plus the extra beds.

Since a room whose capacity is set too low would then report an extra
bed every single night, the wizard lists the nights it reported one.

(cherry picked from commit 91727cf)

Assisted-by: Claude Opus 5
The survey carries the country of residence in ID_PAIS, taken from the
ISO 3166-1 alpha-3 code, but the INE publishes its own country list next
to the survey schemas and it does not match ISO everywhere. Kosovo has
no alpha-3 in ISO, so Odoo leaves it empty and the file came out with an
empty ID_PAIS, rejected by the schema with nothing pointing at the guest
who caused it. The INE codes it as KOS.

The code of the country of residence now goes through the INE list, and
a country left without one is reported by name instead of producing a
file the establishment can only see rejected days later, on the portal.

The exception lives in the code because the countries of the base module
carry noupdate and a data row over them is silently ignored. The new
ine_country_code field is there for whatever the INE changes later
without waiting for a release.

Checked against the whole fleet, 226 countries of residence in use over
50 databases: Kosovo is the only one missing a code, and Spain the only
one outside the INE list, which is correct because Spanish residents
travel in ID_PROVINCIA_ISLA.

(cherry picked from commit a69e00f)

Assisted-by: Claude Opus 5
The wizard had grown to hold three different jobs at once: reading the
occupancy out of the PMS, building the XML, and checking the result
against the rules the INE applies after the schema. The last one only
needs the built file, so it moves to ine_content_checks, where each rule
carries the identifier the INE publishes it under and can be read, and
tested, without a record in between.

The module docstring now says what the two surveys are, why two schema
variants coexist and in what order a file is built, which is the context
missing to read the rest.

Two defects found while going through it:

- The hotel survey cut the phone at 12 characters while the schema takes
  13 and the apartments survey already used 13, so an international
  number lost its last digit.
- The file was named INE_7_2026.xml while the REST API named the same
  file INE_07_2026.xml.

(cherry picked from commit 949e4a1)

Assisted-by: Claude Opus 5
The module readme gains what has to be configured on the property for the
surveys, how the file is built and the limits that are left: the surveys
with no XML channel, the web service submission that is not implemented
and the days open being reported as the days of the month.

Assisted-by: Claude Opus 5
@davidpachecop

Copy link
Copy Markdown
Author

cc @DarioLodeiros as maintainer of this module.

A note on the size, since it looks heavier than it is: 2,234 of the 4,351 added lines are the four .xsd fixtures, the survey schemas as the INE publishes them. They are not loaded at runtime — only the tests use them, to prove the generated file validates against both the published schema and the versioned one IRIA serves. Another 564 lines are the tests and 218 the reference data from the official annexes, so the code to review is around 1,300 lines.

Happy to split it if you would rather review the apartments survey and the hotel fixes separately, although they share the ALOJAMIENTO builder and most of the test fixtures.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants