From 1193785151307ad061e8e06dd59f23c43f42098b Mon Sep 17 00:00:00 2001 From: solocla Date: Sat, 24 Oct 2026 10:14:49 +0200 Subject: [PATCH] mago probe pruchase --- public/userarea/mago_probe_purchases.php | 386 +++++++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 public/userarea/mago_probe_purchases.php diff --git a/public/userarea/mago_probe_purchases.php b/public/userarea/mago_probe_purchases.php new file mode 100644 index 00000000..fcfe3016 --- /dev/null +++ b/public/userarea/mago_probe_purchases.php @@ -0,0 +1,386 @@ +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; +} + +/* ---------------- stampa ---------------- */ +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 field(array $keys, array $candidates): ?string +{ + foreach ($candidates as $c) { + foreach ($keys as $k) if (strcasecmp($k, $c) === 0) return $k; + } + return null; +} +function norm(string $s): string +{ + return strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $s)); +} + +$envFile = probe_load_env(__DIR__); +echo "Prova acquisti Mago4 · dal $since ($months mesi) · max " . ($maxPages * 200) . " righe\n"; +echo ".env: " . ($envFile ?: 'non trovato, uso le variabili d\'ambiente') . "\n"; + +$mago = null; +$t0 = microtime(true); + +try { + $mago = MagoClient::fromEnv(); + $mago->login(); + echo "Login Mago: OK\n"; + + /* ========================================================= + * 1. TABELLE + * ========================================================= */ + h('1. TABELLE D\'ACQUISTO IN MAGO'); + $tables = [ + 'MA_PurchaseOrd' => 'Ordini a fornitore (testata)', + 'MA_PurchaseOrdDetails' => 'Ordini a fornitore (righe)', + 'MA_PurchaseOrdSummary' => 'Ordini a fornitore (totali)', + 'MA_PurchaseDoc' => 'DDT / fatture fornitore (testata)', + 'MA_PurchaseDocDetail' => 'DDT / fatture fornitore (righe)', + 'MA_PurchaseDocSummary' => 'DDT / fatture fornitore (totali)', + 'MA_Items' => 'Anagrafica articoli', + ]; + $found = []; + foreach ($tables as $t => $label) { + try { + $r = $mago->getDataTable($t, ['recordsPerPage' => 1]); + $keys = $r['records'] ? array_keys($r['records'][0]) : []; + $found[$t] = ['keys' => $keys, 'sample' => $r['records'][0] ?? []]; + echo pad($t, 24) . pad($label, 36) . pad(num($r['total']) . ' record', 16, true) . ' ' . count($keys) . " campi\n"; + } catch (Throwable $e) { + echo pad($t, 24) . pad($label, 36) . " NON DISPONIBILE: " . cut($e->getMessage(), 80) . "\n"; + } + } + + foreach ($found as $t => $f) { + if (!$f['keys']) continue; + echo "\n-- $t: campi --\n"; + if ($showFields) { + foreach ($f['sample'] as $k => $v) echo ' ' . pad($k, 32) . cut(is_scalar($v) ? (string)$v : json_encode($v), 60) . "\n"; + } else { + echo ' ' . wordwrap(implode(', ', $f['keys']), 74, "\n ") . "\n"; + } + } + + /* scelgo la fonte: prima i documenti fornitore (prezzo pagato), poi gli ordini */ + $src = null; + foreach ([['MA_PurchaseDoc', 'MA_PurchaseDocDetail', 'Documenti fornitore'], ['MA_PurchaseOrd', 'MA_PurchaseOrdDetails', 'Ordini a fornitore']] as $c) { + if (!empty($found[$c[0]]['keys']) && !empty($found[$c[1]]['keys'])) { + $src = $c; + break; + } + } + if (!$src) { + h('Nessuna coppia testata/righe d\'acquisto leggibile: mi fermo qui'); + exit(0); + } + [$headT, $detT, $srcLabel] = $src; + $hk = $found[$headT]['keys']; + $dk = $found[$detT]['keys']; + + $hId = field($hk, ['PurchaseDocId', 'PurchaseOrdId']); + $dId = field($dk, ['PurchaseDocId', 'PurchaseOrdId']); + $hDate = field($hk, ['DocumentDate', 'OrderDate', 'PostingDate']); + $hNo = field($hk, ['DocNo', 'InternalOrdNo', 'SupplierDocNo']); + $hSupp = field($hk, ['Supplier', 'CustSupp']); + $hType = field($hk, ['DocumentType']); + $dItem = field($dk, ['Item']); + $dDesc = field($dk, ['Description']); + $dUom = field($dk, ['UoM', 'BaseUoM']); + $dQty = field($dk, ['Qty']); + $dVal = field($dk, ['TaxableAmount']); + $dUnit = field($dk, ['UnitValue']); + $dMod = field($dk, ['TBModified', 'TbModified']); + + echo "\nFonte usata: $srcLabel ($headT + $detT)\n"; + echo "Campi riconosciuti: id=$hId/$dId data=$hDate n°=$hNo fornitore=$hSupp tipo=" . ($hType ?: '-') . + " | articolo=$dItem UM=$dUom qtà=$dQty imponibile=" . ($dVal ?: '-') . " prezzo=" . ($dUnit ?: '-') . "\n"; + if (!$hId || !$dId || !$hDate || !$dItem || !$dQty) { + h('Mancano campi indispensabili (id, data, articolo, quantità): rilancia con --fields e mandami l\'output'); + exit(0); + } + + /* ========================================================= + * 2. TESTATE DEL PERIODO + * ========================================================= */ + $heads = []; + $types = []; + foreach ($mago->iterate($headT, ['filters' => [['key' => $hDate, 'value' => $since, 'operator' => '>=']]], $maxPages) as $rec) { + $id = (string)$rec[$hId]; + $heads[$id] = [ + 'date' => MagoClient::toMysqlDate($rec[$hDate] ?? null), + 'no' => $hNo ? (string)$rec[$hNo] : '', + 'supp' => $hSupp ? (string)$rec[$hSupp] : '', + 'type' => $hType ? (string)$rec[$hType] : '', + ]; + if ($hType) { + $tp = (string)$rec[$hType]; + $types[$tp]['n'] = ($types[$tp]['n'] ?? 0) + 1; + $types[$tp]['ex'] = $types[$tp]['ex'] ?? ($hNo ? $rec[$hNo] : ''); + } + } + h("2. DOCUMENTI DEL PERIODO: " . num(count($heads))); + if ($types) { + echo pad('Tipo documento', 16) . pad('Quanti', 10, true) . " Esempio n°\n"; + arsort($types); + foreach ($types as $tp => $x) echo pad($tp, 16) . pad(num($x['n']), 10, true) . ' ' . $x['ex'] . "\n"; + echo "(Per le vendite: 3407874 fattura, 3407883 acconto, 3407876 nota credito, 3407873 DDT. Gli acquisti hanno codici simili.)\n"; + } + if (!$heads) { + echo "Nessun documento nel periodo.\n"; + exit(0); + } + + /* ========================================================= + * 3. RIGHE + * ========================================================= */ + $filters = $dMod ? [['key' => $dMod, 'value' => $since, 'operator' => '>=']] : []; + $items = []; + $last = []; + $lines = 0; + $skipped = 0; + foreach ($mago->iterate($detT, ['filters' => $filters], $maxPages) as $rec) { + $id = (string)$rec[$dId]; + if (!isset($heads[$id])) { + $skipped++; + continue; + } + $item = trim((string)($rec[$dItem] ?? '')); + $qty = MagoClient::toDecimal($rec[$dQty] ?? 0); + if ($item === '' || $qty == 0) continue; + $val = $dVal ? MagoClient::toDecimal($rec[$dVal]) : 0; + if ($val == 0 && $dUnit) $val = $qty * MagoClient::toDecimal($rec[$dUnit]); + $hd = $heads[$id]; + $lines++; + + $it = &$items[$item]; + $it['item'] = $item; + $it['desc'] = $it['desc'] ?? ($dDesc ? (string)$rec[$dDesc] : ''); + $it['uom'][$dUom ? strtoupper(trim((string)$rec[$dUom])) : '?'] = 1; + $it['n'] = ($it['n'] ?? 0) + 1; + $it['qty'] = ($it['qty'] ?? 0) + $qty; + $it['val'] = ($it['val'] ?? 0) + $val; + $it['supp'][$hd['supp']] = 1; + if (($it['last'] ?? '') <= $hd['date']) { + $it['last'] = $hd['date']; + $it['last_price'] = $qty ? $val / $qty : 0; + } + unset($it); + + $last[] = [$hd['date'], $hd['no'], $hd['supp'], $hd['type'], $item, $dDesc ? $rec[$dDesc] : '', $dUom ? $rec[$dUom] : '', $qty, $qty ? $val / $qty : 0, $val]; + } + uasort($items, fn($a, $b) => $b['val'] <=> $a['val']); + + /* nomi fornitori */ + $suppNames = []; + $allSupp = []; + foreach ($items as $it) foreach (array_keys($it['supp']) as $s) $allSupp[$s] = ($allSupp[$s] ?? 0) + $it['val']; + arsort($allSupp); + foreach (array_slice(array_keys($allSupp), 0, 30) as $s) { + if ($s === '') continue; + try { + $r = $mago->getDataTable('MA_CustSupp', ['filters' => [['key' => 'CustSupp', 'value' => $s, 'operator' => '=']], 'recordsPerPage' => 5]); + foreach ($r['records'] as $c) { + $suppNames[$s] = $c['CompanyName'] ?? $s; + if (($c['CustSuppType'] ?? '') != 3211264) break; // preferisco il record fornitore + } + } catch (Throwable $e) { + } + } + + h("3. ARTICOLI ACQUISTATI: " . num(count($items)) . " articoli su " . num($lines) . " righe" . ($skipped ? " (" . num($skipped) . " righe fuori periodo)" : '')); + echo pad('Articolo', 16) . pad('Descrizione', 30) . pad('UM', 6) . pad('Quantità', 12, true) . pad('Valore €', 13, true) . + pad('€/UM medio', 11, true) . pad('Ultimo €', 10, true) . ' ' . pad('Ultimo', 10) . " Fornitore\n"; + echo str_repeat('-', 140) . "\n"; + foreach (array_slice($items, 0, 80) as $it) { + $s = array_keys($it['supp']); + $sn = $suppNames[$s[0]] ?? $s[0]; + echo pad($it['item'], 16) . pad($it['desc'], 30) . pad(implode('/', array_keys($it['uom'])), 6) . + pad(num($it['qty'], 1), 12, true) . pad(num($it['val'], 0), 13, true) . + pad($it['qty'] ? num($it['val'] / $it['qty'], 3) : '-', 11, true) . pad(num($it['last_price'], 3), 10, true) . + ' ' . pad($it['last'], 10) . ' ' . cut($sn, 26) . (count($s) > 1 ? ' +' . (count($s) - 1) : '') . "\n"; + } + if (count($items) > 80) echo "… e altri " . num(count($items) - 80) . " articoli (tutti nel CSV)\n"; + + /* ========================================================= + * 4. MESCOLE DEL PORTALE + * ========================================================= */ + h('4. ARTICOLI CHE SOMIGLIANO ALLE MESCOLE DEL PORTALE'); + $pdo = null; + 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) { + echo "Database del portale non raggiungibile da qui: confronto saltato (lo faremo dopo, dal CSV).\n"; + } else { + $names = []; + 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) { + } + echo "Mescole nel portale: " . num($pdo->query("SELECT COUNT(*) FROM mescole")->fetchColumn()) . " · nomi confrontati: " . num(count($names)) . "\n\n"; + + $hit = 0; + $exact = 0; + foreach ($items as $it) { + $ni = norm($it['item']); + $nd = norm($it['desc']); + $match = null; + $how = ''; + if (isset($names[$ni])) { + $match = $names[$ni]; + $how = 'codice = ' . $match['via']; + $exact++; + } else { + foreach ($names as $n => $m) { + if (strlen($n) >= 4 && (strpos($ni, $n) !== false || strpos($nd, $n) !== false)) { + $match = $m; + $how = 'contenuto in ' . (strpos($ni, $n) !== false ? 'codice' : 'descrizione') . ' (' . $m['via'] . ')'; + break; + } + } + } + if (!$match) continue; + $hit++; + if ($hit <= 60) { + echo pad($it['item'], 16) . pad($it['desc'], 32) . ' → ' . pad($match['name'], 22) . pad(implode('/', array_keys($it['uom'])), 6) . + pad($it['qty'] ? num($it['val'] / $it['qty'], 3) . ' €' : '-', 12, true) . ' ' . $how . "\n"; + } + } + echo "\nArticoli collegabili a una mescola: " . num($hit) . " (di cui " . num($exact) . " con codice identico) su " . num(count($items)) . "\n"; + } + + /* ========================================================= + * 5. ULTIME RIGHE + * ========================================================= */ + h('5. ULTIME 30 RIGHE D\'ACQUISTO'); + usort($last, fn($a, $b) => strcmp($b[0], $a[0])); + foreach (array_slice($last, 0, 30) as $l) { + echo pad($l[0], 11) . pad($l[1], 12) . pad($suppNames[$l[2]] ?? $l[2], 22) . pad($l[4], 14) . pad($l[5], 28) . + pad($l[6], 5) . pad(num($l[7], 1), 10, true) . pad(num($l[8], 3), 10, true) . pad(num($l[9], 2), 12, true) . "\n"; + } + + /* CSV */ + $csv = sys_get_temp_dir() . '/mago_acquisti_articoli_' . date('Ymd_His') . '.csv'; + $fh = fopen($csv, 'w'); + fputcsv($fh, ['articolo', 'descrizione', 'um', 'righe', 'quantita', 'valore', 'prezzo_medio', 'ultimo_prezzo', 'ultima_data', 'fornitori'], ';'); + foreach ($items as $it) { + fputcsv($fh, [ + $it['item'], + $it['desc'], + implode('/', array_keys($it['uom'])), + $it['n'], + number_format($it['qty'], 3, ',', ''), + number_format($it['val'], 2, ',', ''), + $it['qty'] ? number_format($it['val'] / $it['qty'], 4, ',', '') : '', + number_format($it['last_price'], 4, ',', ''), + $it['last'], + implode(' | ', array_map(fn($s) => $suppNames[$s] ?? $s, array_keys($it['supp']))), + ], ';'); + } + fclose($fh); + h('FINE'); + echo "CSV articoli: $csv\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"; +}