cache and view improvements
This commit is contained in:
parent
7463fc6726
commit
4f0dbc7e91
@ -33,15 +33,26 @@ try {
|
|||||||
$stmt->execute([$templateId, $userId, $idclient, $importReferenceCode]);
|
$stmt->execute([$templateId, $userId, $idclient, $importReferenceCode]);
|
||||||
$iddatadb = (int)$pdo->lastInsertId();
|
$iddatadb = (int)$pdo->lastInsertId();
|
||||||
|
|
||||||
// Create empty import_data_details for all mappings
|
// Create import_data_details for all mappings (with auto_value support)
|
||||||
$mappingStmt = $pdo->prepare("SELECT id FROM template_mapping WHERE template_id = ?");
|
$mappingStmt = $pdo->prepare("SELECT id, auto_value, manual_default, data_type FROM template_mapping WHERE template_id = ?");
|
||||||
$mappingStmt->execute([$templateId]);
|
$mappingStmt->execute([$templateId]);
|
||||||
$mappings = $mappingStmt->fetchAll(PDO::FETCH_COLUMN);
|
$mappings = $mappingStmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
if (!empty($mappings)) {
|
if (!empty($mappings)) {
|
||||||
$insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, '')");
|
$insertStmt = $pdo->prepare("INSERT INTO import_data_details (id, mapping_id, field_value) VALUES (?, ?, ?)");
|
||||||
foreach ($mappings as $mappingId) {
|
foreach ($mappings as $m) {
|
||||||
$insertStmt->execute([$iddatadb, $mappingId]);
|
$val = '';
|
||||||
|
$auto = $m['auto_value'] ?? 'none';
|
||||||
|
if ($auto === 'import_date') {
|
||||||
|
$val = date('Y-m-d');
|
||||||
|
} elseif ($auto === 'import_time') {
|
||||||
|
$val = date('H:i');
|
||||||
|
} elseif ($m['data_type'] === 'DATE' && ($m['manual_default'] ?? '') === 'today') {
|
||||||
|
$val = date('Y-m-d');
|
||||||
|
} elseif (!empty($m['manual_default'])) {
|
||||||
|
$val = $m['manual_default'];
|
||||||
|
}
|
||||||
|
$insertStmt->execute([$iddatadb, $m['id'], $val]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -74,10 +74,21 @@ try {
|
|||||||
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
|
throw new Exception("Massimo numero di tentativi raggiunto per $endpoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache file (1 hour TTL)
|
||||||
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
readfile($cacheFile);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
// Esegui la chiamata con retry
|
// Esegui la chiamata con retry
|
||||||
$data = makeApiRequest($api, $endpoint);
|
$data = makeApiRequest($api, $endpoint);
|
||||||
|
|
||||||
echo json_encode($data);
|
$json = json_encode($data);
|
||||||
|
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||||
|
file_put_contents($cacheFile, $json);
|
||||||
|
|
||||||
|
echo $json;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
$errorResponse = [
|
$errorResponse = [
|
||||||
|
|||||||
@ -21,17 +21,26 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$results = [];
|
$results = [];
|
||||||
|
$cacheDir = __DIR__ . '/cache';
|
||||||
|
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
|
||||||
|
|
||||||
foreach ($fieldIds as $customFieldId) {
|
foreach ($fieldIds as $customFieldId) {
|
||||||
|
$cacheFile = $cacheDir . '/customfield_' . $customFieldId . '.json';
|
||||||
|
|
||||||
|
// Use cache if fresh (1 hour)
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
$results[$customFieldId] = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
|
$endpoint = "CustomField($customFieldId)?\$expand=CustomFieldsValues";
|
||||||
$data = $api->get($endpoint);
|
$data = $api->get($endpoint);
|
||||||
|
$values = $data['CustomFieldsValues'] ?? [];
|
||||||
|
$results[$customFieldId] = $values;
|
||||||
|
|
||||||
$results[$customFieldId] = $data['CustomFieldsValues'] ?? [];
|
file_put_contents($cacheFile, json_encode($values));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug ფაილი
|
|
||||||
file_put_contents(__DIR__ . '/customfield_values_response.json', json_encode($results));
|
|
||||||
|
|
||||||
echo json_encode($results);
|
echo json_encode($results);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
|||||||
@ -10,7 +10,8 @@
|
|||||||
|
|
||||||
const PAGE_SIZE = 20;
|
const PAGE_SIZE = 20;
|
||||||
let revealedCount = PAGE_SIZE;
|
let revealedCount = PAGE_SIZE;
|
||||||
let dropdownOptionsCache = {}; // fieldId -> [{id, text}]
|
let dropdownOptionsCache = {}; // fieldId -> [{id, text}] — used only for small lists
|
||||||
|
const dropdownNameCache = {}; // "fieldId_valueId" -> label
|
||||||
let clientData = []; // loaded from get_clienti.php
|
let clientData = []; // loaded from get_clienti.php
|
||||||
let fixedFieldCache = window.fixedFieldDataCache || {};
|
let fixedFieldCache = window.fixedFieldDataCache || {};
|
||||||
window.fixedFieldDataCache = fixedFieldCache;
|
window.fixedFieldDataCache = fixedFieldCache;
|
||||||
@ -53,36 +54,75 @@
|
|||||||
data[rowIndex]._dirty = true;
|
data[rowIndex]._dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Client data loading ────────────────────────────────────────────────
|
// ── Client data (AJAX Select2, no bulk loading) ──────────────────────
|
||||||
|
|
||||||
function formatClientLabel(client) {
|
function formatClientLabel(client) {
|
||||||
const nome = client.Nominativo || '';
|
return (client.Nominativo || '').trim();
|
||||||
const id = client.IdCliente || '';
|
|
||||||
const code = (client.CodiceCliente || '').toString().trim();
|
|
||||||
const suffix = (code.split('_')[1] || '').trim();
|
|
||||||
const short = suffix || (code ? code.charAt(0) : '--');
|
|
||||||
return `${nome.trim()} - ${short} (ID: ${id})`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache of resolved client names: id → name
|
||||||
|
const clientNameCache = {};
|
||||||
|
|
||||||
|
// Build select with only the selected option
|
||||||
function buildClientOptionsHTML(selectedId) {
|
function buildClientOptionsHTML(selectedId) {
|
||||||
let html = '<option value="">Select a client...</option>';
|
let html = '<option value="">Select a client...</option>';
|
||||||
clientData.forEach(c => {
|
if (selectedId) {
|
||||||
const id = c.IdCliente || '';
|
const label = clientNameCache[selectedId] || selectedId;
|
||||||
const sel = String(id) === String(selectedId) ? ' selected' : '';
|
html += `<option value="${esc(String(selectedId))}" selected>${esc(String(label))}</option>`;
|
||||||
html += `<option value="${id}"${sel}>${esc(formatClientLabel(c))}</option>`;
|
}
|
||||||
});
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadClientData() {
|
// Pre-resolve all unique client IDs used in data rows, then re-render
|
||||||
if (clientData.length > 0) return;
|
async function resolveClientNames() {
|
||||||
try {
|
const ids = new Set();
|
||||||
const resp = await fetch('get_clienti.php');
|
data.forEach(row => {
|
||||||
const json = await resp.json();
|
if (row.idclient) ids.add(String(row.idclient));
|
||||||
clientData = json.value || [];
|
if (row.cliente_fornitore_id) ids.add(String(row.cliente_fornitore_id));
|
||||||
} catch (e) {
|
// Fixed fields that are client-sourced
|
||||||
console.error('Failed to load clients:', e);
|
if (row.fixedFields) {
|
||||||
|
for (const [key, val] of Object.entries(row.fixedFields)) {
|
||||||
|
const cfg = fixedFieldApiConfig[key];
|
||||||
|
if (cfg && cfg.source === 'clients' && val) ids.add(String(val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Batch resolve via single request per ID
|
||||||
|
const unresolvedIds = [...ids].filter(id => !clientNameCache[id]);
|
||||||
|
if (unresolvedIds.length === 0) return;
|
||||||
|
|
||||||
|
await Promise.all(unresolvedIds.map(async (id) => {
|
||||||
|
try {
|
||||||
|
const resp = await fetch('search_clienti.php?id=' + encodeURIComponent(id));
|
||||||
|
const json = await resp.json();
|
||||||
|
const item = (json.results || [])[0];
|
||||||
|
if (item) clientNameCache[id] = item.text;
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select2 AJAX config for client selects
|
||||||
|
const clientSelect2Config = {
|
||||||
|
placeholder: 'Search client...',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
minimumInputLength: 0,
|
||||||
|
dropdownCssClass: 'select2-dropdown-smaller',
|
||||||
|
ajax: {
|
||||||
|
url: 'search_clienti.php',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 150,
|
||||||
|
data: function(params) { return { q: params.term || '', limit: 20 }; },
|
||||||
|
processResults: function(data) { return { results: data.results || [] }; },
|
||||||
|
cache: true
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function loadClientData() {
|
||||||
|
// No longer loads all clients — AJAX Select2 handles search
|
||||||
|
// Just resolve names for pre-selected values
|
||||||
|
await resolveClientNames();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Fixed field data loading ───────────────────────────────────────────
|
// ── Fixed field data loading ───────────────────────────────────────────
|
||||||
@ -102,13 +142,10 @@
|
|||||||
const config = fixedFieldApiConfig[fieldKey];
|
const config = fixedFieldApiConfig[fieldKey];
|
||||||
if (!config) return [];
|
if (!config) return [];
|
||||||
|
|
||||||
// Client-sourced fields
|
// Client-sourced fields — handled by AJAX Select2, skip preloading
|
||||||
if (config.source === 'clients') {
|
if (config.source === 'clients') {
|
||||||
await loadClientData();
|
fixedFieldCache[fieldKey] = [];
|
||||||
const results = clientData.map(c => ({ id: c.IdCliente, text: formatClientLabel(c) }));
|
return [];
|
||||||
results.sort((a, b) => String(a.text).localeCompare(String(b.text), 'it', { sensitivity: 'base' }));
|
|
||||||
fixedFieldCache[fieldKey] = results;
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheKey = fieldKey + (clientId ? '_' + clientId : '');
|
const cacheKey = fieldKey + (clientId ? '_' + clientId : '');
|
||||||
@ -144,26 +181,23 @@
|
|||||||
|
|
||||||
// ── Custom field dropdown data loading ─────────────────────────────────
|
// ── Custom field dropdown data loading ─────────────────────────────────
|
||||||
|
|
||||||
async function loadDropdownOptions(fieldIds) {
|
|
||||||
const missing = fieldIds.filter(id => !dropdownOptionsCache[id]);
|
// Select2 AJAX config factory for SceltaMultipla
|
||||||
if (missing.length > 0) {
|
function sceltaSelect2Config(fieldId) {
|
||||||
try {
|
return {
|
||||||
const resp = await fetch('get_customfield_values.php?field_ids=' + missing.join(','));
|
placeholder: 'Search...',
|
||||||
const json = await resp.json();
|
allowClear: true,
|
||||||
// API returns { fieldId: [values] } directly (no success/data wrapper)
|
width: '100%',
|
||||||
const entries = json.data ? json.data : json;
|
minimumInputLength: 0,
|
||||||
for (const [fid, values] of Object.entries(entries)) {
|
ajax: {
|
||||||
if (Array.isArray(values)) {
|
url: 'search_customfield_values.php',
|
||||||
const sorted = values.sort((a, b) =>
|
dataType: 'json',
|
||||||
String(a.Valore || '').localeCompare(String(b.Valore || ''), 'it', { sensitivity: 'base' })
|
delay: 150,
|
||||||
);
|
data: function(params) { return { field_id: fieldId, q: params.term || '', limit: 10 }; },
|
||||||
dropdownOptionsCache[fid] = sorted;
|
processResults: function(data) { return { results: data.results || [] }; },
|
||||||
}
|
cache: true
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to load dropdown options:', e);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Preload all data ───────────────────────────────────────────────────
|
// ── Preload all data ───────────────────────────────────────────────────
|
||||||
@ -192,13 +226,26 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Custom field dropdowns
|
// 4. Warm server cache + resolve SceltaMultipla names in one request
|
||||||
const fieldIds = columns
|
const allFieldIds = [...new Set(
|
||||||
.filter(c => c.type === 'detail' && c.dataType === 'SceltaMultipla' && c.fieldId)
|
columns.filter(c => (c.type === 'detail' || c.type === 'main_field') && c.dataType === 'SceltaMultipla' && c.fieldId)
|
||||||
.map(c => String(c.fieldId));
|
.map(c => String(c.fieldId))
|
||||||
const uniqueIds = [...new Set(fieldIds)];
|
)];
|
||||||
if (uniqueIds.length > 0) {
|
if (allFieldIds.length > 0) {
|
||||||
await loadDropdownOptions(uniqueIds);
|
try {
|
||||||
|
const resp = await fetch('get_customfield_values.php?field_ids=' + allFieldIds.join(','));
|
||||||
|
const json = await resp.json();
|
||||||
|
const entries = json.data ? json.data : json;
|
||||||
|
for (const [fid, values] of Object.entries(entries)) {
|
||||||
|
if (Array.isArray(values)) {
|
||||||
|
values.forEach(v => {
|
||||||
|
dropdownNameCache[fid + '_' + v.IdCustomFieldsValue] = v.Valore || '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to preload dropdown data:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[gridRenderer] preload done:', {
|
console.log('[gridRenderer] preload done:', {
|
||||||
@ -308,7 +355,7 @@
|
|||||||
|
|
||||||
if (col.dataType === 'SceltaMultipla') {
|
if (col.dataType === 'SceltaMultipla') {
|
||||||
const options = buildDropdownOptionsHTML(col.fieldId, value);
|
const options = buildDropdownOptionsHTML(col.fieldId, value);
|
||||||
return `<select class="cell-input dropdown-select ${cls}${reqCls}" data-field-id="${col.fieldId}"${req}>${options}</select>`;
|
return `<select class="cell-input dropdown-select searchable-dropdown ${cls}${reqCls}" data-field-id="${col.fieldId}"${req}>${options}</select>`;
|
||||||
}
|
}
|
||||||
if (col.dataType === 'Data') {
|
if (col.dataType === 'Data') {
|
||||||
return `<input type="text" class="cell-input date-picker ${cls}${reqCls}" value="${v}"${req}>`;
|
return `<input type="text" class="cell-input date-picker ${cls}${reqCls}" value="${v}"${req}>`;
|
||||||
@ -329,11 +376,24 @@
|
|||||||
return `<input type="text" class="cell-input date-picker manual-input${reqCls} fixed-input" data-fixed-key="${col.key}" value="${esc(value)}"${req}>`;
|
return `<input type="text" class="cell-input date-picker manual-input${reqCls} fixed-input" data-fixed-key="${col.key}" value="${esc(value)}"${req}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Client-sourced fields → AJAX Select2 (like idclient)
|
||||||
|
const config = fixedFieldApiConfig[col.key];
|
||||||
|
if (config && config.source === 'clients') {
|
||||||
|
const reqCls = col.isRequired ? ' required-input' : '';
|
||||||
|
const req = col.isRequired ? ' required' : '';
|
||||||
|
let opts = '<option value="">Select...</option>';
|
||||||
|
if (value) {
|
||||||
|
const label = clientNameCache[value] || value;
|
||||||
|
opts += `<option value="${esc(String(value))}" selected>${esc(String(label))}</option>`;
|
||||||
|
}
|
||||||
|
return `<select class="cell-input manual-input fixed-input searchable-client api-fixed-select${reqCls}" data-fixed-key="${col.key}" data-current-value="${esc(value)}"${req}>${opts}</select>`;
|
||||||
|
}
|
||||||
|
|
||||||
// Select — build from cache
|
// Select — build from cache
|
||||||
const isApiField = !!fixedFieldApiConfig[col.key];
|
const isApiField = !!config;
|
||||||
const selectClass = isApiField ? 'api-fixed-select' : '';
|
const selectClass = isApiField ? 'api-fixed-select' : '';
|
||||||
let options = '<option value="">Seleziona...</option>';
|
let options = '<option value="">Seleziona...</option>';
|
||||||
const cacheKey = fixedFieldApiConfig[col.key]?.dependsOn
|
const cacheKey = config?.dependsOn
|
||||||
? col.key + '_' + (data[rowIndex].idclient || '')
|
? col.key + '_' + (data[rowIndex].idclient || '')
|
||||||
: col.key;
|
: col.key;
|
||||||
const items = fixedFieldCache[cacheKey] || [];
|
const items = fixedFieldCache[cacheKey] || [];
|
||||||
@ -349,11 +409,10 @@
|
|||||||
|
|
||||||
function buildDropdownOptionsHTML(fieldId, selectedValue) {
|
function buildDropdownOptionsHTML(fieldId, selectedValue) {
|
||||||
let html = '<option value="">Seleziona...</option>';
|
let html = '<option value="">Seleziona...</option>';
|
||||||
const items = dropdownOptionsCache[fieldId] || [];
|
if (selectedValue) {
|
||||||
items.forEach(item => {
|
const label = dropdownNameCache[fieldId + '_' + selectedValue] || selectedValue;
|
||||||
const sel = String(item.IdCustomFieldsValue) === String(selectedValue) ? ' selected' : '';
|
html += `<option value="${esc(String(selectedValue))}" selected>${esc(String(label))}</option>`;
|
||||||
html += `<option value="${item.IdCustomFieldsValue}"${sel}>${esc(item.Valore)}</option>`;
|
}
|
||||||
});
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -597,16 +656,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function populateTopRowSelects() {
|
async function populateTopRowSelects() {
|
||||||
// Client selects in top row
|
// Client selects in top row — AJAX mode
|
||||||
const clientSel = document.getElementById('clientSelect');
|
const clientSel = document.getElementById('clientSelect');
|
||||||
if (clientSel) {
|
if (clientSel) {
|
||||||
clientSel.innerHTML = buildClientOptionsHTML(meta.defaultIdclient);
|
clientSel.innerHTML = buildClientOptionsHTML(meta.defaultIdclient);
|
||||||
$(clientSel).select2({ placeholder: 'Select a client...', allowClear: true, width: '100%', minimumInputLength: 1 });
|
$(clientSel).select2(clientSelect2Config);
|
||||||
}
|
}
|
||||||
const fornitSel = document.getElementById('clienteFornitoreSelect');
|
const fornitSel = document.getElementById('clienteFornitoreSelect');
|
||||||
if (fornitSel) {
|
if (fornitSel) {
|
||||||
fornitSel.innerHTML = buildClientOptionsHTML('');
|
fornitSel.innerHTML = buildClientOptionsHTML('');
|
||||||
$(fornitSel).select2({ placeholder: 'Select a supplier...', allowClear: true, width: '100%', minimumInputLength: 1 });
|
$(fornitSel).select2(clientSelect2Config);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fixed field selects in top row
|
// Fixed field selects in top row
|
||||||
@ -614,6 +673,12 @@
|
|||||||
const fieldKey = sel.dataset.fixedKey;
|
const fieldKey = sel.dataset.fixedKey;
|
||||||
const config = fixedFieldApiConfig[fieldKey];
|
const config = fixedFieldApiConfig[fieldKey];
|
||||||
|
|
||||||
|
// Client-sourced → init as AJAX Select2
|
||||||
|
if (config && config.source === 'clients') {
|
||||||
|
$(sel).select2(clientSelect2Config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (config && config.dependsOn) {
|
if (config && config.dependsOn) {
|
||||||
// For dependent fields: merge all cached values across all clientIds
|
// For dependent fields: merge all cached values across all clientIds
|
||||||
const allItems = new Map();
|
const allItems = new Map();
|
||||||
@ -633,14 +698,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom field dropdowns in top row
|
// Custom field dropdowns in top row — AJAX Select2
|
||||||
topContainer.querySelectorAll('.dropdown-select[data-field-id]').forEach(sel => {
|
topContainer.querySelectorAll('.dropdown-select[data-field-id]').forEach(sel => {
|
||||||
const fieldId = sel.dataset.fieldId;
|
const fieldId = sel.dataset.fieldId;
|
||||||
const items = dropdownOptionsCache[fieldId] || [];
|
if (fieldId) $(sel).select2(sceltaSelect2Config(fieldId));
|
||||||
sel.innerHTML = '<option value="">Seleziona...</option>';
|
|
||||||
items.forEach(item => {
|
|
||||||
sel.add(new Option(item.Valore, item.IdCustomFieldsValue));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Flatpickr in top row
|
// Flatpickr in top row
|
||||||
@ -700,6 +761,17 @@
|
|||||||
if (!input) return;
|
if (!input) return;
|
||||||
const value = $(input).hasClass('select2-hidden-accessible') ? $(input).val() : input.value;
|
const value = $(input).hasClass('select2-hidden-accessible') ? $(input).val() : input.value;
|
||||||
|
|
||||||
|
// Cache Select2 label so re-render shows name not ID
|
||||||
|
if (value && $(input).hasClass('select2-hidden-accessible')) {
|
||||||
|
const label = $(input).find('option:selected').text();
|
||||||
|
if (label && label !== value) {
|
||||||
|
clientNameCache[value] = label;
|
||||||
|
// Also cache for SceltaMultipla
|
||||||
|
const fieldId = input.dataset?.fieldId;
|
||||||
|
if (fieldId) dropdownNameCache[fieldId + '_' + value] = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const col = columns[colIndex] || null;
|
const col = columns[colIndex] || null;
|
||||||
|
|
||||||
if (column === 'idclient') {
|
if (column === 'idclient') {
|
||||||
@ -730,6 +802,12 @@
|
|||||||
const colType = cell.dataset.colType;
|
const colType = cell.dataset.colType;
|
||||||
const value = $(this).val() || '';
|
const value = $(this).val() || '';
|
||||||
|
|
||||||
|
// Cache selected label
|
||||||
|
if (value) {
|
||||||
|
const label = $(this).find('option:selected').text();
|
||||||
|
if (label && label !== value) clientNameCache[value] = label;
|
||||||
|
}
|
||||||
|
|
||||||
if (colType === 'idclient') {
|
if (colType === 'idclient') {
|
||||||
data[rowIndex].idclient = value;
|
data[rowIndex].idclient = value;
|
||||||
data[rowIndex]._dirty = true;
|
data[rowIndex]._dirty = true;
|
||||||
@ -741,6 +819,16 @@
|
|||||||
updateDirtyIndicator();
|
updateDirtyIndicator();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cache labels on SceltaMultipla change
|
||||||
|
$(rowContainer).on('change', '.searchable-dropdown', function () {
|
||||||
|
const val = $(this).val();
|
||||||
|
const fieldId = this.dataset.fieldId;
|
||||||
|
if (val && fieldId) {
|
||||||
|
const label = $(this).find('option:selected').text();
|
||||||
|
if (label && label !== val) dropdownNameCache[fieldId + '_' + val] = label;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Select2 change on fixed field selects
|
// Select2 change on fixed field selects
|
||||||
$(rowContainer).on('change', '.api-fixed-select', function () {
|
$(rowContainer).on('change', '.api-fixed-select', function () {
|
||||||
const cell = this.closest('.grid-cell');
|
const cell = this.closest('.grid-cell');
|
||||||
@ -826,18 +914,11 @@
|
|||||||
function initLazySelect2() {
|
function initLazySelect2() {
|
||||||
$(document).on('mouseenter', '.grid-row .grid-cell', function () {
|
$(document).on('mouseenter', '.grid-row .grid-cell', function () {
|
||||||
$(this).find('.searchable-client:not(.select2-hidden-accessible)').each(function () {
|
$(this).find('.searchable-client:not(.select2-hidden-accessible)').each(function () {
|
||||||
$(this).select2({
|
$(this).select2(clientSelect2Config);
|
||||||
placeholder: 'Select a client...',
|
|
||||||
allowClear: true,
|
|
||||||
width: '100%',
|
|
||||||
dropdownCssClass: 'select2-dropdown-smaller',
|
|
||||||
minimumInputLength: 1
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
$(this).find('select[data-field-id]:not(.select2-hidden-accessible)').each(function () {
|
$(this).find('.searchable-dropdown:not(.select2-hidden-accessible)').each(function () {
|
||||||
if ((this.options || []).length > 12) {
|
const fieldId = this.dataset.fieldId;
|
||||||
$(this).select2({ placeholder: 'Seleziona...', allowClear: true, width: '100%' });
|
if (fieldId) $(this).select2(sceltaSelect2Config(fieldId));
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,7 +47,7 @@ $pdo = $db->getConnection();
|
|||||||
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
$importReferenceCode = date('YmdHis') . '-' . uniqid();
|
||||||
|
|
||||||
// Recupera tutti i mapping dal template
|
// Recupera tutti i mapping dal template
|
||||||
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field FROM template_mapping WHERE template_id = ?");
|
$stmt = $pdo->prepare("SELECT id, excel_column, data_type, is_required, manual_default, is_manual, field_label, field_id, main_field, auto_value FROM template_mapping WHERE template_id = ?");
|
||||||
$stmt->execute([$template_id]);
|
$stmt->execute([$template_id]);
|
||||||
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$allMappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
@ -135,6 +135,15 @@ foreach ($selected_rows as $rowIndex) {
|
|||||||
$fieldValue = date('Y-m-d');
|
$fieldValue = date('Y-m-d');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Apply auto_value if field is still empty
|
||||||
|
if (($fieldValue === null || $fieldValue === '') && !empty($mapping['auto_value']) && $mapping['auto_value'] !== 'none') {
|
||||||
|
if ($mapping['auto_value'] === 'import_date') {
|
||||||
|
$fieldValue = date('Y-m-d');
|
||||||
|
} elseif ($mapping['auto_value'] === 'import_time') {
|
||||||
|
$fieldValue = date('H:i');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
|
if ($mapping['is_required'] && (is_null($fieldValue) || $fieldValue === '')) {
|
||||||
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
|
error_log("Required field missing for mapping ID: " . $mapping['id'] . ", field: " . $mapping['field_label']);
|
||||||
}
|
}
|
||||||
|
|||||||
58
public/userarea/search_clienti.php
Normal file
58
public/userarea/search_clienti.php
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
$q = mb_strtolower(trim($_GET['q'] ?? ''));
|
||||||
|
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
|
||||||
|
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load from cache or API
|
||||||
|
$cacheFile = __DIR__ . '/cache/clienti.json';
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
$data = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
} else {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$params = [
|
||||||
|
'$select' => 'IdCliente,Nominativo,CodiceCliente',
|
||||||
|
'$orderby' => 'Nominativo asc'
|
||||||
|
];
|
||||||
|
$data = $api->get("Cliente?" . http_build_query($params));
|
||||||
|
if (!is_dir(__DIR__ . '/cache')) mkdir(__DIR__ . '/cache', 0777, true);
|
||||||
|
file_put_contents($cacheFile, json_encode($data));
|
||||||
|
}
|
||||||
|
|
||||||
|
$clients = $data['value'] ?? [];
|
||||||
|
|
||||||
|
// If requesting by specific ID (for loading selected value)
|
||||||
|
if ($id !== null) {
|
||||||
|
foreach ($clients as $c) {
|
||||||
|
if ((int)$c['IdCliente'] === $id) {
|
||||||
|
echo json_encode(['results' => [['id' => $c['IdCliente'], 'text' => trim($c['Nominativo'] ?? '')]]]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode(['results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search by query
|
||||||
|
$results = [];
|
||||||
|
foreach ($clients as $c) {
|
||||||
|
$name = trim($c['Nominativo'] ?? '');
|
||||||
|
$code = trim($c['CodiceCliente'] ?? '');
|
||||||
|
if ($q === '' || mb_strpos(mb_strtolower($name), $q) !== false || mb_strpos(mb_strtolower($code), $q) !== false) {
|
||||||
|
$results[] = ['id' => $c['IdCliente'], 'text' => $name];
|
||||||
|
if (count($results) >= $limit) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode(['results' => $results]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
62
public/userarea/search_customfield_values.php
Normal file
62
public/userarea/search_customfield_values.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
$fieldId = intval($_GET['field_id'] ?? 0);
|
||||||
|
$q = mb_strtolower(trim($_GET['q'] ?? ''));
|
||||||
|
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||||
|
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
|
||||||
|
|
||||||
|
if (!$fieldId) {
|
||||||
|
echo json_encode(['results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$cacheDir = __DIR__ . '/cache';
|
||||||
|
$cacheFile = $cacheDir . '/customfield_' . $fieldId . '.json';
|
||||||
|
|
||||||
|
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
|
||||||
|
$values = json_decode(file_get_contents($cacheFile), true);
|
||||||
|
} else {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$data = $api->get("CustomField($fieldId)?\$expand=CustomFieldsValues");
|
||||||
|
$values = $data['CustomFieldsValues'] ?? [];
|
||||||
|
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
|
||||||
|
file_put_contents($cacheFile, json_encode($values));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup by ID
|
||||||
|
if ($id !== null) {
|
||||||
|
foreach ($values as $v) {
|
||||||
|
if ((int)($v['IdCustomFieldsValue'] ?? 0) === $id) {
|
||||||
|
echo json_encode(['results' => [['id' => $v['IdCustomFieldsValue'], 'text' => $v['Valore'] ?? '']]]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode(['results' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search by query
|
||||||
|
$results = [];
|
||||||
|
foreach ($values as $v) {
|
||||||
|
$text = $v['Valore'] ?? '';
|
||||||
|
if ($q === '' || mb_strpos(mb_strtolower($text), $q) !== false) {
|
||||||
|
$results[] = ['id' => $v['IdCustomFieldsValue'], 'text' => $text];
|
||||||
|
if (count($results) >= $limit) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort alphabetically
|
||||||
|
usort($results, fn($a, $b) => strcasecmp($a['text'], $b['text']));
|
||||||
|
|
||||||
|
echo json_encode(['results' => $results]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
@ -1007,6 +1007,48 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
padding-right: 18px !important;
|
padding-right: 18px !important;
|
||||||
appearance: auto;
|
appearance: auto;
|
||||||
}
|
}
|
||||||
|
/* Select2 inside filter row */
|
||||||
|
.grid-filter-row .select2-container {
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
.grid-filter-row .select2-container .select2-selection--single {
|
||||||
|
height: 26px !important;
|
||||||
|
min-height: 26px !important;
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center !important;
|
||||||
|
padding: 0 24px 0 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.grid-filter-row .select2-container .select2-selection__rendered {
|
||||||
|
line-height: 1 !important;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 0 !important;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.grid-filter-row .select2-container .select2-selection__arrow {
|
||||||
|
height: 26px !important;
|
||||||
|
position: absolute;
|
||||||
|
right: 2px;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.grid-filter-row .filter-wrap {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
/* Hide our clear button when Select2 is active — Select2 has its own allowClear */
|
||||||
|
.filter-wrap:has(.select2-container) .filter-clear-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.grid-filter-row .grid-cell {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
.filter-wrap {
|
.filter-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -1747,15 +1789,14 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
</div>
|
</div>
|
||||||
<?php include('jsinclude.php'); ?>
|
<?php include('jsinclude.php'); ?>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// Resolve SceltaMultipla IDs → Valore (cells + filter selects)
|
// Resolve SceltaMultipla IDs → Valore in cells
|
||||||
(function() {
|
(function() {
|
||||||
const sceltaEls = document.querySelectorAll('.scelta-value');
|
const sceltaEls = document.querySelectorAll('.scelta-value');
|
||||||
const sceltaFilters = document.querySelectorAll('select.scelta-filter');
|
|
||||||
const fieldIds = new Set();
|
const fieldIds = new Set();
|
||||||
sceltaEls.forEach(el => { if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId); });
|
sceltaEls.forEach(el => { if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId); });
|
||||||
sceltaFilters.forEach(el => { if (el.dataset.fieldId) fieldIds.add(el.dataset.fieldId); });
|
|
||||||
if (!fieldIds.size) return;
|
if (!fieldIds.size) return;
|
||||||
|
|
||||||
$.getJSON('get_customfield_values.php', { field_ids: [...fieldIds].join(',') }, function(data) {
|
$.getJSON('get_customfield_values.php', { field_ids: [...fieldIds].join(',') }, function(data) {
|
||||||
@ -1766,7 +1807,6 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
lookup[fid][v.IdCustomFieldsValue] = v.Valore;
|
lookup[fid][v.IdCustomFieldsValue] = v.Valore;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Resolve cell values
|
|
||||||
sceltaEls.forEach(el => {
|
sceltaEls.forEach(el => {
|
||||||
const fid = el.dataset.fieldId;
|
const fid = el.dataset.fieldId;
|
||||||
const raw = (el.textContent || '').trim();
|
const raw = (el.textContent || '').trim();
|
||||||
@ -1774,55 +1814,116 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
|||||||
el.textContent = lookup[fid][raw];
|
el.textContent = lookup[fid][raw];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// Populate filter selects
|
|
||||||
sceltaFilters.forEach(sel => {
|
|
||||||
const fid = sel.dataset.fieldId;
|
|
||||||
if (!fid || !lookup[fid]) return;
|
|
||||||
const currentVal = sel.value;
|
|
||||||
// Sort by Valore
|
|
||||||
const sorted = Object.entries(lookup[fid]).sort((a, b) => a[1].localeCompare(b[1]));
|
|
||||||
sel.innerHTML = '<option value="">All</option>';
|
|
||||||
sorted.forEach(([id, label]) => {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = id;
|
|
||||||
opt.textContent = label;
|
|
||||||
if (id === currentVal) opt.selected = true;
|
|
||||||
sel.appendChild(opt);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Load client names → resolve cells + populate filter selects
|
// SceltaMultipla filter selects → AJAX Select2
|
||||||
$.getJSON('get_clienti.php', function(data) {
|
document.querySelectorAll('select.scelta-filter').forEach(sel => {
|
||||||
const clients = data.value || [];
|
const fieldId = sel.dataset.fieldId;
|
||||||
const map = {};
|
if (!fieldId) return;
|
||||||
const sorted = [];
|
const currentVal = sel.value;
|
||||||
clients.forEach(c => {
|
if (currentVal) {
|
||||||
const name = (c.Nominativo || '').trim();
|
fetch('search_customfield_values.php?field_id=' + fieldId + '&id=' + encodeURIComponent(currentVal))
|
||||||
map[c.IdCliente] = name;
|
.then(r => r.json())
|
||||||
sorted.push({ id: c.IdCliente, name: name });
|
.then(json => {
|
||||||
|
const item = (json.results || [])[0];
|
||||||
|
if (item) sel.innerHTML = '<option value="">All</option><option value="' + item.id + '" selected>' + item.text + '</option>';
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
$(sel).select2({
|
||||||
|
placeholder: 'All',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
minimumInputLength: 0,
|
||||||
|
ajax: {
|
||||||
|
url: 'search_customfield_values.php',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 150,
|
||||||
|
data: function(params) { return { field_id: fieldId, q: params.term || '', limit: 20 }; },
|
||||||
|
processResults: function(data) { return { results: data.results || [] }; },
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
sorted.sort((a, b) => a.name.localeCompare(b.name));
|
});
|
||||||
|
|
||||||
// Resolve cell values
|
// SceltaMultipla filter → submit on change
|
||||||
|
$(document).on('change', 'select.scelta-filter', function() {
|
||||||
|
submitFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resolve client names by unique IDs (lightweight)
|
||||||
|
(function() {
|
||||||
|
const ids = new Set();
|
||||||
document.querySelectorAll('.client-text').forEach(el => {
|
document.querySelectorAll('.client-text').forEach(el => {
|
||||||
const id = el.getAttribute('data-client-id');
|
const id = el.getAttribute('data-client-id');
|
||||||
if (id && map[id]) el.textContent = map[id];
|
if (id) ids.add(id);
|
||||||
});
|
});
|
||||||
|
if (!ids.size) return;
|
||||||
|
|
||||||
// Populate client filter selects
|
const promises = [...ids].map(id =>
|
||||||
document.querySelectorAll('select.client-filter').forEach(sel => {
|
fetch('search_clienti.php?id=' + encodeURIComponent(id))
|
||||||
const currentVal = sel.value;
|
.then(r => r.json())
|
||||||
sel.innerHTML = '<option value="">All</option>';
|
.then(json => {
|
||||||
sorted.forEach(c => {
|
const item = (json.results || [])[0];
|
||||||
const opt = document.createElement('option');
|
if (item) {
|
||||||
opt.value = c.id;
|
document.querySelectorAll(`.client-text[data-client-id="${id}"]`).forEach(el => {
|
||||||
opt.textContent = c.name;
|
el.textContent = item.text;
|
||||||
if (String(c.id) === currentVal) opt.selected = true;
|
});
|
||||||
sel.appendChild(opt);
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
);
|
||||||
|
Promise.all(promises);
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Client filter selects → AJAX Select2
|
||||||
|
document.querySelectorAll('select.client-filter').forEach(sel => {
|
||||||
|
const currentVal = sel.value;
|
||||||
|
if (currentVal) {
|
||||||
|
fetch('search_clienti.php?id=' + encodeURIComponent(currentVal))
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(json => {
|
||||||
|
const item = (json.results || [])[0];
|
||||||
|
if (item) {
|
||||||
|
sel.innerHTML = '<option value="">All</option><option value="' + item.id + '" selected>' + item.text + '</option>';
|
||||||
|
}
|
||||||
|
$(sel).select2({
|
||||||
|
placeholder: 'All',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
minimumInputLength: 0,
|
||||||
|
ajax: {
|
||||||
|
url: 'search_clienti.php',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 150,
|
||||||
|
data: function(params) { return { q: params.term || '', limit: 20 }; },
|
||||||
|
processResults: function(data) { return { results: data.results || [] }; },
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$(sel).select2({
|
||||||
|
placeholder: 'All',
|
||||||
|
allowClear: true,
|
||||||
|
width: '100%',
|
||||||
|
minimumInputLength: 0,
|
||||||
|
ajax: {
|
||||||
|
url: 'search_clienti.php',
|
||||||
|
dataType: 'json',
|
||||||
|
delay: 150,
|
||||||
|
data: function(params) { return { q: params.term || '', limit: 20 }; },
|
||||||
|
processResults: function(data) { return { results: data.results || [] }; },
|
||||||
|
cache: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Client filter select2 → submit on change
|
||||||
|
$(document).on('change', 'select.client-filter', function() {
|
||||||
|
submitFilters();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Modal close handlers
|
// Modal close handlers
|
||||||
|
|||||||
110
public/userarea/warm_cache.php
Normal file
110
public/userarea/warm_cache.php
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
// Cache warm-up script.
|
||||||
|
// Run via CLI: php warm_cache.php
|
||||||
|
// Run via cron: every 30 min: php /path/to/warm_cache.php
|
||||||
|
// Run via HTTP: http://localhost:8000/public/userarea/warm_cache.php (requires auth)
|
||||||
|
|
||||||
|
$startTime = microtime(true);
|
||||||
|
$isCli = (php_sapi_name() === 'cli');
|
||||||
|
|
||||||
|
if (!$isCli) {
|
||||||
|
// When called via HTTP, require auth
|
||||||
|
include('include/headscript.php');
|
||||||
|
if (!Auth::check()) {
|
||||||
|
http_response_code(403);
|
||||||
|
echo json_encode(['error' => 'Unauthorized']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||||
|
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||||
|
require_once __DIR__ . '/class/db-functions.php';
|
||||||
|
|
||||||
|
$cacheDir = __DIR__ . '/cache';
|
||||||
|
if (!is_dir($cacheDir)) mkdir($cacheDir, 0777, true);
|
||||||
|
|
||||||
|
$log = [];
|
||||||
|
function warmLog(string $msg, bool $isCli) {
|
||||||
|
global $log;
|
||||||
|
$line = date('H:i:s') . " $msg";
|
||||||
|
$log[] = $line;
|
||||||
|
if ($isCli) echo $line . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$api = VisualLimsApiClient::getInstance();
|
||||||
|
$pdo = DBHandlerSelect::getInstance()->getConnection();
|
||||||
|
|
||||||
|
// 1. Clients
|
||||||
|
warmLog('[clients] Fetching...', $isCli);
|
||||||
|
$params = ['$select' => 'IdCliente,Nominativo,CodiceCliente', '$orderby' => 'Nominativo asc'];
|
||||||
|
$clientData = $api->get("Cliente?" . http_build_query($params));
|
||||||
|
file_put_contents($cacheDir . '/clienti.json', json_encode($clientData));
|
||||||
|
$clientCount = count($clientData['value'] ?? []);
|
||||||
|
warmLog("[clients] Cached $clientCount clients", $isCli);
|
||||||
|
|
||||||
|
// 2. Fixed fields: MoltiplicatorePrezzo, AnagraficaCertestObject, AnagraficaCertestService
|
||||||
|
$fixedFields = [
|
||||||
|
'MoltiplicatorePrezzo' => ['endpoint' => 'MoltiplicatorePrezzi', 'file' => 'moltiplicatori_prezzo.json'],
|
||||||
|
'AnagraficaCertestObject' => ['endpoint' => 'AnagraficaCertestObject', 'file' => 'anagrafica_certest_object.json'],
|
||||||
|
'AnagraficaCertestService' => ['endpoint' => 'AnagraficaCertestService', 'file' => 'anagrafica_certest_service.json'],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($fixedFields as $name => $cfg) {
|
||||||
|
warmLog("[$name] Fetching...", $isCli);
|
||||||
|
$data = $api->get($cfg['endpoint']);
|
||||||
|
file_put_contents($cacheDir . '/' . $cfg['file'], json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
$count = count($data['value'] ?? $data ?? []);
|
||||||
|
warmLog("[$name] Cached $count items", $isCli);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. ClienteResponsabile — for each unique client in datadb
|
||||||
|
$stmt = $pdo->query("SELECT DISTINCT idclient FROM datadb WHERE idclient IS NOT NULL AND idclient > 0 ORDER BY idclient ASC");
|
||||||
|
$clientIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
warmLog("[ClienteResponsabile] Fetching for " . count($clientIds) . " clients...", $isCli);
|
||||||
|
|
||||||
|
foreach ($clientIds as $cid) {
|
||||||
|
$cid = (int)$cid;
|
||||||
|
$endpoint = "Cliente($cid)?\$expand=Responsabili";
|
||||||
|
$cacheFile = $cacheDir . '/cliente_responsabili_' . $cid . '.json';
|
||||||
|
try {
|
||||||
|
$data = $api->get($endpoint);
|
||||||
|
file_put_contents($cacheFile, json_encode($data, JSON_PRETTY_PRINT));
|
||||||
|
$count = count($data['Responsabili'] ?? []);
|
||||||
|
warmLog("[ClienteResponsabile] Client $cid: $count responsabili", $isCli);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
warmLog("[ClienteResponsabile] Client $cid: ERROR " . $e->getMessage(), $isCli);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. CustomField values — all SceltaMultipla field_ids from template_mapping
|
||||||
|
$stmt = $pdo->query("SELECT DISTINCT field_id FROM template_mapping WHERE data_type = 'SceltaMultipla' AND is_visible_import = 1 AND field_id IS NOT NULL ORDER BY field_id");
|
||||||
|
$fieldIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
warmLog("[CustomFields] Fetching " . count($fieldIds) . " fields...", $isCli);
|
||||||
|
|
||||||
|
foreach ($fieldIds as $fid) {
|
||||||
|
$fid = (int)$fid;
|
||||||
|
$cacheFile = $cacheDir . '/customfield_' . $fid . '.json';
|
||||||
|
try {
|
||||||
|
$endpoint = "CustomField($fid)?\$expand=CustomFieldsValues";
|
||||||
|
$data = $api->get($endpoint);
|
||||||
|
$values = $data['CustomFieldsValues'] ?? [];
|
||||||
|
file_put_contents($cacheFile, json_encode($values));
|
||||||
|
warmLog("[CustomField $fid] Cached " . count($values) . " values", $isCli);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
warmLog("[CustomField $fid] ERROR " . $e->getMessage(), $isCli);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$elapsed = round(microtime(true) - $startTime, 1);
|
||||||
|
warmLog("Done in {$elapsed}s", $isCli);
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
warmLog("FATAL: " . $e->getMessage(), $isCli);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$isCli) {
|
||||||
|
echo json_encode(['success' => true, 'log' => $log]);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user