# NRA HFRG - Flat XML Import Format

Simplified XML for feeding declaration data into the hub. "Flat" means all single-value fields sit at **one level** under the root (e.g. `CarrierName` instead of `carrier/reprfirm/name`). Only the repeating collections - counterparties, addresses, goods - are nested.

Two entry points consume this format:

- **SAP input folder** - the hub watches the configured input folder, converts each file into NRA declaration XML and places the result in the regular input folder, where it is signed and submitted.
- **Import XML** button on each declaration form - fills the form in the browser instead of submitting directly.

Both paths use the same element names. Where their behaviour differs, it is called out below.

## Files in this folder

<!-- table:files -->

| File | Root element | Direction | What it is |
|---|---|---|---|
| [flat-import-template.xml](flat-import-template.xml) | `HFRGBULGARIA` | input | **Transport declaration template** - Import (9), Export (10), Internal (13), ThirdCountry (14). Every element is present; fill only those relevant to the chosen OperationType. Colour-coded copy: [flat-import-template.html](flat-import-template.html) |
| [flat-import-annul.xml](flat-import-annul.xml) | `HFRGAnnul` | input | **Cancel template** - cancellation (11) |
| [flat-import-confirm.xml](flat-import-confirm.xml) | `HFRGConfirm` | input | **Confirm template** - confirmation (12) |
| [flat-import-result.xml](flat-import-result.xml) | `ETRANSPORTBGUINCODE` | output | **Result file** the hub writes back once the NRA assigns a UIN |
| [flat-import.xsd](flat-import.xsd) | - | - | **Schema** for the three input formats - see below |
| [field-mapping.md](field-mapping.md) | - | - | This document, in Markdown |

## Schema

[flat-import.xsd](flat-import.xsd) describes all three input roots, so one schema covers every input file. Validation is optional - the hub does not require it and never asks for a schema reference - but a file can be checked against it before being sent, by pointing the document at it:

```xml
<HFRGBULGARIA xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:noNamespaceSchemaLocation="flat-import.xsd">
```

Every length, pattern and enumeration in it is copied from the NRA schema for the element it converts into, so nothing in it is stricter than the corresponding NRA rule. Where the NRA schemas differ between a person and a firm - the carrier identifier and its type - the schema accepts the union of both, because XSD 1.0 cannot switch on the value of another element.

It checks values, not completeness: which elements are **required** depends on `OperationType`, which XSD 1.0 cannot express, so nearly everything is optional and the tables below remain the reference. Element order is not enforced either, and empty elements are valid everywhere, both matching how the converter reads the file.

## Declaration types

| OperationType | Declaration | Description | Root element |
|---|---|---|---|
| 9 | Import | Goods from another EU Member State into Bulgaria | `HFRGBULGARIA` |
| 10 | Export | Goods from Bulgaria to another EU Member State | `HFRGBULGARIA` |
| 13 | Internal Transport | Goods transported within Bulgaria | `HFRGBULGARIA` |
| 14 | Third Country | Goods from a non-EU country after customs clearance | `HFRGBULGARIA` |
| 11 | Cancellation (Annul) | Cancel a previously submitted declaration | `HFRGAnnul` |
| 12 | Confirmation | Confirm receipt or dispatch of goods | `HFRGConfirm` |

`HFRGAnnul` and `HFRGConfirm` are routed by their root element name; their `OperationType` is not read.

## Header

| Element | Description | Read by the hub |
|---|---|---|
| `OperationType` | Declaration type code: 9, 10, 13 or 14 | YES - selects the target declaration |
| `InternalReference` | Reference from the source system | YES - see below |
| `UIN` | UIN of the declaration being corrected | YES - leave empty for a new declaration |
| `Version` | Format version | no - accepted and ignored |
| `FiscalRegNo` | Bulstat/EIK of the submitting entity | no - accepted and ignored |
| `TransportDate` | Date/time of transport | no - accepted and ignored |

