/tmp/probe_mescole.txt). * CSV: /tmp/mago_mescole_descrizioni_*.csv e /tmp/mago_mescole_righe_*.csv */ if (PHP_SAPI !== 'cli') { http_response_code(403); exit("Solo da terminale\n"); } require_once __DIR__ . '/class/MagoClient.php'; $opt = getopt('', ['months::', 'item::', 'types::']); $months = max(1, (int)($opt['months'] ?? 24)); $itemCode = (string)($opt['item'] ?? 'MESCOLE'); $docTypes = array_values(array_filter(array_map('trim', explode(',', (string)($opt['types'] ?? '9830401'))), 'strlen')); $since = date('Y-m-d', strtotime("-$months months")); const MAX_PAGES = 100; function probe_load_env(string $dir): ?string { for ($i = 0; $i < 5; $i++) { if (is_file($dir . '/vendor/autoload.php')) require_once $dir . '/vendor/autoload.php'; if (is_file($dir . '/.env')) { if (class_exists('Dotenv\\Dotenv')) { Dotenv\Dotenv::createImmutable($dir)->safeLoad(); } else { foreach (file($dir . '/.env', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) { if ($line[0] === '#' || strpos($line, '=') === false) continue; [$k, $v] = array_map('trim', explode('=', $line, 2)); $v = trim($v, "\"'"); $_ENV[$k] = $v; putenv("$k=$v"); } } return $dir . '/.env'; } $dir = dirname($dir); } return null; } function h(string $t): void { echo "\n" . str_repeat('=', 78) . "\n " . $t . "\n" . str_repeat('=', 78) . "\n"; } function cut($s, int $n): string { $s = preg_replace('/\s+/', ' ', trim((string)$s)); return mb_strlen($s) > $n ? mb_substr($s, 0, $n - 1) . '…' : $s; } function pad($s, int $n, bool $right = false): string { $s = cut($s, $n); $p = str_repeat(' ', max(0, $n - mb_strlen($s))); return $right ? $p . $s : $s . $p; } function num($v, int $d = 0): string { return number_format((float)$v, $d, ',', '.'); } function norm(string $s): string { return strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $s)); } /** descrizione "pulita" per raggruppare: maiuscole, spazi singoli, senza spessori/misure finali tipo "X 9mm spessore" */ function descKey(string $s): string { $s = strtoupper(preg_replace('/\s+/', ' ', trim($s))); return $s; } probe_load_env(__DIR__); echo "Prova MESCOLE in Mago4 · articolo $itemCode · tipi " . implode(',', $docTypes) . " · dal $since\n"; $mago = null; $t0 = microtime(true); try { $mago = MagoClient::fromEnv(); $mago->login(); echo "Login Mago: OK\n"; $fields = [ 'PurchaseDocId', 'Line', 'DocumentType', 'DocumentDate', 'DocNo', 'Supplier', 'Item', 'Description', 'Department', 'SupplierCode', 'ReferenceDocNo', 'UoM', 'Qty', 'UnitValue', 'TaxableAmount', 'DiscountAmount', 'Lot', 'SupplierLotNo', 'Notes', 'PurchaseOrdNo' ]; $rows = []; foreach ($docTypes as $tp) { $flt = [ ['key' => 'Item', 'value' => $itemCode, 'operator' => '='], ['key' => 'DocumentDate', 'value' => $since, 'operator' => '>='], ['key' => 'DocumentType', 'value' => $tp, 'operator' => '='], ]; for ($page = 1; $page <= MAX_PAGES; $page++) { $tq = microtime(true); $r = $mago->getDataTable('MA_PurchaseDocDetail', ['fields' => $fields, 'filters' => $flt, 'recordsPerPage' => 200, 'pageNumber' => $page]); fwrite(STDERR, sprintf(" tipo %s · pagina %d: %d righe su %s (%.1f s)\n", $tp, $page, $r['count'], num($r['total']), microtime(true) - $tq)); foreach ($r['records'] as $rec) { $rec['_type'] = $tp; $rows[] = $rec; } if (!$r['records'] || $page * 200 >= $r['total']) break; } } /* nomi fornitori */ $supp = []; foreach ($rows as $x) $supp[(string)($x['Supplier'] ?? '')] = 1; $suppNames = []; foreach (array_keys($supp) as $s) { if ($s === '') continue; try { $r = $mago->getDataTable('MA_CustSupp', ['fields' => ['CustSuppType', 'CustSupp', 'CompanyName'], 'filters' => [['key' => 'CustSupp', 'value' => $s, 'operator' => '=']], 'recordsPerPage' => 5]); foreach ($r['records'] as $c) { $suppNames[$s] = $c['CompanyName'] ?: $s; if ((string)$c['CustSuppType'] !== '3211264') break; } } catch (Throwable $e) { } } h('1. RIGHE LETTE: ' . num(count($rows))); if (!$rows) exit(0); /* quanto sono compilati i campi che potrebbero identificare la mescola */ $fill = []; foreach (['Description', 'Department', 'SupplierCode', 'ReferenceDocNo', 'Notes', 'Lot', 'SupplierLotNo'] as $f) { $n = 0; $vals = []; foreach ($rows as $x) { $v = trim((string)($x[$f] ?? '')); if ($v !== '') { $n++; $vals[cut($v, 40)] = 1; } } echo pad($f, 16) . pad(num($n) . ' righe compilate', 22, true) . ' ' . num(count($vals)) . " valori diversi" . ($vals ? ' es: ' . cut(implode(' · ', array_slice(array_keys($vals), 0, 4)), 90) : '') . "\n"; } $uoms = []; foreach ($rows as $x) $uoms[strtoupper(trim((string)($x['UoM'] ?? '')))] = ($uoms[strtoupper(trim((string)($x['UoM'] ?? '')))] ?? 0) + 1; echo "UM: " . implode(', ', array_map(fn($k, $v) => "$k ($v)", array_keys($uoms), $uoms)) . "\n"; /* raggruppo per descrizione */ $g = []; foreach ($rows as $x) { $k = descKey((string)($x['Description'] ?? '')); $qty = MagoClient::toDecimal($x['Qty']); $val = MagoClient::toDecimal($x['TaxableAmount']); if ($val == 0) $val = $qty * MagoClient::toDecimal($x['UnitValue']); $d = MagoClient::toMysqlDate($x['DocumentDate']); $a = &$g[$k]; $a['desc'] = $k; $a['n'] = ($a['n'] ?? 0) + 1; $a['qty'] = ($a['qty'] ?? 0) + $qty; $a['val'] = ($a['val'] ?? 0) + $val; $a['supp'][$suppNames[$x['Supplier'] ?? ''] ?? ($x['Supplier'] ?? '')] = 1; $a['dep'][trim((string)($x['Department'] ?? ''))] = 1; $a['sc'][trim((string)($x['SupplierCode'] ?? ''))] = 1; if (($a['last'] ?? '') <= $d) { $a['last'] = $d; $a['last_price'] = $qty ? $val / $qty : 0; } $a['first'] = isset($a['first']) ? min($a['first'], $d) : $d; unset($a); } uasort($g, fn($a, $b) => $b['val'] <=> $a['val']); /* mescole del portale */ $pdo = null; $names = []; try { foreach (['/class/DBHandlerSelect.php', '/include/DBHandlerSelect.php', '/class/DBHandler.php'] as $f) { if (!class_exists('DBHandlerSelect') && is_file(__DIR__ . $f)) { ob_start(); require_once __DIR__ . $f; ob_end_clean(); } } if (class_exists('DBHandlerSelect')) $pdo = DBHandlerSelect::getInstance()->getConnection(); } catch (Throwable $e) { $pdo = null; } if ($pdo) { foreach ($pdo->query("SELECT id, nome, nomeuscita FROM mescole") as $m) { foreach (['nome', 'nomeuscita'] as $k) { $n = norm((string)$m[$k]); if (strlen($n) >= 3) $names[$n] = ['id' => $m['id'], 'name' => $m['nome'], 'via' => $k]; } } try { foreach ($pdo->query("SELECT sn.idmescola, sn.supplier_mix_name, m.nome FROM mescole_supplier_names sn JOIN mescole m ON m.id = sn.idmescola") as $m) { $n = norm((string)$m['supplier_mix_name']); if (strlen($n) >= 3) $names[$n] = ['id' => $m['idmescola'], 'name' => $m['nome'], 'via' => 'nome fornitore']; } } catch (Throwable $e) { } uksort($names, fn($a, $b) => strlen($b) <=> strlen($a)); // prima i nomi più lunghi } $matchOf = function (string $desc) use ($names) { $nd = norm($desc); foreach ($names as $n => $m) { if (strpos($nd, $n) !== false) return $m; } return null; }; h('2. DESCRIZIONI DIVERSE: ' . num(count($g)) . ($pdo ? ' · mescole portale confrontate: ' . num(count($names)) . ' nomi' : ' · database portale NON raggiungibile')); echo pad('Descrizione', 40) . pad('Righe', 7, true) . pad('Kg', 12, true) . pad('Valore €', 12, true) . pad('€/kg', 8, true) . pad('Ultimo', 8, true) . ' ' . pad('Ult. data', 10) . ' ' . pad('Mescola portale', 20) . " Fornitore / Department\n"; echo str_repeat('-', 150) . "\n"; $matched = 0; $matchedVal = 0; $totVal = 0; foreach ($g as $a) { $m = $pdo ? $matchOf($a['desc']) : null; $totVal += $a['val']; if ($m) { $matched++; $matchedVal += $a['val']; } $g[$a['desc']]['match'] = $m; } foreach (array_slice($g, 0, 100) as $a) { $dep = implode(',', array_filter(array_keys($a['dep']))); echo pad($a['desc'], 40) . pad(num($a['n']), 7, true) . pad(num($a['qty']), 12, true) . pad(num($a['val']), 12, true) . pad($a['qty'] ? num($a['val'] / $a['qty'], 3) : '-', 8, true) . pad(num($a['last_price'], 3), 8, true) . ' ' . pad($a['last'], 10) . ' ' . pad($a['match'] ? $a['match']['name'] : ($pdo ? '—' : ''), 20) . ' ' . cut(implode(' | ', array_keys($a['supp'])), 30) . ($dep ? ' · dep: ' . cut($dep, 20) : '') . "\n"; } if (count($g) > 100) echo "… e altre " . num(count($g) - 100) . " descrizioni (tutte nel CSV)\n"; if ($pdo) echo "\nDescrizioni abbinate a una mescola: " . num($matched) . " su " . num(count($g)) . " · " . ($totVal ? num($matchedVal / $totVal * 100, 1) : 0) . "% del valore\n"; /* esempi grezzi */ h('3. 15 RIGHE D\'ESEMPIO (tutti i campi letti)'); foreach (array_slice($rows, -15) as $x) { $out = []; foreach ($fields as $f) if (isset($x[$f]) && trim((string)$x[$f]) !== '' && !in_array($f, ['PurchaseDocId', 'Item'], true)) $out[] = $f . '=' . cut($x[$f], 40); echo ' · ' . implode(' | ', $out) . "\n"; } /* CSV */ $stamp = date('Ymd_His'); $csv1 = sys_get_temp_dir() . "/mago_mescole_descrizioni_$stamp.csv"; $fh = fopen($csv1, 'w'); fputcsv($fh, ['descrizione', 'righe', 'kg', 'valore', 'prezzo_medio', 'ultimo_prezzo', 'prima_data', 'ultima_data', 'mescola_portale', 'fornitori', 'department', 'codice_fornitore'], ';'); foreach ($g as $a) { fputcsv($fh, [ $a['desc'], $a['n'], number_format($a['qty'], 3, ',', ''), number_format($a['val'], 2, ',', ''), $a['qty'] ? number_format($a['val'] / $a['qty'], 4, ',', '') : '', number_format($a['last_price'], 4, ',', ''), $a['first'], $a['last'], $a['match']['name'] ?? '', implode(' | ', array_keys($a['supp'])), implode(' | ', array_filter(array_keys($a['dep']))), implode(' | ', array_filter(array_keys($a['sc']))) ], ';'); } fclose($fh); $csv2 = sys_get_temp_dir() . "/mago_mescole_righe_$stamp.csv"; $fh = fopen($csv2, 'w'); fputcsv($fh, array_merge(['fornitore_nome'], $fields), ';'); foreach ($rows as $x) { $line = [$suppNames[$x['Supplier'] ?? ''] ?? ($x['Supplier'] ?? '')]; foreach ($fields as $f) $line[] = is_scalar($x[$f] ?? '') ? (string)($x[$f] ?? '') : json_encode($x[$f]); fputcsv($fh, $line, ';'); } fclose($fh); h('FINE'); echo "CSV descrizioni: $csv1\nCSV righe: $csv2\n"; } catch (Throwable $e) { echo "\nERRORE: " . $e->getMessage() . "\n"; } finally { if ($mago) { $ok = $mago->logoff(); echo "Logoff Mago: " . ($ok ? 'OK' : 'NON riuscito → ' . $mago->getLastLogoffError()) . "\n"; } echo "Durata: " . round(microtime(true) - $t0, 1) . " s\n"; }