📡 Documentation API IzyScan Server

Guide d'intégration pour les systèmes de vente avec IzyScan

Version du Document: 2.2

Dernière Mise à Jour: 16 Juillet 2025 à 15h57

Vue d'Ensemble

Ce document fournit les spécifications techniques pour intégrer les systèmes de vente avec IzyScan Server. IzyScan est un système d'affichage des prix et de gestion des stocks qui nécessite une synchronisation avec les systèmes POS/vente existants.

💡 Note: IzyScan supporte plusieurs méthodes d'authentification et offre une API RESTful complète pour une intégration transparente.

🖥️ Démarrage du Serveur Web API

Cette section vous montre comment créer et démarrer un serveur web simple sur le port 80 dans différents langages de programmation.

Création d'un Serveur Web Basique

PHP - Serveur Intégré

// server.php
<?php
// Route principale
if ($_SERVER['REQUEST_URI'] == '/') {
    header('Content-Type: application/json');
    echo json_encode(['status' => 'success', 'message' => 'Serveur en marche']);
    exit;
}

// Autres routes retournent 404
http_response_code(404);
echo json_encode(['status' => 'error', 'message' => 'Route non trouvée']);

// Pour démarrer le serveur sur le port 80:
// sudo php -S 0.0.0.0:80 server.php
?>

Node.js - Serveur HTTP

// server.js
const http = require('http');

const server = http.createServer((req, res) => {
    res.setHeader('Content-Type', 'application/json');
    
    if (req.url === '/' && req.method === 'GET') {
        res.statusCode = 200;
        res.end(JSON.stringify({
            status: 'success',
            message: 'Serveur en marche'
        }));
    } else {
        res.statusCode = 404;
        res.end(JSON.stringify({
            status: 'error',
            message: 'Route non trouvée'
        }));
    }
});

const PORT = 80;
server.listen(PORT, () => {
    console.log(`Serveur démarré sur http://localhost:${PORT}`);
});

// Pour démarrer: sudo node server.js

C# - Serveur HTTP Simple

// Program.cs
using System;
using System.Net;
using System.Text;
using System.Text.Json;

class Program
{
    static void Main()
    {
        HttpListener listener = new HttpListener();
        listener.Prefixes.Add("http://*:80/");
        listener.Start();
        
        Console.WriteLine("Serveur démarré sur http://localhost:80");
        
        while (true)
        {
            HttpListenerContext context = listener.GetContext();
            HttpListenerResponse response = context.Response;
            
            response.ContentType = "application/json";
            
            string responseString;
            if (context.Request.Url.AbsolutePath == "/")
            {
                responseString = JsonSerializer.Serialize(new {
                    status = "success",
                    message = "Serveur en marche"
                });
                response.StatusCode = 200;
            }
            else
            {
                responseString = JsonSerializer.Serialize(new {
                    status = "error",
                    message = "Route non trouvée"
                });
                response.StatusCode = 404;
            }
            
            byte[] buffer = Encoding.UTF8.GetBytes(responseString);
            response.ContentLength64 = buffer.Length;
            response.OutputStream.Write(buffer, 0, buffer.Length);
            response.Close();
        }
    }
}

// Pour compiler et démarrer:
// dotnet run (en tant qu'administrateur)

Java - Serveur HTTP

// SimpleServer.java
import com.sun.net.httpserver.*;
import java.io.*;
import java.net.InetSocketAddress;

public class SimpleServer {
    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(80), 0);
        
        server.createContext("/", exchange -> {
            String response;
            int statusCode;
            
            if (exchange.getRequestURI().getPath().equals("/")) {
                response = "{\"status\":\"success\",\"message\":\"Serveur en marche\"}";
                statusCode = 200;
            } else {
                response = "{\"status\":\"error\",\"message\":\"Route non trouvée\"}";
                statusCode = 404;
            }
            
            exchange.getResponseHeaders().set("Content-Type", "application/json");
            exchange.sendResponseHeaders(statusCode, response.length());
            
            OutputStream os = exchange.getResponseBody();
            os.write(response.getBytes());
            os.close();
        });
        
        server.start();
        System.out.println("Serveur démarré sur http://localhost:80");
    }
}

// Pour compiler et démarrer:
// javac SimpleServer.java
// sudo java SimpleServer

Python - Serveur HTTP

# server.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import json

class SimpleHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200 if self.path == '/' else 404)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        
        if self.path == '/':
            response = {
                'status': 'success',
                'message': 'Serveur en marche'
            }
        else:
            response = {
                'status': 'error',
                'message': 'Route non trouvée'
            }
        
        self.wfile.write(json.dumps(response).encode())

if __name__ == '__main__':
    server = HTTPServer(('', 80), SimpleHandler)
    print('Serveur démarré sur http://localhost:80')
    server.serve_forever()

# Pour démarrer: sudo python server.py

Delphi - Serveur HTTP avec Indy

program SimpleWebServer;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  IdHTTPServer,
  IdContext,
  IdCustomHTTPServer;

type
  TSimpleServer = class
    procedure HandleRequest(AContext: TIdContext; 
      ARequestInfo: TIdHTTPRequestInfo; 
      AResponseInfo: TIdHTTPResponseInfo);
  end;

