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
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.
Cette section vous montre comment créer et démarrer un serveur web simple sur le port 80 dans différents langages de programmation.
// 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
?>
// 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
// 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)
// 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
# 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
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.
// 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()
sudo pour démarrer le serveurhttp://localhost/IzyScan supporte trois méthodes d'authentification (configurables par installation):
Si les champs login et mot de passe sont vides, les requêtes sont envoyées sans en-têtes d'authentification.
Si seul le champ mot de passe est configuré, il est traité comme un token d'accès:
Authorization: Bearer votre_token_acces_ici
Si le login et le mot de passe sont fournis:
Authorization: Basic base64(nom_utilisateur:mot_de_passe)
Tous les points de terminaison doivent accepter du contenu JSON et retourner des réponses JSON.
Content-Type: application/json et Accept: application/json
Objectif: Récupérer les produits pour la synchronisation avec IzyScan
| 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) |
GET /api/products?updated_since=2025-07-01T12:00:00Z&limit=500&offset=0
{
"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
}
}
// 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
# 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
Objectif: Mettre à jour le prix d'un produit depuis IzyScan
barcode: Code-barres du produit{
"barcode": "1234567890123",
"price": 32.99,
"server_id": 123
}
{
"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"
}
}
// 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
Ces points de terminaison gèrent la création et la gestion des inventaires depuis IzyScan.
Objectif: Recevoir les données d'inventaire depuis IzyScan
{
"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"
}
]
}
{
"status": "success",
"message": "Inventaire créé avec succès",
"data": {
"inventory_id": 456,
"lines_processed": 25,
"total_quantity": 150.75
}
}
Statut HTTP: 201 Created
// 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
| 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 |
| 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 |
{
"status": "error",
"error_code": "CODE_ERREUR",
"message": "Message d'erreur lisible",
"details": {
"field": "Détails supplémentaires"
}
}
| 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 |
INVALID_CREDENTIALS - Authentification échouéePRODUCT_NOT_FOUND - Produit avec le code-barres donné non trouvéINVALID_BARCODE - Format du code-barres invalideINVALID_PRICE - Valeur du prix invalideDUPLICATE_BARCODE - Le code-barres existe déjàVALIDATION_ERROR - Validation des données échouéebarcode, updated_at{
"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"
}
]
}
}
Pour le support technique et les questions concernant cette intégration: