> ## Documentation Index
> Fetch the complete documentation index at: https://docs.proptyerp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WHISE import

> How ots_whise_odoo_migration maps WHISE CSV into Propty

Module: **`ots_whise_odoo_migration`**. It imports WHISE **estates, pictures and documents** from three semicolon-separated UTF-8 CSVs into Propty.

Property-level fields: [WHISE import fields](/docs/api-reference/fields-whise).

This is **not** a live WHISE API sync. It is a batch CSV importer (UI: **WHISE-imports**, group `real_estate.group_realestate_manager`).

## Batch models

| Technical name | Role |
| - | - |
| `ots.whise.import.batch` | One import run (files, options, counters) |
| `ots.whise.import.estate.line` | One `estates.csv` row (`payload` = full JSON row) |
| `ots.whise.import.picture.line` | One `pictures.csv` row |
| `ots.whise.import.document.line` | One `documents.csv` row |

Batch `state` keys: `draft`, `ready`, `running`, `storage_full`, `done`, `done_errors`, `cancelled`.

Estate line `state` keys: `pending`, `done`, `skipped`, `conflict`, `error`.\
Estate line `operation` keys: `create`, `update`, `link`.

Picture/document line `state` keys: `pending`, `done`, `skipped`, `error`.

## Identifiers

| WHISE CSV | Odoo model | Field |
| - | - | - |
| `EstateId` | `real_estate.real_estate` | `whise_estate_id` |
| `PictureId` | `real_estate.image` (public) or `ir.attachment` (private) | `whise_picture_id` |
| `DocumentId` | `ir.attachment` | `whise_document_id` |

Upsert: search by those ids before create. If a property has the same normalized `whise_address_key` and **no** `whise_estate_id`, the importer **links** (`operation` = `link`) instead of creating a duplicate.

## Estate CSV → property fields

Required `estates.csv` columns: `EstateId`, `StatusId`, `Address1`, `Number`, `Zip`, `Box`, `City`, `UpdateDate`.

| WHISE column | Odoo field |
| - | - |
| `EstateId` | `whise_estate_id` |
| `ReferenceNumber` | `whise_reference_number` and `reference` (fallback: EstateId) |
| `OfficeId` | `whise_office_name` |
| `StatusId` | `whise_source_status`; `active` if value is `actief` |
| `CreateDate` / `UpdateDate` | `whise_source_create_date` / `whise_source_update_date` |
| `Address1` / `Number` / `Box` / `Zip` / `City` | `street`, `street_number`, `bus_number`, `zip`, `city` |
| `CountryId` | `re_country_id` (name/code map, default Belgium) |
| `Floor` / `FloorsNumber` | `floor` / `amount_of_floors` |
| `CoordX` / `CoordY` | `latitude` / `longitude` |
| `PurposeStatusId` | `transaction_id` (Huur vs Verkoop) and `selling_state_id` |
| **`RepresentativeId`** | **`responsible_realtor_id`** (`res.users` matched by name; empty if 0 or 2+ matches). No WHISE user id is stored |
| `DisplayStatusId` | `website_published` if `online` |
| `SubcategoryId` / `CategoryId` | `real_estate_type` |
| `TitleNL` / `Name` | `advertisement_title` |
| `LongDescriptionNL` / `ShortDescriptionNL` | `description` |
| `Price` | `estate_rental_price` or `estate_asking_price` (from purpose) |
| `PriceObtained` | `estate_selling_price` |
| `HabitableArea` / `GroundArea` / `TotalSurface` | `living_area` / `land_area` / `square_footage_area_amount` |
| `ConstructionYear` | `year_build` |
| `Furnished` / `Garden` / `Terrace` / … | matching `is_furnished`, `has_garden`, `has_terrace`, … |
| `PEBLetter` / `PEBSpec` / `PEBUniqueCode` | `epc_class` / `epc_value` / `epc_unique_code` |
| `FloodRisk` | `flood_risk`, `flood_risk_area` |
| `StateCondition` | `state_of_property` |
| `AvailabilityDateTime` | `availability_date` |
| `HeatingType` | `heating_info_ids` (find or create tag by name) |

Numeric sentinel `-1002` is treated as empty.

### Child lines (only if the property has none yet)

| WHISE | Created on |
| - | - |
| `Bedrooms`, `Room1`–`Room5` | `real_estate.real_estate_bedroom` |
| `Bathrooms`, `BathroomSurface` | `real_estate.real_estate_bathroom` |
| `KitchenYesNo`, `KitchenType`, `KitchenSurface` | `real_estate.real_estate_kitchen` (`kitchen_are`, `kitchen_type`) |
| `Terrace`, `Terrace1Surf`, `TerraceSurface` | `real_estate.real_estate_terrace` |
| `Garage` | `real_estate.real_estate_parking_spot` with `parking_type` = `garage` |
| `Parking` | `parking_type` = `outdoor_parking` |

### Pictures & documents

`pictures.csv`: `PictureId`, `EstateId`, `Url`, `Order`, `Public`. Public → `real_estate.image` (`sequence` = `Order * 10`). Private → `ir.attachment` unless skipped.

`documents.csv`: `DocumentId`, `EstateID` (capital D), `Url`, `Public`, `Filename`. Filename keywords set certificate flags (`has_epc_certificate`, `has_asbestos_certificate`, …) and the matching `*_attachment_id`.

## Representative / dossierverantwoordelijke

`RepresentativeId` is **not** stored as a WHISE id. The importer looks up `res.users` by `name` or `partner_id.name` (`=ilike`). On a unique match it writes `responsible_realtor_id`.

```python theme={null}
# Properties of one realtor
[["responsible_realtor_id", "=", 6]]

# WHISE listings without a matched realtor
[["whise_estate_id", "!=", False], ["responsible_realtor_id", "=", False]]
```

## Read a raw CSV row after import

```python theme={null}
lines = models.execute_kw(
    db, uid, password,
    "ots.whise.import.estate.line",
    "search_read",
    [[["whise_estate_id", "=", "12345"], ["state", "=", "done"]]],
    {"fields": ["id", "operation", "real_estate_id", "payload", "message"], "limit": 1},
)
```

`payload` is the full estate CSV row as JSON.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.