procedure TSimpleServer.HandleRequest(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
begin
  AResponseInfo.ContentType := 'application/json';
  
  if ARequestInfo.Document = '/' then
  begin
    AResponseInfo.ResponseNo := 200;
    AResponseInfo.ContentText := '{"status":"success","message":"Serveur en marche"}';
  end
  else
  begin
    AResponseInfo.ResponseNo := 404;
    AResponseInfo.ContentText := '{"status":"error","message":"Route non trouvée"}';
  end;
end;

var
  Server: TIdHTTPServer;
  Handler: TSimpleServer;
begin
  Server := TIdHTTPServer.Create(nil);
  Handler := TSimpleServer.Create;
  try
    Server.DefaultPort := 80;
    Server.OnCommandGet := Handler.HandleRequest;
    Server.Active := True;
    
    WriteLn('Serveur démarré sur http://localhost:80');
    WriteLn('Appuyez sur Entrée pour arrêter...');
    ReadLn;
  finally
    Server.Free;
    Handler.Free;
  end;
end.

WinDev - Serveur Web Simple

// Procédure principale
PROCEDURE DémarrerServeur()
    // Créer un serveur web sur le port 80
    MonServeur est un httpServeur
    MonServeur..Port = 80
    MonServeur..RacineDocumentaire = fRepExe()
    
    // Ajouter une route pour "/"
    MonServeur..AjouteRoute("/", ProcédureAccueil)
    
    // Démarrer le serveur
    SI MonServeur..Démarre() ALORS
        Info("Serveur démarré sur http://localhost:80")
    SINON
        Erreur("Impossible de démarrer le serveur")
    FIN
FIN

// Procédure pour gérer la route principale
PROCEDURE ProcédureAccueil(Requête est un httpRequête, Réponse est un httpRéponse)
    Réponse..TypeContenu = "application/json"
    Réponse..CodeStatut = 200
    
    vRéponse est un Variant
    vRéponse.status = "success"
    vRéponse.message = "Serveur en marche"
    
    Réponse..Contenu = VariantVersJSON(vRéponse)
FIN

// Appel de la procédure
DémarrerServeur()
💡 Notes Importantes:
  • Le port 80 nécessite généralement des privilèges administrateur/root
  • Sur Linux/Mac, utilisez sudo pour démarrer le serveur
  • Sur Windows, exécutez en tant qu'administrateur
  • Pour tester: ouvrez votre navigateur sur http://localhost/

🔐 Authentification

IzyScan supporte trois méthodes d'authentification (configurables par installation):

1. Sans Authentification

Si les champs login et mot de passe sont vides, les requêtes sont envoyées sans en-têtes d'authentification.

2. Authentification par Token

Si seul le champ mot de passe est configuré, il est traité comme un token d'accès:

Authorization: Bearer votre_token_acces_ici

3. Authentification Basique

Si le login et le mot de passe sont fournis:

Authorization: Basic base64(nom_utilisateur:mot_de_passe)

📦 Points de Terminaison Produits

Tous les points de terminaison doivent accepter du contenu JSON et retourner des réponses JSON.

⚡ En-têtes Requis: Content-Type: application/json et Accept: application/json
GET /api/products

Synchronisation des Produits

Objectif: Récupérer les produits pour la synchronisation avec IzyScan

Paramètres de Requête:
Paramètre Type Obligatoire Description
updated_since string (ISO 8601) Optionnel Pour la synchronisation incrémentale
limit integer Optionnel Nombre maximum de produits (défaut: 1000)
offset integer Optionnel Nombre de produits à ignorer (défaut: 0)
Exemple de Requête:
GET /api/products?updated_since=2025-07-01T12:00:00Z&limit=500&offset=0
Réponse:
{
  "status": "success",
  "data": [
    {
      "server_id": 123,
      "barcode": "1234567890123",
      "name": "Nom du Produit",
      "price": 29.99,
      "promo_price": 25.99,
      "created_at": "2025-07-10T10:00:00Z",
      "updated_at": "2025-07-15T11:30:00Z"
    }
  ],
  "pagination": {
    "total": 1500,
    "limit": 500,
    "offset": 0,
    "has_more": true
  }
}

📝 Exemples de Code

// PHP - GET /api/products
public function getProducts(Request $request) {
    $updatedSince = $request->query('updated_since');
    $limit = min($request->query('limit', 1000), 1000);
    $offset = $request->query('offset', 0);
    
    $query = Product::where('barcode', '!=', null);
    
    if ($updatedSince) {
        $query->where('updated_at', '>=', $updatedSince);
    }
    
    $products = $query->offset($offset)->limit($limit)->get();
    $total = $query->count();
    
    return response()->json([
        'status' => 'success',
        'data' => $products->map(function($product) {
            return [
                'server_id' => $product->id,
                'barcode' => $product->barcode,
                'name' => $product->name,
                'price' => (float) $product->price,
                'promo_price' => (float) $product->promo_price,
                'created_at' => $product->created_at->toISOString(),
                'updated_at' => $product->updated_at->toISOString(),
            ];
        }),
        'pagination' => [
            'total' => $total,
            'limit' => $limit,
            'offset' => $offset,
            'has_more' => ($offset + $limit) < $total,
        ]
    ]);
}
// Node.js - GET /api/products
app.get('/api/products', async (req, res) => {
  const { updated_since, limit = 1000, offset = 0 } = req.query;
  
  let query = db.products.find({ barcode: { $ne: null } });
  
  if (updated_since) {
    query = query.where('updated_at').gte(new Date(updated_since));
  }
  
  const products = await query
    .skip(parseInt(offset))
    .limit(Math.min(parseInt(limit), 1000))
    .exec();
    
  const total = await db.products.countDocuments({ barcode: { $ne: null } });
  
  res.json({
    status: 'success',
    data: products.map(product => ({
      server_id: product._id,
      barcode: product.barcode,
      name: product.name,
      price: parseFloat(product.price),
      promo_price: parseFloat(product.promo_price || 0),
      created_at: product.created_at.toISOString(),
      updated_at: product.updated_at.toISOString(),
    })),
    pagination: {
      total,
      limit: parseInt(limit),
      offset: parseInt(offset),
      has_more: (parseInt(offset) + parseInt(limit)) < total,
    }
  });
});
// C# - GET /api/products
[HttpGet("products")]
public async Task GetProducts(
    [FromQuery] DateTime? updatedSince,
    [FromQuery] int limit = 1000,
    [FromQuery] int offset = 0)
{
    limit = Math.Min(limit, 1000);
    
    var query = _context.Products
        .Where(p => p.Barcode != null);
    
    if (updatedSince.HasValue)
    {
        query = query.Where(p => p.UpdatedAt >= updatedSince.Value);
    }
    
    var total = await query.CountAsync();
    
    var products = await query
        .Skip(offset)
        .Take(limit)
        .Select(p => new
        {
            server_id = p.Id,
            barcode = p.Barcode,
            name = p.Name,
            price = p.Price,
            promo_price = p.PromoPrice,
            created_at = p.CreatedAt.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'"),
            updated_at = p.UpdatedAt.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'")
        })
        .ToListAsync();
    
    return Ok(new
    {
        status = "success",
        data = products,
        pagination = new
        {
            total,
            limit,
            offset,
            has_more = (offset + limit) < total
        }
    });
}
// Java - GET /api/products
@GetMapping("/api/products")
public ResponseEntity getProducts(
    @RequestParam(required = false) String updatedSince,
    @RequestParam(defaultValue = "1000") int limit,
    @RequestParam(defaultValue = "0") int offset) {
    
    limit = Math.min(limit, 1000);
    
    Specification spec = (root, query, cb) -> 
        cb.isNotNull(root.get("barcode"));
    
    if (updatedSince != null) {
        LocalDateTime date = LocalDateTime.parse(updatedSince);
        spec = spec.and((root, query, cb) -> 
            cb.greaterThanOrEqualTo(root.get("updatedAt"), date));
    }
    
    Pageable pageable = PageRequest.of(offset / limit, limit);
    Page page = productRepository.findAll(spec, pageable);
    
    List> products = page.getContent().stream()
        .map(p -> Map.of(
            "server_id", p.getId(),
            "barcode", p.getBarcode(),
            "name", p.getName(),
            "price", p.getPrice(),
            "promo_price", p.getPromoPrice(),
            "created_at", p.getCreatedAt().toString(),
            "updated_at", p.getUpdatedAt().toString()
        ))
        .collect(Collectors.toList());
    
    return ResponseEntity.ok(Map.of(
        "status", "success",
        "data", products,
        "pagination", Map.of(
            "total", page.getTotalElements(),
            "limit", limit,
            "offset", offset,
            "has_more", offset + limit < page.getTotalElements()
        )
    ));
}
# Python - GET /api/products
from flask import Flask, request, jsonify
from datetime import datetime

@app.route('/api/products', methods=['GET'])
def get_products():
    updated_since = request.args.get('updated_since')
    limit = min(int(request.args.get('limit', 1000)), 1000)
    offset = int(request.args.get('offset', 0))
    
    query = Product.query.filter(Product.barcode.isnot(None))
    
    if updated_since:
        date = datetime.fromisoformat(updated_since.replace('Z', '+00:00'))
        query = query.filter(Product.updated_at >= date)
    
    total = query.count()
    products = query.offset(offset).limit(limit).all()
    
    return jsonify({
        'status': 'success',
        'data': [{
            'server_id': p.id,
            'barcode': p.barcode,
            'name': p.name,
            'price': float(p.price),
            'promo_price': float(p.promo_price) if p.promo_price else None,
            'created_at': p.created_at.isoformat() + 'Z',
            'updated_at': p.updated_at.isoformat() + 'Z'
        } for p in products],
        'pagination': {
            'total': total,
            'limit': limit,
            'offset': offset,
            'has_more': (offset + limit) < total
        }
    })
// Delphi - GET /api/products
procedure GetProducts(const UpdatedSince: string; Limit, Offset: Integer);
var
  HttpClient: TNetHTTPClient;
  Response: IHTTPResponse;
  URL: string;
  JSONResponse: TJSONObject;
  Products: TJSONArray;
begin
  HttpClient := TNetHTTPClient.Create(nil);
  try
    URL := Format('https://api.example.com/api/products?limit=%d&offset=%d',
                  [Limit, Offset]);
    
    if UpdatedSince <> '' then
      URL := URL + '&updated_since=' + UpdatedSince;
    
    HttpClient.Accept := 'application/json';
    Response := HttpClient.Get(URL);
    
    if Response.StatusCode = 200 then
    begin
      JSONResponse := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject;
      try
        if JSONResponse.GetValue('status').Value = 'success' then
        begin
          Products := JSONResponse.GetValue('data');
          // Traiter les produits...
        end;
      finally
        JSONResponse.Free;
      end;
    end;
  finally
    HttpClient.Free;
  end;
end;
// WinDev - GET /api/products
PROCEDURE RécupérerProduits(dtDepuisMiseAJour est un DateHeure = "", nLimite est un entier = 1000, nOffset est un entier = 0)
    tabProduits est un tableau de STProduct
    
    sURL = gsURLAPI + "/api/products?"
    sURL += "limit=" + nLimite
    sURL += "&offset=" + nOffset
    
    SI dtDepuisMiseAJour <> "" ALORS
        sURL += "&updated_since=" + DateHeureVersISO8601(dtDepuisMiseAJour)
    FIN
    
    requeteHTTP est un httpRequête
    requeteHTTP.URL = sURL
    requeteHTTP.Méthode = httpGet
    
    tabEntetes est un tableau associatif de chaînes = ObtenirEntetes()
    POUR TOUT sValeur, sCle DE tabEntetes
        requeteHTTP.Entête[sCle] = sValeur
    FIN
    
    réponseHTTP est un httpRéponse = HTTPEnvoie(requeteHTTP)
    
    SI réponseHTTP.CodeEtat = 200 ALORS
        vJSON est un Variant = JSONVersVariant(réponseHTTP.Contenu)
        
        SI vJSON.status = "success" ALORS
            POUR TOUT vProduit DE vJSON.data
                stProduit est un STProduct
                stProduit.server_id = vProduit.server_id
                stProduit.barcode = vProduit.barcode
                stProduit.name = vProduit.name
                stProduit.price = vProduit.price
                
                Ajoute(tabProduits, stProduit)
            FIN
        FIN
    FIN
    
    RENVOYER tabProduits
FIN
PUT POST /api/products/{barcode}/price

Mise à Jour du Prix

Objectif: Mettre à jour le prix d'un produit depuis IzyScan

💡 Note: Ce point de terminaison accepte les méthodes PUT et POST pour une compatibilité maximale.
Paramètres d'URL:
  • barcode: Code-barres du produit
Corps de la Requête:
{
  "barcode": "1234567890123",
  "price": 32.99,
  "server_id": 123
}
Réponse:
{
  "status": "success",
  "message": "Prix mis à jour avec succès",
  "data": {
    "server_id": 123,
    "barcode": "1234567890123",
    "old_price": 29.99,
    "new_price": 32.99,
    "updated_at": "2025-07-16T15:30:00Z"
  }
}

📝 Exemples de Code

// PHP - PUT ou POST /api/products/{barcode}/price
public function updatePrice(Request $request, $barcode) {
    $validated = $request->validate([
        'price' => 'required|numeric|min:0',
        'server_id' => 'required|integer'
    ]);
    
    $product = Product::where('barcode', $barcode)->first();
    
    if (!$product) {
        return response()->json([
            'status' => 'error',
            'error_code' => 'PRODUCT_NOT_FOUND',
            'message' => 'Produit non trouvé'
        ], 404);
    }
    
    $oldPrice = $product->price;
    $product->price = $validated['price'];
    $product->save();
    
    return response()->json([
        'status' => 'success',
        'message' => 'Prix mis à jour avec succès',
        'data' => [
            'server_id' => $product->id,
            'barcode' => $product->barcode,
            'old_price' => (float) $oldPrice,
            'new_price' => (float) $product->price,
            'updated_at' => $product->updated_at->toISOString(),
        ]
    ]);
}

// Dans routes/api.php
Route::match(['PUT', 'POST'], '/api/products/{barcode}/price', 'ProductController@updatePrice');
// Node.js - PUT ou POST /api/products/:barcode/price
app.put('/api/products/:barcode/price', updatePrice);
app.post('/api/products/:barcode/price', updatePrice);

async function updatePrice(req, res) {
  const { barcode } = req.params;
  const { price, server_id } = req.body;
  
  if (!price || price < 0) {
    return res.status(400).json({
      status: 'error',
      error_code: 'INVALID_PRICE',
      message: 'Le prix doit être un nombre positif'
    });
  }
  
  const product = await db.products.findOne({ barcode });
  
  if (!product) {
    return res.status(404).json({
      status: 'error',
      error_code: 'PRODUCT_NOT_FOUND',
      message: 'Produit non trouvé'
    });
  }
  
  const oldPrice = product.price;
  product.price = price;
  product.updated_at = new Date();
  await product.save();
  
  res.json({
    status: 'success',
    message: 'Prix mis à jour avec succès',
    data: {
      server_id: product._id,
      barcode: product.barcode,
      old_price: parseFloat(oldPrice),
      new_price: parseFloat(price),
      updated_at: product.updated_at.toISOString(),
    }
  });
}
// C# - PUT ou POST /api/products/{barcode}/price
[HttpPut("products/{barcode}/price")]
[HttpPost("products/{barcode}/price")]
public async Task UpdatePrice(
    string barcode,
    [FromBody] PriceUpdateRequest request)
{
    if (request.Price < 0)
    {
        return BadRequest(new ErrorResponse
        {
            ErrorCode = "INVALID_PRICE",
            Message = "Le prix doit être un nombre positif"
        });
    }
    
    var product = await _context.Products
        .FirstOrDefaultAsync(p => p.Barcode == barcode);
    
    if (product == null)
    {
        return NotFound(new ErrorResponse
        {
            ErrorCode = "PRODUCT_NOT_FOUND",
            Message = "Produit non trouvé"
        });
    }
    
    var oldPrice = product.Price;
    product.Price = request.Price;
    product.UpdatedAt = DateTime.UtcNow;
    
    await _context.SaveChangesAsync();
    
    return Ok(new
    {
        status = "success",
        message = "Prix mis à jour avec succès",
        data = new
        {
            server_id = product.Id,
            barcode = product.Barcode,
            old_price = oldPrice,
            new_price = product.Price,
            updated_at = product.UpdatedAt.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'")
        }
    });
}
// Java - PUT ou POST /api/products/{barcode}/price
@PutMapping("/api/products/{barcode}/price")
@PostMapping("/api/products/{barcode}/price")
public ResponseEntity updatePrice(
    @PathVariable String barcode,
    @RequestBody PriceUpdateRequest request) {
    
    if (request.getPrice().compareTo(BigDecimal.ZERO) < 0) {
        return ResponseEntity.badRequest().body(Map.of(
            "status", "error",
            "error_code", "INVALID_PRICE",
            "message", "Le prix doit être un nombre positif"
        ));
    }
    
    Optional productOpt = productRepository.findByBarcode(barcode);
    
    if (productOpt.isEmpty()) {
        return ResponseEntity.status(404).body(Map.of(
            "status", "error",
            "error_code", "PRODUCT_NOT_FOUND",
            "message", "Produit non trouvé"
        ));
    }
    
    Product product = productOpt.get();
    BigDecimal oldPrice = product.getPrice();
    product.setPrice(request.getPrice());
    product.setUpdatedAt(LocalDateTime.now());
    
    productRepository.save(product);
    
    return ResponseEntity.ok(Map.of(
        "status", "success",
        "message", "Prix mis à jour avec succès",
        "data", Map.of(
            "server_id", product.getId(),
            "barcode", product.getBarcode(),
            "old_price", oldPrice,
            "new_price", product.getPrice(),
            "updated_at", product.getUpdatedAt().toString()
        )
    ));
}
# Python - PUT ou POST /api/products/{barcode}/price
@app.route('/api/products//price', methods=['PUT', 'POST'])
def update_price(barcode):
    data = request.get_json()
    
    if 'price' not in data or data['price'] < 0:
        return jsonify({
            'status': 'error',
            'error_code': 'INVALID_PRICE',
            'message': 'Le prix doit être un nombre positif'
        }), 400
    
    product = Product.query.filter_by(barcode=barcode).first()
    
    if not product:
        return jsonify({
            'status': 'error',
            'error_code': 'PRODUCT_NOT_FOUND',
            'message': 'Produit non trouvé'
        }), 404
    
    old_price = product.price
    product.price = data['price']
    product.updated_at = datetime.utcnow()
    
    db.session.commit()
    
    return jsonify({
        'status': 'success',
        'message': 'Prix mis à jour avec succès',
        'data': {
            'server_id': product.id,
            'barcode': product.barcode,
            'old_price': float(old_price),
            'new_price': float(product.price),
            'updated_at': product.updated_at.isoformat() + 'Z'
        }
    })
// Delphi - PUT ou POST /api/products/{barcode}/price
procedure UpdatePrice(const Barcode: string; NewPrice: Currency; ServerID: Integer);
var
  HttpClient: TNetHTTPClient;
  Response: IHTTPResponse;
  RequestBody: TJSONObject;
  ResponseJSON: TJSONObject;
begin
  HttpClient := TNetHTTPClient.Create(nil);
  try
    RequestBody := TJSONObject.Create;
    try
      RequestBody.AddPair('barcode', Barcode);
      RequestBody.AddPair('price', TJSONNumber.Create(NewPrice));
      RequestBody.AddPair('server_id', TJSONNumber.Create(ServerID));
      
      HttpClient.ContentType := 'application/json';
      HttpClient.Accept := 'application/json';
      
      Response := HttpClient.Put(
        Format('https://api.example.com/api/products/%s/price', [Barcode]),
        TStringStream.Create(RequestBody.ToString, TEncoding.UTF8)
      );
      
      if Response.StatusCode = 200 then
      begin
        ResponseJSON := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject;
        try
          ShowMessage('Prix mis à jour avec succès');
        finally
          ResponseJSON.Free;
        end;
      end;
    finally
      RequestBody.Free;
    end;
  finally
    HttpClient.Free;
  end;
end;
// WinDev - PUT ou POST /api/products/{barcode}/price
PROCEDURE MettreAJourPrix(sBarcode est une chaîne, mNouveauPrix est un monétaire, nServerID est un entier) : booléen
    vRequete est un Variant
    vRequete.barcode = sBarcode
    vRequete.price = mNouveauPrix
    vRequete.server_id = nServerID
    
    sJSONRequete = VariantVersJSON(vRequete)
    
    requeteHTTP est un httpRequête
    requeteHTTP.URL = gsURLAPI + "/api/products/" + sBarcode + "/price"
    requeteHTTP.Méthode = httpPut  // ou httpPost
    requeteHTTP.Contenu = sJSONRequete
    
    tabEntetes est un tableau associatif de chaînes = ObtenirEntetes()
    POUR TOUT sValeur, sCle DE tabEntetes
        requeteHTTP.Entête[sCle] = sValeur
    FIN
    
    réponseHTTP est un httpRéponse = HTTPEnvoie(requeteHTTP)
    
    SI réponseHTTP.CodeEtat = 200 ALORS
        vRéponse est un Variant = JSONVersVariant(réponseHTTP.Contenu)
        SI vRéponse.status = "success" ALORS
            Info("Prix mis à jour", "Ancien: " + vRéponse.data.old_price, "Nouveau: " + vRéponse.data.new_price)
            RENVOYER Vrai
        FIN
    FIN
    
    RENVOYER Faux
FIN

📋 Points de Terminaison Inventaires

Ces points de terminaison gèrent la création et la gestion des inventaires depuis IzyScan.

POST /api/inventories

Création d'Inventaire

Objectif: Recevoir les données d'inventaire depuis IzyScan

Corps de la Requête:
{
  "name": "Nom de l'Inventaire",
  "device_id": "device_001",
  "status": "finished",
  "created_at": "2025-07-16T10:00:00Z",
  "updated_at": "2025-07-16T14:00:00Z",
  "started_at": "2025-07-16T10:05:00Z",
  "finished_at": "2025-07-16T14:00:00Z",
  "lines": [
    {
      "barcode": "1234567890123",
      "product_name": "Nom du Produit",
      "quantity": 15.5,
      "scanned_at": "2025-07-16T13:45:00Z"
    }
  ]
}
Réponse:
{
  "status": "success",
  "message": "Inventaire créé avec succès",
  "data": {
    "inventory_id": 456,
    "lines_processed": 25,
    "total_quantity": 150.75
  }
}

Statut HTTP: 201 Created

📝 Exemples de Code

// PHP - POST /api/inventories
public function createInventory(Request $request) {
    $validated = $request->validate([
        'name' => 'required|string|max:100',
        'device_id' => 'required|string|max:50',
        'status' => 'required|in:waiting,progress,finished',
        'lines' => 'required|array',
        'lines.*.barcode' => 'required|string',
        'lines.*.product_name' => 'required|string',
        'lines.*.quantity' => 'required|numeric|min:0',
        'lines.*.scanned_at' => 'required|date'
    ]);
    
    DB::beginTransaction();
    
    try {
        $inventory = Inventory::create([
            'name' => $validated['name'],
            'device_id' => $validated['device_id'],
            'status' => $validated['status'],
            'created_at' => $request->created_at ?? now(),
            'updated_at' => $request->updated_at ?? now(),
            'started_at' => $request->started_at,
            'finished_at' => $request->finished_at
        ]);
        
        $totalQuantity = 0;
        foreach ($validated['lines'] as $line) {
            InventoryLine::create([
                'inventory_id' => $inventory->id,
                'barcode' => $line['barcode'],
                'product_name' => $line['product_name'],
                'quantity' => $line['quantity'],
                'scanned_at' => $line['scanned_at']
            ]);
            $totalQuantity += $line['quantity'];
        }
        
        DB::commit();
        
        return response()->json([
            'status' => 'success',
            'message' => 'Inventaire créé avec succès',
            'data' => [
                'inventory_id' => $inventory->id,
                'lines_processed' => count($validated['lines']),
                'total_quantity' => $totalQuantity
            ]
        ], 201);
        
    } catch (\Exception $e) {
        DB::rollBack();
        return response()->json([
            'status' => 'error',
            'message' => 'Erreur lors de la création de l\'inventaire'
        ], 500);
    }
}
// Node.js - POST /api/inventories
app.post('/api/inventories', async (req, res) => {
  const { name, device_id, status, lines, created_at, updated_at, started_at, finished_at } = req.body;
  
  // Validation
  if (!name || !device_id || !status || !lines || !Array.isArray(lines)) {
    return res.status(400).json({
      status: 'error',
      message: 'Données requises manquantes'
    });
  }
  
  const session = await mongoose.startSession();
  session.startTransaction();
  
  try {
    const inventory = new Inventory({
      name,
      device_id,
      status,
      created_at: created_at || new Date(),
      updated_at: updated_at || new Date(),
      started_at,
      finished_at
    });
    
    await inventory.save({ session });
    
    let totalQuantity = 0;
    const inventoryLines = [];
    
    for (const line of lines) {
      const inventoryLine = new InventoryLine({
        inventory_id: inventory._id,
        barcode: line.barcode,
        product_name: line.product_name,
        quantity: line.quantity,
        scanned_at: line.scanned_at
      });
      
      inventoryLines.push(inventoryLine);
      totalQuantity += line.quantity;
    }
    
    await InventoryLine.insertMany(inventoryLines, { session });
    
    await session.commitTransaction();
    
    res.status(201).json({
      status: 'success',
      message: 'Inventaire créé avec succès',
      data: {
        inventory_id: inventory._id,
        lines_processed: lines.length,
        total_quantity: totalQuantity
      }
    });
    
  } catch (error) {
    await session.abortTransaction();
    res.status(500).json({
      status: 'error',
      message: 'Erreur lors de la création de l\'inventaire'
    });
  } finally {
    session.endSession();
  }
});
// C# - POST /api/inventories
[HttpPost("inventories")]
public async Task CreateInventory([FromBody] InventoryRequest request)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(new ErrorResponse
        {
            ErrorCode = "VALIDATION_ERROR",
            Message = "Données invalides"
        });
    }
    
    using var transaction = await _context.Database.BeginTransactionAsync();
    
    try
    {
        var inventory = new Inventory
        {
            Name = request.Name,
            DeviceId = request.DeviceId,
            Status = request.Status,
            CreatedAt = request.CreatedAt,
            UpdatedAt = request.UpdatedAt,
            StartedAt = request.StartedAt,
            FinishedAt = request.FinishedAt
        };
        
        _context.Inventories.Add(inventory);
        await _context.SaveChangesAsync();
        
        var inventoryLines = request.Lines.Select(line => new InventoryLine
        {
            InventoryId = inventory.Id,
            Barcode = line.Barcode,
            ProductName = line.ProductName,
            Quantity = line.Quantity,
            ScannedAt = line.ScannedAt
        }).ToList();
        
        _context.InventoryLines.AddRange(inventoryLines);
        await _context.SaveChangesAsync();
        
        await transaction.CommitAsync();
        
        var totalQuantity = request.Lines.Sum(l => l.Quantity);
        
        return StatusCode(201, new
        {
            status = "success",
            message = "Inventaire créé avec succès",
            data = new
            {
                inventory_id = inventory.Id,
                lines_processed = request.Lines.Count,
                total_quantity = totalQuantity
            }
        });
    }
    catch (Exception)
    {
        await transaction.RollbackAsync();
        return StatusCode(500, new ErrorResponse
        {
            ErrorCode = "INTERNAL_ERROR",
            Message = "Erreur lors de la création de l'inventaire"
        });
    }
}
// Java - POST /api/inventories
@PostMapping("/api/inventories")
@Transactional
public ResponseEntity createInventory(@RequestBody @Valid InventoryRequest request) {
    try {
        Inventory inventory = new Inventory();
        inventory.setName(request.getName());
        inventory.setDeviceId(request.getDeviceId());
        inventory.setStatus(request.getStatus());
        inventory.setCreatedAt(request.getCreatedAt());
        inventory.setUpdatedAt(request.getUpdatedAt());
        inventory.setStartedAt(request.getStartedAt());
        inventory.setFinishedAt(request.getFinishedAt());
        
        inventory = inventoryRepository.save(inventory);
        
        List lines = new ArrayList<>();
        BigDecimal totalQuantity = BigDecimal.ZERO;
        
        for (InventoryLineRequest lineRequest : request.getLines()) {
            InventoryLine line = new InventoryLine();
            line.setInventory(inventory);
            line.setBarcode(lineRequest.getBarcode());
            line.setProductName(lineRequest.getProductName());
            line.setQuantity(lineRequest.getQuantity());
            line.setScannedAt(lineRequest.getScannedAt());
            
            lines.add(line);
            totalQuantity = totalQuantity.add(lineRequest.getQuantity());
        }
        
        inventoryLineRepository.saveAll(lines);
        
        return ResponseEntity.status(201).body(Map.of(
            "status", "success",
            "message", "Inventaire créé avec succès",
            "data", Map.of(
                "inventory_id", inventory.getId(),
                "lines_processed", lines.size(),
                "total_quantity", totalQuantity
            )
        ));
        
    } catch (Exception e) {
        return ResponseEntity.status(500).body(Map.of(
            "status", "error",
            "message", "Erreur lors de la création de l'inventaire"
        ));
    }
}
# Python - POST /api/inventories
@app.route('/api/inventories', methods=['POST'])
def create_inventory():
    data = request.get_json()
    
    # Validation
    required_fields = ['name', 'device_id', 'status', 'lines']
    for field in required_fields:
        if field not in data:
            return jsonify({
                'status': 'error',
                'message': f'Champ requis manquant: {field}'
            }), 400
    
    try:
        # Début de transaction
        inventory = Inventory(
            name=data['name'],
            device_id=data['device_id'],
            status=data['status'],
            created_at=datetime.fromisoformat(data.get('created_at', datetime.utcnow().isoformat())),
            updated_at=datetime.fromisoformat(data.get('updated_at', datetime.utcnow().isoformat())),
            started_at=datetime.fromisoformat(data['started_at']) if 'started_at' in data else None,
            finished_at=datetime.fromisoformat(data['finished_at']) if 'finished_at' in data else None
        )
        
        db.session.add(inventory)
        db.session.flush()  # Pour obtenir l'ID
        
        total_quantity = 0
        for line_data in data['lines']:
            line = InventoryLine(
                inventory_id=inventory.id,
                barcode=line_data['barcode'],
                product_name=line_data['product_name'],
                quantity=line_data['quantity'],
                scanned_at=datetime.fromisoformat(line_data['scanned_at'])
            )
            db.session.add(line)
            total_quantity += line_data['quantity']
        
        db.session.commit()
        
        return jsonify({
            'status': 'success',
            'message': 'Inventaire créé avec succès',
            'data': {
                'inventory_id': inventory.id,
                'lines_processed': len(data['lines']),
                'total_quantity': total_quantity
            }
        }), 201
        
    except Exception as e:
        db.session.rollback()
        return jsonify({
            'status': 'error',
            'message': 'Erreur lors de la création de l\'inventaire'
        }), 500
