feat: refactor dlv_fleetflow_contactflow — add customers/suppliers endpoints, settings tab, make busenco_custom optional
This commit is contained in:
@@ -5,10 +5,23 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.1.0] - 2026-06-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- `GET /fleetflow/contactflow/customers` endpoint (`res.partner`, `customer_rank > 0`, requires phone or mobile).
|
||||||
|
- `GET /fleetflow/contactflow/suppliers` endpoint (`res.partner`, `supplier_rank > 0`, requires phone or mobile).
|
||||||
|
- FleetFlow Settings tab (ContactFlow API) showing endpoint status, authentication instructions, and response format.
|
||||||
|
- Live `busenco_custom` install check — a warning is shown in Settings when the drivers endpoint is unavailable.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Removed `busenco_custom` from hard `depends` — it is now optional at runtime.
|
||||||
|
- Added `dlv_fleetflow_base` to `depends`.
|
||||||
|
- Drivers endpoint returns a 404 JSON error when `busenco_custom` is not installed.
|
||||||
|
|
||||||
## [1.0.0] - 2026-06-14
|
## [1.0.0] - 2026-06-14
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Initial release.
|
- Initial release.
|
||||||
- `GET /fleetflow/contactflow/drivers` endpoint returning active trip drivers as JSON contacts.
|
- `GET /fleetflow/contactflow/drivers?location=<location>` endpoint.
|
||||||
- Bearer API key authentication via Odoo native API keys (`res.users.apikeys`, `rpc` scope).
|
- Bearer token authentication via Odoo native API keys.
|
||||||
- Optional `location` query parameter to filter drivers on `location_id.name`.
|
- Optional `location` filter on the drivers endpoint.
|
||||||
|
|||||||
@@ -2,60 +2,43 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
FleetFlow ContactFlow API exposes the active trip drivers managed by the
|
`dlv_fleetflow_contactflow` is a generic contact sync feed addon. It provides
|
||||||
`busenco_custom` addon (`trip.driver`) over a token-authenticated HTTP endpoint
|
token-authenticated JSON endpoints consumed by the WordPress
|
||||||
that returns JSON contacts. It is designed to feed external systems — such as a
|
`dlv-odoo-contactflow` plugin to keep an external contact list in sync with
|
||||||
WordPress ContactFlow integration — with an always-current driver contact list,
|
Odoo.
|
||||||
optionally filtered by office location.
|
|
||||||
|
|
||||||
Driver names are stored as a single `name` field on `trip.driver`. The endpoint
|
The **customers** and **suppliers** endpoints (backed by `res.partner`) are
|
||||||
splits `trip.driver.name` on the **first space** into `first_name` and
|
always available. The **drivers** endpoint requires the `busenco_custom` addon
|
||||||
`last_name`:
|
(which provides the `trip.driver` model); when `busenco_custom` is not
|
||||||
|
installed, the drivers endpoint responds with a 404 JSON error and the FleetFlow
|
||||||
- `"Jan Peeters"` → `first_name="Jan"`, `last_name="Peeters"`
|
Settings tab shows a warning.
|
||||||
- `"Jan Van den Berg"` → `first_name="Jan"`, `last_name="Van den Berg"`
|
|
||||||
- `"Jan"` (no space) → `first_name="Jan"`, `last_name=""`
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- `base`
|
- `base`
|
||||||
- `busenco_custom` (provides the `trip.driver` model)
|
- `base_setup`
|
||||||
|
- `dlv_fleetflow_base`
|
||||||
|
- `busenco_custom` *(optional — only needed for the drivers endpoint)*
|
||||||
|
|
||||||
## Endpoint
|
## Endpoints
|
||||||
|
|
||||||
### URL
|
| Endpoint | Requires |
|
||||||
|
|----------|----------|
|
||||||
|
| `GET /fleetflow/contactflow/customers` | always active |
|
||||||
|
| `GET /fleetflow/contactflow/suppliers` | always active |
|
||||||
|
| `GET /fleetflow/contactflow/drivers?location=X` | `busenco_custom` installed |
|
||||||
|
|
||||||
```
|
## Authentication
|
||||||
GET /fleetflow/contactflow/drivers
|
|
||||||
```
|
|
||||||
|
|
||||||
Optional query parameter:
|
Bearer token using Odoo's native API keys:
|
||||||
|
|
||||||
| Param | Description |
|
|
||||||
|-------|-------------|
|
|
||||||
| `location` | Filter drivers on `location_id.name` (exact match) |
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /fleetflow/contactflow/drivers?location=Antwerpen
|
|
||||||
```
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
Bearer token using Odoo's **native API keys**.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Authorization: Bearer <your_api_key>
|
Authorization: Bearer <your_api_key>
|
||||||
```
|
```
|
||||||
|
|
||||||
The key is validated against `res.users.apikeys` with the `rpc` scope. A missing,
|
Generate a key via **Settings → Users → (user) → API Keys → New API Key**.
|
||||||
malformed, or invalid key returns **401 Unauthorized**.
|
|
||||||
|
|
||||||
Create a key in Odoo: **Settings → Users → (select user) → Account Security →
|
## Response format
|
||||||
API Keys → New API Key**.
|
|
||||||
|
|
||||||
### Response format
|
|
||||||
|
|
||||||
`Content-Type: application/json;charset=utf-8`
|
`Content-Type: application/json;charset=utf-8`
|
||||||
|
|
||||||
@@ -74,53 +57,37 @@ API Keys → New API Key**.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
On error the endpoint returns **500** with:
|
## Name splitting
|
||||||
|
|
||||||
```json
|
Contacts expose separate `first_name` / `last_name` fields, derived from a
|
||||||
{ "error": "<message>" }
|
single source name field:
|
||||||
```
|
|
||||||
|
|
||||||
### Field mapping
|
- **People** (drivers, individual partners): the name is split on the **first
|
||||||
|
space** — `"Jan Van den Berg"` → `first_name="Jan"`, `last_name="Van den Berg"`.
|
||||||
|
- **Companies** (`is_company = True`): `first_name` is empty and `last_name`
|
||||||
|
holds the full company name.
|
||||||
|
|
||||||
| JSON field | Source (`trip.driver`) | Notes |
|
## Settings
|
||||||
|--------------|-------------------------------|-------|
|
|
||||||
| `id` | `id` | Cast to string |
|
|
||||||
| `first_name` | `name` (before first space) | Split on first space |
|
|
||||||
| `last_name` | `name` (after first space) | Remainder after first space; `""` if none |
|
|
||||||
| `phone` | `phone_number` | `""` if empty |
|
|
||||||
| `mobile` | `phone_number2` | `""` if empty |
|
|
||||||
| `email` | `email` | `""` if empty |
|
|
||||||
|
|
||||||
Only drivers with `active = True` are returned, ordered by `name` ascending.
|
A **ContactFlow API** tab is available under **Settings → FleetFlow**. It shows
|
||||||
|
the live status of each endpoint, authentication instructions, and the response
|
||||||
|
format. When `busenco_custom` is installed the drivers endpoint is reported as
|
||||||
|
active; otherwise a warning explains how to enable it.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Install `dlv_fleetflow_base` first (it owns the FleetFlow Settings entry).
|
||||||
|
2. Install `dlv_fleetflow_contactflow`.
|
||||||
|
3. *(Optional)* Install `busenco_custom` to enable the drivers endpoint.
|
||||||
|
4. Create an API key for the user that should own the feed
|
||||||
|
(**Settings → Users → API Keys → New API Key**).
|
||||||
|
|
||||||
## WordPress setup
|
## WordPress setup
|
||||||
|
|
||||||
1. Store the Odoo base URL and the API key in your WordPress configuration
|
In the `dlv-odoo-contactflow` WordPress plugin, configure a sync profile with:
|
||||||
(e.g. as constants in `wp-config.php` or via your integration plugin's
|
|
||||||
settings), never hard-coded in templates.
|
|
||||||
2. Make a server-side `GET` request to
|
|
||||||
`https://<odoo-host>/fleetflow/contactflow/drivers` with the header
|
|
||||||
`Authorization: Bearer <your_api_key>`.
|
|
||||||
3. Add the `location` query parameter if you only want drivers for a specific
|
|
||||||
office location.
|
|
||||||
4. Parse the `contacts` array from the JSON response and render it through your
|
|
||||||
ContactFlow templates.
|
|
||||||
5. Cache the response (e.g. a transient) to avoid polling Odoo on every page
|
|
||||||
load.
|
|
||||||
|
|
||||||
## Installation steps
|
- the endpoint URL (e.g. `https://<odoo-host>/fleetflow/contactflow/customers`),
|
||||||
|
- the Bearer token generated above.
|
||||||
|
|
||||||
1. Copy the `dlv_fleetflow_contactflow` directory into your Odoo addons path
|
The plugin polls the endpoint and maps the returned `contacts` array into its
|
||||||
(alongside `busenco_custom`).
|
ContactFlow records.
|
||||||
2. Restart the Odoo server.
|
|
||||||
3. Enable **Developer Mode** and update the apps list
|
|
||||||
(**Apps → Update Apps List**).
|
|
||||||
4. Search for **FleetFlow ContactFlow API** and click **Install**.
|
|
||||||
5. Create an API key for the user that should own the feed
|
|
||||||
(**Settings → Users → Account Security → API Keys → New API Key**).
|
|
||||||
6. Test the endpoint:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -H "Authorization: Bearer <your_api_key>" \
|
|
||||||
"https://<odoo-host>/fleetflow/contactflow/drivers"
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
{
|
{
|
||||||
"name": "FleetFlow ContactFlow API",
|
"name": "FleetFlow ContactFlow API",
|
||||||
"summary": "Expose Busenco trip drivers as a ContactFlow JSON API "
|
"summary": "Generic contact sync feed endpoints for WordPress "
|
||||||
"secured by an API key.",
|
"ContactFlow integration",
|
||||||
'description': """
|
'description': """
|
||||||
Exposes trip.driver records as a JSON feed endpoint, consumed by the
|
Exposes trip.driver records as a JSON feed endpoint, consumed by the
|
||||||
WordPress dlv-odoo-contactflow plugin.
|
WordPress dlv-odoo-contactflow plugin.
|
||||||
@@ -17,8 +17,10 @@ Generate a key via Settings > Users > (user) > API Keys > New.
|
|||||||
"category": "Technical",
|
"category": "Technical",
|
||||||
"version": "16.0.1.0.0",
|
"version": "16.0.1.0.0",
|
||||||
"license": "LGPL-3",
|
"license": "LGPL-3",
|
||||||
"depends": ["base", "busenco_custom"],
|
"depends": ["base", "base_setup", "dlv_fleetflow_base"],
|
||||||
"data": [],
|
"data": [
|
||||||
|
"views/res_config_settings_views.xml",
|
||||||
|
],
|
||||||
"installable": True,
|
"installable": True,
|
||||||
"application": False,
|
"application": False,
|
||||||
"auto_install": False,
|
"auto_install": False,
|
||||||
|
|||||||
@@ -10,6 +10,35 @@ _logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class FleetFlowContactFlowController(http.Controller):
|
class FleetFlowContactFlowController(http.Controller):
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@staticmethod
|
||||||
|
def _json_response(payload, status=200):
|
||||||
|
"""Return a JSON HTTP response with the ContactFlow content type."""
|
||||||
|
return request.make_response(
|
||||||
|
json.dumps(payload),
|
||||||
|
headers=[("Content-Type", "application/json;charset=utf-8")],
|
||||||
|
status=status,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _busenco_installed():
|
||||||
|
"""Return True when the busenco_custom module is installed."""
|
||||||
|
return request.env["ir.module.module"].sudo().search_count([
|
||||||
|
("name", "=", "busenco_custom"),
|
||||||
|
("state", "=", "installed"),
|
||||||
|
]) > 0
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _split_name(name):
|
||||||
|
"""Split a free-text name on the first space into (first, last)."""
|
||||||
|
first_name, _sep, last_name = (name or "").partition(" ")
|
||||||
|
return first_name, last_name
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Drivers (requires busenco_custom)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
@http.route(
|
@http.route(
|
||||||
"/fleetflow/contactflow/drivers",
|
"/fleetflow/contactflow/drivers",
|
||||||
type="http",
|
type="http",
|
||||||
@@ -17,12 +46,21 @@ class FleetFlowContactFlowController(http.Controller):
|
|||||||
methods=["GET"],
|
methods=["GET"],
|
||||||
csrf=False,
|
csrf=False,
|
||||||
)
|
)
|
||||||
def fleetflow_contactflow_drivers(self, location=None, **kwargs):
|
def get_drivers(self, location=None, **kwargs):
|
||||||
"""Return active trip drivers as ContactFlow JSON contacts.
|
"""Return active trip drivers as ContactFlow JSON contacts.
|
||||||
|
|
||||||
Optional query param ``location`` filters drivers on
|
Optional query param ``location`` filters drivers on
|
||||||
``location_id.name``.
|
``location_id.name``. Returns 404 when busenco_custom is not installed.
|
||||||
"""
|
"""
|
||||||
|
if not self._busenco_installed():
|
||||||
|
return self._json_response(
|
||||||
|
{
|
||||||
|
"error": "busenco_custom module is not installed. "
|
||||||
|
"The drivers endpoint is unavailable."
|
||||||
|
},
|
||||||
|
status=404,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
domain = [("active", "=", True)]
|
domain = [("active", "=", True)]
|
||||||
if location:
|
if location:
|
||||||
@@ -34,8 +72,7 @@ class FleetFlowContactFlowController(http.Controller):
|
|||||||
|
|
||||||
contacts = []
|
contacts = []
|
||||||
for d in drivers:
|
for d in drivers:
|
||||||
name = d.name or ""
|
first_name, last_name = self._split_name(d.name)
|
||||||
first_name, sep, last_name = name.partition(" ")
|
|
||||||
contacts.append({
|
contacts.append({
|
||||||
"id": str(d.id),
|
"id": str(d.id),
|
||||||
"first_name": first_name,
|
"first_name": first_name,
|
||||||
@@ -50,18 +87,84 @@ class FleetFlowContactFlowController(http.Controller):
|
|||||||
len(contacts), location,
|
len(contacts), location,
|
||||||
)
|
)
|
||||||
|
|
||||||
return request.make_response(
|
return self._json_response({"contacts": contacts})
|
||||||
json.dumps({"contacts": contacts}),
|
|
||||||
headers=[
|
|
||||||
("Content-Type", "application/json;charset=utf-8"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_logger.exception("FleetFlow ContactFlow: error building response")
|
_logger.exception("FleetFlow ContactFlow: error building drivers response")
|
||||||
return request.make_response(
|
return self._json_response({"error": str(exc)}, status=500)
|
||||||
json.dumps({"error": str(exc)}),
|
|
||||||
headers=[
|
# ------------------------------------------------------------------
|
||||||
("Content-Type", "application/json;charset=utf-8"),
|
# Customers
|
||||||
],
|
# ------------------------------------------------------------------
|
||||||
status=500,
|
@http.route(
|
||||||
|
"/fleetflow/contactflow/customers",
|
||||||
|
type="http",
|
||||||
|
auth="fleetflow_api_key",
|
||||||
|
methods=["GET"],
|
||||||
|
csrf=False,
|
||||||
|
)
|
||||||
|
def get_customers(self, **kwargs):
|
||||||
|
"""Return active customer partners (with a phone or mobile) as contacts."""
|
||||||
|
return self._partner_feed(
|
||||||
|
rank_field="customer_rank", label="customers"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Suppliers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
@http.route(
|
||||||
|
"/fleetflow/contactflow/suppliers",
|
||||||
|
type="http",
|
||||||
|
auth="fleetflow_api_key",
|
||||||
|
methods=["GET"],
|
||||||
|
csrf=False,
|
||||||
|
)
|
||||||
|
def get_suppliers(self, **kwargs):
|
||||||
|
"""Return active supplier partners (with a phone or mobile) as contacts."""
|
||||||
|
return self._partner_feed(
|
||||||
|
rank_field="supplier_rank", label="suppliers"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Shared res.partner feed
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _partner_feed(self, rank_field, label):
|
||||||
|
"""Build a ContactFlow feed from res.partner filtered by rank field."""
|
||||||
|
try:
|
||||||
|
domain = [
|
||||||
|
("active", "=", True),
|
||||||
|
(rank_field, ">", 0),
|
||||||
|
"|",
|
||||||
|
("phone", "!=", False),
|
||||||
|
("mobile", "!=", False),
|
||||||
|
]
|
||||||
|
|
||||||
|
partners = request.env["res.partner"].sudo().search(
|
||||||
|
domain, order="name asc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
contacts = []
|
||||||
|
for p in partners:
|
||||||
|
if p.is_company:
|
||||||
|
first_name = ""
|
||||||
|
last_name = p.name or ""
|
||||||
|
else:
|
||||||
|
first_name, last_name = self._split_name(p.name)
|
||||||
|
contacts.append({
|
||||||
|
"id": str(p.id),
|
||||||
|
"first_name": first_name,
|
||||||
|
"last_name": last_name,
|
||||||
|
"phone": p.phone or "",
|
||||||
|
"mobile": p.mobile or "",
|
||||||
|
"email": p.email or "",
|
||||||
|
})
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
"FleetFlow ContactFlow: returned %s %s", len(contacts), label
|
||||||
|
)
|
||||||
|
|
||||||
|
return self._json_response({"contacts": contacts})
|
||||||
|
except Exception as exc:
|
||||||
|
_logger.exception(
|
||||||
|
"FleetFlow ContactFlow: error building %s response", label
|
||||||
|
)
|
||||||
|
return self._json_response({"error": str(exc)}, status=500)
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from . import ir_http
|
from . import ir_http
|
||||||
|
from . import res_config_settings
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
_inherit = "res.config.settings"
|
||||||
|
|
||||||
|
fleetflow_busenco_installed = fields.Boolean(
|
||||||
|
string="busenco_custom installed",
|
||||||
|
compute="_compute_fleetflow_busenco_installed",
|
||||||
|
readonly=True,
|
||||||
|
store=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends_context("uid")
|
||||||
|
def _compute_fleetflow_busenco_installed(self):
|
||||||
|
installed = self.env["ir.module.module"].sudo().search_count([
|
||||||
|
("name", "=", "busenco_custom"),
|
||||||
|
("state", "=", "installed"),
|
||||||
|
]) > 0
|
||||||
|
for rec in self:
|
||||||
|
rec.fleetflow_busenco_installed = installed
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="fleetflow_contactflow_config_settings_view_form" model="ir.ui.view">
|
||||||
|
<field name="name">res.config.settings.view.form.inherit.fleetflow.contactflow</field>
|
||||||
|
<field name="model">res.config.settings</field>
|
||||||
|
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[@data-key='dlv_fleetflow_base']//notebook" position="inside">
|
||||||
|
<page string="ContactFlow API">
|
||||||
|
<field name="fleetflow_busenco_installed" invisible="1"/>
|
||||||
|
|
||||||
|
<div class="row mt16 o_settings_container">
|
||||||
|
<div class="col-12 o_setting_box">
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
|
||||||
|
<span class="o_form_label">Available Endpoints</span>
|
||||||
|
<div class="text-muted mt8">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Endpoint</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>/fleetflow/contactflow/customers</code></td>
|
||||||
|
<td>Active</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>/fleetflow/contactflow/suppliers</code></td>
|
||||||
|
<td>Active</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>/fleetflow/contactflow/drivers?location=X</code></td>
|
||||||
|
<td>see below</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="o_form_label mt16 d-block">Bus Drivers Endpoint</span>
|
||||||
|
<div class="alert alert-success mt8" role="alert"
|
||||||
|
attrs="{'invisible': [('fleetflow_busenco_installed', '=', False)]}">
|
||||||
|
busenco_custom is installed. The drivers endpoint is active.
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-warning mt8" role="alert"
|
||||||
|
attrs="{'invisible': [('fleetflow_busenco_installed', '=', True)]}">
|
||||||
|
The drivers endpoint requires the busenco_custom addon.
|
||||||
|
Install busenco_custom to enable:
|
||||||
|
<code>GET /fleetflow/contactflow/drivers?location=<location></code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="o_form_label mt16 d-block">Authentication</span>
|
||||||
|
<div class="text-muted mt8">
|
||||||
|
Use a Bearer token. Generate via
|
||||||
|
Settings > Users > (user) > API Keys > New API Key
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="o_form_label mt16 d-block">Response format</span>
|
||||||
|
<div class="text-muted mt8">
|
||||||
|
<code>{ "contacts": [ { "id", "first_name", "last_name", "phone", "mobile", "email" } ] }</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
Reference in New Issue
Block a user