`InternalReference` is more than a label: it becomes the job folder name (invalid file name characters and spaces are stripped) and is echoed back as `INTERNALREFEREN` in the result file. When it is empty a GUID is used instead, and the result can no longer be matched to the source document.

## What the hub does with the file

- A file whose root element is already a native NRA declaration (`decHfrImport`, `decHfrExport`, `decHfrAnnul`, `decHfrConfirm`, `decHfrInternal`, `decHfrThirdcountry`) is **passed through unchanged** - no conversion is attempted.
- The converted declaration is written as `<prefix>_<yyyyMMdd_HHmmss>.xml` in Windows-1251, and the source file is deleted.
- If conversion fails, the source is renamed to `<file>.convert-error` so it is not retried in a loop.
- A UTF-8 BOM and an `encoding="..."` declaration are honoured. A prolog `version=` other than `1.0` is rewritten to `1.0` before parsing.
- Booleans accept `true` (any case) or `1`; anything else counts as false.
- Decimals accept both `.` and `,` as the decimal separator.
- Identifier type elements fall back to `1` when missing or non-numeric.
- Once the NRA returns a UIN, the hub writes `<ResultsFolder>/sap/<reference>_<yyyyMMdd_HHmmss>.xml` - see [flat-import-result.xml](flat-import-result.xml).

### Fields the hub fills in itself

| Element | Behaviour |
|---|---|
| `ContactName`, `ContactPhone`, `ContactPhoneCode`, `ContactEmail`, `DeclarantName` | Optional. When empty, taken from the default signer in Settings > Senders. The signing pipeline then overwrites them with the details of the signer that actually succeeded, so values supplied in the file are not guaranteed to survive. |
| `IsCorrect` | Not read from the file. The hub always submits `true`. |

## Address codes: postal code, not EKATTE

Every `*Ekatte` element expects a **4-digit postal code**. The hub resolves it to an EKATTE code through `wwwroot/data/postal-to-ekatte.json`.

| In the flat XML | In the submitted declaration |
|---|---|
| `<Ekatte>1000</Ekatte>` | `<ekatte>68134</ekatte>` |

A value that is not a known postal code is left **blank** on purpose, so the declaration fails validation instead of being submitted with a wrong EKATTE. Passing an EKATTE code directly (`68134`) therefore drops the field.

The browser importer is more forgiving: when the lookup fails it leaves the raw value in the form field. The SAP folder path does not.

Affects `LoadEkatte`, `TranshipmentEkatte`, `TransferEkatte` and `Address/Ekatte`.

## 1. Flat XML element reference by declaration type