// Delphi - POST /api/inventories
procedure CreateInventory(const InventoryData: TInventoryRequest);
var
  HttpClient: TNetHTTPClient;
  Response: IHTTPResponse;
  RequestJSON, LineJSON: TJSONObject;
  LinesArray: TJSONArray;
  Line: TInventoryLine;
  ResponseJSON: TJSONObject;
begin
  HttpClient := TNetHTTPClient.Create(nil);
  try
    RequestJSON := TJSONObject.Create;
    try
      RequestJSON.AddPair('name', InventoryData.Name);
      RequestJSON.AddPair('device_id', InventoryData.DeviceId);
      RequestJSON.AddPair('status', InventoryData.Status);
      RequestJSON.AddPair('created_at', DateTimeToISO8601(InventoryData.CreatedAt));
      RequestJSON.AddPair('updated_at', DateTimeToISO8601(InventoryData.UpdatedAt));
      RequestJSON.AddPair('started_at', DateTimeToISO8601(InventoryData.StartedAt));
      RequestJSON.AddPair('finished_at', DateTimeToISO8601(InventoryData.FinishedAt));
      
      LinesArray := TJSONArray.Create;
      for Line in InventoryData.Lines do
      begin
        LineJSON := TJSONObject.Create;
        LineJSON.AddPair('barcode', Line.Barcode);
        LineJSON.AddPair('product_name', Line.ProductName);
        LineJSON.AddPair('quantity', TJSONNumber.Create(Line.Quantity));
        LineJSON.AddPair('scanned_at', DateTimeToISO8601(Line.ScannedAt));
        LinesArray.AddElement(LineJSON);
      end;
      RequestJSON.AddPair('lines', LinesArray);
      
      HttpClient.ContentType := 'application/json';
      HttpClient.Accept := 'application/json';
      
      Response := HttpClient.Post(
        'https://api.example.com/api/inventories',
        TStringStream.Create(RequestJSON.ToString, TEncoding.UTF8)
      );
      
      if Response.StatusCode = 201 then
      begin
        ResponseJSON := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject;
        try
          if ResponseJSON.GetValue('status').Value = 'success' then
            ShowMessage('Inventaire créé avec succès');
        finally
          ResponseJSON.Free;
        end;
      end;
    finally
      RequestJSON.Free;
    end;
  finally
    HttpClient.Free;
  end;
