Files
zibo-dashboard/public/userarea/class/MagoProduction.php
T
2026-10-21 10:03:59 +02:00

548 lines
27 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'), ',');
}
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;
}
}
}