> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.fincome.co/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Mapping d'intégration Netsuite

![](https://storage.crisp.chat/users/helpdesk/website/-/6/2/0/e/620ea71851239000/netsuite_1lm1zla.svg =100x100)
||| Attention : Le connecteur s'appuie sur plusieurs champs et enregistrements personnalisés (custcol_iw_*, CUSTOMLIST_IW_*, CUSTOMRECORD_CSEG_IW_*). 
||| Ces éléments dépendent de la configuration du compte NetSuite. Une nouvelle instance doit exposer des champs équivalents (ou être mappée spécifiquement) avant l'onboarding.

# Connexion & authentification

* **API utilisée :** SuiteQL (langage de requête SQL de NetSuite), exposé via un unique endpoint REST :
  `POST&#32;https://{account_id}.suitetalk.api.netsuite.com/services/rest/query/v1/suiteql`
* **Authentification :** OAuth 2.0 Machine-to-Machine (M2M), via une assertion JWT signée en ES256 (grant_type=client_credentials). Il n'y a pas de flux OAuth interactif côté utilisateur.
* Endpoint de jeton : `https://{account_id}.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token`
* En-têtes des requêtes : `Authorization: Bearer <token>` et `Prefer: transient`.
* Sur une réponse `401`, le jeton est régénéré automatiquement et la requête est rejouée une fois ; en cas d'échec, une erreur `InvalidCredentials` est levée.
* **Prérequis côté NetSuite :**
1. Activer les fonctionnalités *REST Web Services* et *OAuth 2.0*.
2. Un rôle disposant des droits de lecture sur `customer`, `item`, `transaction`, `transactionLine`, `currency`, `classification` et les enregistrements personnalisés éventuels, ainsi que l'accès aux *REST Web Services* / SuiteQL.

### Mise en place de la connexion

La connexion est *machine-to-machine* : vous enregistrez Fincome comme application de confiance dans **votre** compte NetSuite, puis vous reportez trois identifiants dans Fincome. La clé privée de signature ne quitte jamais Fincome — vous n'importez que le **certificat public** Fincome.

1. **Créer l'enregistrement d'intégration.**
*Setup ▸ Integration ▸ Manage Integrations ▸ New*. Cochez toutes les cases **Token-Based Authentication**, **OAuth 2.0** et **Client Credentials (Machine to Machine) Grant**, enregistrez, puis notez le **Client ID** (Consumer Key).
2. **Téléverser le certificat Fincome.** 
Téléchargez le certificat public Fincome : <https://fincome-public-assets.s3.eu-west-3.amazonaws.com/netsuite/fincome-netsuite.pem>
Puis allez dans *Setup ▸ Integration ▸ OAuth 2.0 Client Credentials (M2M) Setup ▸ New* : sélectionnez l'intégration de l'étape 1, importez le certificat et rattachez-le à une **entité** et un **rôle** disposant des droits *REST Web Services / SuiteQL* (cf. prérequis). Notez le **Certificate ID** renvoyé.
3. **Renseigner Fincome.** 
Dans Fincome, ouvrez le connecteur NetSuite et saisissez votre **Account ID** (*Setup ▸ Company ▸ Company Information*), votre **Client ID** (étape 1) et votre **Certificate ID** (étape 2). Optionnellement, indiquez les IDs de vos champs personnalisés de reconnaissance du revenu et votre/vos catégorie(s) de revenu récurrent.

### Objets synchronisés

Le connecteur traite cinq flux, dans cet ordre :

| Flux | Source NetSuite | Objets Fincome créés | Condition de filtrage |
| ---- |
| customer | table `customer` | Customer | aucune (sync complète) |
| product | table `item` | Product + Price | aucune |
| subscription | transactions `salesorder` | Subscription | `taxline = 'F'` ET `tranDate > TRUNC(CURRENT_DATE)` |
| invoice | transactions `salesorder` | Invoice + InvoiceLineItem | `taxline = 'F'` ET `tranDate <= TRUNC(CURRENT_DATE)` |
| credit_note | transactions `creditmemo` | Invoice + InvoiceLineItem | `taxline = 'F'` |


|| Point clé — factures vs abonnements. Les factures et les abonnements proviennent du même type d'enregistrement (salesorder). Ils sont séparés uniquement par la date de transaction : 
|| -tranDate dans le futur → abonnement (Subscription), 
|| -tranDate aujourd'hui ou dans le passé → facture (Invoice).


||| Seules les lignes dont taxline = 'F' sont importées (liste blanche) : toute autre valeur — notamment les lignes de taxe — est exclue. La taxe n'est donc jamais importée et tax_amount vaut toujours 0.
---
# Mapping des champs

### Clients (customer)

| Champ NetSuite | Champ Fincome | Remarque |
| ---- |
| id | original_id |  |
| entitytitle | name |  |
| email | email |  |
| toplevelparent | root_parent_id | Renseigné uniquement si `toplevelparent ≠ id` (hiérarchie parent/enfant), résolu via une clé étrangère Customer |

### Produits & tarifs (product)

À partir de la table `item`, le connecteur crée un Product et un Price :

| Champ NetSuite | Champ Fincome (Product) |
| ---- |
| id | original_id |
| itemid | name |

| Champ NetSuite | Champ Fincome (Price) |
| ---- |
| id | original_id |
| id | product_id (clé étrangère vers Product) |
| itemid | name |
| itemrevenuecategory | type → `subscription` si = "1", sinon `one_off` |

### Factures (invoice)

###### En-tête (Invoice) :

| Champ NetSuite | Champ Fincome | Remarque |
| ---- |
| id | original_id |  |
| tranId | invoice_number |  |
| entity | customer_id | clé étrangère vers Customer |
| tranDate | date | format JJ/MM/AAAA ; si absente, la facture est ignorée |
| status | status | mappé via la table `salesorder` (voir § Mapping des statuts) |

###### Lignes (InvoiceLineItem) :

| Champ NetSuite | Champ Fincome | Remarque |
| ---- |
| transactionLine.id | original_id (combiné à l'id facture) |  |
| — | invoice_id | clé étrangère vers Invoice (ligne ignorée si la facture est absente) |
| itemrevenuecategory + dates de période | type | `subscription` si période valide (`period_start < period_end`) et `itemrevenuecategory = "1"`, sinon `one_off` |
| creditforeignamount / debitforeignamount | amount_excluding_tax_after_discount | voir § Montants |
| — | tax_amount | toujours 0 (lignes de taxe exclues) |
| quantity | quantity | transformé en `-1 × arrondi_supérieur(quantity)` |
| currency.symbol | currency_code |  |
| transactionLine.memo | description |  |
| custcol_iw_rr_start_date | period_start | date de début de reconnaissance du revenu |
| custcol_iw_rr_end_date | period_end | 1 jour (NetSuite borne la fin de façon inclusive) |
| item | price_id | clé étrangère vers Price |

### Axes personnalisés (custom_axis_field) :

| Enregistrement NetSuite | Champ Fincome |
| ---- |
| CUSTOMLIST_IW_CONTRACT_TYPE.name | contract_type |
| CUSTOMRECORD_CSEG_IW_CA_COUNTRY.name | country_name |
| classification.name | class |

### Abonnements (subscription)

| Champ NetSuite | Champ Fincome | Remarque |
| ---- |
| id (+ id de ligne) | original_id | identifiant combiné |
| id (transaction) | subscription_set_id | regroupe les lignes d'un même `salesorder` |
| entity | customer_id | clé étrangère vers Customer |
| custcol_iw_rr_start_date | subscription_start_date |  |
| montant de la ligne ÷ nombre de mois de la période | monthly_value | les mois partiels sont calculés au prorata |
| — | monthly_value_main | `monthly_value` converti dans la devise de base (voir § Montants) |

### Avoirs (credit_note)

Structure identique aux factures, à trois différences près :

* source = transactions `creditmemo` (et non `salesorder`) ;
* status mappé via la table `creditmemo` (voir § Mapping des statuts) ;
* `original_id` = l'id de l'avoir lui-même (ce n'est pas un lien vers une facture d'origine).
---
# Mapping des statuts

Les statuts dépendent du type d'enregistrement source.

### Bons de commande (salesorder) → utilisés pour les factures

| Code NetSuite | Libellé NetSuite | Statut Fincome |
| ---- |
| A | Pending Approval | pending |
| B | Pending Fulfillment | open |
| C | Cancelled | *ignoré* |
| D | Partially Fulfilled | open |
| E | Pending Billing/Part. Fulf. | open |
| F | Pending Billing | open |
| G | Billed | open |
| H | Closed | *ignoré* |
| Y | Undefined | *ignoré* |

### Avoirs (creditmemo)

| Code NetSuite | Libellé NetSuite | Statut Fincome |
| ---- |
| A | Open | open |
| B | Fully Applied | paid |
| C | Undefined | *ignoré* |
| D | Voided | *ignoré* |

Un enregistrement passé à un statut *ignoré* (commande annulée/clôturée, avoir annulé…) est marqué comme supprimé côté Fincome lors du sync suivant.
---
# Montants, quantités, dates & devises

* **Montants.** NetSuite scinde chaque ligne en deux pieds comptables. Le connecteur lit les montants en devise étrangère :
* `creditforeignamount` → montant positif ;
* sinon `debitforeignamount` → montant négatif (`-1 ×`).
* Le champ `netamount` n'est pas utilisé.
* **Quantités.** Inversées et arrondies au supérieur : `-1 × ceil(quantity)`.
* **Dates.** Format attendu JJ/MM/AAAA. Les dates de fin de période (`period_end`) sont décalées de +1 jour.
* **Devises.** Les montants sont exprimés dans la devise de la transaction (`*foreignamount`). Ils sont convertis dans la devise de base de l'entreprise au taux de la date de la transaction (champs `*_main`). À noter : pour les abonnements, cette date est dans le futur (`tranDate > aujourd'hui`), la conversion utilise donc le taux de change associé à cette date future.

# Synchronisation incrémentale & pagination

* **Pagination :** par `limit` (1000) et `offset` sur SuiteQL.
* **Curseur :** `{ offset, last_modified_date }`. Une fois un flux épuisé (`hasMore = false`), `offset` repasse à 0 et `last_modified_date` avance à la date de début du dernier sync.
* **Champ de dernière modification :**
* customer → `lastmodifieddate` ;
* subscription / invoice / credit_note → `t.lastmodifieddate`.
* **Suppressions.** Les suppressions définitives côté NetSuite ne sont pas propagées : un enregistrement supprimé cesse simplement d'apparaître, sans être retiré de Fincome. En revanche, un enregistrement passé à un statut *ignoré* (voir § Mapping des statuts) est marqué comme supprimé.
---
# Champs & enregistrements personnalisés requis

Ces éléments doivent exister dans l'instance NetSuite pour un mapping complet :

| Élément | Usage Fincome |
| ---- |
| custcol_iw_rr_start_date | début de période (period_start, subscription_start_date) |
| custcol_iw_rr_end_date | fin de période (period_end) |
| CUSTOMLIST_IW_CONTRACT_TYPE | axe personnalisé contract_type |
| CUSTOMRECORD_CSEG_IW_CA_COUNTRY | axe personnalisé country_name |

|| À noter : itemrevenuecategory (qui distingue abonnement / one-off) est un champ standard de l'objet item, pas un champ personnalisé.