end;
// WinDev - POST /api/inventories
PROCEDURE CréerInventaire(sNom est une chaîne, sDeviceID est une chaîne, tabLignes est un tableau de STInventoryLine) : entier
    vInventaire est un Variant
    vInventaire.name = sNom
    vInventaire.device_id = sDeviceID
    vInventaire.status = "finished"
    vInventaire.created_at = DateHeureVersISO8601(DateHeureSys())
    vInventaire.updated_at = DateHeureVersISO8601(DateHeureSys())
    vInventaire.started_at = DateHeureVersISO8601(DateHeureSys())
    vInventaire.finished_at = DateHeureVersISO8601(DateHeureSys())
    
    Dimension(vInventaire.lines, 0)
    POUR TOUT stLigne DE tabLignes
        vLigne est un Variant
        vLigne.barcode = stLigne.barcode
        vLigne.product_name = stLigne.product_name
        vLigne.quantity = stLigne.quantity
        vLigne.scanned_at = stLigne.scanned_at
        
        Ajoute(vInventaire.lines, vLigne)
    FIN
    
    sJSONRequete = VariantVersJSON(vInventaire)
    
    requeteHTTP est un httpRequête
    requeteHTTP.URL = gsURLAPI + "/api/inventories"
    requeteHTTP.Méthode = httpPost
    requeteHTTP.Contenu = sJSONRequete
    
    tabEntetes est un tableau associatif de chaînes = ObtenirEntetes()
    POUR TOUT sValeur, sCle DE tabEntetes
        requeteHTTP.Entête[sCle] = sValeur
    FIN
    
    réponseHTTP est un httpRéponse = HTTPEnvoie(requeteHTTP)
    
    SI réponseHTTP.CodeEtat = 201 ALORS
        vRéponse est un Variant = JSONVersVariant(réponseHTTP.Contenu)
        SI vRéponse.status = "success" ALORS
            Info("Inventaire créé", "ID: " + vRéponse.data.inventory_id)
            RENVOYER vRéponse.data.inventory_id
        FIN
    FIN
    
    RENVOYER 0
