Files
zibo-dashboard/public/userarea/mago_probe_purchases.php
T
2026-10-24 10:14:49 +02:00

387 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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";
}