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"; }