FIN

📊 Modèles de Données

Modèle Produit

Champ Type Obligatoire Description
server_id integer Obligatoire Identifiant unique dans votre système
barcode string (max 50) Obligatoire Code-barres du produit
name string (max 255) Obligatoire Nom du produit
price decimal Obligatoire Prix actuel (positif)
promo_price decimal Optionnel Prix promotionnel (positif)
created_at datetime (ISO 8601) Optionnel Date de création
updated_at datetime (ISO 8601) Optionnel Date de dernière modification

Modèle Inventaire

Champ Type Obligatoire Description
name string (max 100) Obligatoire Nom de l'inventaire
device_id string (max 50) Obligatoire ID du dispositif de scan
status enum Obligatoire ['waiting', 'progress', 'finished']
lines array Obligatoire Lignes d'inventaire

⚠️ Gestion des Erreurs

Format de Réponse d'Erreur Standard

{
  "status": "error",
  "error_code": "CODE_ERREUR",
  "message": "Message d'erreur lisible",
  "details": {
    "field": "Détails supplémentaires"
  }
}

Codes de Statut HTTP

Code Description
200 OK Requêtes GET/PUT réussies
201 Created Requêtes POST réussies
400 Bad Request Données de requête invalides
401 Unauthorized Authentification requise ou échouée
404 Not Found Ressource non trouvée
422 Unprocessable Entity Erreurs de validation
500 Internal Server Error Erreur serveur

Codes d'Erreur Courants

✨ Meilleures Pratiques

1. 🚀 Optimisation des Performances

2. 🔄 Cohérence des Données

3. 🔐 Sécurité

4. 🧪 Tests

Liste de Vérification des Tests

Données de Test Échantillon

{
  "test_product": {
    "server_id": 999,
    "barcode": "9999999999999",
    "name": "Produit Test",
    "price": 10.99,
    "promo_price": 8.99
  },
  "test_inventory": {
    "name": "Inventaire Test",
    "device_id": "test_device",
    "status": "finished",
    "lines": [
      {
        "barcode": "9999999999999",
        "product_name": "Produit Test",
        "quantity": 5.0,
        "scanned_at": "2025-07-16T12:00:00Z"
      }
    ]
  }
}

📞 Support

Pour le support technique et les questions concernant cette intégration: