mago probe pruchase

This commit is contained in:
2026-10-24 10:14:49 +02:00
parent 60a3bcabfa
commit 1193785151
+386
View File
@@ -0,0 +1,386 @@
<?php
/**
* mago_probe_purchases.php - PROVA in sola lettura: acquisti di Zibo Gomma in Mago4 (mescole).
*
* Non scrive niente nel database. Fa sempre il logoff da Mago.
*
* Uso (da terminale, nella cartella userarea):
* php mago_probe_purchases.php ultimi 24 mesi
* php mago_probe_purchases.php --months=36 periodo diverso
* php mago_probe_purchases.php --pages=60 righe lette al massimo = pagine × 200 (default 40)
* php mago_probe_purchases.php --fields stampa anche tutti i campi con un valore d'esempio
*
* Stampa:
* 1. quali tabelle d'acquisto esistono, quanti record hanno e i loro campi
* 2. i tipi di documento d'acquisto (per capire qual è la fattura fornitore)
* 3. gli articoli acquistati, ordinati per valore, con UM, quantità, prezzo medio e ultimo
* 4. quali articoli somigliano alle mescole del portale (se il database è raggiungibile)
* 5. le ultime righe d'acquisto
* e salva un CSV con gli articoli in /tmp.
*/
if (PHP_SAPI !== 'cli') {
http_response_code(403);
exit("Solo da terminale\n");
}
require_once __DIR__ . '/class/MagoClient.php';
/* ---------------- opzioni ---------------- */
$opt = getopt('', ['months::', 'pages::', 'fields']);
$months = max(1, (int)($opt['months'] ?? 24));
$maxPages = max(1, (int)($opt['pages'] ?? 40));
$showFields = isset($opt['fields']);
$since = date('Y-m-d', strtotime("-$months months"));
/* ---------------- .env ---------------- */
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;
}
/* ---------------- 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";
}