getConnection(); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $data = json_decode(file_get_contents('php://input'), true); $sourceIddatadb = isset($data['source_iddatadb']) ? (int)$data['source_iddatadb'] : 0; $targetList = $data['target_iddatadb_list'] ?? []; // Default true: se il client non manda il parametro, cloniamo comunque le note // (comportamento di default richiesto, coerente con la checkbox pre-spuntata) $cloneNotes = array_key_exists('clone_notes', $data) ? !empty($data['clone_notes']) : true; $cloneAnalyses = !empty($data['clone_analyses']); $overwrite = !empty($data['overwrite']); $targetIds = array_values(array_unique(array_filter(array_map('intval', (array)$targetList), function ($v) use ($sourceIddatadb) { return $v > 0 && $v !== $sourceIddatadb; }))); if ($sourceIddatadb <= 0 || empty($targetIds)) { echo json_encode([ 'success' => false, 'message' => 'Missing source or target records' ]); exit; } try { $pdo->beginTransaction(); // 1. Load source parts $stmtParts = $pdo->prepare(" SELECT id, part_number, part_description, mix, idmatrice, note, dateexpiry FROM identification_parts WHERE iddatadb = ? AND part_description IS NOT NULL AND TRIM(part_description) <> '' ORDER BY part_number ASC, id ASC "); $stmtParts->execute([$sourceIddatadb]); $sourceParts = $stmtParts->fetchAll(PDO::FETCH_ASSOC); if (empty($sourceParts)) { $pdo->rollBack(); echo json_encode([ 'success' => false, 'message' => 'No parts found for source record' ]); exit; } // 2. Prepare statements $stmtInsertPart = $pdo->prepare(" INSERT INTO identification_parts (iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry, created_at, updated_at) VALUES (:iddatadb, :part_number, :part_description, :mix, :idmatrice, :note, :dateexpiry, NOW(), NOW()) "); $stmtLoadCF = $pdo->prepare(" SELECT field_id, value_id, value_text FROM identification_parts_customfields WHERE part_id = ? ORDER BY id ASC "); $stmtLoadAnalyses = $pdo->prepare(" SELECT idmatrice, analysis_recordkey, analysis_name, analysis_method, analysis_level, is_web_selectable, is_accredited FROM identification_parts_analyses WHERE part_id = ? ORDER BY id ASC "); $stmtInsertAnalysis = $pdo->prepare(" INSERT INTO identification_parts_analyses (part_id, iddatadb, idmatrice, analysis_recordkey, analysis_name, analysis_method, analysis_level, is_web_selectable, is_accredited, created_at, updated_at) VALUES (:part_id, :iddatadb, :idmatrice, :analysis_recordkey, :analysis_name, :analysis_method, :analysis_level, :is_web_selectable, :is_accredited, NOW(), NOW()) "); $stmtInsertCF = $pdo->prepare(" INSERT INTO identification_parts_customfields (part_id, field_id, value_id, value_text, created_at, updated_at) VALUES (:part_id, :field_id, :value_id, :value_text, NOW(), NOW()) "); $details = []; $totalClonedParts = 0; $totalClonedAnalyses = 0; $totalDeletedParts = 0; // 2b. Se overwrite: elimina le parti esistenti dei target (e i loro figli) if ($overwrite) { $stmtSelectExisting = $pdo->prepare("SELECT id FROM identification_parts WHERE iddatadb = ?"); $stmtDeleteCFByPart = $pdo->prepare("DELETE FROM identification_parts_customfields WHERE part_id = ?"); $stmtDeleteAnByPart = $pdo->prepare("DELETE FROM identification_parts_analyses WHERE part_id = ?"); $stmtDeletePart = $pdo->prepare("DELETE FROM identification_parts WHERE id = ?"); foreach ($targetIds as $targetIddatadb) { $stmtSelectExisting->execute([$targetIddatadb]); $existingIds = $stmtSelectExisting->fetchAll(PDO::FETCH_COLUMN); foreach ($existingIds as $existingPartId) { $existingPartId = (int)$existingPartId; $stmtDeleteCFByPart->execute([$existingPartId]); $stmtDeleteAnByPart->execute([$existingPartId]); $stmtDeletePart->execute([$existingPartId]); $totalDeletedParts++; } } } // 3. Clone source parts to each target record foreach ($targetIds as $targetIddatadb) { $clonedCountForTarget = 0; foreach ($sourceParts as $part) { $stmtInsertPart->execute([ ':iddatadb' => $targetIddatadb, ':part_number' => $part['part_number'], ':part_description' => $part['part_description'], ':mix' => $part['mix'] ?? 'N', ':idmatrice' => $part['idmatrice'] !== '' ? $part['idmatrice'] : null, ':note' => $cloneNotes ? ($part['note'] ?? null) : null, ':dateexpiry' => $part['dateexpiry'] ?? null, ]); $newPartId = (int)$pdo->lastInsertId(); // Load source custom fields for this part $stmtLoadCF->execute([(int)$part['id']]); $customFields = $stmtLoadCF->fetchAll(PDO::FETCH_ASSOC); foreach ($customFields as $cf) { $stmtInsertCF->execute([ ':part_id' => $newPartId, ':field_id' => (int)$cf['field_id'], ':value_id' => ($cf['value_id'] !== null && $cf['value_id'] !== '') ? (int)$cf['value_id'] : null, ':value_text' => $cf['value_text'] !== '' ? $cf['value_text'] : null, ]); } // Clone linked analyses, if requested if ($cloneAnalyses) { $stmtLoadAnalyses->execute([(int)$part['id']]); $analyses = $stmtLoadAnalyses->fetchAll(PDO::FETCH_ASSOC); foreach ($analyses as $an) { $stmtInsertAnalysis->execute([ ':part_id' => $newPartId, ':iddatadb' => $targetIddatadb, ':idmatrice' => ($an['idmatrice'] !== null && $an['idmatrice'] !== '') ? (int)$an['idmatrice'] : null, ':analysis_recordkey' => $an['analysis_recordkey'], ':analysis_name' => $an['analysis_name'] ?? null, ':analysis_method' => $an['analysis_method'] ?? null, ':analysis_level' => ($an['analysis_level'] !== null && $an['analysis_level'] !== '') ? (int)$an['analysis_level'] : null, ':is_web_selectable' => (int)($an['is_web_selectable'] ?? 0), ':is_accredited' => (int)($an['is_accredited'] ?? 0), ]); $totalClonedAnalyses++; } } $clonedCountForTarget++; $totalClonedParts++; } $details[] = [ 'target_iddatadb' => $targetIddatadb, 'cloned_parts' => $clonedCountForTarget ]; } $pdo->commit(); echo json_encode([ 'success' => true, 'source_iddatadb' => $sourceIddatadb, 'cloned_targets' => count($targetIds), 'total_cloned_parts' => $totalClonedParts, 'total_cloned_analyses' => $totalClonedAnalyses, 'details' => $details ]); } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } echo json_encode([ 'success' => false, 'message' => 'Clone failed: ' . $e->getMessage() ]); }