multiple analysis on same parts

This commit is contained in:
2026-07-23 12:13:28 +02:00
parent a300932421
commit b2a79aec1b
4 changed files with 107 additions and 58 deletions
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class DropUniqPartAnalysisIndex extends AbstractMigration
{
public function up(): void
{
$this->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();
}
}
+55 -38
View File
@@ -106,8 +106,9 @@
} }
container.innerHTML = items container.innerHTML = items
.map(function (item) { .map(function (item, index) {
const recordKey = item.analysis_recordkey || ""; const recordKey = item.analysis_recordkey || "";
const rowId = item.id != null ? item.id : "";
const title = item.analysis_name || "Unnamed analysis"; const title = item.analysis_name || "Unnamed analysis";
const method = item.analysis_method || ""; const method = item.analysis_method || "";
@@ -121,6 +122,8 @@
class="analysis-remove-btn" class="analysis-remove-btn"
data-part-id="${escapeHtml(partId)}" data-part-id="${escapeHtml(partId)}"
data-recordkey="${escapeHtml(recordKey)}" data-recordkey="${escapeHtml(recordKey)}"
data-row-id="${escapeHtml(rowId)}"
data-index="${index}"
title="Remove analysis">×</button> title="Remove analysis">×</button>
</div> </div>
`; `;
@@ -166,19 +169,21 @@
}; };
} }
function addAnalysisToLocalState(partId, payload, iddatadb, idmatrice) { function addAnalysisToLocalState(
partId,
payload,
iddatadb,
idmatrice,
newId,
) {
const key = String(partId); const key = String(partId);
if (!Array.isArray(analysisAssignedState[key])) { if (!Array.isArray(analysisAssignedState[key])) {
analysisAssignedState[key] = []; analysisAssignedState[key] = [];
} }
const exists = analysisAssignedState[key].some(function (item) { {
return item.analysis_recordkey === payload.analysis_recordkey;
});
if (!exists) {
analysisAssignedState[key].push({ analysisAssignedState[key].push({
id: null, id: newId || null,
part_id: parseInt(partId, 10), part_id: parseInt(partId, 10),
iddatadb: iddatadb || null, iddatadb: iddatadb || null,
idmatrice: idmatrice || null, idmatrice: idmatrice || null,
@@ -195,17 +200,24 @@
} }
} }
function removeAnalysisFromLocalState(partId, recordKey) { function removeAnalysisFromLocalState(partId, recordKey, index) {
const key = String(partId); const key = String(partId);
if (!Array.isArray(analysisAssignedState[key])) { if (!Array.isArray(analysisAssignedState[key])) {
return; return;
} }
analysisAssignedState[key] = analysisAssignedState[key].filter( const i = parseInt(index, 10);
function (item) { if (!isNaN(i) && i >= 0 && i < analysisAssignedState[key].length) {
return item.analysis_recordkey !== recordKey; 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) { function saveAnalysisAssociation(partId, payload, callback) {
@@ -237,12 +249,13 @@
is_accredited: payload.is_accredited, is_accredited: payload.is_accredited,
}, },
}) })
.done(function () { .done(function (resp) {
addAnalysisToLocalState( addAnalysisToLocalState(
partId, partId,
payload, payload,
iddatadb, iddatadb,
idmatrice !== "NO_MATRIX" ? idmatrice : null, idmatrice !== "NO_MATRIX" ? idmatrice : null,
resp && resp.id ? resp.id : null,
); );
renderAssignedAnalysesForPart(partId); renderAssignedAnalysesForPart(partId);
if (typeof callback === "function") callback(true); if (typeof callback === "function") callback(true);
@@ -257,7 +270,13 @@
}); });
} }
function deleteAnalysisAssociation(partId, recordKey, callback) { function deleteAnalysisAssociation(
partId,
recordKey,
rowId,
index,
callback,
) {
$.ajax({ $.ajax({
url: "delete_part_analysis.php", url: "delete_part_analysis.php",
method: "POST", method: "POST",
@@ -265,10 +284,11 @@
data: { data: {
part_id: partId, part_id: partId,
analysis_recordkey: recordKey, analysis_recordkey: recordKey,
row_id: rowId || "",
}, },
}) })
.done(function () { .done(function () {
removeAnalysisFromLocalState(partId, recordKey); removeAnalysisFromLocalState(partId, recordKey, index);
renderAssignedAnalysesForPart(partId); renderAssignedAnalysesForPart(partId);
if (typeof callback === "function") callback(true); if (typeof callback === "function") callback(true);
}) })
@@ -707,10 +727,18 @@
const partId = removeBtn.getAttribute("data-part-id"); const partId = removeBtn.getAttribute("data-part-id");
const recordKey = removeBtn.getAttribute("data-recordkey"); const recordKey = removeBtn.getAttribute("data-recordkey");
const rowId = removeBtn.getAttribute("data-row-id");
const index = removeBtn.getAttribute("data-index");
deleteAnalysisAssociation(partId, recordKey, function () { deleteAnalysisAssociation(
const stillUsed = Object.keys(analysisAssignedState).some( partId,
function (pid) { recordKey,
rowId,
index,
function () {
const stillUsed = Object.keys(
analysisAssignedState,
).some(function (pid) {
return ( return (
Array.isArray(analysisAssignedState[pid]) && Array.isArray(analysisAssignedState[pid]) &&
analysisAssignedState[pid].some( analysisAssignedState[pid].some(
@@ -722,15 +750,15 @@
}, },
) )
); );
}, });
);
if (!stillUsed) { if (!stillUsed) {
delete analysisSelectedState[recordKey]; delete analysisSelectedState[recordKey];
} }
syncSelectedAnalysisRows(); syncSelectedAnalysisRows();
}); },
);
return; return;
} }
@@ -751,17 +779,6 @@
} }
const recordKey = payload.analysis_recordkey; 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 pending = selectedPartIds.length;
let atLeastOneSaved = false; let atLeastOneSaved = false;
+25 -10
View File
@@ -15,8 +15,9 @@ try {
$partId = isset($_POST['part_id']) ? (int)$_POST['part_id'] : 0; $partId = isset($_POST['part_id']) ? (int)$_POST['part_id'] : 0;
$analysisRecordkey = trim($_POST['analysis_recordkey'] ?? ''); $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); http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Missing required data']); echo json_encode(['success' => false, 'message' => 'Missing required data']);
exit; exit;
@@ -25,15 +26,29 @@ try {
$db = DBHandlerSelect::getInstance(); $db = DBHandlerSelect::getInstance();
$pdo = $db->getConnection(); $pdo = $db->getConnection();
$stmt = $pdo->prepare(" if ($rowId > 0) {
DELETE FROM identification_parts_analyses $stmt = $pdo->prepare("
WHERE part_id = :part_id DELETE FROM identification_parts_analyses
AND analysis_recordkey = :analysis_recordkey WHERE id = :id
"); AND part_id = :part_id
$stmt->execute([ LIMIT 1
':part_id' => $partId, ");
':analysis_recordkey' => $analysisRecordkey, $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([ echo json_encode([
'success' => true, 'success' => true,
+2 -10
View File
@@ -54,15 +54,6 @@ try {
:is_web_selectable, :is_web_selectable,
:is_accredited :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([ $stmt->execute([
@@ -79,7 +70,8 @@ try {
echo json_encode([ echo json_encode([
'success' => true, 'success' => true,
'message' => 'Association saved' 'message' => 'Association saved',
'id' => (int)$pdo->lastInsertId()
]); ]);
} catch (Throwable $e) { } catch (Throwable $e) {
http_response_code(500); http_response_code(500);