| Flat XML element | XSD field | Import (9) | Export (10) | Internal (13) | ThirdCountry (14) |
|---|---|---|---|---|---|
| `RecipientIdent` | `recipient/ident` | REQ | REQ | - | - |
| `RecipientIdentType` | `recipient/identtype` | REQ | REQ | - | - |
| `ShipperIdent` | `shipper/ident` | - | - | REQ | REQ |
| `ShipperIdentType` | `shipper/identtype` | - | - | REQ | REQ |
| `IsSr` | `issr` | - | REQ | - | - |
| `CustomsOffice` | `customs/customsoffice` | - | - | - | REQ |
| `CustomsDocument` | `customs/document` | - | - | - | REQ |
| `CustomsDate` | `customsdate` | - | - | - | REQ |
| `CustomsTime` | `customstime` | - | - | - | REQ (empty becomes `00:00`) |
| `CustomsLocation` | `customslocation` | - | - | - | REQ |
| `HasCarrier`, `CarrierIsPerson`, `CarrierName`, `CarrierIdent`, `CarrierIdentType` | `carrier` | opt | opt | opt | opt |
| `HasOrganizer`, `OrganizerIsPerson`, `OrganizerName`, `OrganizerIdent`, `OrganizerIdentType` | `organizer` | opt | opt | opt | opt |
| `RegVehicle` | `regvehicle` | REQ | REQ | REQ | REQ |
| `RegTrailer1` | `regtrailer1` | opt | opt | opt | opt |
| `RegTrailer2` | `regtrailer2` | opt | opt | opt | opt |
| `LoadLocalAddress`, `LoadEkatte` | `receivedaddress` / `loadaddress` | - | REQ | REQ | - |
| `LoadStreet`, `LoadNumber` | `receivedaddress` / `loadaddress` | - | opt | opt | - |
| `LoadDate` | `receiveddate` / `loaddate` | - | REQ | REQ | - |
| `LoadTime` | `receivedtime` / `loadtime` | - | opt | REQ (empty becomes `00:00`) | - |
| `IsTranshipment` | `transhipment/istranshipment` | opt | opt | opt | - |
| `TranshipmentTransportType` | `transhipment/transporttype` | opt | opt | opt | - |
| `TranshipmentDate` | `transhipment/transdate` | opt | opt | opt | - |
| `TranshipmentTime` | `transhipment/transtime` | opt | opt | opt | - |
| `TranshipmentLocalAddress`, `TranshipmentStreet`, `TranshipmentNumber`, `TranshipmentEkatte` | `transhipment/transaddress` | opt | opt | opt | - |
| `IsTransfer` | `istransfer` | REQ | REQ | REQ | REQ |
| `TransferLocalAddress`, `TransferStreet`, `TransferNumber`, `TransferEkatte` | `transferaddress` | opt | opt | opt | opt |
| `TransferDate` | `transferdate` | opt | opt | opt | opt |
| `TransferTime` | `transfertime` | opt | opt | opt | opt |
| `TransCarrierIsPerson`, `TransCarrierName`, `TransCarrierIdent`, `TransCarrierIdentType` | `transcarrier` | opt | opt | opt | opt |
| `TransRegVehicle` | `transregvehicle` | opt | opt | opt | opt |
| `TransRegTrailer1` | `transregtrailer1` | opt | opt | opt | opt |
| `TransRegTrailer2` | `transregtrailer2` | opt | opt | opt | opt |
| `ContactName` | `cname` | auto | auto | auto | auto |
| `ContactPhone` | `cphone` | auto | auto | auto | auto |
| `ContactPhoneCode` | `cphonecode` | auto | auto | auto | auto |
| `ContactEmail` | `cpemail` | auto | auto | auto | auto |
| `DeclarantName` | `dname` | auto | auto | auto | auto |

`auto` = optional in the file; filled from the signer settings and rewritten per signer at signing time.

The transhipment block is ignored unless `IsTranshipment` is `true`; the transfer block unless `IsTransfer` is `true`; carrier and organizer unless the matching `Has...` flag is `true` **and** the corresponding `...Ident` is non-empty. The transfer carrier is skipped when `TransCarrierIdent` is empty.

## 2. Counterparties (Import, Internal, ThirdCountry)

These declaration types have **no** `Country` element - it exists only under `Buyer` in Export.

| Flat XML element | XSD field | Required |
|---|---|---|
| `Counterparty/Ident` | `cr/rowenum/crident` | REQ |
| `Counterparty/IdentType` | `cr/rowenum/cridenttype` | REQ |
| `Counterparty/Name` | `cr/rowenum/crname` | REQ |
| `Counterparty/TIdent` | `cr/rowenum/tident` | opt |
| `Counterparty/TIdentType` | `cr/rowenum/tidenttype` | opt |
| `Counterparty/TName` | `cr/rowenum/tname` | opt |
| `Address/LocalAddress` | `address/rowenum/receivedaddress/localaddress` | REQ |
| `Address/Street` | `address/rowenum/receivedaddress/street` | opt |
| `Address/Number` | `address/rowenum/receivedaddress/numb` | opt |
| `Address/Ekatte` | `address/rowenum/receivedaddress/ekatte` | REQ - postal code |
| `Address/ReceivedDate` | `address/rowenum/receiveddate` | REQ |
| `Address/ReceivedTime` | `address/rowenum/receivedtime` | opt |

## 3. Buyers (Export only)

| Flat XML element | XSD field | Required |
|---|---|---|
| `Buyer/Ident` | `cr/rowenum/crident` | REQ |
| `Buyer/IdentType` | `cr/rowenum/cridenttype` | REQ |
| `Buyer/Name` | `cr/rowenum/crname` | REQ |
| `Buyer/TIdent` | `cr/rowenum/tident` | opt |
| `Buyer/TIdentType` | `cr/rowenum/tidenttype` | opt |
| `Buyer/TName` | `cr/rowenum/tname` | opt |
| `Buyer/Country` | `cr/rowenum/country` | REQ - 2-letter ISO code (`DE`, not `Germany`) |

## 4. Goods (all types)

| Flat XML element | XSD field | Required |
|---|---|---|
| `Good/Code` | `goods/rowenum/code` | REQ |
| `Good/QuantityType` | `goods/rowenum/quantitytype` | opt - see below |
| `Good/QuantityNet` | `goods/rowenum/quantitynet` | REQ |
| `Good/QuantityGross` | `goods/rowenum/quantitygross` | opt - omitted when 0 |
| `Good/Price` | `goods/rowenum/price` | REQ |
| `Good/Purpose` | `goods/rowenum/purpose` | REQ |
| `Good/PurposeOther` | `goods/rowenum/purposeother` | opt - only with purpose 06 |

### Quantity type

| In the flat XML | Submitted as |
|---|---|
| `kg` | `кг` |
| `m2` | `м2` |
| `pcs` | `броя` |
| empty | `кг` |

Any other value is passed through unchanged and will fail validation.

### Purpose codes by type

| Code | Description | Import | Export | Internal | ThirdCountry |
|---|---|---|---|---|---|
| 01 | Delivery | YES | YES | YES | YES |
| 02 | Complaint | YES | YES | YES | - |
| 03 | Donation | YES | YES | YES | - |
| 04 | Processing | YES | YES | YES | - |
| 05 | Storage until demand | YES | YES | - | - |
| 06 | Other | YES | YES | YES | YES |
| 07 | Transit with transshipment | YES | YES | - | YES |

## 5. Identifier types

### Main identifier (recipient/shipper)

| Code | Type |
|---|---|
| 1 | Bulstat |
| 2 | PIN |
| 3 | FPN/PN |
| 5 | NRA Service Number |

### Person identifier (carrier/organizer/transfer carrier as person)

| Code | Type |
|---|---|
| 2 | PIN |
| 3 | FPN/PN |
| 5 | NRA Service Number |
| 6 | Other |

### Firm identifier (carrier/organizer/transfer carrier as firm, counterparty, buyer)

| Code | Type |
|---|---|
| 1 | Bulstat |
| 5 | NRA Service Number |
| 6 | Other |
| 9 | VIN |

## 6. Annul (OperationType 11) - separate format

| Flat XML element | XSD field | Required |
|---|---|---|
| `Ident` | `part1/ident` | REQ |
| `IdentType` | `part1/identtype` | REQ |
| `UIN` | `part1/uin` | REQ - read from the root, not from the Header |
| `IsCorrect` | `part1/iscorrect` | auto - always sent as `true` |

## 7. Confirm (OperationType 12) - separate format

| Flat XML element | XSD field | Required |
|---|---|---|
| `Ident` | `part1/ident` | REQ |
| `IdentType` | `part1/identtype` | REQ |
| `IsReceived` | `part1/isreceived` | opt |
| `UinReceived` | `part1/uinreceived` | opt |
| `IsSent` | `part1/issent` | opt |
| `UinSent` | `part1/uinsent` | opt |
| `IsCorrect` | `part1/iscorrect` | auto - always sent as `true` |
