563 lines
28 KiB
PHP
563 lines
28 KiB
PHP
<?php
|
|
|
|
/**
|
|
* MagoProduction - ponte tra le righe d'ordine Mago e la programmazione produzione.
|
|
*
|
|
* MagoProduction::refreshQueue($pdo) allinea la coda "Nuovi da Mago" (dopo ogni sync)
|
|
* MagoProduction::import($pdo, $id, $data) porta una riga della coda in productiondata
|
|
*
|
|
* Mago resta in sola lettura: qui si legge solo dalle tabelle locali mago_*.
|
|
*/
|
|
|
|
require_once __DIR__ . '/MagoSalesStats.php';
|
|
|
|
class MagoProduction
|
|
{
|
|
/* ===================== IMPOSTAZIONI ===================== */
|
|
|
|
/** Righe d'ordine considerate: solo ordini da questa data in poi (per partire con poche righe di prova). */
|
|
const QUEUE_FROM_DATE = '2026-10-01';
|
|
|
|
/** Produzione solo se la quantità è maggiore di questo valore (1 o 0 = servizio, indicazione del cliente). */
|
|
const MIN_QTY = 1;
|
|
|
|
/** Articoli che non si producono mai (prefissi del codice articolo Mago). */
|
|
const EXCLUDED_ITEM_PREFIXES = ['SPES', 'SPATT'];
|
|
|
|
/** Import automatico in programmazione delle righe con matrice certa e linea unica (false = solo su conferma). */
|
|
const AUTO_IMPORT = false;
|
|
|
|
/** Status di productiondata assegnato all'import. */
|
|
const STATUS_DA_PROGRAMMARE = 1;
|
|
const STATUS_PROGRAMMATO = 6;
|
|
|
|
/** Etichette dei tipi di Matrice/Misura. */
|
|
const TIPI = [
|
|
'M' => 'Matrice',
|
|
'D' => 'Diametro',
|
|
'R' => 'Rettangolo',
|
|
'T' => 'Tubo',
|
|
'Q' => 'Quadrato',
|
|
'S' => 'Sezione',
|
|
'?' => 'Altro',
|
|
];
|
|
|
|
/* ===================== MATRICE / MISURA ===================== */
|
|
|
|
/**
|
|
* Scompone il valore "Matrice/Misura" di Mago.
|
|
* M00786S → tipo M, codice 00786S
|
|
* M00407P-1 → tipo M, codice 00407P-1
|
|
* Q8X8M00936P → tipo M, codice 00936P, misura 8X8 (profilo Q)
|
|
* D 3,3 → tipo D, misura 3,3
|
|
* ZB00193P → tipo ?, codice ZB00193P (si cerca il valore intero tra le matrici)
|
|
*/
|
|
public static function parse($raw)
|
|
{
|
|
$v = strtoupper(trim(preg_replace('/\s+/', ' ', (string)$raw)));
|
|
$out = ['raw' => $v, 'tipo' => '', 'codice' => '', 'misura' => ''];
|
|
if ($v === '') return $out;
|
|
|
|
if (preg_match('/M(\d{4,6}[A-Z]?(?:-\d+)?)/', $v, $m, PREG_OFFSET_CAPTURE)) {
|
|
$out['tipo'] = 'M';
|
|
$out['codice'] = $m[1][0];
|
|
$before = trim(substr($v, 0, $m[0][1]));
|
|
if (preg_match('/^[DRTQS]\s*([0-9][0-9.,X ]*)$/', $before, $mm)) $out['misura'] = trim($mm[1]);
|
|
return $out;
|
|
}
|
|
if (preg_match('/^([DRTQS])\s*([0-9][0-9.,X ]*)$/', $v, $m)) {
|
|
$out['tipo'] = $m[1];
|
|
$out['misura'] = trim($m[2]);
|
|
return $out;
|
|
}
|
|
$out['tipo'] = '?';
|
|
$out['codice'] = $v;
|
|
return $out;
|
|
}
|
|
|
|
/** Indice delle matrici del portale per il collegamento (caricato una volta). */
|
|
private static function matriceIndex(PDO $pdo)
|
|
{
|
|
$idx = ['exact' => [], 'base' => [], 'num' => []];
|
|
foreach ($pdo->query("SELECT id, nome FROM matrice WHERE TRIM(nome) <> ''") as $r) {
|
|
$tok = strtoupper(strtok(trim($r['nome']), ' '));
|
|
if ($tok === '' || $tok === false) continue;
|
|
$idx['exact'][$tok] = ['id' => (int)$r['id'], 'nome' => trim($r['nome'])];
|
|
$base = preg_replace('/-\d+$/', '', $tok);
|
|
$idx['base'][$base][] = ['id' => (int)$r['id'], 'nome' => trim($r['nome']), 'tok' => $tok];
|
|
if (preg_match('/^(\d+)/', $tok, $n)) $idx['num'][(int)$n[1]][] = trim($r['nome']);
|
|
}
|
|
foreach ($idx['base'] as &$list) {
|
|
usort($list, function ($a, $b) {
|
|
return strnatcmp($b['tok'], $a['tok']); // revisione più alta per prima
|
|
});
|
|
}
|
|
unset($list);
|
|
$idx['map'] = [];
|
|
try {
|
|
foreach ($pdo->query("SELECT codice_mago, idmatrice FROM matrice_mago_map") as $r) {
|
|
$idx['map'][strtoupper(trim($r['codice_mago']))] = (int)$r['idmatrice'];
|
|
}
|
|
} catch (Throwable $e) {
|
|
}
|
|
return $idx;
|
|
}
|
|
|
|
/**
|
|
* Trova la matrice del portale per un valore Matrice/Misura.
|
|
* Ritorna [idmatrice|null, match_status, match_info, parse]
|
|
*/
|
|
public static function resolve(array $idx, $raw)
|
|
{
|
|
$p = self::parse($raw);
|
|
if ($p['raw'] === '') return [null, 'anomalia', 'Matrice/Misura vuoto in Mago', $p];
|
|
|
|
// 0) corrispondenza manuale
|
|
if (isset($idx['map'][$p['raw']])) return [$idx['map'][$p['raw']], 'mappa', 'Corrispondenza salvata', $p];
|
|
|
|
if ($p['codice'] !== '') {
|
|
$c = $p['codice'];
|
|
// 1) esatta
|
|
if (isset($idx['exact'][$c])) return [$idx['exact'][$c]['id'], 'esatta', '', $p];
|
|
// 2) stessa matrice, altra revisione
|
|
$base = preg_replace('/-\d+$/', '', $c);
|
|
if (!empty($idx['base'][$base])) {
|
|
$best = $idx['base'][$base][0];
|
|
return [$best['id'], 'revisione', "Revisione diversa: Mago $c, anagrafica {$best['nome']}", $p];
|
|
}
|
|
// 3) stesso numero, lettera diversa: solo suggerimento
|
|
$sim = '';
|
|
if (preg_match('/^(\d+)/', $c, $n) && !empty($idx['num'][(int)$n[1]])) {
|
|
$sim = ' · simili: ' . implode(', ', array_slice($idx['num'][(int)$n[1]], 0, 3));
|
|
}
|
|
if ($p['tipo'] === 'M') return [null, 'da_creare', "Matrice $c non presente in anagrafica" . $sim, $p];
|
|
return [null, 'anomalia', "Codice $c non riconosciuto" . $sim, $p];
|
|
}
|
|
|
|
// profilo standard (D/R/T/Q/S + misura)
|
|
$label = self::TIPI[$p['tipo']] ?? $p['tipo'];
|
|
return [null, 'profilo', "$label {$p['misura']}: scegliere la matrice", $p];
|
|
}
|
|
|
|
/* ===================== CODA "NUOVI DA MAGO" ===================== */
|
|
|
|
/**
|
|
* Allinea la coda con le righe d'ordine Mago locali.
|
|
* Ritorna un piccolo report (nuove, aggiornate, chiuse, modificate dopo l'import, importate in automatico).
|
|
*/
|
|
public static function refreshQueue(PDO $pdo)
|
|
{
|
|
$rep = ['candidati' => 0, 'nuove' => 0, 'aggiornate' => 0, 'chiuse' => 0, 'modificate' => 0, 'auto_importate' => 0];
|
|
$idx = self::matriceIndex($pdo);
|
|
|
|
$excl = '';
|
|
foreach (self::EXCLUDED_ITEM_PREFIXES as $i => $pfx) {
|
|
$excl .= " AND d.item NOT LIKE " . $pdo->quote($pfx . '%');
|
|
}
|
|
|
|
$rows = MagoSalesStats::fetchAllQ($pdo, "
|
|
SELECT d.sale_ord_id, d.sub_id, d.line, d.item, d.description, d.uom, d.qty, d.delivered_qty,
|
|
d.notes, d.matrice_misura, d.cod_art_cli,
|
|
" . MagoSalesStats::ziboDate('d') . " AS zibo_date,
|
|
" . MagoSalesStats::clientDate('d') . " AS client_date,
|
|
x.internal_ord_no, x.order_date, x.customer, x.company_name
|
|
FROM mago_sale_ord_detail d
|
|
JOIN " . MagoSalesStats::baseFrom() . " ON x.sale_ord_id = d.sale_ord_id
|
|
WHERE x.order_date >= :from
|
|
AND x.status IN ('aperto', 'bloccato')
|
|
AND d.delivered = 0 AND d.cancelled = 0
|
|
AND d.item <> '' AND d.qty > :minq
|
|
$excl
|
|
", ['from' => self::QUEUE_FROM_DATE, 'minq' => self::MIN_QTY]);
|
|
$rep['candidati'] = count($rows);
|
|
|
|
$existing = [];
|
|
foreach ($pdo->query("SELECT * FROM mago_prod_queue") as $q) {
|
|
$existing[$q['sale_ord_id'] . '-' . $q['sub_id']] = $q;
|
|
}
|
|
|
|
$ins = $pdo->prepare("
|
|
INSERT INTO mago_prod_queue (
|
|
sale_ord_id, sub_id, internal_ord_no, line_no, order_date, customer, company_name, item, description,
|
|
cod_art_cli, uom, qty, delivered_qty, residual_qty, zibo_date, client_date, mago_notes,
|
|
matrice_misura, mm_tipo, mm_codice, mm_misura, idmatrice, match_status, match_info,
|
|
queue_status, first_seen_at, updated_at
|
|
) VALUES (
|
|
:sale_ord_id, :sub_id, :internal_ord_no, :line_no, :order_date, :customer, :company_name, :item, :description,
|
|
:cod_art_cli, :uom, :qty, :delivered_qty, :residual_qty, :zibo_date, :client_date, :mago_notes,
|
|
:matrice_misura, :mm_tipo, :mm_codice, :mm_misura, :idmatrice, :match_status, :match_info,
|
|
'nuovo', NOW(), NOW()
|
|
)");
|
|
$upd = $pdo->prepare("
|
|
UPDATE mago_prod_queue SET
|
|
internal_ord_no = :internal_ord_no, line_no = :line_no, order_date = :order_date, customer = :customer,
|
|
company_name = :company_name, item = :item, description = :description, cod_art_cli = :cod_art_cli,
|
|
uom = :uom, qty = :qty, delivered_qty = :delivered_qty, residual_qty = :residual_qty,
|
|
zibo_date = :zibo_date, client_date = :client_date, mago_notes = :mago_notes,
|
|
matrice_misura = :matrice_misura, mm_tipo = :mm_tipo, mm_codice = :mm_codice, mm_misura = :mm_misura,
|
|
idmatrice = :idmatrice, match_status = :match_status, match_info = :match_info,
|
|
queue_status = :queue_status, mago_changed = :mago_changed, mago_changes = :mago_changes,
|
|
updated_at = NOW()
|
|
WHERE id = :id");
|
|
|
|
$seen = [];
|
|
$pdo->beginTransaction();
|
|
try {
|
|
foreach ($rows as $r) {
|
|
$key = $r['sale_ord_id'] . '-' . $r['sub_id'];
|
|
$seen[$key] = true;
|
|
[$idm, $ms, $mi, $p] = self::resolve($idx, $r['matrice_misura']);
|
|
$residual = max(0, (float)$r['qty'] - (float)$r['delivered_qty']);
|
|
$data = [
|
|
'internal_ord_no' => $r['internal_ord_no'],
|
|
'line_no' => (int)$r['line'],
|
|
'order_date' => $r['order_date'],
|
|
'customer' => $r['customer'],
|
|
'company_name' => mb_substr((string)$r['company_name'], 0, 200),
|
|
'item' => $r['item'],
|
|
'description' => mb_substr((string)$r['description'], 0, 255),
|
|
'cod_art_cli' => (string)$r['cod_art_cli'],
|
|
'uom' => $r['uom'],
|
|
'qty' => $r['qty'],
|
|
'delivered_qty' => $r['delivered_qty'],
|
|
'residual_qty' => $residual,
|
|
'zibo_date' => $r['zibo_date'],
|
|
'client_date' => $r['client_date'],
|
|
'mago_notes' => $r['notes'],
|
|
'matrice_misura' => (string)$r['matrice_misura'],
|
|
'mm_tipo' => $p['tipo'],
|
|
'mm_codice' => mb_substr($p['codice'], 0, 40),
|
|
'mm_misura' => mb_substr($p['misura'], 0, 40),
|
|
'idmatrice' => $idm,
|
|
'match_status' => $ms,
|
|
'match_info' => mb_substr($mi, 0, 255),
|
|
];
|
|
|
|
if (!isset($existing[$key])) {
|
|
$ins->execute($data + ['sale_ord_id' => $r['sale_ord_id'], 'sub_id' => $r['sub_id']]);
|
|
$rep['nuove']++;
|
|
continue;
|
|
}
|
|
|
|
$q = $existing[$key];
|
|
$status = $q['queue_status'];
|
|
$changed = (int)$q['mago_changed'];
|
|
$changes = $q['mago_changes'];
|
|
|
|
// scelta manuale della matrice: non la tocco
|
|
if ($q['match_status'] === 'manuale' && $q['idmatrice']) {
|
|
$data['idmatrice'] = $q['idmatrice'];
|
|
$data['match_status'] = 'manuale';
|
|
$data['match_info'] = $q['match_info'];
|
|
}
|
|
|
|
if ($status === 'importato') {
|
|
$diff = self::diff($q, $data);
|
|
if ($diff) {
|
|
$changed = 1;
|
|
$changes = mb_substr(trim(($changes ? $changes . ' | ' : '') . date('d/m') . ': ' . implode(', ', $diff)), -500);
|
|
$rep['modificate']++;
|
|
}
|
|
// dopo l'import la matrice resta quella scelta in produzione
|
|
$data['idmatrice'] = $q['idmatrice'];
|
|
$data['match_status'] = $q['match_status'];
|
|
$data['match_info'] = $q['match_info'];
|
|
} elseif ($status === 'chiuso_mago') {
|
|
$status = 'nuovo'; // riaperta in Mago
|
|
}
|
|
|
|
$upd->execute($data + ['queue_status' => $status, 'mago_changed' => $changed, 'mago_changes' => $changes, 'id' => $q['id']]);
|
|
$rep['aggiornate']++;
|
|
}
|
|
|
|
// righe non più "da produrre" in Mago (consegnate, annullate, cancellate, quantità ≤ 1...)
|
|
$why = $pdo->prepare("SELECT delivered, cancelled FROM mago_sale_ord_detail WHERE sale_ord_id = ? AND sub_id = ?");
|
|
$close = $pdo->prepare("UPDATE mago_prod_queue SET queue_status = :s, mago_changed = :c, mago_changes = :t, updated_at = NOW() WHERE id = :id");
|
|
foreach ($existing as $key => $q) {
|
|
if (isset($seen[$key]) || in_array($q['queue_status'], ['chiuso_mago'], true)) continue;
|
|
$why->execute([$q['sale_ord_id'], $q['sub_id']]);
|
|
$d = $why->fetch(PDO::FETCH_ASSOC);
|
|
$reason = !$d ? 'riga cancellata in Mago' : ((int)$d['cancelled'] ? 'riga annullata in Mago' : ((int)$d['delivered'] ? 'riga consegnata in Mago' : 'riga non più da produrre in Mago'));
|
|
|
|
if ($q['queue_status'] === 'importato') {
|
|
// già in produzione: segnalo solo se è un annullamento/cancellazione (la consegna è normale)
|
|
if (strpos($reason, 'consegnata') === false && strpos((string)$q['mago_changes'], $reason) === false) {
|
|
$close->execute(['s' => 'importato', 'c' => 1, 't' => mb_substr(trim(($q['mago_changes'] ? $q['mago_changes'] . ' | ' : '') . date('d/m') . ': ' . $reason), -500), 'id' => $q['id']]);
|
|
$rep['modificate']++;
|
|
}
|
|
continue;
|
|
}
|
|
if ($q['queue_status'] === 'nuovo' || $q['queue_status'] === 'scartato') {
|
|
$close->execute(['s' => 'chiuso_mago', 'c' => 0, 't' => $reason, 'id' => $q['id']]);
|
|
$rep['chiuse']++;
|
|
}
|
|
}
|
|
$pdo->commit();
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
|
throw $e;
|
|
}
|
|
|
|
// import automatico (spento finché AUTO_IMPORT = false)
|
|
if (self::AUTO_IMPORT) {
|
|
$ids = $pdo->query("SELECT id FROM mago_prod_queue WHERE queue_status = 'nuovo' AND match_status IN ('esatta','mappa') AND idmatrice IS NOT NULL")->fetchAll(PDO::FETCH_COLUMN);
|
|
foreach ($ids as $id) {
|
|
$pre = self::prefill($pdo, (int)$id);
|
|
if (!$pre || !$pre['id_linea']) continue; // linea non univoca: resta da completare a mano
|
|
try {
|
|
self::import($pdo, (int)$id, $pre, 'automatico');
|
|
$rep['auto_importate']++;
|
|
} catch (Throwable $e) {
|
|
}
|
|
}
|
|
}
|
|
return $rep;
|
|
}
|
|
|
|
/** Differenze rilevanti tra la riga in coda e la riga Mago aggiornata. */
|
|
private static function diff(array $q, array $d)
|
|
{
|
|
$out = [];
|
|
if (abs((float)$q['residual_qty'] - (float)$d['residual_qty']) > 0.0005) {
|
|
$out[] = 'quantità ' . self::num($q['residual_qty']) . ' → ' . self::num($d['residual_qty']) . ' ' . $d['uom'];
|
|
}
|
|
if ((string)$q['zibo_date'] !== (string)$d['zibo_date']) $out[] = 'data ZIBO ' . self::dt($q['zibo_date']) . ' → ' . self::dt($d['zibo_date']);
|
|
if ((string)$q['client_date'] !== (string)$d['client_date']) $out[] = 'data cliente ' . self::dt($q['client_date']) . ' → ' . self::dt($d['client_date']);
|
|
if (strtoupper(trim($q['matrice_misura'])) !== strtoupper(trim($d['matrice_misura']))) $out[] = 'matrice ' . $q['matrice_misura'] . ' → ' . $d['matrice_misura'];
|
|
return $out;
|
|
}
|
|
|
|
private static function num($v)
|
|
{
|
|
return rtrim(rtrim(number_format((float)$v, 3, ',', '.'), '0'), ',');
|
|
}
|
|
|
|
/** Riconosce la matrice di un valore Matrice/Misura (indice caricato una volta per richiesta). [idmatrice|null, match_status, match_info] */
|
|
public static function lookup(PDO $pdo, $raw)
|
|
{
|
|
static $idx = null;
|
|
if ($idx === null) {
|
|
try {
|
|
$idx = self::matriceIndex($pdo);
|
|
} catch (Throwable $e) {
|
|
$idx = ['exact' => [], 'base' => [], 'num' => [], 'map' => []];
|
|
}
|
|
}
|
|
[$id, $ms, $mi] = self::resolve($idx, $raw);
|
|
return [$id, $ms, $mi];
|
|
}
|
|
|
|
private static function dt($v)
|
|
{
|
|
return $v ? date('d/m/Y', strtotime($v)) : '—';
|
|
}
|
|
|
|
/* ===================== PRECOMPILAZIONE ===================== */
|
|
|
|
/** Dati tecnici di una matrice: linee, mescole e velocità dalla scheda di lavoro. */
|
|
public static function matriceInfo(PDO $pdo, $idmatrice)
|
|
{
|
|
$idmatrice = (int)$idmatrice;
|
|
$info = ['lines' => [], 'mescole' => [], 'speed_m_h' => null, 'speed_kg_h' => null, 'g_m' => null, 'worksheet' => null];
|
|
if (!$idmatrice) return $info;
|
|
|
|
$st = $pdo->prepare("SELECT idlinea FROM matrici_lines WHERE idmatrice = ? ORDER BY idlinea");
|
|
$st->execute([$idmatrice]);
|
|
$info['lines'] = array_map('intval', $st->fetchAll(PDO::FETCH_COLUMN));
|
|
|
|
$st = $pdo->prepare("SELECT id, worksheet_number, speed_expected_m_h, speed_expected_kg_h
|
|
FROM work_sheets WHERE idmatrice = ? AND worksheet_status = 'active'
|
|
ORDER BY worksheet_date DESC, id DESC LIMIT 1");
|
|
$st->execute([$idmatrice]);
|
|
if ($ws = $st->fetch(PDO::FETCH_ASSOC)) {
|
|
$info['worksheet'] = (int)$ws['worksheet_number'];
|
|
$info['speed_m_h'] = $ws['speed_expected_m_h'] !== null ? (float)$ws['speed_expected_m_h'] : null;
|
|
$info['speed_kg_h'] = $ws['speed_expected_kg_h'] !== null ? (float)$ws['speed_expected_kg_h'] : null;
|
|
$st = $pdo->prepare("SELECT idmescola, mix_weight_g_m FROM work_sheet_mescole WHERE worksheet_id = ? ORDER BY mix_position");
|
|
$st->execute([$ws['id']]);
|
|
$g = 0;
|
|
foreach ($st->fetchAll(PDO::FETCH_ASSOC) as $m) {
|
|
$info['mescole'][] = (int)$m['idmescola'];
|
|
$g += (float)$m['mix_weight_g_m'];
|
|
}
|
|
$info['g_m'] = $g > 0 ? $g : null;
|
|
}
|
|
if (!$info['mescole']) {
|
|
$st = $pdo->prepare("SELECT idmescola FROM matrici_mescole WHERE idmatrice = ?");
|
|
$st->execute([$idmatrice]);
|
|
$info['mescole'] = array_map('intval', $st->fetchAll(PDO::FETCH_COLUMN));
|
|
}
|
|
return $info;
|
|
}
|
|
|
|
/** Proposta dei campi di productiondata per una riga della coda. */
|
|
public static function prefill(PDO $pdo, $queueId, $idmatrice = null)
|
|
{
|
|
$st = $pdo->prepare("SELECT * FROM mago_prod_queue WHERE id = ?");
|
|
$st->execute([(int)$queueId]);
|
|
$q = $st->fetch(PDO::FETCH_ASSOC);
|
|
if (!$q) return null;
|
|
|
|
$idm = $idmatrice !== null ? (int)$idmatrice : (int)$q['idmatrice'];
|
|
$info = self::matriceInfo($pdo, $idm);
|
|
$uom = strtoupper(trim($q['uom']));
|
|
$qty = (float)$q['residual_qty'];
|
|
$metri = in_array($uom, ['MT', 'M', 'ML'], true) ? $qty : 0;
|
|
|
|
$ore = 0;
|
|
if ($metri > 0 && $info['speed_m_h'] > 0) $ore = $metri / $info['speed_m_h'];
|
|
elseif ($uom === 'KG' && $info['speed_kg_h'] > 0) $ore = $qty / $info['speed_kg_h'];
|
|
|
|
$note = [];
|
|
$note[] = "Da Mago: ordine {$q['internal_ord_no']} riga {$q['line_no']} · {$q['item']}" . ($q['cod_art_cli'] ? " · cod. cliente {$q['cod_art_cli']}" : '');
|
|
$note[] = 'Matrice/Misura Mago: ' . $q['matrice_misura'] . ($q['mm_misura'] !== '' ? ' (' . (self::TIPI[$q['mm_tipo']] ?? '') . ' ' . $q['mm_misura'] . ')' : '');
|
|
if ($metri <= 0) $note[] = 'Quantità ordinata: ' . self::num($qty) . ' ' . $q['uom'];
|
|
if ($metri > 0 && $info['g_m']) $note[] = 'Kg stimati: ' . self::num(round($metri * $info['g_m'] / 1000, 1)) . ' (scheda di lavoro ' . $info['worksheet'] . ')';
|
|
if (trim((string)$q['mago_notes']) !== '') $note[] = trim($q['mago_notes']);
|
|
|
|
return [
|
|
'queue' => $q,
|
|
'idmatrice' => $idm ?: null,
|
|
'id_linea' => count($info['lines']) === 1 ? $info['lines'][0] : null,
|
|
'lines' => $info['lines'],
|
|
'mescole' => $info['mescole'],
|
|
'metri' => round($metri, 2),
|
|
'kg_sp' => 0,
|
|
'kg_p' => 0,
|
|
'ore_previste' => round($ore, 2),
|
|
'data_zibo' => $q['zibo_date'],
|
|
'data_cliente' => $q['client_date'],
|
|
'conferma_ordine' => $q['internal_ord_no'],
|
|
'note_extra' => implode("\n", $note),
|
|
'id_status' => self::STATUS_DA_PROGRAMMARE,
|
|
'worksheet' => $info['worksheet'],
|
|
];
|
|
}
|
|
|
|
/* ===================== IMPORT IN PRODUZIONE ===================== */
|
|
|
|
/** Cliente del portale per un codice cliente Mago (lo crea se non esiste). */
|
|
public static function clientId(PDO $pdo, $code, $name)
|
|
{
|
|
$code = trim((string)$code);
|
|
$name = trim((string)$name) ?: $code;
|
|
if ($code === '') return null;
|
|
|
|
$st = $pdo->prepare("SELECT id FROM clients WHERE mago_code = ? LIMIT 1");
|
|
$st->execute([$code]);
|
|
if ($id = $st->fetchColumn()) return (int)$id;
|
|
|
|
$st = $pdo->prepare("SELECT id FROM clients WHERE (mago_code IS NULL OR mago_code = '') AND UPPER(TRIM(nome)) = UPPER(TRIM(?)) LIMIT 1");
|
|
$st->execute([$name]);
|
|
if ($id = $st->fetchColumn()) {
|
|
$pdo->prepare("UPDATE clients SET mago_code = ? WHERE id = ?")->execute([$code, $id]);
|
|
return (int)$id;
|
|
}
|
|
|
|
// nuovo cliente (il nome è unico: se esiste già con un altro codice aggiungo il codice)
|
|
$st = $pdo->prepare("SELECT COUNT(*) FROM clients WHERE nome = ?");
|
|
$st->execute([$name]);
|
|
$nome = $st->fetchColumn() ? "$name ($code)" : $name;
|
|
$pdo->prepare("INSERT INTO clients (nome, mago_code) VALUES (?, ?)")->execute([mb_substr($nome, 0, 255), $code]);
|
|
return (int)$pdo->lastInsertId();
|
|
}
|
|
|
|
/**
|
|
* Prossima posizione libera (in fondo) per uno stato della programmazione.
|
|
* La pagina numera le posizioni di tutte le linee in un'unica sequenza per stato,
|
|
* quindi MAX + 1 mette il lavoro in fondo anche alla sua linea.
|
|
*/
|
|
public static function nextPriority(PDO $pdo, $status)
|
|
{
|
|
$st = $pdo->prepare("SELECT COALESCE(MAX(priority), 0) + 1 FROM productiondata WHERE id_status = ?");
|
|
$st->execute([(int)$status]);
|
|
return (int)$st->fetchColumn();
|
|
}
|
|
|
|
/**
|
|
* Crea il lavoro in productiondata e collega la riga della coda.
|
|
* $d: idmatrice, id_linea, mescole[], metri, kg_sp, kg_p, ore_previste, data_zibo, data_cliente,
|
|
* note_extra, id_status, remember (salva la corrispondenza codice Mago → matrice)
|
|
*/
|
|
public static function import(PDO $pdo, $queueId, array $d, $user = '')
|
|
{
|
|
$st = $pdo->prepare("SELECT * FROM mago_prod_queue WHERE id = ? FOR UPDATE");
|
|
$own = !$pdo->inTransaction();
|
|
if ($own) $pdo->beginTransaction();
|
|
try {
|
|
$st->execute([(int)$queueId]);
|
|
$q = $st->fetch(PDO::FETCH_ASSOC);
|
|
if (!$q) throw new RuntimeException('Riga non trovata');
|
|
if ($q['queue_status'] === 'importato') throw new RuntimeException("Ordine {$q['internal_ord_no']} riga {$q['line_no']}: già in programmazione");
|
|
|
|
$idm = (int)($d['idmatrice'] ?? 0);
|
|
$lin = (int)($d['id_linea'] ?? 0);
|
|
if (!$idm) throw new RuntimeException("Ordine {$q['internal_ord_no']} riga {$q['line_no']}: manca la matrice");
|
|
if (!$lin) throw new RuntimeException("Ordine {$q['internal_ord_no']} riga {$q['line_no']}: manca la linea");
|
|
$status = (int)($d['id_status'] ?? self::STATUS_DA_PROGRAMMARE);
|
|
if (!in_array($status, [self::STATUS_DA_PROGRAMMARE, self::STATUS_PROGRAMMATO], true)) $status = self::STATUS_DA_PROGRAMMARE;
|
|
|
|
$idCliente = self::clientId($pdo, $q['customer'], $q['company_name']);
|
|
$date = function ($v) {
|
|
return MagoSalesStats::isValidDate((string)$v) ? $v : null;
|
|
};
|
|
|
|
$priority = self::nextPriority($pdo, $status);
|
|
|
|
$pdo->prepare("
|
|
INSERT INTO productiondata
|
|
(Data, data_produzione, conferma_ordine, idmatrice, id_linea, id_cliente, data_zibo, data_cliente,
|
|
metri, kg_sp, kg_p, ore_previste, note_extra, id_status, priority,
|
|
fermomacchina, `hour`, hourprod, kgprod, mtprod, scarto)
|
|
VALUES
|
|
(CURDATE(), NULL, :conferma, :idmatrice, :id_linea, :id_cliente, :data_zibo, :data_cliente,
|
|
:metri, :kg_sp, :kg_p, :ore, :note, :status, :priority,
|
|
0, '00:00:00', '00:00:00', 0, 0, 0)
|
|
")->execute([
|
|
'priority' => $priority,
|
|
'conferma' => mb_substr($q['internal_ord_no'], 0, 50),
|
|
'idmatrice' => $idm,
|
|
'id_linea' => $lin,
|
|
'id_cliente' => $idCliente,
|
|
'data_zibo' => $date($d['data_zibo'] ?? $q['zibo_date']),
|
|
'data_cliente' => $date($d['data_cliente'] ?? $q['client_date']),
|
|
'metri' => round((float)($d['metri'] ?? 0), 2),
|
|
'kg_sp' => round((float)($d['kg_sp'] ?? 0), 2),
|
|
'kg_p' => round((float)($d['kg_p'] ?? 0), 2),
|
|
'ore' => min(999.99, round((float)($d['ore_previste'] ?? 0), 2)),
|
|
'note' => (string)($d['note_extra'] ?? '') ?: null,
|
|
'status' => $status,
|
|
]);
|
|
$pid = (int)$pdo->lastInsertId();
|
|
|
|
$mescole = array_values(array_unique(array_filter(array_map('intval', (array)($d['mescole'] ?? [])))));
|
|
if ($mescole) {
|
|
$insM = $pdo->prepare("INSERT INTO productiondata_mescole (id_productiondata, id_mescola) VALUES (?, ?)");
|
|
foreach ($mescole as $m) $insM->execute([$pid, $m]);
|
|
}
|
|
|
|
// la scelta della matrice fatta a mano diventa la regola per le prossime righe con lo stesso codice
|
|
$matchStatus = $q['match_status'];
|
|
if ((int)$q['idmatrice'] !== $idm) $matchStatus = 'manuale';
|
|
if (!empty($d['remember']) && trim($q['matrice_misura']) !== '') {
|
|
$pdo->prepare("INSERT INTO matrice_mago_map (codice_mago, idmatrice, note, created_by, created_at)
|
|
VALUES (?, ?, ?, ?, NOW())
|
|
ON DUPLICATE KEY UPDATE idmatrice = VALUES(idmatrice), created_by = VALUES(created_by), created_at = NOW()")
|
|
->execute([strtoupper(trim($q['matrice_misura'])), $idm, 'da import ordine ' . $q['internal_ord_no'], mb_substr((string)$user, 0, 100)]);
|
|
$matchStatus = 'mappa';
|
|
}
|
|
|
|
$pdo->prepare("UPDATE mago_prod_queue SET queue_status = 'importato', productiondata_id = ?, idmatrice = ?,
|
|
match_status = ?, imported_at = NOW(), imported_by = ?, mago_changed = 0, mago_changes = '', updated_at = NOW()
|
|
WHERE id = ?")
|
|
->execute([$pid, $idm, $matchStatus, mb_substr((string)$user, 0, 100), $q['id']]);
|
|
|
|
if ($own) $pdo->commit();
|
|
return $pid;
|
|
} catch (Throwable $e) {
|
|
if ($own && $pdo->inTransaction()) $pdo->rollBack();
|
|
throw $e;
|
|
}
|
|
}
|
|
}
|