From b2a79aec1ba7e576538f43cdc9620240b46dcb6a Mon Sep 17 00:00:00 2001 From: solocla Date: Thu, 23 Jul 2026 12:13:28 +0200 Subject: [PATCH] multiple analysis on same parts --- ...23100916_drop_uniq_part_analysis_index.php | 25 +++++ public/userarea/analysisModal.js | 93 +++++++++++-------- public/userarea/delete_part_analysis.php | 35 +++++-- public/userarea/save_part_analysis.php | 12 +-- 4 files changed, 107 insertions(+), 58 deletions(-) create mode 100644 db/migrations/20260723100916_drop_uniq_part_analysis_index.php diff --git a/db/migrations/20260723100916_drop_uniq_part_analysis_index.php b/db/migrations/20260723100916_drop_uniq_part_analysis_index.php new file mode 100644 index 0000000..f78d146 --- /dev/null +++ b/db/migrations/20260723100916_drop_uniq_part_analysis_index.php @@ -0,0 +1,25 @@ +table('identification_parts_analyses') + ->removeIndexByName('uniq_part_analysis') + ->update(); + } + + public function down(): void + { + $this->table('identification_parts_analyses') + ->addIndex(['part_id', 'analysis_recordkey'], [ + 'unique' => true, + 'name' => 'uniq_part_analysis', + ]) + ->update(); + } +} diff --git a/public/userarea/analysisModal.js b/public/userarea/analysisModal.js index dc813e5..96bee35 100644 --- a/public/userarea/analysisModal.js +++ b/public/userarea/analysisModal.js @@ -106,8 +106,9 @@ } container.innerHTML = items - .map(function (item) { + .map(function (item, index) { const recordKey = item.analysis_recordkey || ""; + const rowId = item.id != null ? item.id : ""; const title = item.analysis_name || "Unnamed analysis"; const method = item.analysis_method || ""; @@ -121,6 +122,8 @@ class="analysis-remove-btn" data-part-id="${escapeHtml(partId)}" data-recordkey="${escapeHtml(recordKey)}" + data-row-id="${escapeHtml(rowId)}" + data-index="${index}" title="Remove analysis">× `; @@ -166,19 +169,21 @@ }; } - function addAnalysisToLocalState(partId, payload, iddatadb, idmatrice) { + function addAnalysisToLocalState( + partId, + payload, + iddatadb, + idmatrice, + newId, + ) { const key = String(partId); if (!Array.isArray(analysisAssignedState[key])) { analysisAssignedState[key] = []; } - const exists = analysisAssignedState[key].some(function (item) { - return item.analysis_recordkey === payload.analysis_recordkey; - }); - - if (!exists) { + { analysisAssignedState[key].push({ - id: null, + id: newId || null, part_id: parseInt(partId, 10), iddatadb: iddatadb || null, idmatrice: idmatrice || null, @@ -195,17 +200,24 @@ } } - function removeAnalysisFromLocalState(partId, recordKey) { + function removeAnalysisFromLocalState(partId, recordKey, index) { const key = String(partId); if (!Array.isArray(analysisAssignedState[key])) { return; } - analysisAssignedState[key] = analysisAssignedState[key].filter( - function (item) { - return item.analysis_recordkey !== recordKey; - }, - ); + const i = parseInt(index, 10); + if (!isNaN(i) && i >= 0 && i < analysisAssignedState[key].length) { + analysisAssignedState[key].splice(i, 1); + return; + } + + const found = analysisAssignedState[key].findIndex(function (item) { + return item.analysis_recordkey === recordKey; + }); + if (found !== -1) { + analysisAssignedState[key].splice(found, 1); + } } function saveAnalysisAssociation(partId, payload, callback) { @@ -237,12 +249,13 @@ is_accredited: payload.is_accredited, }, }) - .done(function () { + .done(function (resp) { addAnalysisToLocalState( partId, payload, iddatadb, idmatrice !== "NO_MATRIX" ? idmatrice : null, + resp && resp.id ? resp.id : null, ); renderAssignedAnalysesForPart(partId); if (typeof callback === "function") callback(true); @@ -257,7 +270,13 @@ }); } - function deleteAnalysisAssociation(partId, recordKey, callback) { + function deleteAnalysisAssociation( + partId, + recordKey, + rowId, + index, + callback, + ) { $.ajax({ url: "delete_part_analysis.php", method: "POST", @@ -265,10 +284,11 @@ data: { part_id: partId, analysis_recordkey: recordKey, + row_id: rowId || "", }, }) .done(function () { - removeAnalysisFromLocalState(partId, recordKey); + removeAnalysisFromLocalState(partId, recordKey, index); renderAssignedAnalysesForPart(partId); if (typeof callback === "function") callback(true); }) @@ -707,10 +727,18 @@ const partId = removeBtn.getAttribute("data-part-id"); const recordKey = removeBtn.getAttribute("data-recordkey"); + const rowId = removeBtn.getAttribute("data-row-id"); + const index = removeBtn.getAttribute("data-index"); - deleteAnalysisAssociation(partId, recordKey, function () { - const stillUsed = Object.keys(analysisAssignedState).some( - function (pid) { + deleteAnalysisAssociation( + partId, + recordKey, + rowId, + index, + function () { + const stillUsed = Object.keys( + analysisAssignedState, + ).some(function (pid) { return ( Array.isArray(analysisAssignedState[pid]) && analysisAssignedState[pid].some( @@ -722,15 +750,15 @@ }, ) ); - }, - ); + }); - if (!stillUsed) { - delete analysisSelectedState[recordKey]; - } + if (!stillUsed) { + delete analysisSelectedState[recordKey]; + } - syncSelectedAnalysisRows(); - }); + syncSelectedAnalysisRows(); + }, + ); return; } @@ -751,17 +779,6 @@ } const recordKey = payload.analysis_recordkey; - const alreadySelected = !!analysisSelectedState[recordKey]; - - if (alreadySelected) { - selectedPartIds.forEach(function (partId) { - deleteAnalysisAssociation(partId, recordKey); - }); - - delete analysisSelectedState[recordKey]; - syncSelectedAnalysisRows(); - return; - } let pending = selectedPartIds.length; let atLeastOneSaved = false; diff --git a/public/userarea/delete_part_analysis.php b/public/userarea/delete_part_analysis.php index d8bb468..91ed5b0 100644 --- a/public/userarea/delete_part_analysis.php +++ b/public/userarea/delete_part_analysis.php @@ -15,8 +15,9 @@ try { $partId = isset($_POST['part_id']) ? (int)$_POST['part_id'] : 0; $analysisRecordkey = trim($_POST['analysis_recordkey'] ?? ''); + $rowId = isset($_POST['row_id']) && $_POST['row_id'] !== '' ? (int)$_POST['row_id'] : 0; - if ($partId <= 0 || $analysisRecordkey === '') { + if ($partId <= 0 || ($analysisRecordkey === '' && $rowId <= 0)) { http_response_code(400); echo json_encode(['success' => false, 'message' => 'Missing required data']); exit; @@ -25,15 +26,29 @@ try { $db = DBHandlerSelect::getInstance(); $pdo = $db->getConnection(); - $stmt = $pdo->prepare(" - DELETE FROM identification_parts_analyses - WHERE part_id = :part_id - AND analysis_recordkey = :analysis_recordkey - "); - $stmt->execute([ - ':part_id' => $partId, - ':analysis_recordkey' => $analysisRecordkey, - ]); + if ($rowId > 0) { + $stmt = $pdo->prepare(" + DELETE FROM identification_parts_analyses + WHERE id = :id + AND part_id = :part_id + LIMIT 1 + "); + $stmt->execute([ + ':id' => $rowId, + ':part_id' => $partId, + ]); + } else { + $stmt = $pdo->prepare(" + DELETE FROM identification_parts_analyses + WHERE part_id = :part_id + AND analysis_recordkey = :analysis_recordkey + LIMIT 1 + "); + $stmt->execute([ + ':part_id' => $partId, + ':analysis_recordkey' => $analysisRecordkey, + ]); + } echo json_encode([ 'success' => true, diff --git a/public/userarea/save_part_analysis.php b/public/userarea/save_part_analysis.php index 8be48a1..dd166df 100644 --- a/public/userarea/save_part_analysis.php +++ b/public/userarea/save_part_analysis.php @@ -54,15 +54,6 @@ try { :is_web_selectable, :is_accredited ) - ON DUPLICATE KEY UPDATE - analysis_name = VALUES(analysis_name), - analysis_method = VALUES(analysis_method), - analysis_level = VALUES(analysis_level), - is_web_selectable = VALUES(is_web_selectable), - is_accredited = VALUES(is_accredited), - iddatadb = VALUES(iddatadb), - idmatrice = VALUES(idmatrice), - updated_at = CURRENT_TIMESTAMP "); $stmt->execute([ @@ -79,7 +70,8 @@ try { echo json_encode([ 'success' => true, - 'message' => 'Association saved' + 'message' => 'Association saved', + 'id' => (int)$pdo->lastInsertId() ]); } catch (Throwable $e) { http_response_code(500);