Files
zibo-dashboard/public/userarea/mago_probe_purchases.php
T

397 lines
18 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
* php mago_probe_purchases.php --types=9830401,9830402 tipi documento da leggere (default fattura fornitore)
*
* L'avanzamento (pagina per pagina) esce a video anche se l'output va in un file.
*
* 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', 'types::']);
$months = max(1, (int)($opt['months'] ?? 24));
$maxPages = max(1, (int)($opt['pages'] ?? 40));
$showFields = isset($opt['fields']);
// tipi documento da leggere: 9830401 = fattura fornitore (dal primo test). Più tipi separati da virgola.
$docTypes = array_values(array_filter(array_map('trim', explode(',', (string)($opt['types'] ?? '9830401'))), 'strlen'));
$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";
}
}
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";
}