gitignore
This commit is contained in:
parent
99a30e4d9f
commit
a14aa6eb98
4
.gitignore
vendored
4
.gitignore
vendored
@ -32,3 +32,7 @@ auth.json
|
||||
# File XLSX temporanei importati
|
||||
/public/userarea/imported_trf/*.xlsx
|
||||
/public/userarea/xlstemplates/*.xlsx
|
||||
|
||||
# Ignora cartelle di foto generate
|
||||
/public/photostrf/
|
||||
/public/photostrf/qrcodes/
|
||||
|
||||
464
public/userarea/mapping_template_xls_scheme2bck31072025.php
Normal file
464
public/userarea/mapping_template_xls_scheme2bck31072025.php
Normal file
@ -0,0 +1,464 @@
|
||||
<?php include('include/headscript.php');
|
||||
|
||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||
die("Invalid template ID");
|
||||
}
|
||||
|
||||
$id = intval($_GET['id']);
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
$stmt = $pdo->prepare("SELECT name, header_row, start_column, target_table, sample_xlsx, idclient, clientname, idschema, schemaname, schemajson, xls_headers FROM excel_templates WHERE id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$template) {
|
||||
die("Template not found");
|
||||
}
|
||||
|
||||
$clientName = $template['clientname'] ?: '';
|
||||
$schemaName = $template['schemaname'] ?: '';
|
||||
$schemajson = $template['schemajson'] ? json_decode($template['schemajson'], true) : [];
|
||||
$isSchemajsonEmpty = empty(trim($template['schemajson']));
|
||||
|
||||
// Recupera i campi dalla tabella template_mapping
|
||||
$stmt = $pdo->prepare("SELECT id, field_id, excel_column, is_manual, manual_default, data_type, is_required, default_value, has_list, length, decimals, min_value, max_value, default_curr_date, tablename, field_label FROM template_mapping WHERE template_id = ?");
|
||||
$stmt->execute([$id]);
|
||||
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Recupera le colonne già associate nel database
|
||||
$usedColumnsFromDB = array_filter(array_column($mappings, 'excel_column'));
|
||||
|
||||
// Decodifica l'header XLS salvato, se presente
|
||||
$xlsHeaders = $template['xls_headers'] ? json_decode($template['xls_headers'], true) : [];
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<title>Configure Template <?= htmlspecialchars($template['name'], ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<?php include('include/navbar.php'); ?>
|
||||
<?php include('include/topbar.php'); ?>
|
||||
<div class="page-wrapper">
|
||||
<div class="page-content">
|
||||
<?php include('top_stat_widget.php'); ?>
|
||||
|
||||
<div class="card radius-10">
|
||||
<div class="card-header">
|
||||
<div class="d-flex align-items-center">
|
||||
<div>
|
||||
<h6 class="mb-0">Configure Template: <span id="templateName"><?php echo htmlspecialchars($template['name']); ?></span></h6>
|
||||
<p>
|
||||
Client: <span id="clientName"><?php echo htmlspecialchars($clientName); ?></span> |
|
||||
Schema: <span id="schemaName"><?php echo htmlspecialchars($schemaName); ?></span> |
|
||||
Header Row: <span id="headerRow"><?php echo $template['header_row']; ?></span> |
|
||||
Start Column: <span id="startColumn"><?php echo htmlspecialchars($template['start_column']); ?></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Upload XLS Example:</label>
|
||||
<input type="file" id="xlsUpload" class="form-control">
|
||||
<small id="uploadStatus">
|
||||
<?php if (!empty($template['sample_xlsx'])): ?>
|
||||
✅ Current file: <a href="xlstemplates/<?php echo htmlspecialchars($template['sample_xlsx']); ?>" target="_blank"><?php echo htmlspecialchars($template['sample_xlsx']); ?></a>
|
||||
<?php else: ?>
|
||||
No file uploaded yet.
|
||||
<?php endif; ?>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<h5>Schema Fields Configuration</h5>
|
||||
<button id="updateSchemaButton" class="btn btn-primary ms-2" data-empty="<?php echo $isSchemajsonEmpty ? 'true' : 'false'; ?>">
|
||||
<?php echo $isSchemajsonEmpty ? 'Load Schema Details' : 'Update Schema Details'; ?>
|
||||
</button>
|
||||
</div>
|
||||
<table id="schemaFieldsTable" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>ID</th>
|
||||
<th>Type</th>
|
||||
<th>Mapping</th>
|
||||
<th>Default Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="schemaFieldsBody">
|
||||
<?php foreach ($mappings as $mapping): ?>
|
||||
<tr>
|
||||
<td><?php echo htmlspecialchars($mapping['field_label'] ?? 'N/A'); ?></td>
|
||||
<td><?php echo htmlspecialchars($mapping['field_id'] ?? 'N/A'); ?></td>
|
||||
<td><?php echo htmlspecialchars($mapping['data_type'] ?? 'N/A'); ?></td>
|
||||
<td>
|
||||
<select class="form-select mapping-select" data-id="<?php echo $mapping['id']; ?>" data-field-id="<?php echo $mapping['field_id']; ?>">
|
||||
<option value="">Select Option</option>
|
||||
<option value="xls" <?php echo !$mapping['is_manual'] && $mapping['excel_column'] ? 'selected' : ''; ?>>Map to XLS Column</option>
|
||||
<option value="manual" <?php echo $mapping['is_manual'] ? 'selected' : ' '; ?>>Manual Entry</option>
|
||||
</select>
|
||||
<select class="form-select xls-columns" style="display:<?php echo !$mapping['is_manual'] && $mapping['excel_column'] ? 'block' : 'none'; ?>" data-id="<?php echo $mapping['id']; ?>" <?php echo $mapping['excel_column'] ? 'data-current-xls="' . htmlspecialchars($mapping['excel_column']) . '"' : ''; ?>></select>
|
||||
<?php if ($mapping['excel_column']): ?>
|
||||
<span class="mapped-column" style="margin-left: 5px;"><?php echo htmlspecialchars($mapping['excel_column']); ?></span>
|
||||
<button class="btn btn-danger btn-sm remove-xls" data-id="<?php echo $mapping['id']; ?>" style="margin-left: 5px;">X</button>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" class="form-control manual-default" placeholder="Default value" value="<?php echo htmlspecialchars($mapping['manual_default'] ?? ''); ?>" style="display:<?php echo $mapping['is_manual'] ? 'block' : 'none'; ?>" data-field-id="<?php echo $mapping['field_id']; ?>">
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-end">
|
||||
<a href="templates_dashboard.php" class="btn btn-primary">⬅ Back to Template Dashboard</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay toggle-icon"></div>
|
||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
|
||||
<?php include('jsinclude.php'); ?>
|
||||
<script>
|
||||
let availableXlsColumns = <?php echo json_encode($xlsHeaders); ?> || [];
|
||||
let usedColumnsFromDB = <?php echo json_encode($usedColumnsFromDB); ?> || [];
|
||||
|
||||
document.getElementById('xlsUpload').addEventListener('change', function(event) {
|
||||
let file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("xls_file", file);
|
||||
formData.append("template_id", <?php echo $id; ?>);
|
||||
|
||||
let statusText = document.getElementById('uploadStatus');
|
||||
statusText.innerText = "Uploading...";
|
||||
|
||||
fetch('upload_xls_example.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
statusText.innerText = "❌ Upload failed: " + data.message;
|
||||
return;
|
||||
}
|
||||
statusText.innerHTML = `✅ File uploaded: <a href="xlstemplates/${data.filename}" target="_blank">${data.filename}</a>`;
|
||||
processXLSX(file);
|
||||
})
|
||||
.catch(error => {
|
||||
statusText.innerText = "❌ Upload failed. Check console.";
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
|
||||
function processXLSX(file) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
let data = new Uint8Array(e.target.result);
|
||||
let workbook = XLSX.read(data, {
|
||||
type: 'array'
|
||||
});
|
||||
let sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
let rowIndex = parseInt(document.getElementById('headerRow').textContent) || 1; // Usa header_row dal template
|
||||
let startColumn = parseInt(document.getElementById('startColumn').textContent) || 1; // Usa start_column come numero
|
||||
|
||||
let sheetData = XLSX.utils.sheet_to_json(sheet, {
|
||||
header: 1,
|
||||
defval: "",
|
||||
raw: false,
|
||||
range: 0
|
||||
});
|
||||
console.log("Dati della riga " + rowIndex + ":", sheetData[rowIndex - 1]); // Debug: stampa la riga delle intestazioni
|
||||
if (!sheetData[rowIndex - 1]) {
|
||||
document.getElementById('schemaFieldsBody').querySelectorAll('select.xls-columns').forEach(select => {
|
||||
select.innerHTML = '<option value="">Nessuna intestazione trovata</option>';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Estrai le intestazioni a partire dalla colonna specificata, includendo le vuote
|
||||
let headers = sheetData[rowIndex - 1].slice(startColumn - 1).map(header => header === undefined ? "" : header);
|
||||
console.log("Intestazioni estratte:", headers); // Debug: stampa le intestazioni estratte
|
||||
availableXlsColumns = [...headers];
|
||||
usedColumnsFromDB = []; // Resetta le colonne usate dopo un nuovo caricamento
|
||||
saveXlsHeaders(headers);
|
||||
updateXlsDropdowns();
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
|
||||
function saveXlsHeaders(headers) {
|
||||
fetch('update_xls_headers.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
template_id: <?php echo $id; ?>,
|
||||
xls_headers: JSON.stringify(headers)
|
||||
})
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) console.error("❌ Error saving XLS headers:", data.message);
|
||||
})
|
||||
.catch(error => console.error("❌ Fetch error:", error));
|
||||
}
|
||||
|
||||
function updateXlsDropdowns() {
|
||||
let usedColumns = Array.from(document.querySelectorAll('select.xls-columns'))
|
||||
.filter(select => select.style.display === 'block' && select.value)
|
||||
.map(select => select.value)
|
||||
.concat(usedColumnsFromDB); // Aggiunge le colonne già salvate nel DB
|
||||
|
||||
document.querySelectorAll('select.xls-columns').forEach(select => {
|
||||
let currentValue = select.value || select.dataset.currentXls || '';
|
||||
let options = availableXlsColumns
|
||||
.filter(col => !usedColumns.includes(col) || col === currentValue) // Esclude colonne già usate, tranne la corrente
|
||||
.map(col => `<option value="${col}" ${col === currentValue ? 'selected' : ''}>${col}</option>`)
|
||||
.join('');
|
||||
select.innerHTML = '<option value="">Select XLS Column</option>' + options;
|
||||
select.dataset.currentXls = currentValue;
|
||||
if (currentValue && !options.includes(currentValue)) {
|
||||
select.value = ''; // Reset se il valore non è più valido
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
let templateId = <?php echo $id; ?>;
|
||||
let schemaId = <?php echo json_encode($template['idschema'] ?? 0); ?>;
|
||||
let isSchemajsonEmpty = <?php echo json_encode($isSchemajsonEmpty); ?>;
|
||||
|
||||
async function loadClientAndSchemaNames() {
|
||||
if (<?php echo json_encode($template['idclient'] ?? 0); ?> > 0) {
|
||||
let response = await fetch(`get_clienti.php?id=<?php echo $template['idclient']; ?>`);
|
||||
let data = await response.json();
|
||||
if (response.ok && data.value?.length) document.getElementById('clientName').textContent = data.value[0].Nominativo || 'N/A';
|
||||
}
|
||||
if (schemaId > 0) {
|
||||
let response = await fetch(`get_schemi.php?id=${schemaId}`);
|
||||
let data = await response.json();
|
||||
if (response.ok && data.value?.length) document.getElementById('schemaName').textContent = data.value[0].Nome || 'N/A';
|
||||
}
|
||||
}
|
||||
|
||||
loadClientAndSchemaNames();
|
||||
|
||||
async function updateSchemaDetails() {
|
||||
if (!schemaId) {
|
||||
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="5" class="text-warning">No schema associated.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
let updateSchemaButton = document.getElementById('updateSchemaButton');
|
||||
updateSchemaButton.disabled = true;
|
||||
updateSchemaButton.textContent = 'Loading...';
|
||||
|
||||
try {
|
||||
let response = await fetch(`get_schema_details.php?id=${schemaId}`);
|
||||
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
let data = JSON.parse(await response.text());
|
||||
if (!data.SchemiCustomFieldsDettagli) throw new Error('Missing "SchemiCustomFieldsDettagli"');
|
||||
|
||||
await saveSchemaJson(templateId, JSON.stringify(data));
|
||||
alert('Schema updated successfully. Refresh the page to see changes.');
|
||||
} catch (error) {
|
||||
document.getElementById('schemaFieldsBody').innerHTML = '<tr><td colspan="5" class="text-danger">Error: ' + error.message + '</td></tr>';
|
||||
} finally {
|
||||
updateSchemaButton.disabled = false;
|
||||
updateSchemaButton.textContent = 'Update Schema Details';
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSchemaJson(templateId, schemaJson) {
|
||||
let response = await fetch('update_schemajson.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
template_id: templateId,
|
||||
schemajson: schemaJson
|
||||
})
|
||||
});
|
||||
let data = await response.json();
|
||||
if (!data.success) throw new Error(data.message || 'Failed to save schema JSON');
|
||||
}
|
||||
|
||||
document.getElementById('updateSchemaButton').addEventListener('click', updateSchemaDetails);
|
||||
|
||||
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
||||
if (event.target.classList.contains('mapping-select')) {
|
||||
let tr = event.target.closest('tr');
|
||||
let mappingId = event.target.getAttribute('data-id');
|
||||
let xlsSelect = tr.querySelector('.xls-columns');
|
||||
let manualInput = tr.querySelector('.manual-default');
|
||||
let mappedColumn = tr.querySelector('.mapped-column');
|
||||
let removeBtn = tr.querySelector('.remove-xls');
|
||||
if (event.target.value === 'xls') {
|
||||
xlsSelect.style.display = 'block';
|
||||
manualInput.style.display = 'none';
|
||||
manualInput.value = '';
|
||||
if (mappedColumn) mappedColumn.style.display = 'none';
|
||||
if (removeBtn) removeBtn.style.display = xlsSelect.value ? 'inline-block' : 'none';
|
||||
} else if (event.target.value === 'manual') {
|
||||
xlsSelect.style.display = 'none';
|
||||
manualInput.style.display = 'block';
|
||||
if (mappedColumn) mappedColumn.style.display = 'none';
|
||||
if (removeBtn) removeBtn.style.display = 'none';
|
||||
} else {
|
||||
xlsSelect.style.display = 'none';
|
||||
manualInput.style.display = 'none';
|
||||
manualInput.value = '';
|
||||
if (mappedColumn) mappedColumn.style.display = 'none';
|
||||
if (removeBtn) removeBtn.style.display = 'none';
|
||||
}
|
||||
saveMapping(mappingId, event.target.value, manualInput.value, xlsSelect.value);
|
||||
updateXlsDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('schemaFieldsBody').addEventListener('change', function(event) {
|
||||
if (event.target.classList.contains('xls-columns')) {
|
||||
let tr = event.target.closest('tr');
|
||||
let mappingId = event.target.getAttribute('data-id');
|
||||
let manualInput = tr.querySelector('.manual-default');
|
||||
let mappedColumn = tr.querySelector('.mapped-column');
|
||||
let removeBtn = tr.querySelector('.remove-xls');
|
||||
|
||||
// Aggiungi dinamicamente mappedColumn e removeBtn se non esistono
|
||||
if (!mappedColumn) {
|
||||
mappedColumn = document.createElement('span');
|
||||
mappedColumn.className = 'mapped-column';
|
||||
mappedColumn.style.marginLeft = '5px';
|
||||
tr.querySelector('td:nth-child(4)').appendChild(mappedColumn);
|
||||
}
|
||||
if (!removeBtn) {
|
||||
removeBtn = document.createElement('button');
|
||||
removeBtn.className = 'btn btn-danger btn-sm remove-xls';
|
||||
removeBtn.textContent = 'X';
|
||||
removeBtn.style.marginLeft = '5px';
|
||||
removeBtn.setAttribute('data-id', mappingId);
|
||||
tr.querySelector('td:nth-child(4)').appendChild(removeBtn);
|
||||
|
||||
// Aggiungi l'event listener per il nuovo pulsante
|
||||
removeBtn.addEventListener('click', function(e) {
|
||||
let tr = e.target.closest('tr');
|
||||
let xlsSelect = tr.querySelector('.xls-columns');
|
||||
let mappingSelect = tr.querySelector('.mapping-select');
|
||||
xlsSelect.value = '';
|
||||
xlsSelect.style.display = 'none';
|
||||
mappingSelect.value = '';
|
||||
if (mappedColumn) mappedColumn.style.display = 'none';
|
||||
e.target.style.display = 'none';
|
||||
saveMapping(mappingId, '', '', null);
|
||||
updateXlsDropdowns();
|
||||
});
|
||||
}
|
||||
|
||||
console.log("XLS Column changed:", {
|
||||
id: mappingId,
|
||||
value: event.target.value
|
||||
});
|
||||
if (mappedColumn) {
|
||||
mappedColumn.textContent = event.target.value ? `(${event.target.value})` : '';
|
||||
mappedColumn.style.display = event.target.value ? 'inline' : 'none';
|
||||
}
|
||||
if (removeBtn) removeBtn.style.display = event.target.value ? 'inline-block' : 'none';
|
||||
saveMapping(mappingId, 'xls', manualInput.value, event.target.value);
|
||||
updateXlsDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('schemaFieldsBody').addEventListener('input', function(event) {
|
||||
if (event.target.classList.contains('manual-default')) {
|
||||
let tr = event.target.closest('tr');
|
||||
let mappingId = tr.querySelector('.mapping-select').getAttribute('data-id');
|
||||
let xlsSelect = tr.querySelector('.xls-columns');
|
||||
console.log("Manual default changed:", {
|
||||
id: mappingId,
|
||||
value: event.target.value
|
||||
});
|
||||
saveMapping(mappingId, 'manual', event.target.value, xlsSelect.value);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('schemaFieldsBody').addEventListener('click', function(event) {
|
||||
if (event.target.classList.contains('remove-xls')) {
|
||||
let mappingId = event.target.getAttribute('data-id');
|
||||
let tr = event.target.closest('tr');
|
||||
let xlsSelect = tr.querySelector('.xls-columns');
|
||||
let mappingSelect = tr.querySelector('.mapping-select');
|
||||
let mappedColumn = tr.querySelector('.mapped-column');
|
||||
xlsSelect.value = '';
|
||||
xlsSelect.style.display = 'none';
|
||||
mappingSelect.value = '';
|
||||
if (mappedColumn) mappedColumn.style.display = 'none';
|
||||
event.target.style.display = 'none';
|
||||
console.log("Removing XLS mapping:", {
|
||||
id: mappingId
|
||||
});
|
||||
saveMapping(mappingId, '', '', null);
|
||||
updateXlsDropdowns();
|
||||
}
|
||||
});
|
||||
|
||||
function saveMapping(mappingId, mappingType, defaultValue, excelColumn) {
|
||||
console.log("Saving mapping:", {
|
||||
id: mappingId,
|
||||
mapping_type: mappingType,
|
||||
excel_column: excelColumn,
|
||||
manual_default: defaultValue
|
||||
});
|
||||
fetch('save_mapping_json.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: mappingId,
|
||||
mapping_type: mappingType,
|
||||
excel_column: mappingType === 'xls' ? excelColumn : null,
|
||||
manual_default: mappingType === 'manual' ? defaultValue : null,
|
||||
tablename: "<?php echo $template['target_table']; ?>"
|
||||
})
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
console.log("Save response:", data);
|
||||
if (!data.success) console.error("❌ Error saving mapping:", data.message);
|
||||
if (data.success && excelColumn) {
|
||||
usedColumnsFromDB = usedColumnsFromDB.filter(col => col !== excelColumn); // Rimuovi dalla lista se salvata
|
||||
usedColumnsFromDB.push(excelColumn); // Aggiungi la nuova colonna usata
|
||||
updateXlsDropdowns();
|
||||
}
|
||||
})
|
||||
.catch(error => console.error("❌ Fetch error:", error));
|
||||
}
|
||||
|
||||
if (availableXlsColumns.length) updateXlsDropdowns();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user