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"; } } if ($showFields) { foreach ($found as $t => $f) { if (!$f['keys']) continue; echo "\n-- $t: campi --\n"; foreach ($f['sample'] as $k => $v) echo ' ' . pad($k, 32) . cut(is_scalar($v) ? (string)$v : json_encode($v), 60) . "\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 (!$dId || !$dItem || !$dQty) { h('Mancano campi indispensabili (id, data, articolo, quantità): rilancia con --fields e mandami l\'output'); exit(0); } /* ========================================================= * 2. TIPI DI DOCUMENTO DEL PERIODO * ========================================================= */ $dType = field($dk, ['DocumentType']); $dDate = field($dk, ['DocumentDate', 'OrderDate']); $dNo = field($dk, ['DocNo', 'PurchaseOrdNo']); $dSupp = field($dk, ['Supplier']); h('2. TIPI DI DOCUMENTO DEL PERIODO'); $known = ['9830400' => 'DDT fornitore?', '9830401' => 'Fattura fornitore?', '9830402' => 'Nota di credito?']; if ($hType && $hDate) { foreach ($known as $tp => $lbl) { try { $n = $mago->count($headT, [['key' => $hDate, 'value' => $since, 'operator' => '>='], ['key' => $hType, 'value' => $tp, 'operator' => '=']]); echo pad($tp, 12) . pad($lbl, 22) . pad(num($n), 10, true) . "\n"; } catch (Throwable $e) { echo pad($tp, 12) . " errore: " . cut($e->getMessage(), 60) . "\n"; } } } echo "Righe lette solo dai tipi: " . implode(', ', $docTypes) . " (cambia con --types=…)\n"; /* ========================================================= * 3. RIGHE (solo i campi che servono, filtro su data e tipo documento) * ========================================================= */ if (!$dDate) { h('Nella tabella righe manca la data documento: rilancia con --fields e mandami l\'output'); exit(0); } $fields = array_values(array_filter([$dId, $dDate, $dNo, $dSupp, $dType, $dItem, $dDesc, $dUom, $dQty, $dVal, $dUnit, field($dk, ['LineType'])])); $items = []; $last = []; $lines = 0; $read = 0; $total = null; foreach ($docTypes as $tp) { $flt = [['key' => $dDate, 'value' => $since, 'operator' => '>=']]; if ($dType && $tp !== '') $flt[] = ['key' => $dType, 'value' => $tp, 'operator' => '=']; for ($page = 1; $page <= $maxPages; $page++) { $tq = microtime(true); $r = $mago->getDataTable($detT, ['fields' => $fields, 'filters' => $flt, 'recordsPerPage' => 200, 'pageNumber' => $page]); $total = $r['total']; fwrite(STDERR, sprintf(" tipo %s · pagina %d: %d righe su %s (%.1f s)\n", $tp, $page, $r['count'], num($total), microtime(true) - $tq)); if (!$r['records']) break; foreach ($r['records'] as $rec) { $read++; $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]); $date = MagoClient::toMysqlDate($rec[$dDate] ?? null); $supp = $dSupp ? (string)$rec[$dSupp] : ''; $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'][$supp] = 1; if (($it['last'] ?? '') <= $date) { $it['last'] = $date; $it['last_price'] = $qty ? $val / $qty : 0; } unset($it); $last[] = [$date, $dNo ? $rec[$dNo] : '', $supp, $tp, $item, $dDesc ? $rec[$dDesc] : '', $dUom ? $rec[$dUom] : '', $qty, $qty ? $val / $qty : 0, $val]; } if ($page * 200 >= $total) break; } if ($total !== null && $total > $maxPages * 200) echo "ATTENZIONE tipo $tp: lette " . num($maxPages * 200) . " righe su " . num($total) . " (aumenta --pages)\n"; } $skipped = 0; 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"; }