section ClientVentes;
// ===========================
// PUBLISH (entrée dans "Obtenir des données")
// ===========================
[DataSource.Kind="ClientVentes", Publish="ClientVentes.Publish"]
shared ClientVentes.Contents =
Value.ReplaceType(ClientVentesImpl,
type function (
ApiBaseUrl as (type text meta [Documentation.FieldCaption="API Base URL, par ex. https://akbbuehslmcrjsgupcon.supabase.co/functions/v1"]),
ApiPathClients as (type text meta [Documentation.FieldCaption="Chemin Clients par ex. /api/clients", Publish.Preview.Default="/api/clients"]),
ApiPathVentes as (type text meta [Documentation.FieldCaption="Chemin Ventes par ex. /api/ventes", Publish.Preview.Default="/api/ventes"]),
AuthBaseUrl as (type text meta [Documentation.FieldCaption="Auth Base URL, par ex. https://akbbuehslmcrjsgupcon.supabase.co", Publish.Preview.Default="https://akbbuehslmcrjsgupcon.supabase.co"]),
optional LoginPath as (type text meta [Documentation.FieldCaption="Login/Token Path", Publish.Preview.Default="/api/auth/token-from-key"]),
optional APIKey as (type text meta [Documentation.FieldCaption="API Key (prioritaire si non vide)"]),
optional ClientId as (type text meta [Documentation.FieldCaption="Client ID"]),
optional ClientSecret as (type text meta [Documentation.FieldCaption="Client Secret"]),
optional Scope as (type text meta [Documentation.FieldCaption="Scope"]),
optional Email as (type text meta [Documentation.FieldCaption="Email"]),
optional Password as (type text meta [Documentation.FieldCaption="Password"])
) as table
);
// ===========================
// IMPL
// ===========================
ClientVentesImpl =
(
ApiBaseUrl as text,
ApiPathClients as text,
ApiPathVentes as text,
AuthBaseUrl as text,
optional LoginPath as nullable text,
optional APIKey as nullable text,
optional ClientId as nullable text,
optional ClientSecret as nullable text,
optional Scope as nullable text,
optional Email as nullable text,
optional Password as nullable text
) as table =>
let
// ---- Defaults (adapter) ----
_ApiBaseUrl = ApiBaseUrl,
_ApiPathClients = ApiPathClients,
_ApiPathVentes = ApiPathVentes,
_AuthBaseUrl = AuthBaseUrl,
_LoginPath = if IsNonEmpty(LoginPath) then LoginPath else "/api/auth/token-from-key",
// ---- Auth precedence ----
Headers = GetAuthHeaders(_AuthBaseUrl, _LoginPath, APIKey, ClientId, ClientSecret, Scope, Email, Password),
// ---- Calls ----
ClientsJson = CallApiJson(_ApiBaseUrl, _ApiPathClients, Headers),
VentesJson = CallApiJson(_ApiBaseUrl, _ApiPathVentes, Headers),
ClientsData = GetDataArray(ClientsJson),
VentesData = GetDataArray(VentesJson),
TblClients = Table.FromList(ClientsData, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
TblClients2 =
if Table.RowCount(TblClients) > 0 and Value.Is(TblClients{0}[Column1], type record)
then Table.ExpandRecordColumn(TblClients, "Column1", Record.FieldNames(TblClients{0}[Column1]))
else TblClients,
TblVentes = Table.FromList(VentesData, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
TblVentes2 =
if Table.RowCount(TblVentes) > 0 and Value.Is(TblVentes{0}[Column1], type record)
then Table.ExpandRecordColumn(TblVentes, "Column1", Record.FieldNames(TblVentes{0}[Column1]))
else TblVentes,
// ---- Navigation table (Clients & Ventes) ----
Nav = #table(
type table [Name = text, Data = any, ItemKind = text, ItemName = text, IsLeaf = logical],
{
{"Clients", TblClients2, "Table", "Table", true},
{"Ventes", TblVentes2, "Table", "Table", true}
}
)
//Nav2 = Table.ToNavigationTable(Nav, {"Name"}, "Name", "Data", "ItemKind", "ItemName", "IsLeaf")
in
Nav;
// ===========================
// AUTH HELPERS
// ===========================
GetAuthHeaders =
(
AuthBaseUrl as text,
LoginPath as text,
APIKey as nullable text,
ClientId as nullable text,
ClientSecret as nullable text,
Scope as nullable text,
Email as nullable text,
Password as nullable text
) as record =>
let
HasApiKey = IsNonEmpty(APIKey),
HasClientCred = IsNonEmpty(ClientId) and IsNonEmpty(ClientSecret),
HasUserPass = IsNonEmpty(Email) and IsNonEmpty(Password),
Result =
if HasApiKey then
// Mode 1 : APIKey uniquement (prioritaire)
[Accept="application/json", #"X-API-Key"=Text.From(APIKey)]
else if HasClientCred then
// Mode 2 : Client credentials -> token -> Bearer
let
Token = GetToken_ClientCredentials(AuthBaseUrl, LoginPath, Text.From(ClientId), Text.From(ClientSecret), Scope),
TokenType = "Bearer"
in
[Accept="application/json", Authorization = TokenType & " " & Token]
else if HasUserPass then
// Mode 3 : Email/Password -> token -> Bearer
let
Token = GetToken_EmailPassword(AuthBaseUrl, LoginPath, Text.From(Email), Text.From(Password)),
TokenType = "Bearer"
in
[Accept="application/json", Authorization = TokenType & " " & Token]
else
error "Aucune auth valide : renseigner APIKey, ou ClientId+ClientSecret, ou Email+Password."
in
Result;
GetToken_ClientCredentials =
(AuthBaseUrl as text, LoginPath as text, ClientId as text, ClientSecret as text, Scope as nullable text) as text =>
let
BodyText =
Uri.BuildQueryString([
grant_type = "client_credentials",
client_id = ClientId,
client_secret = ClientSecret,
scope = if Scope = null then "" else Text.From(Scope)
]),
Resp =
Json.Document(
Web.Contents(
AuthBaseUrl,
[
RelativePath = LoginPath,