importzugferd/datanorm_update.php
2026-02-01 09:25:12 +01:00

868 lines
34 KiB
PHP

<?php
/* Copyright (C) 2026 ZUGFeRD Import Module
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*/
/**
* \file datanorm_update.php
* \ingroup importzugferd
* \brief Mass update products from Datanorm catalogs
*/
// Load Dolibarr environment
$res = 0;
if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) {
$res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
}
$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME'];
$tmp2 = realpath(__FILE__);
$i = strlen($tmp) - 1;
$j = strlen($tmp2) - 1;
while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) {
$i--;
$j--;
}
if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) {
$res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
}
if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) {
$res = @include dirname(substr($tmp, 0, ($i + 1)))."/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 && file_exists("../../../main.inc.php")) {
$res = @include "../../../main.inc.php";
}
if (!$res) {
die("Include of main fails");
}
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
dol_include_once('/importzugferd/class/datanorm.class.php');
dol_include_once('/importzugferd/lib/importzugferd.lib.php');
// Load translations
$langs->loadLangs(array("importzugferd@importzugferd", "products", "bills"));
// Security check
if (!$user->hasRight('produit', 'creer')) {
accessforbidden();
}
// Get parameters
$action = GETPOST('action', 'aZ09');
$fk_soc = GETPOSTINT('fk_soc');
$search_mode = GETPOST('search_mode', 'alpha') ?: 'supplier'; // supplier, manual
$search_term = GETPOST('search_term', 'alphanohtml');
$search_by_name = GETPOSTINT('search_by_name');
$search_by_ean = GETPOSTINT('search_by_ean');
$search_by_ref = GETPOSTINT('search_by_ref');
// Filters for what to update
$filter_price = GETPOSTISSET('filter_price') ? GETPOSTINT('filter_price') : 1;
$filter_description = GETPOSTISSET('filter_description') ? GETPOSTINT('filter_description') : 1;
$filter_label = GETPOSTISSET('filter_label') ? GETPOSTINT('filter_label') : 0;
// Only show differences
$only_differences = GETPOSTINT('only_differences');
// Initialize objects
$form = new Form($db);
$formcompany = new FormCompany($db);
$datanorm = new Datanorm($db);
// Store pending changes in session
if (!isset($_SESSION['datanorm_pending_changes'])) {
$_SESSION['datanorm_pending_changes'] = array();
}
/*
* Actions
*/
// Apply single row update
if ($action == 'apply_single' && GETPOSTINT('product_id') && GETPOST('datanorm_key', 'alphanohtml')) {
$product_id = GETPOSTINT('product_id');
$datanorm_key = GETPOST('datanorm_key', 'alphanohtml');
$apply_price = GETPOSTINT('apply_price');
$apply_description = GETPOSTINT('apply_description');
$apply_label = GETPOSTINT('apply_label');
$result = applyDatanormUpdate($db, $user, $product_id, $datanorm_key, $fk_soc, $apply_price, $apply_description, $apply_label);
if ($result > 0) {
setEventMessages($langs->trans('ProductUpdated'), null, 'mesgs');
} else {
setEventMessages($langs->trans('ErrorUpdatingProduct'), null, 'errors');
}
// Redirect to same page with same parameters
header('Location: '.$_SERVER['PHP_SELF'].'?fk_soc='.$fk_soc.'&search_mode='.$search_mode.'&search_term='.urlencode($search_term).'&filter_price='.$filter_price.'&filter_description='.$filter_description.'&filter_label='.$filter_label.'&only_differences='.$only_differences);
exit;
}
// Add to pending changes
if ($action == 'add_pending') {
$product_id = GETPOSTINT('product_id');
$datanorm_key = GETPOST('datanorm_key', 'alphanohtml');
$apply_fields = GETPOST('apply_fields', 'array');
if ($product_id > 0 && !empty($datanorm_key)) {
$_SESSION['datanorm_pending_changes'][$product_id] = array(
'datanorm_key' => $datanorm_key,
'fk_soc' => $fk_soc,
'apply_fields' => $apply_fields
);
setEventMessages($langs->trans('AddedToPendingChanges'), null, 'mesgs');
}
}
// Remove from pending
if ($action == 'remove_pending') {
$product_id = GETPOSTINT('product_id');
unset($_SESSION['datanorm_pending_changes'][$product_id]);
}
// Clear all pending
if ($action == 'clear_pending') {
$_SESSION['datanorm_pending_changes'] = array();
setEventMessages($langs->trans('PendingChangesCleared'), null, 'mesgs');
}
// Show confirmation dialog
if ($action == 'confirm_apply_all') {
// Will be handled in view section
}
// Apply all pending changes
if ($action == 'apply_all_confirmed' && GETPOST('confirm', 'alpha') == 'yes') {
$success = 0;
$errors = 0;
foreach ($_SESSION['datanorm_pending_changes'] as $product_id => $change) {
$apply_price = in_array('price', $change['apply_fields']) ? 1 : 0;
$apply_description = in_array('description', $change['apply_fields']) ? 1 : 0;
$apply_label = in_array('label', $change['apply_fields']) ? 1 : 0;
$result = applyDatanormUpdate($db, $user, $product_id, $change['datanorm_key'], $change['fk_soc'], $apply_price, $apply_description, $apply_label);
if ($result > 0) {
$success++;
} else {
$errors++;
}
}
$_SESSION['datanorm_pending_changes'] = array();
setEventMessages($langs->trans('DatanormMassUpdateComplete', $success, $errors), null, 'mesgs');
header('Location: '.$_SERVER['PHP_SELF']);
exit;
}
/*
* View
*/
$title = $langs->trans('DatanormMassUpdate');
llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-importzugferd page-datanorm-update');
print load_fiche_titre($title, '', 'fa-sync');
// Check if Datanorm data exists
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."importzugferd_datanorm";
$resql = $db->query($sql);
$obj = $db->fetch_object($resql);
if ($obj->cnt == 0) {
print '<div class="warning">'.$langs->trans('NoDatanormData').'</div>';
print '<br><a href="'.dol_buildpath('/importzugferd/datanorm.php', 1).'" class="button">'.$langs->trans('UploadDatanorm').'</a>';
llxFooter();
$db->close();
exit;
}
// Search form
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" name="searchform">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="search">';
print '<div class="fichecenter">';
print '<div class="div-table-responsive-no-min">';
print '<table class="noborder centpercent">';
// Supplier selection
print '<tr class="liste_titre">';
print '<td colspan="4">'.$langs->trans('SelectSupplier').'</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td class="titlefield">'.$langs->trans('Supplier').'</td>';
print '<td colspan="3">';
// Get suppliers with Datanorm data
$sql = "SELECT DISTINCT s.rowid, s.nom FROM ".MAIN_DB_PREFIX."societe s";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."importzugferd_datanorm d ON d.fk_soc = s.rowid";
$sql .= " WHERE s.fournisseur = 1";
$sql .= " ORDER BY s.nom";
$resql = $db->query($sql);
print '<select name="fk_soc" class="flat minwidth300" onchange="this.form.submit()">';
print '<option value="">'.$langs->trans('SelectASupplier').'</option>';
while ($obj = $db->fetch_object($resql)) {
$selected = ($obj->rowid == $fk_soc) ? 'selected' : '';
print '<option value="'.$obj->rowid.'" '.$selected.'>'.dol_escape_htmltag($obj->nom).'</option>';
}
print '</select>';
print '</td>';
print '</tr>';
// Search mode
print '<tr class="oddeven">';
print '<td>'.$langs->trans('SearchMode').'</td>';
print '<td colspan="3">';
print '<input type="radio" name="search_mode" value="supplier" id="mode_supplier" '.($search_mode == 'supplier' ? 'checked' : '').'>';
print '<label for="mode_supplier"> '.$langs->trans('SearchBySupplierProducts').'</label>';
print ' &nbsp; &nbsp; ';
print '<input type="radio" name="search_mode" value="manual" id="mode_manual" '.($search_mode == 'manual' ? 'checked' : '').'>';
print '<label for="mode_manual"> '.$langs->trans('ManualSearch').'</label>';
print '</td>';
print '</tr>';
// Manual search term
print '<tr class="oddeven" id="manual_search_row" style="'.($search_mode != 'manual' ? 'display:none;' : '').'">';
print '<td>'.$langs->trans('SearchTerm').'</td>';
print '<td colspan="3">';
print '<input type="text" name="search_term" value="'.dol_escape_htmltag($search_term).'" class="minwidth300" placeholder="'.$langs->trans('ArticleNumberOrName').'">';
print '</td>';
print '</tr>';
// Additional search options
print '<tr class="oddeven">';
print '<td>'.$langs->trans('AdditionalSearchOptions').'</td>';
print '<td colspan="3">';
print '<input type="checkbox" name="search_by_name" value="1" id="search_by_name" '.($search_by_name ? 'checked' : '').'>';
print '<label for="search_by_name"> '.$langs->trans('AlsoSearchByName').'</label>';
print ' &nbsp; ';
print '<input type="checkbox" name="search_by_ean" value="1" id="search_by_ean" '.($search_by_ean ? 'checked' : '').'>';
print '<label for="search_by_ean"> '.$langs->trans('AlsoSearchByEAN').'</label>';
print ' &nbsp; ';
print '<input type="checkbox" name="search_by_ref" value="1" id="search_by_ref" '.($search_by_ref ? 'checked' : '').'>';
print '<label for="search_by_ref"> '.$langs->trans('AlsoSearchByRef').'</label>';
print '</td>';
print '</tr>';
// Filter: What to compare/update
print '<tr class="liste_titre">';
print '<td colspan="4">'.$langs->trans('FieldsToCompare').'</td>';
print '</tr>';
print '<tr class="oddeven">';
print '<td>'.$langs->trans('Fields').'</td>';
print '<td colspan="3">';
print '<input type="checkbox" name="filter_price" value="1" id="filter_price" '.($filter_price ? 'checked' : '').'>';
print '<label for="filter_price"> '.$langs->trans('Price').'</label>';
print ' &nbsp; ';
print '<input type="checkbox" name="filter_description" value="1" id="filter_description" '.($filter_description ? 'checked' : '').'>';
print '<label for="filter_description"> '.$langs->trans('Description').'</label>';
print ' &nbsp; ';
print '<input type="checkbox" name="filter_label" value="1" id="filter_label" '.($filter_label ? 'checked' : '').'>';
print '<label for="filter_label"> '.$langs->trans('Label').'</label>';
print '</td>';
print '</tr>';
// Only show differences
print '<tr class="oddeven">';
print '<td>'.$langs->trans('Display').'</td>';
print '<td colspan="3">';
print '<input type="checkbox" name="only_differences" value="1" id="only_differences" '.($only_differences ? 'checked' : '').'>';
print '<label for="only_differences"> '.$langs->trans('OnlyShowDifferences').'</label>';
print '</td>';
print '</tr>';
print '</table>';
print '</div>';
print '</div>';
print '<div class="center" style="margin: 10px;">';
print '<input type="submit" class="button button-primary" value="'.$langs->trans('Search').'">';
if (!empty($_SESSION['datanorm_pending_changes'])) {
print ' &nbsp; <a href="'.$_SERVER['PHP_SELF'].'?action=clear_pending&token='.newToken().'" class="button">'.$langs->trans('ClearPendingChanges').' ('.count($_SESSION['datanorm_pending_changes']).')</a>';
}
print '</div>';
print '</form>';
// JavaScript for toggling manual search
print '<script>
document.querySelectorAll("input[name=search_mode]").forEach(function(radio) {
radio.addEventListener("change", function() {
document.getElementById("manual_search_row").style.display = (this.value == "manual") ? "" : "none";
});
});
</script>';
// Results
if ($fk_soc > 0 && ($action == 'search' || GETPOST('search_mode'))) {
$comparison_results = array();
if ($search_mode == 'supplier') {
// Find all products linked to this supplier
$comparison_results = findProductsForSupplier($db, $fk_soc, $search_by_name, $search_by_ean, $search_by_ref);
} elseif ($search_mode == 'manual' && !empty($search_term)) {
// Manual search in Datanorm
$comparison_results = searchDatanormProducts($db, $fk_soc, $search_term, $search_by_name, $search_by_ean, $search_by_ref);
}
// Filter results if needed
if ($only_differences) {
$comparison_results = array_filter($comparison_results, function($item) use ($filter_price, $filter_description, $filter_label) {
return ($filter_price && $item['price_differs']) ||
($filter_description && $item['description_differs']) ||
($filter_label && $item['label_differs']);
});
}
if (!empty($comparison_results)) {
print '<br>';
print '<div class="div-table-responsive">';
print '<table class="noborder centpercent">';
// Header
print '<tr class="liste_titre">';
print '<th>'.$langs->trans('Product').'</th>';
print '<th>'.$langs->trans('DatanormArticle').'</th>';
if ($filter_price) {
print '<th class="right">'.$langs->trans('CurrentPrice').'</th>';
print '<th class="right">'.$langs->trans('DatanormPrice').'</th>';
}
if ($filter_description) {
print '<th>'.$langs->trans('CurrentDescription').'</th>';
print '<th>'.$langs->trans('DatanormDescription').'</th>';
}
if ($filter_label) {
print '<th>'.$langs->trans('CurrentLabel').'</th>';
print '<th>'.$langs->trans('DatanormLabel').'</th>';
}
print '<th class="center">'.$langs->trans('Actions').'</th>';
print '</tr>';
foreach ($comparison_results as $item) {
$has_difference = ($filter_price && $item['price_differs']) ||
($filter_description && $item['description_differs']) ||
($filter_label && $item['label_differs']);
$rowClass = $has_difference ? 'oddeven highlighted' : 'oddeven';
print '<tr class="'.$rowClass.'">';
// Product
print '<td>';
if ($item['product_id'] > 0) {
$product = new Product($db);
$product->fetch($item['product_id']);
print $product->getNomUrl(1, '', 0, 0, 0, 1, 1); // Open in new tab
print '<br><span class="opacitymedium">'.$product->ref.'</span>';
} else {
print '<span class="opacitymedium">'.$langs->trans('ProductNotInDatabase').'</span>';
}
print '</td>';
// Datanorm article
print '<td>';
print '<strong>'.dol_escape_htmltag($item['datanorm_ref']).'</strong>';
print '<br><span class="opacitymedium">'.dol_escape_htmltag(dol_trunc($item['datanorm_name'], 50)).'</span>';
print '</td>';
// Price comparison
if ($filter_price) {
$priceStyle = $item['price_differs'] ? 'background-color: #fcf8e3;' : '';
print '<td class="right nowraponall" style="'.$priceStyle.'">';
if ($item['product_id'] > 0) {
print price($item['current_price']);
} else {
print '-';
}
print '</td>';
print '<td class="right nowraponall" style="'.$priceStyle.'">';
print price($item['datanorm_price']);
if ($item['price_differs'] && $item['product_id'] > 0) {
$diff = $item['datanorm_price'] - $item['current_price'];
$diffPercent = ($item['current_price'] > 0) ? ($diff / $item['current_price'] * 100) : 0;
print '<br>';
if ($diff > 0) {
print '<span style="color: #d9534f;"><i class="fas fa-arrow-up"></i> +'.number_format($diffPercent, 1).'%</span>';
} else {
print '<span style="color: #5cb85c;"><i class="fas fa-arrow-down"></i> '.number_format($diffPercent, 1).'%</span>';
}
}
print '</td>';
}
// Description comparison
if ($filter_description) {
$descStyle = $item['description_differs'] ? 'background-color: #fcf8e3;' : '';
print '<td style="'.$descStyle.'">';
print dol_escape_htmltag(dol_trunc($item['current_description'], 80));
print '</td>';
print '<td style="'.$descStyle.'">';
print dol_escape_htmltag(dol_trunc($item['datanorm_description'], 80));
print '</td>';
}
// Label comparison
if ($filter_label) {
$labelStyle = $item['label_differs'] ? 'background-color: #fcf8e3;' : '';
print '<td style="'.$labelStyle.'">';
print dol_escape_htmltag($item['current_label']);
print '</td>';
print '<td style="'.$labelStyle.'">';
print dol_escape_htmltag($item['datanorm_label']);
print '</td>';
}
// Actions
print '<td class="center nowraponall">';
if ($item['product_id'] > 0 && $has_difference) {
// Quick apply form
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'" style="display:inline;">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="apply_single">';
print '<input type="hidden" name="product_id" value="'.$item['product_id'].'">';
print '<input type="hidden" name="datanorm_key" value="'.dol_escape_htmltag($item['datanorm_key']).'">';
print '<input type="hidden" name="fk_soc" value="'.$fk_soc.'">';
print '<input type="hidden" name="search_mode" value="'.$search_mode.'">';
print '<input type="hidden" name="search_term" value="'.dol_escape_htmltag($search_term).'">';
print '<input type="hidden" name="filter_price" value="'.$filter_price.'">';
print '<input type="hidden" name="filter_description" value="'.$filter_description.'">';
print '<input type="hidden" name="filter_label" value="'.$filter_label.'">';
print '<input type="hidden" name="only_differences" value="'.$only_differences.'">';
// Checkboxes for what to apply
if ($filter_price && $item['price_differs']) {
print '<input type="checkbox" name="apply_price" value="1" checked title="'.$langs->trans('Price').'">';
print '<span class="opacitymedium">P</span> ';
}
if ($filter_description && $item['description_differs']) {
print '<input type="checkbox" name="apply_description" value="1" checked title="'.$langs->trans('Description').'">';
print '<span class="opacitymedium">D</span> ';
}
if ($filter_label && $item['label_differs']) {
print '<input type="checkbox" name="apply_label" value="1" checked title="'.$langs->trans('Label').'">';
print '<span class="opacitymedium">L</span> ';
}
print '<button type="submit" class="button smallpaddingimp" title="'.$langs->trans('ApplyChanges').'">';
print '<i class="fas fa-check"></i>';
print '</button>';
print '</form>';
// Add to pending
$isPending = isset($_SESSION['datanorm_pending_changes'][$item['product_id']]);
if (!$isPending) {
print ' <a href="'.$_SERVER['PHP_SELF'].'?action=add_pending&product_id='.$item['product_id'].'&datanorm_key='.urlencode($item['datanorm_key']).'&fk_soc='.$fk_soc.'&apply_fields[]=price&apply_fields[]=description&apply_fields[]=label&token='.newToken().'" class="button smallpaddingimp" title="'.$langs->trans('AddToPending').'">';
print '<i class="fas fa-plus"></i>';
print '</a>';
} else {
print ' <span class="badge badge-status4">'.$langs->trans('Pending').'</span>';
}
} elseif ($item['product_id'] == 0) {
// Create product link
print '<a href="'.dol_buildpath('/product/card.php', 1).'?action=create&type=0" class="button smallpaddingimp" title="'.$langs->trans('CreateProduct').'" target="_blank">';
print '<i class="fas fa-plus"></i>';
print '</a>';
} else {
print '<span class="opacitymedium">'.$langs->trans('NoChanges').'</span>';
}
print '</td>';
print '</tr>';
}
print '</table>';
print '</div>';
// Summary and mass apply button
$pendingCount = count($_SESSION['datanorm_pending_changes']);
if ($pendingCount > 0) {
print '<br>';
print '<div class="center">';
print '<a href="'.$_SERVER['PHP_SELF'].'?action=confirm_apply_all&token='.newToken().'" class="button button-primary">';
print '<i class="fas fa-check-double paddingright"></i>'.$langs->trans('ApplyAllPendingChanges').' ('.$pendingCount.')';
print '</a>';
print '</div>';
}
} else {
print '<br><div class="opacitymedium center">'.$langs->trans('NoResultsFound').'</div>';
}
}
// Confirmation dialog for mass apply
if ($action == 'confirm_apply_all' && !empty($_SESSION['datanorm_pending_changes'])) {
print '<br><br>';
print '<div class="confirmmessage">';
print '<h3>'.$langs->trans('ConfirmMassUpdate').'</h3>';
print '<p>'.$langs->trans('FollowingProductsWillBeUpdated').':</p>';
print '<table class="noborder centpercent">';
print '<tr class="liste_titre">';
print '<th>'.$langs->trans('Product').'</th>';
print '<th>'.$langs->trans('Changes').'</th>';
print '</tr>';
foreach ($_SESSION['datanorm_pending_changes'] as $product_id => $change) {
$product = new Product($db);
$product->fetch($product_id);
print '<tr class="oddeven">';
print '<td>'.$product->getNomUrl(1).' - '.$product->label.'</td>';
print '<td>';
$changes = array();
if (in_array('price', $change['apply_fields'])) $changes[] = $langs->trans('Price');
if (in_array('description', $change['apply_fields'])) $changes[] = $langs->trans('Description');
if (in_array('label', $change['apply_fields'])) $changes[] = $langs->trans('Label');
print implode(', ', $changes);
print '</td>';
print '</tr>';
}
print '</table>';
print '<br>';
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'">';
print '<input type="hidden" name="token" value="'.newToken().'">';
print '<input type="hidden" name="action" value="apply_all_confirmed">';
print '<input type="hidden" name="confirm" value="yes">';
print '<div class="center">';
print '<input type="submit" class="button button-primary" value="'.$langs->trans('Confirm').'">';
print ' &nbsp; ';
print '<a href="'.$_SERVER['PHP_SELF'].'" class="button">'.$langs->trans('Cancel').'</a>';
print '</div>';
print '</form>';
print '</div>';
}
print '<style>
.highlighted { background-color: #fff3cd !important; }
.confirmmessage { background: #f8f9fa; padding: 20px; border: 1px solid #dee2e6; border-radius: 5px; }
</style>';
llxFooter();
$db->close();
/*
* Helper functions
*/
/**
* Find products linked to a supplier and compare with Datanorm
*/
function findProductsForSupplier($db, $fk_soc, $search_by_name = 0, $search_by_ean = 0, $search_by_ref = 0)
{
global $conf;
$results = array();
// Get all supplier products
$sql = "SELECT DISTINCT pf.fk_product, pf.ref_fourn, pf.price as fourn_price, p.ref, p.label, p.description, p.barcode";
$sql .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price pf";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = pf.fk_product";
$sql .= " WHERE pf.fk_soc = ".((int)$fk_soc);
$sql .= " AND pf.entity IN (".getEntity('product').")";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
// Try to find matching Datanorm article
$datanorm = findDatanormMatch($db, $fk_soc, $obj->ref_fourn, $obj->label, $obj->barcode, $obj->ref, $search_by_name, $search_by_ean, $search_by_ref);
if ($datanorm) {
$results[] = buildComparisonResult($obj, $datanorm);
}
}
}
return $results;
}
/**
* Search Datanorm products manually
*/
function searchDatanormProducts($db, $fk_soc, $search_term, $search_by_name = 0, $search_by_ean = 0, $search_by_ref = 0)
{
global $conf;
$results = array();
// Search in Datanorm
$sql = "SELECT d.* FROM ".MAIN_DB_PREFIX."importzugferd_datanorm d";
$sql .= " WHERE d.fk_soc = ".((int)$fk_soc);
$sql .= " AND (d.article_number LIKE '%".$db->escape($search_term)."%'";
$sql .= " OR d.short_text1 LIKE '%".$db->escape($search_term)."%'";
$sql .= " OR d.short_text2 LIKE '%".$db->escape($search_term)."%'";
if ($search_by_ean) {
$sql .= " OR d.ean LIKE '%".$db->escape($search_term)."%'";
}
$sql .= ")";
$sql .= " ORDER BY d.article_number";
$sql .= " LIMIT 100";
$resql = $db->query($sql);
if ($resql) {
while ($datanorm = $db->fetch_object($resql)) {
// Try to find matching product in database
$product = findProductMatch($db, $fk_soc, $datanorm);
$results[] = array(
'product_id' => $product ? $product->rowid : 0,
'current_price' => $product ? getSupplierPrice($db, $product->rowid, $fk_soc) : 0,
'current_description' => $product ? $product->description : '',
'current_label' => $product ? $product->label : '',
'datanorm_key' => $datanorm->article_number,
'datanorm_ref' => $datanorm->article_number,
'datanorm_name' => $datanorm->short_text1,
'datanorm_price' => $datanorm->price,
'datanorm_description' => trim($datanorm->short_text1.' '.$datanorm->short_text2),
'datanorm_label' => $datanorm->short_text1,
'price_differs' => $product && abs(getSupplierPrice($db, $product->rowid, $fk_soc) - $datanorm->price) > 0.01,
'description_differs' => $product && $product->description != trim($datanorm->short_text1.' '.$datanorm->short_text2),
'label_differs' => $product && $product->label != $datanorm->short_text1,
);
}
}
return $results;
}
/**
* Find Datanorm match for a product
*/
function findDatanormMatch($db, $fk_soc, $ref_fourn, $label, $barcode, $ref, $search_by_name, $search_by_ean, $search_by_ref)
{
// First try by supplier reference (article number)
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."importzugferd_datanorm";
$sql .= " WHERE fk_soc = ".((int)$fk_soc);
$sql .= " AND article_number = '".$db->escape($ref_fourn)."'";
$sql .= " LIMIT 1";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
return $db->fetch_object($resql);
}
// Try by EAN if enabled
if ($search_by_ean && !empty($barcode)) {
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."importzugferd_datanorm";
$sql .= " WHERE fk_soc = ".((int)$fk_soc);
$sql .= " AND ean = '".$db->escape($barcode)."'";
$sql .= " LIMIT 1";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
return $db->fetch_object($resql);
}
}
// Try by product ref if enabled
if ($search_by_ref && !empty($ref)) {
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."importzugferd_datanorm";
$sql .= " WHERE fk_soc = ".((int)$fk_soc);
$sql .= " AND article_number = '".$db->escape($ref)."'";
$sql .= " LIMIT 1";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
return $db->fetch_object($resql);
}
}
return null;
}
/**
* Find product match for Datanorm article
*/
function findProductMatch($db, $fk_soc, $datanorm)
{
// Try by supplier reference
$sql = "SELECT p.* FROM ".MAIN_DB_PREFIX."product p";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."product_fournisseur_price pf ON pf.fk_product = p.rowid";
$sql .= " WHERE pf.fk_soc = ".((int)$fk_soc);
$sql .= " AND pf.ref_fourn = '".$db->escape($datanorm->article_number)."'";
$sql .= " LIMIT 1";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
return $db->fetch_object($resql);
}
// Try by EAN
if (!empty($datanorm->ean)) {
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."product";
$sql .= " WHERE barcode = '".$db->escape($datanorm->ean)."'";
$sql .= " LIMIT 1";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
return $db->fetch_object($resql);
}
}
return null;
}
/**
* Get supplier price for a product
*/
function getSupplierPrice($db, $product_id, $fk_soc)
{
$sql = "SELECT price FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
$sql .= " WHERE fk_product = ".((int)$product_id);
$sql .= " AND fk_soc = ".((int)$fk_soc);
$sql .= " ORDER BY rowid DESC LIMIT 1";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
$obj = $db->fetch_object($resql);
return $obj->price;
}
return 0;
}
/**
* Build comparison result array
*/
function buildComparisonResult($product, $datanorm)
{
global $db;
$fk_soc = $datanorm->fk_soc;
$current_price = $product->fourn_price;
return array(
'product_id' => $product->fk_product,
'current_price' => $current_price,
'current_description' => $product->description,
'current_label' => $product->label,
'datanorm_key' => $datanorm->article_number,
'datanorm_ref' => $datanorm->article_number,
'datanorm_name' => $datanorm->short_text1,
'datanorm_price' => $datanorm->price,
'datanorm_description' => trim($datanorm->short_text1.' '.$datanorm->short_text2),
'datanorm_label' => $datanorm->short_text1,
'price_differs' => abs($current_price - $datanorm->price) > 0.01,
'description_differs' => $product->description != trim($datanorm->short_text1.' '.$datanorm->short_text2),
'label_differs' => $product->label != $datanorm->short_text1,
);
}
/**
* Apply Datanorm update to a product
*/
function applyDatanormUpdate($db, $user, $product_id, $datanorm_key, $fk_soc, $apply_price, $apply_description, $apply_label)
{
// Get Datanorm data
$sql = "SELECT * FROM ".MAIN_DB_PREFIX."importzugferd_datanorm";
$sql .= " WHERE fk_soc = ".((int)$fk_soc);
$sql .= " AND article_number = '".$db->escape($datanorm_key)."'";
$resql = $db->query($sql);
if (!$resql || $db->num_rows($resql) == 0) {
return -1;
}
$datanorm = $db->fetch_object($resql);
// Load product
$product = new Product($db);
$result = $product->fetch($product_id);
if ($result <= 0) {
return -2;
}
$updated = false;
// Update label
if ($apply_label && $product->label != $datanorm->short_text1) {
$product->label = $datanorm->short_text1;
$updated = true;
}
// Update description
if ($apply_description) {
$new_desc = trim($datanorm->short_text1.' '.$datanorm->short_text2);
if ($product->description != $new_desc) {
$product->description = $new_desc;
$updated = true;
}
}
// Save product changes
if ($updated) {
$result = $product->update($product->id, $user);
if ($result < 0) {
return -3;
}
}
// Update supplier price
if ($apply_price) {
$productFourn = new ProductFournisseur($db);
$productFourn->fetch($product_id);
// Find existing supplier price
$sql = "SELECT rowid, quantity FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
$sql .= " WHERE fk_product = ".((int)$product_id);
$sql .= " AND fk_soc = ".((int)$fk_soc);
$sql .= " ORDER BY rowid DESC LIMIT 1";
$resql = $db->query($sql);
if ($resql && $db->num_rows($resql) > 0) {
$priceObj = $db->fetch_object($resql);
// Update existing price
$result = $productFourn->update_buyprice(
$priceObj->quantity,
$datanorm->price,
$user,
'HT',
$fk_soc,
0, // availability
$datanorm->article_number, // ref_fourn
0, // tva_tx
0, // charges
0, // remise_percent
0, // remise
0, // newnpr
0, // delivery_time_days
'', // supplier_reputation
array(), // localtaxes
'', // newdefaultvatcode
0, // multicurrency_buyprice
'', // multicurrency_price_base_type
0, // multicurrency_tx
'', // multicurrency_code
'', // desc_fourn
'', // barcode
0, // fk_barcode_type
array() // options
);
if ($result < 0) {
return -4;
}
}
}
return 1;
}