- Menü aus Header entfernt, neuer Eintrag unter Produkte > Scanner - Barcode-Erkennung: patchSize medium, grösserer Scan-Bereich, höhere Frequenz - Timeout-Hinweis nach 8s wenn kein Barcode erkannt wird - Tab-Wechsel (Order/Shop/Inventur) ohne Seitenreload, Kamera bleibt aktiv - PWA: gleiche Tab-Logik, Buttons statt Links - Changelog und README aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
186 lines
5.5 KiB
PHP
Executable file
186 lines
5.5 KiB
PHP
Executable file
<?php
|
|
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
|
*
|
|
* AJAX: Find product by barcode
|
|
* Searches in: product.barcode, product_fournisseur_price (supplier EAN)
|
|
*/
|
|
|
|
if (!defined('NOTOKENRENEWAL')) {
|
|
define('NOTOKENRENEWAL', '1');
|
|
}
|
|
if (!defined('NOREQUIREMENU')) {
|
|
define('NOREQUIREMENU', '1');
|
|
}
|
|
if (!defined('NOREQUIREHTML')) {
|
|
define('NOREQUIREHTML', '1');
|
|
}
|
|
if (!defined('NOREQUIREAJAX')) {
|
|
define('NOREQUIREAJAX', '1');
|
|
}
|
|
|
|
// Load Dolibarr environment
|
|
$res = 0;
|
|
if (!$res && file_exists("../../main.inc.php")) {
|
|
$res = @include "../../main.inc.php";
|
|
}
|
|
if (!$res && file_exists("../../../main.inc.php")) {
|
|
$res = @include "../../../main.inc.php";
|
|
}
|
|
if (!$res && file_exists("../../../../main.inc.php")) {
|
|
$res = @include "../../../../main.inc.php";
|
|
}
|
|
if (!$res) {
|
|
die(json_encode(['success' => false, 'error' => 'Failed to load Dolibarr']));
|
|
}
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
// Security check
|
|
if (!$user->hasRight('produit', 'lire') && !$user->hasRight('product', 'read')) {
|
|
echo json_encode(['success' => false, 'error' => 'Access denied']);
|
|
exit;
|
|
}
|
|
|
|
$barcode = GETPOST('barcode', 'alphanohtml');
|
|
|
|
if (empty($barcode)) {
|
|
echo json_encode(['success' => false, 'error' => 'No barcode provided']);
|
|
exit;
|
|
}
|
|
|
|
$product = new Product($db);
|
|
$found = false;
|
|
|
|
// Build list of barcode variants to search
|
|
// QuaggaJS sometimes adds a check digit to 12-digit codes, making them 13 digits
|
|
$barcodesToSearch = array($barcode);
|
|
|
|
// If 13 digits, also try without last digit (might be added check digit)
|
|
if (strlen($barcode) == 13 && preg_match('/^\d{13}$/', $barcode)) {
|
|
$barcodesToSearch[] = substr($barcode, 0, 12);
|
|
}
|
|
|
|
// If 12 digits, also try with EAN-13 check digit
|
|
if (strlen($barcode) == 12 && preg_match('/^\d{12}$/', $barcode)) {
|
|
$checkDigit = calculateEAN13CheckDigit($barcode);
|
|
$barcodesToSearch[] = $barcode . $checkDigit;
|
|
}
|
|
|
|
// 1. Search by product barcode (try all variants)
|
|
foreach ($barcodesToSearch as $searchCode) {
|
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product WHERE barcode = '".$db->escape($searchCode)."' AND entity IN (".getEntity('product').")";
|
|
$resql = $db->query($sql);
|
|
if ($resql && $db->num_rows($resql) > 0) {
|
|
$obj = $db->fetch_object($resql);
|
|
$product->fetch($obj->rowid);
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 2. Search by supplier barcode (EAN in product_fournisseur_price) - try all variants
|
|
if (!$found) {
|
|
foreach ($barcodesToSearch as $searchCode) {
|
|
$sql = "SELECT DISTINCT pfp.fk_product FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON pfp.fk_product = p.rowid";
|
|
$sql .= " WHERE (pfp.barcode = '".$db->escape($searchCode)."' OR pfp.ref_fourn = '".$db->escape($searchCode)."')";
|
|
$sql .= " AND p.entity IN (".getEntity('product').")";
|
|
|
|
$resql = $db->query($sql);
|
|
if ($resql && $db->num_rows($resql) > 0) {
|
|
$obj = $db->fetch_object($resql);
|
|
$product->fetch($obj->fk_product);
|
|
$found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Search by product ref (in case barcode equals ref)
|
|
if (!$found) {
|
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product WHERE ref = '".$db->escape($barcode)."' AND entity IN (".getEntity('product').")";
|
|
$resql = $db->query($sql);
|
|
if ($resql && $db->num_rows($resql) > 0) {
|
|
$obj = $db->fetch_object($resql);
|
|
$product->fetch($obj->rowid);
|
|
$found = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate EAN-13 check digit for a 12-digit code
|
|
*/
|
|
function calculateEAN13CheckDigit($code) {
|
|
$sum = 0;
|
|
for ($i = 0; $i < 12; $i++) {
|
|
$sum += intval($code[$i]) * ($i % 2 === 0 ? 1 : 3);
|
|
}
|
|
return (10 - ($sum % 10)) % 10;
|
|
}
|
|
|
|
if (!$found) {
|
|
echo json_encode(['success' => false, 'error' => 'Product not found']);
|
|
exit;
|
|
}
|
|
|
|
// Get supplier prices
|
|
$suppliers = [];
|
|
$productFourn = new ProductFournisseur($db);
|
|
$resql = $productFourn->list_product_fournisseur_price($product->id);
|
|
|
|
if ($resql) {
|
|
foreach ($resql as $supplierPrice) {
|
|
// Get supplier info mit shop_url aus extrafields
|
|
$supplierSql = "SELECT s.nom, s.url, se.shop_url
|
|
FROM ".MAIN_DB_PREFIX."societe s
|
|
LEFT JOIN ".MAIN_DB_PREFIX."societe_extrafields se ON se.fk_object = s.rowid
|
|
WHERE s.rowid = ".((int) $supplierPrice->fourn_id);
|
|
$supplierRes = $db->query($supplierSql);
|
|
$supplierObj = $db->fetch_object($supplierRes);
|
|
|
|
// ref_fourn kann unter verschiedenen Namen im Objekt sein
|
|
$refFourn = '';
|
|
if (!empty($supplierPrice->ref_supplier)) {
|
|
$refFourn = $supplierPrice->ref_supplier;
|
|
} elseif (!empty($supplierPrice->fourn_ref)) {
|
|
$refFourn = $supplierPrice->fourn_ref;
|
|
} elseif (!empty($supplierPrice->ref_fourn)) {
|
|
$refFourn = $supplierPrice->ref_fourn;
|
|
}
|
|
|
|
// shop_url aus Extrafields verwenden, sonst normale URL
|
|
$shopUrl = $supplierObj->shop_url ?? $supplierObj->url ?? '';
|
|
|
|
$suppliers[] = [
|
|
'id' => $supplierPrice->fourn_id,
|
|
'name' => $supplierObj->nom ?? 'Unknown',
|
|
'price' => (float) $supplierPrice->fourn_price,
|
|
'ref_fourn' => $refFourn,
|
|
'url' => $shopUrl
|
|
];
|
|
}
|
|
|
|
// Sort by price (cheapest first)
|
|
usort($suppliers, function($a, $b) {
|
|
return $a['price'] <=> $b['price'];
|
|
});
|
|
}
|
|
|
|
// Response
|
|
$response = [
|
|
'success' => true,
|
|
'product' => [
|
|
'id' => $product->id,
|
|
'ref' => $product->ref,
|
|
'label' => $product->label,
|
|
'barcode' => $product->barcode,
|
|
'stock' => (float) $product->stock_reel,
|
|
'stock_unit' => $product->fk_unit ? $product->getLabelOfUnit() : 'Stk',
|
|
'suppliers' => $suppliers
|
|
]
|
|
];
|
|
|
|
echo json_encode($response);
|