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

# TOTVS RM as a destination

> Create sales orders in TOTVS RM from a Nekt table.

TOTVS RM is the ERP of the TOTVS backoffice line, used for finance, billing, stock and sales. The TOTVS RM destination **creates sales orders** (*movimentos*, for example a *pedido de venda* of type 2.1.xx) in your RM from a table in your Lakehouse: each order with its customer, payment terms and items.

The connector only **creates** orders. It never changes or cancels a movement that already exists in RM. It takes several precautions against creating the same order twice (see [Avoiding duplicates](#avoiding-duplicates)), and when it cannot be sure, it stops and sends nothing else.

It writes through the REST server of your RM (RM.Host), with an RM user and password: the same access as the [TOTVS RM source](https://docs.nekt.com/sources/totvs-rm.md).

> [!WARNING]
>
> Every order is created in your **production** RM, which has no test mode. The destination starts with **Dry run** turned on: it checks every order against RM and shows what it would send, **without creating anything**. Turn it off only after reading the dry run's results, and start with a small **Max orders created per run**.

## What you need in RM

Before adding the destination, ask your RM administrator for:

1. **The RM REST server published over HTTPS** and reachable from the internet, with the firewall open to Nekt's IP addresses. It is the address of RM.Host, usually on port 8051, for example `https://rm.yourcompany.com.br:8051`. RM answers plain HTTP on that port by default: TOTVS documents how to turn on HTTPS as "Habilitar SSL/TLS no Host".
2. **An RM user dedicated to this integration**, not used by people. Orders typed in RM with this login and without an `IDINTEGRACAO` make the connector stop writing (see [Avoiding duplicates](#avoiding-duplicates)). In **Serviços Globais > Segurança > Perfis**, its profile needs:
   * the item **Integração com WebService do Movimento (MovMovimentoTBCData)** in **Acesso a Menus**;
   * permission to **consult and include** the movement type used for the orders. The connector reads the orders back to check whether they already exist, so permission to include alone is not enough;
   * optionally, **Desconsiderar Filtro de Produto em WebService** for the company. Without it, RM's data protection filter can hide customers from the user, and the dry run reports them as not found.
3. **The movement type of the sales order** (for example 2.1.01), already configured in RM: its series and numbering (in the movement type's parameters, **Mov. Identificação > Série**, and in **Definições Gerais > Numeração de movimento**). When the numbering is automatic, RM numbers each order; otherwise send the number in the `NUMEROMOV` column. Fields the movement type makes mandatory must be sent too (see **Fixed values** below).

## 1. Add your TOTVS RM access

1. In the [Destinations](https://app.nekt.ai/destinations) tab, click on the "Add destination" button located on the top right of your screen. Then, select the TOTVS RM option from the list of connectors.

2. Click **Next** and you'll be prompted to add your access:

   * **RM server URL**: the HTTPS address of your RM REST server, with the port, for example `https://rm.yourcompany.com.br:8051`.
   * **Username** and **Password**: the RM user for the integration.
   * **Company (coligada) code**: the RM company (*CODCOLIGADA*) where the orders are created, for example `1`.
   * **Branch (filial) code**: the branch (*CODFILIAL*) of the orders, for example `1`.
   * **Warehouse (local de estoque) code**: the stock location (*CODLOC*) of the orders, for example `01`.
   * **Movement type code**: the movement type (*CODTMV*) of your sales orders, for example `2.1.01`.
   * **Dry run**: on by default. The connector signs in, checks every order against RM with read-only lookups, and shows the first orders it would send and a summary in the run log, **without creating anything**. Turn it off to create the orders.
   * **Max orders created per run**: the most orders created in one run; the others wait for the next runs. Leave it empty for no limit. Use 1 on the first real run, then raise it.
   * **Enable partial success** (advanced): what happens when RM refuses some orders. Off: the other orders are created and the run ends as **failed**, so the refusals are noticed. On: the run ends successfully and the refusals are listed in the run log.
   * **Fixed values** (advanced): RM columns sent with every order, written as a JSON object, for example `{"SERIE": "1", "item.CODUND": "UN"}`. Use it for values your movement type requires and your table does not have. A column of your table mapped to the same RM column takes precedence.
   * **RM server timezone** (advanced): the timezone of your RM server. Dates and times with a timezone are converted to it.
   * **Verify SSL certificate** (advanced): turn off only when the RM server uses a self-signed certificate.

   The branch, warehouse and movement type can also come from your table, per order, by mapping a column to `CODFILIAL`, `CODLOC` or `CODTMV`.

3. Click **Next**.

## 2. Select your data to send

1. The next step is letting us know which data you want to send. Select the layer and then the desired table.

   > Tip: The table can be found more easily by typing its name.

2. Click **Next**.

### How the table must look

The table has **one row per order item**. The columns of the order (customer, date, payment terms) repeat on every item row, and the rows with the same **order id** form one order. You can build it with a [Query](https://docs.nekt.com/queries/overview.md) that joins your orders with their items, without any JSON or array functions:

```sql theme={null}
SELECT p.id                AS pedido_id,    -- the order's id in your system
       p.codigo_cliente_rm AS cliente,      -- customer code in RM (CODCFO)
       p.data              AS data_pedido,  -- order date
       p.condicao_rm       AS condicao,     -- payment terms code in RM (CODCPG)
       i.idprd_rm          AS produto_id,   -- product id in RM (IDPRD)
       i.quantidade,
       i.preco_unitario,
       i.unidade                            -- unit code in RM, e.g. UN
FROM pedidos p
JOIN itens_pedido i ON i.pedido_id = p.id
WHERE p.status = 'pronto_para_integrar'
```

**Example input table:**

| pedido\_id | cliente | data\_pedido | condicao | produto\_id | quantidade | preco\_unitario | unidade |
| :- | :- | :- | :- | :- | :- | :- | :- |
| NEKT-1001 | C0001 | 2026-10-30 | 001 | 101 | 2 | 10.50 | UN |
| NEKT-1001 | C0001 | 2026-10-30 | 001 | 102 | 1 | 99.90 | UN |
| NEKT-1002 | C0002 | 2026-10-30 | 002 | 101 | 5 | 10.50 | UN |

This table creates two orders: NEKT-1001 with two items and NEKT-1002 with one.

> [!NOTE]
>
> Send only **complete** orders. An order that already exists in RM is skipped as a whole, so items added to your table after the order was created are not added to it. Filter the table to orders that are final, as in the `WHERE` above.

* The product is identified by RM's internal product id (*IDPRD*). If you also use the [TOTVS RM source](https://docs.nekt.com/sources/totvs-rm.md), you can find it by joining with its `products` table.
* Numbers use a dot as the decimal separator. Quantities can have up to 4 decimal places and unit prices up to 10; an order with more is refused, so round them in your Query. Dates can be dates or date-times.
* Discounts (`VALORDESC`, `PERCENTUALDESC`) are not supported yet and are refused; they will come in a future version.
* Columns of the order must have the same value on every row of the order; an order whose rows disagree is refused. An empty cell on some rows is fine.

## 3. Map your columns

Map the columns of your table to RM columns. Columns of the order use the RM column name (from the movement table, TMOV); columns of the items use the `item.` prefix (from the item table, TITMMOV).

| RM column | Required | Description |
| :- | :- | :- |
| `IDINTEGRACAO` | Yes | The order's id in your system, up to 100 characters. It groups the rows of an order, and RM keeps it on the movement, so the connector can tell which orders already exist. Use an id that is unique in your system; ids that differ only in upper/lower case are refused. |
| `CODCFO` | Yes | Customer code in RM. |
| `DATAEMISSAO` | Yes | Order date. |
| `CODCPG` | No | Payment terms code in RM. |
| `SERIE`, `NUMEROMOV` | No | Series and number of the movement, when the movement type does not number automatically. |
| `CODTMV`, `CODFILIAL`, `CODLOC` | No | Movement type, branch and warehouse of the order, when they change from order to order. |
| `CAMPOLIVRE1`, `CAMPOLIVRE2`, `CAMPOLIVRE3` | No | Free text fields of the movement. Any other column of the movement can be mapped by its name. |
| `item.IDPRD` | Yes | RM's internal product id. |
| `item.QUANTIDADE` | Yes | Quantity, greater than zero. |
| `item.PRECOUNITARIO` | Yes | Unit price. |
| `item.CODUND` | Yes | Unit of measure code in RM, for example `UN`. |
| `item.NUMEROSEQUENCIAL` | No | Position of the item in the order. Items are sent in this order. |

Empty values are never sent: RM fills those columns with the movement type's defaults.

### Transforms

Each mapping accepts an optional ordered list of value transforms, applied before sending:

| Transform | Description |
| :- | :- |
| `trim` | Remove surrounding whitespace. |
| `uppercase` / `lowercase` | Change the text case. |
| `to_string` | Cast the value to text. |
| `digits_only` | Keep only the digits. |
| `null_if:<x>` | Drop the value when it equals `<x>` (e.g. `null_if:-`). |
| `zfill:<n>` | Left-pad with zeros to `<n>` characters, for codes that lost their leading zeros in a numeric column (e.g. `zfill:8` turns `36815` into `00036815`). |

## 4. Configure your TOTVS RM data destination

1. Describe your destination for easy identification within your organization. You can inform things like what data it brings, to which team it belongs, etc.

2. To define your [Trigger](https://docs.nekt.com/get-started/core-concepts/triggers), consider how frequently you need to have the data updated on the destination (every day, once a week, only at specific times, etc.).

3. Click **Done**.

### Check your new destination!

1. Once completed, you'll receive confirmation that your new destination is set up!

2. You can view your new destination on the [Destinations](https://app.nekt.ai/destinations) page. Since **Dry run** is on, the first run creates nothing: open its run log to see the result.

## What the run log shows

**In a dry run**, the connector:

* checks that the RM user can read movements of the movement type;
* looks up every order id in RM, to find orders that already exist;
* looks up every customer, product, warehouse and unit used, and whether they are active;
* shows the first three orders exactly as they would be sent (CPF and CNPJ numbers are partially hidden);
* ends with a summary: how many orders **would be created**, how many **already exist** and how many **would be refused**, with the reason for each refused order.

RM does not offer a way to look up payment terms and movement types, so the dry run cannot check them. RM checks them when it creates each order.

**In a real run**, each order is created with its items and the log lists how many were **created** (with their RM movement ids), how many **already existed** and were skipped, how many were **refused** with the reason, and how many wait for the next runs because of **Max orders created per run**. A refused order does not stop the others.

## First real run

1. Keep **Dry run** on and run the destination. Read the orders shown in the run log and the summary.
2. Turn **Dry run** off and set **Max orders created per run** to `1`. Run the destination and open the new movement in RM: check the customer, the items, the values and that the `IDINTEGRACAO` column holds your order id.
3. When it looks right, raise the limit (for example to 10), and later clear it.

## Avoiding duplicates

What the connector does:

* It saves each order's id in the movement's `IDINTEGRACAO` column and looks every id up in RM before sending, and once more right before sending each order. An order found with the same id and the same customer is skipped and listed in the run log with its RM movement id.
* If the id is already used in RM by a movement of **another customer**, or differs only in upper/lower case from an id in RM, the order is not created and the run log says why.
* An order is sent again only when RM clearly refused to process it (for example, the server was busy). If RM does not answer clearly (a timeout or a dropped connection), the connector does not send it again and sends **no other order** in that run, which then fails with a message. Check the order in RM; the next run looks it up before sending anything.
* After creating the first order of each movement type in a run, it reads the order back from RM and checks that the items and the order id were saved. If not, it stops and asks you to pause the pipeline.
* Before sending anything, it checks whether RM has movements that could be orders sent earlier by the connector whose id RM did not keep: same customer and movement type as an order to send, an empty `IDINTEGRACAO`, issued in the last 30 days or later (or on the date of an older order being sent), and at least one product in common; or any movement of that type created by the integration's RM user with an empty `IDINTEGRACAO`. If it finds any, the run sends **nothing** and fails, listing their movement ids. It also stops when there are too many such movements to check in one run. In a dry run these are warnings.
* If RM answers that it is busy, the connector looks the order up again before sending it a second time. If an order id is ever found twice for the same customer and movement type, the run stops with a **possible duplicate** alert. Check the movements listed; if one is a duplicate, change its `IDINTEGRACAO` or delete it (cancelling it is not enough, it keeps the order id), then run again.

### When a run stops because of movements without an order id

The run log lists the movement ids (IDMOV). For each one, in RM:

* if it is an order sent by this connector, fill its `IDINTEGRACAO` with that order's id: the next run treats it as already created;
* if it is not (for example, an order typed by hand for that customer), fill its `IDINTEGRACAO` with any value that is not an order id of your table.

If the run stopped because there were too many such movements, fill the `IDINTEGRACAO` of the movements without one for those customers in the same way, or contact us.

Then run the destination again.

Limits:

* **Do not run the destination twice at the same time.** Keep the interval between scheduled runs longer than a run takes; two runs sending the same order at the same moment could both create it.
* If RM does not keep the order id, an order more than 30 days old whose date you change in your table to another old date is not caught by the check above.
* An order that already exists in RM is skipped as a whole: items added to your table later are not added to it.
* The read-back covers the first order of each movement type in a run, not every order.
* The check above is broad on purpose: orders typed by hand in RM for the same customers and products, without an `IDINTEGRACAO`, stop the runs until their `IDINTEGRACAO` is filled.
* In rare cases (an RM that does not keep the order id combined with an order whose products all changed, or a busy answer while RM was still saving), one duplicate can still be created. The connector then stops with an alert in that run and in every later one until it is resolved.

> If you encounter any issues, reach out to us via Slack, and we'll gladly assist you!


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