Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c3a6dd73b6 | |||
| 44ed1186e0 | |||
| 9050cb1006 | |||
| e6820fdb62 | |||
| 5da37a7836 | |||
| c5f27cb69a | |||
| 1d81d6c996 | |||
| 0c72dbf5ae | |||
| 8455be04e1 | |||
| e42d1b9c51 | |||
| 3e69e3c322 | |||
| 0eb4f7a2ad | |||
| 4f2cfc1930 | |||
| df075dd76a | |||
| 6460454201 | |||
| 574ddbbd32 | |||
| 41f414db5c | |||
| 4a863e8c16 | |||
| b431f1d4e9 | |||
| f97b52f158 | |||
| 836fc055ec | |||
| e8dd585df4 | |||
| 198b8c08ad | |||
| 28c467d55e | |||
| 56eee99a67 | |||
| f514b3d2c7 | |||
| a3eb0f0a57 | |||
| aa355905d7 | |||
| b38f3e1240 | |||
| cbd0c5b68a | |||
| f3e5cb4ffd | |||
| 0f0c6a04b7 | |||
| 813bd66f96 | |||
| ce00247d1c | |||
| 3a7dd266c8 | |||
| ba8dc4c721 | |||
| cfbbc36116 | |||
| 50d578eea1 | |||
| 6b0d2aa9b9 | |||
| fa7997c980 | |||
| 66be442eb6 | |||
| 19a2d6e3f7 | |||
| a15ab08576 | |||
| f71e8a56b5 | |||
| cb38bfb75a | |||
| 28a708dad3 | |||
| 6b9cf20ab9 | |||
| d40fc7d177 |
+6
-1
@@ -40,10 +40,13 @@ yarn-error.log
|
||||
/public/userarea/logaspi/
|
||||
/public/userarea/logs/api/
|
||||
/public/userarea/logs/api/**
|
||||
/public/userarea/logs/
|
||||
/public/userarea/logs/**
|
||||
/public/userarea/photostrf/
|
||||
/public/userarea/class/*.log
|
||||
/public/userarea/class/curl_auth_debug.log
|
||||
/public/userarea/class/curl_request_debug.log
|
||||
/public/userarea/schema_dettagli_response.json
|
||||
|
||||
# File XLSX temporanei importati
|
||||
/public/userarea/imported_trf/*.xlsx
|
||||
@@ -54,4 +57,6 @@ yarn-error.log
|
||||
/public/photostrf/qrcodes/
|
||||
|
||||
# Ignora tutti i log ovunque
|
||||
*.log
|
||||
*.log
|
||||
|
||||
public/userarea/cache/
|
||||
@@ -22,6 +22,8 @@ $(document).ready(function () {
|
||||
let partsListData = [];
|
||||
// DIMENSIONE GLOBALE MARKER
|
||||
let globalMarkerSize = 16;
|
||||
// COLORE TESTO LISTA DESCRIZIONI
|
||||
let globalDescriptionColor = "#000000";
|
||||
|
||||
// ===================
|
||||
// MODAL INITIALIZATION
|
||||
@@ -138,6 +140,7 @@ $(document).ready(function () {
|
||||
descriptionTextbox = null;
|
||||
markerObjects = {};
|
||||
globalMarkerSize = 16;
|
||||
globalDescriptionColor = "#000000";
|
||||
$("#photoSelectorContainerAnnotations").empty().hide();
|
||||
$("#samplePhotoAnnotations").attr("src", "");
|
||||
$("#partsListAnnotations").empty();
|
||||
@@ -177,7 +180,25 @@ $(document).ready(function () {
|
||||
updateMarkers();
|
||||
markUnsaved();
|
||||
});
|
||||
// ===================
|
||||
// COLORE LISTA DESCRIZIONI
|
||||
// ===================
|
||||
$(document)
|
||||
.off("input.descriptionColor", "#descriptionColorPickerAnnotations")
|
||||
.on(
|
||||
"input.descriptionColor",
|
||||
"#descriptionColorPickerAnnotations",
|
||||
function () {
|
||||
globalDescriptionColor = $(this).val();
|
||||
|
||||
if (descriptionTextbox && fabricCanvas) {
|
||||
descriptionTextbox.set("fill", globalDescriptionColor);
|
||||
fabricCanvas.renderAll();
|
||||
}
|
||||
|
||||
markUnsaved();
|
||||
},
|
||||
);
|
||||
// ===================
|
||||
// PHOTO LOADERS
|
||||
// ===================
|
||||
@@ -614,17 +635,11 @@ $(document).ready(function () {
|
||||
partsListData.forEach((part) => {
|
||||
const partNumber = part.part_number;
|
||||
const partDescription = part.part_description;
|
||||
const isMixPart = String(part.mix || "N").toUpperCase() === "Y";
|
||||
|
||||
const partColor =
|
||||
partColors[partNumber] ||
|
||||
(partDescription.toLowerCase().startsWith("mix")
|
||||
? "#0000ff"
|
||||
: "#ff0000");
|
||||
if (
|
||||
partNumber &&
|
||||
partDescription &&
|
||||
(showMixParts ||
|
||||
!partDescription.toLowerCase().startsWith("mix"))
|
||||
) {
|
||||
partColors[partNumber] || (isMixPart ? "#0000ff" : "#ff0000");
|
||||
if (partNumber && partDescription && (showMixParts || !isMixPart)) {
|
||||
const colorOptions = predefinedColors
|
||||
.map(
|
||||
(color) =>
|
||||
@@ -922,11 +937,10 @@ $(document).ready(function () {
|
||||
) {
|
||||
partsListData = response.parts;
|
||||
response.parts.forEach((part) => {
|
||||
const defaultColor = part.part_description
|
||||
.toLowerCase()
|
||||
.startsWith("mix")
|
||||
? "#0000ff"
|
||||
: "#ff0000";
|
||||
const isMixPart =
|
||||
String(part.mix || "N").toUpperCase() === "Y";
|
||||
const defaultColor = isMixPart ? "#0000ff" : "#ff0000";
|
||||
|
||||
partColors[part.part_number] = defaultColor;
|
||||
});
|
||||
updatePartsList();
|
||||
@@ -1003,11 +1017,10 @@ $(document).ready(function () {
|
||||
(p) => p.part_number == marker.partNumber,
|
||||
);
|
||||
const partDescription = part ? part.part_description : "";
|
||||
if (
|
||||
!showMixParts &&
|
||||
partDescription &&
|
||||
partDescription.toLowerCase().startsWith("mix")
|
||||
) {
|
||||
const isMixPart =
|
||||
part && String(part.mix || "N").toUpperCase() === "Y";
|
||||
|
||||
if (!showMixParts && isMixPart) {
|
||||
console.log("Ignoro marker per parte Mix:", marker.partNumber);
|
||||
return;
|
||||
}
|
||||
@@ -1115,11 +1128,10 @@ $(document).ready(function () {
|
||||
}
|
||||
|
||||
const partsList = partsListData
|
||||
.filter(
|
||||
(part) =>
|
||||
showMixParts ||
|
||||
!part.part_description.toLowerCase().startsWith("mix"),
|
||||
)
|
||||
.filter((part) => {
|
||||
const isMixPart = String(part.mix || "N").toUpperCase() === "Y";
|
||||
return showMixParts || !isMixPart;
|
||||
})
|
||||
.map((part) => `${part.part_number} ${part.part_description}`);
|
||||
|
||||
const text = partsList.join("\n");
|
||||
@@ -1139,7 +1151,7 @@ $(document).ready(function () {
|
||||
backgroundColor: "transparent",
|
||||
fontFamily: "Arial",
|
||||
fontSize: Math.max(16, Math.round(globalMarkerSize * 0.8)),
|
||||
fill: "#000000",
|
||||
fill: globalDescriptionColor,
|
||||
padding: 10,
|
||||
editable: false,
|
||||
selectable: true,
|
||||
|
||||
@@ -3,6 +3,9 @@ ob_start();
|
||||
session_start();
|
||||
require_once '../../vendor/autoload.php';
|
||||
|
||||
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
|
||||
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||
|
||||
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'excel_data' => []];
|
||||
|
||||
try {
|
||||
|
||||
@@ -255,4 +255,39 @@ class VisualLimsApiClient
|
||||
{
|
||||
return $this->baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recupera contenuto binario - Adattato per https://bvcpsitaly-elims.com/limsapi
|
||||
*/
|
||||
public function getRaw($endpoint)
|
||||
{
|
||||
$token = $this->getToken();
|
||||
|
||||
// IMPORTANTE: usa /odata/ e NON /api/odata/
|
||||
$url = "{$this->baseUrl}/odata/{$endpoint}";
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
"Authorization: Bearer {$token}",
|
||||
"Accept: */*"
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$curl_error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false) {
|
||||
throw new Exception("Errore cURL: " . $curl_error);
|
||||
}
|
||||
|
||||
if ($http_code !== 200) {
|
||||
throw new Exception("HTTP {$http_code} su endpoint: " . $url);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
|
||||
|
||||
use Dotenv\Dotenv;
|
||||
|
||||
Dotenv::createImmutable(dirname(__DIR__, 3))->safeLoad();
|
||||
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||
|
||||
class DBHandlerSelect
|
||||
{
|
||||
private static $instance = null;
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
include('include/headscript.php');
|
||||
|
||||
$dbHandler = DBHandlerSelect::getInstance();
|
||||
$pdo = $dbHandler->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'] ?? [];
|
||||
|
||||
$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
|
||||
");
|
||||
|
||||
$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;
|
||||
|
||||
// 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' => $part['note'] ?? 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,
|
||||
]);
|
||||
}
|
||||
|
||||
$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,
|
||||
'details' => $details
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Clone failed: ' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
require_once 'class/db-functions.php';
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
|
||||
die("Invalid template ID");
|
||||
}
|
||||
|
||||
$sourceTemplateId = (int)$_GET['id'];
|
||||
|
||||
try {
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// 1. Get source template
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
name,
|
||||
source_type,
|
||||
header_row,
|
||||
start_column,
|
||||
description,
|
||||
target_table,
|
||||
sample_xlsx,
|
||||
button_size,
|
||||
button_bg_color,
|
||||
button_text_color,
|
||||
button_label,
|
||||
status,
|
||||
client_specific_fields,
|
||||
idclient,
|
||||
clientname,
|
||||
schemaname,
|
||||
idschema,
|
||||
schemajson,
|
||||
xls_headers,
|
||||
api_sample_json,
|
||||
json_nodes,
|
||||
idroutine
|
||||
FROM excel_templates
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
$stmt->execute([$sourceTemplateId]);
|
||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$template) {
|
||||
throw new Exception("Template not found.");
|
||||
}
|
||||
|
||||
// 2. Generate unique clone name
|
||||
$baseName = 'Copia di ' . $template['name'];
|
||||
$newName = $baseName;
|
||||
|
||||
$checkStmt = $pdo->prepare("SELECT COUNT(*) FROM excel_templates WHERE name = ?");
|
||||
$checkStmt->execute([$newName]);
|
||||
|
||||
if ((int)$checkStmt->fetchColumn() > 0) {
|
||||
$counter = 2;
|
||||
|
||||
do {
|
||||
$newName = $baseName . ' (' . $counter . ')';
|
||||
$checkStmt->execute([$newName]);
|
||||
$exists = (int)$checkStmt->fetchColumn() > 0;
|
||||
$counter++;
|
||||
} while ($exists);
|
||||
}
|
||||
|
||||
// 3. Insert cloned template
|
||||
$insertTemplate = $pdo->prepare("
|
||||
INSERT INTO excel_templates (
|
||||
name,
|
||||
source_type,
|
||||
created_at,
|
||||
updated_at,
|
||||
header_row,
|
||||
start_column,
|
||||
description,
|
||||
target_table,
|
||||
sample_xlsx,
|
||||
button_size,
|
||||
button_bg_color,
|
||||
button_text_color,
|
||||
button_label,
|
||||
status,
|
||||
client_specific_fields,
|
||||
idclient,
|
||||
clientname,
|
||||
schemaname,
|
||||
idschema,
|
||||
schemajson,
|
||||
xls_headers,
|
||||
api_sample_json,
|
||||
json_nodes,
|
||||
idroutine
|
||||
) VALUES (
|
||||
:name,
|
||||
:source_type,
|
||||
NOW(),
|
||||
NOW(),
|
||||
:header_row,
|
||||
:start_column,
|
||||
:description,
|
||||
:target_table,
|
||||
:sample_xlsx,
|
||||
:button_size,
|
||||
:button_bg_color,
|
||||
:button_text_color,
|
||||
:button_label,
|
||||
:status,
|
||||
:client_specific_fields,
|
||||
:idclient,
|
||||
:clientname,
|
||||
:schemaname,
|
||||
:idschema,
|
||||
:schemajson,
|
||||
:xls_headers,
|
||||
:api_sample_json,
|
||||
:json_nodes,
|
||||
:idroutine
|
||||
)
|
||||
");
|
||||
|
||||
$insertTemplate->execute([
|
||||
':name' => $newName,
|
||||
':source_type' => $template['source_type'],
|
||||
':header_row' => $template['header_row'],
|
||||
':start_column' => $template['start_column'],
|
||||
':description' => $template['description'],
|
||||
':target_table' => $template['target_table'],
|
||||
':sample_xlsx' => $template['sample_xlsx'],
|
||||
':button_size' => $template['button_size'],
|
||||
':button_bg_color' => $template['button_bg_color'],
|
||||
':button_text_color' => $template['button_text_color'],
|
||||
':button_label' => $template['button_label'],
|
||||
':status' => $template['status'],
|
||||
':client_specific_fields' => $template['client_specific_fields'],
|
||||
':idclient' => $template['idclient'],
|
||||
':clientname' => $template['clientname'],
|
||||
':schemaname' => $template['schemaname'],
|
||||
':idschema' => $template['idschema'],
|
||||
':schemajson' => $template['schemajson'],
|
||||
':xls_headers' => $template['xls_headers'],
|
||||
':api_sample_json' => $template['api_sample_json'],
|
||||
':json_nodes' => $template['json_nodes'],
|
||||
':idroutine' => $template['idroutine']
|
||||
]);
|
||||
|
||||
$newTemplateId = (int)$pdo->lastInsertId();
|
||||
|
||||
if ($newTemplateId <= 0) {
|
||||
throw new Exception("Unable to create cloned template.");
|
||||
}
|
||||
|
||||
// 4. Clone template_mapping rows
|
||||
$cloneMappings = $pdo->prepare("
|
||||
INSERT INTO template_mapping (
|
||||
template_id,
|
||||
schema_id,
|
||||
field_id,
|
||||
excel_column,
|
||||
json_node,
|
||||
is_manual,
|
||||
manual_default,
|
||||
auto_value,
|
||||
data_type,
|
||||
is_required,
|
||||
default_value,
|
||||
has_list,
|
||||
length,
|
||||
decimals,
|
||||
min_value,
|
||||
max_value,
|
||||
default_curr_date,
|
||||
tablename,
|
||||
field_label,
|
||||
main_field,
|
||||
is_visible_import,
|
||||
is_visible_parts
|
||||
)
|
||||
SELECT
|
||||
:new_template_id,
|
||||
schema_id,
|
||||
field_id,
|
||||
excel_column,
|
||||
json_node,
|
||||
is_manual,
|
||||
manual_default,
|
||||
auto_value,
|
||||
data_type,
|
||||
is_required,
|
||||
default_value,
|
||||
has_list,
|
||||
length,
|
||||
decimals,
|
||||
min_value,
|
||||
max_value,
|
||||
default_curr_date,
|
||||
tablename,
|
||||
field_label,
|
||||
main_field,
|
||||
is_visible_import,
|
||||
is_visible_parts
|
||||
FROM template_mapping
|
||||
WHERE template_id = :source_template_id
|
||||
");
|
||||
|
||||
$cloneMappings->execute([
|
||||
':new_template_id' => $newTemplateId,
|
||||
':source_template_id' => $sourceTemplateId
|
||||
]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
header("Location: templates_dashboard.php?cloned=1&new_id=" . $newTemplateId);
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
die("Clone error: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8'));
|
||||
}
|
||||
@@ -17,6 +17,8 @@ try {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||
|
||||
// Recupera le variabili d'ambiente
|
||||
$dbHost = $_ENV['DB_HOST'];
|
||||
$dbName = $_ENV['DB_DATABASE'];
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
/**
|
||||
* Uso:
|
||||
* download_rapporto_file.php?id=123
|
||||
*/
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
|
||||
if (!$id) {
|
||||
throw new Exception("ID RapportoFile mancante");
|
||||
}
|
||||
|
||||
$debugDir = __DIR__ . '/logs/lims_get_commessa/';
|
||||
|
||||
if (!is_dir($debugDir)) {
|
||||
mkdir($debugDir, 0755, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recupero il record RapportoFile.
|
||||
*/
|
||||
$data = $api->get("RapportoFile({$id})", []);
|
||||
|
||||
file_put_contents(
|
||||
$debugDir . "rapporto_file_{$id}.json",
|
||||
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
|
||||
/**
|
||||
* Provo vari nomi campo possibili per il contenuto PDF.
|
||||
* Dopo il primo test potremo adattarlo al nome esatto.
|
||||
*/
|
||||
$possibleContentFields = [
|
||||
'File',
|
||||
'file',
|
||||
'Content',
|
||||
'content',
|
||||
'FileContent',
|
||||
'fileContent',
|
||||
'Contenuto',
|
||||
'contenuto',
|
||||
'Bytes',
|
||||
'bytes'
|
||||
];
|
||||
|
||||
$base64 = null;
|
||||
|
||||
foreach ($possibleContentFields as $field) {
|
||||
if (!empty($data[$field])) {
|
||||
$base64 = $data[$field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$base64) {
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Record RapportoFile recuperato, ma non ho trovato un campo base64 del PDF.',
|
||||
'hint' => 'Apri logs/lims_get_commessa/rapporto_file_' . $id . '.json e controlla il nome reale del campo file.',
|
||||
'data' => $data
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Se il contenuto ha prefisso data URI, lo pulisco.
|
||||
*/
|
||||
if (strpos($base64, 'base64,') !== false) {
|
||||
$parts = explode('base64,', $base64);
|
||||
$base64 = end($parts);
|
||||
}
|
||||
|
||||
$pdfBinary = base64_decode($base64, true);
|
||||
|
||||
if ($pdfBinary === false) {
|
||||
throw new Exception("Il contenuto trovato non è un base64 valido");
|
||||
}
|
||||
|
||||
$filename = $data['NomeFile']
|
||||
?? $data['nomeFile']
|
||||
?? $data['FileName']
|
||||
?? $data['fileName']
|
||||
?? "rapporto_{$id}.pdf";
|
||||
|
||||
if (!str_ends_with(strtolower($filename), '.pdf')) {
|
||||
$filename .= '.pdf';
|
||||
}
|
||||
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: inline; filename="' . basename($filename) . '"');
|
||||
header('Content-Length: ' . strlen($pdfBinary));
|
||||
|
||||
echo $pdfBinary;
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -25,6 +25,51 @@ if (!$template) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM routine");
|
||||
$stmt->execute();
|
||||
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Retrieve active API/JSON configurations
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, name, provider_code, api_type, php_class_name
|
||||
FROM api_configurations
|
||||
WHERE is_active = 1
|
||||
ORDER BY name ASC
|
||||
");
|
||||
$stmt->execute();
|
||||
$apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$buttonBgPalette = [
|
||||
'#0d6efd' => 'Blue',
|
||||
'#6610f2' => 'Indigo',
|
||||
'#6f42c1' => 'Purple',
|
||||
'#d63384' => 'Pink',
|
||||
'#dc3545' => 'Red',
|
||||
'#fd7e14' => 'Orange',
|
||||
'#ffc107' => 'Yellow',
|
||||
'#198754' => 'Green',
|
||||
'#20c997' => 'Teal',
|
||||
'#0dcaf0' => 'Cyan',
|
||||
'#212529' => 'Dark',
|
||||
'#6c757d' => 'Gray',
|
||||
'#495057' => 'Slate',
|
||||
'#795548' => 'Brown',
|
||||
'#2f5d50' => 'Sage Green',
|
||||
];
|
||||
|
||||
$buttonTextPalette = [
|
||||
'#ffffff' => 'White',
|
||||
'#6c757d' => 'Gray',
|
||||
'#000000' => 'Black',
|
||||
];
|
||||
|
||||
$currentButtonBgColor = strtolower($template['button_bg_color'] ?? '#007bff');
|
||||
$currentButtonTextColor = strtolower($template['button_text_color'] ?? '#ffffff');
|
||||
|
||||
if (!array_key_exists($currentButtonBgColor, array_change_key_case($buttonBgPalette, CASE_LOWER))) {
|
||||
$currentButtonBgColor = '#0d6efd';
|
||||
}
|
||||
|
||||
if (!array_key_exists($currentButtonTextColor, array_change_key_case($buttonTextPalette, CASE_LOWER))) {
|
||||
$currentButtonTextColor = '#ffffff';
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
@@ -35,6 +80,64 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<link rel="icon" href="assets/images/favicon-32x32.png" type="image/png" />
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<style>
|
||||
.color-palette {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.color-option {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color-option input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.color-swatch {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #d6dbe0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
|
||||
.color-option input:checked+.color-swatch {
|
||||
border: 3px solid #111827;
|
||||
transform: scale(1.08);
|
||||
box-shadow: 0 0 0 4px rgba(13, 110, 253, 0.18);
|
||||
}
|
||||
|
||||
.color-swatch .check-mark {
|
||||
display: none;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.color-option input:checked+.color-swatch .check-mark {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.color-name {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
text-align: center;
|
||||
margin-top: 4px;
|
||||
color: #555;
|
||||
max-width: 55px;
|
||||
}
|
||||
</style>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<title>Edit Template <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
@@ -88,6 +191,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<select name="source_type" id="sourceType" class="form-control" required>
|
||||
<option value="XLS" <?php echo (($template['source_type'] ?? 'XLS') === 'XLS') ? 'selected' : ''; ?>>XLS</option>
|
||||
<option value="API" <?php echo (($template['source_type'] ?? 'XLS') === 'API') ? 'selected' : ''; ?>>API</option>
|
||||
<option value="JSON" <?php echo (($template['source_type'] ?? 'XLS') === 'JSON') ? 'selected' : ''; ?>>JSON</option>
|
||||
<option value="PDF" <?php echo (($template['source_type'] ?? 'XLS') === 'PDF') ? 'selected' : ''; ?>>PDF</option>
|
||||
</select>
|
||||
<small class="text-muted">Choose the source used by this template</small>
|
||||
</div>
|
||||
@@ -102,6 +207,60 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<input type="text" name="start_column" id="startColumn" class="form-control" value="<?php echo htmlspecialchars($template['start_column'] ?? ''); ?>">
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="xlsSheetNumberWrapper">
|
||||
<label class="form-label">XLS Sheet Number</label>
|
||||
<input
|
||||
type="number"
|
||||
name="xls_sheet_index"
|
||||
id="xlsSheetIndex"
|
||||
class="form-control"
|
||||
min="0"
|
||||
value="<?php echo htmlspecialchars($template['xls_sheet_index'] ?? 0); ?>">
|
||||
<small class="text-muted">
|
||||
Use 0 for the first sheet, 1 for the second sheet, 2 for the third sheet, and so on.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="apiConfigWrapper" style="display: none;">
|
||||
<label class="form-label">API / JSON Configuration *</label>
|
||||
<select name="api_config_id" id="apiConfigSelect" class="form-control">
|
||||
<option value="">Select an API configuration...</option>
|
||||
|
||||
<?php foreach ($apiConfigurations as $apiConfig): ?>
|
||||
<?php
|
||||
$apiLabelParts = [];
|
||||
|
||||
if (!empty($apiConfig['name'])) {
|
||||
$apiLabelParts[] = $apiConfig['name'];
|
||||
}
|
||||
|
||||
if (!empty($apiConfig['provider_code'])) {
|
||||
$apiLabelParts[] = '[' . $apiConfig['provider_code'] . ']';
|
||||
}
|
||||
|
||||
if (!empty($apiConfig['api_type'])) {
|
||||
$apiLabelParts[] = '(' . $apiConfig['api_type'] . ')';
|
||||
}
|
||||
|
||||
if (!empty($apiConfig['php_class_name'])) {
|
||||
$apiLabelParts[] = '- ' . $apiConfig['php_class_name'];
|
||||
}
|
||||
|
||||
$apiLabel = implode(' ', $apiLabelParts);
|
||||
?>
|
||||
|
||||
<option
|
||||
value="<?php echo (int)$apiConfig['id']; ?>"
|
||||
<?php echo ((int)($template['api_config_id'] ?? 0) === (int)$apiConfig['id']) ? 'selected' : ''; ?>>
|
||||
<?php echo htmlspecialchars($apiLabel, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<small class="text-muted">
|
||||
Select the API/JSON configuration linked to this template.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
|
||||
<textarea name="description" class="form-control"><?php echo htmlspecialchars($template['description'] ?? ''); ?></textarea>
|
||||
@@ -123,12 +282,46 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Button Background Color</label>
|
||||
<input type="color" name="button_bg_color" class="form-control form-control-color" value="<?php echo htmlspecialchars($template['button_bg_color'] ?? '#007bff'); ?>">
|
||||
|
||||
<div class="color-palette">
|
||||
<?php foreach ($buttonBgPalette as $colorValue => $colorLabel): ?>
|
||||
<?php $isChecked = strtolower($colorValue) === $currentButtonBgColor; ?>
|
||||
|
||||
<label class="color-option" title="<?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<input
|
||||
type="radio"
|
||||
name="button_bg_color"
|
||||
value="<?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
<?= $isChecked ? 'checked' : ''; ?>>
|
||||
<span class="color-swatch" style="background-color: <?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>;">
|
||||
<span class="check-mark">✓</span>
|
||||
</span>
|
||||
<span class="color-name"><?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Button Text Color</label>
|
||||
<input type="color" name="button_text_color" class="form-control form-control-color" value="<?php echo htmlspecialchars($template['button_text_color'] ?? '#ffffff'); ?>">
|
||||
|
||||
<div class="color-palette">
|
||||
<?php foreach ($buttonTextPalette as $colorValue => $colorLabel): ?>
|
||||
<?php $isChecked = strtolower($colorValue) === $currentButtonTextColor; ?>
|
||||
|
||||
<label class="color-option" title="<?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<input
|
||||
type="radio"
|
||||
name="button_text_color"
|
||||
value="<?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
<?= $isChecked ? 'checked' : ''; ?>>
|
||||
<span class="color-swatch" style="background-color: <?= htmlspecialchars($colorValue, ENT_QUOTES, 'UTF-8'); ?>;">
|
||||
<span class="check-mark">✓</span>
|
||||
</span>
|
||||
<span class="color-name"><?= htmlspecialchars($colorLabel, ENT_QUOTES, 'UTF-8'); ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -208,10 +401,16 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
const routineAction3 = document.getElementById("routineAction3");
|
||||
|
||||
const sourceType = document.getElementById("sourceType");
|
||||
|
||||
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
||||
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
||||
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
||||
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
||||
|
||||
const headerRow = document.getElementById("headerRow");
|
||||
const startColumn = document.getElementById("startColumn");
|
||||
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
||||
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
||||
|
||||
const selectedClientId = <?php echo json_encode((int)($template['idclient'] ?? 0)); ?>;
|
||||
const selectedSchemaId = <?php echo json_encode((int)($template['idschema'] ?? 0)); ?>;
|
||||
@@ -231,33 +430,71 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
allowClear: true
|
||||
});
|
||||
|
||||
$('#apiConfigSelect').select2({
|
||||
placeholder: "Select an API configuration...",
|
||||
allowClear: true
|
||||
});
|
||||
|
||||
function updateSourceFields() {
|
||||
const selectedSource = sourceType.value;
|
||||
|
||||
if (selectedSource === 'API') {
|
||||
headerRowWrapper.style.opacity = '0.6';
|
||||
startColumnWrapper.style.opacity = '0.6';
|
||||
const isXls = selectedSource === 'XLS';
|
||||
const isApiOrJson = selectedSource === 'API' || selectedSource === 'JSON';
|
||||
|
||||
headerRow.required = false;
|
||||
startColumn.required = false;
|
||||
|
||||
headerRow.disabled = true;
|
||||
startColumn.disabled = true;
|
||||
} else {
|
||||
headerRowWrapper.style.opacity = '1';
|
||||
startColumnWrapper.style.opacity = '1';
|
||||
if (isXls) {
|
||||
headerRowWrapper.style.display = 'block';
|
||||
startColumnWrapper.style.display = 'block';
|
||||
xlsSheetNumberWrapper.style.display = 'block';
|
||||
|
||||
headerRow.required = true;
|
||||
startColumn.required = true;
|
||||
|
||||
headerRow.disabled = false;
|
||||
startColumn.disabled = false;
|
||||
xlsSheetIndex.disabled = false;
|
||||
|
||||
apiConfigWrapper.style.display = 'none';
|
||||
apiConfigSelect.required = false;
|
||||
apiConfigSelect.disabled = true;
|
||||
$('#apiConfigSelect').val(null).trigger('change');
|
||||
} else {
|
||||
headerRowWrapper.style.display = 'none';
|
||||
startColumnWrapper.style.display = 'none';
|
||||
xlsSheetNumberWrapper.style.display = 'none';
|
||||
|
||||
headerRow.required = false;
|
||||
startColumn.required = false;
|
||||
|
||||
headerRow.disabled = true;
|
||||
startColumn.disabled = true;
|
||||
xlsSheetIndex.disabled = true;
|
||||
|
||||
if (isApiOrJson) {
|
||||
apiConfigWrapper.style.display = 'block';
|
||||
apiConfigSelect.required = true;
|
||||
apiConfigSelect.disabled = false;
|
||||
} else {
|
||||
apiConfigWrapper.style.display = 'none';
|
||||
apiConfigSelect.required = false;
|
||||
apiConfigSelect.disabled = true;
|
||||
$('#apiConfigSelect').val(null).trigger('change');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceType.addEventListener('change', updateSourceFields);
|
||||
updateSourceFields();
|
||||
|
||||
function formatClientLabel(client) {
|
||||
const nome = client.Nominativo || "Name not available";
|
||||
const id = client.IdCliente || "";
|
||||
const codiceCliente = (client.CodiceCliente ?? client.codiceCliente ?? "").toString().trim();
|
||||
const suffix = (codiceCliente.split("_")[1] || "").trim();
|
||||
const shortCode = suffix || (codiceCliente ? codiceCliente.charAt(0) : "--");
|
||||
|
||||
return `${nome.trim()} - ${shortCode} (ID: ${id})`;
|
||||
}
|
||||
|
||||
async function loadClients() {
|
||||
try {
|
||||
clientLoadingStatus.style.display = 'inline';
|
||||
@@ -280,9 +517,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
select.innerHTML = '<option value="">Select a client...</option>';
|
||||
|
||||
data.value.forEach(client => {
|
||||
const nome = client.Nominativo || "Name not available";
|
||||
const id = client.IdCliente || "";
|
||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
||||
const option = new Option(formatClientLabel(client), id);
|
||||
|
||||
if (parseInt(id) === parseInt(selectedClientId)) {
|
||||
option.selected = true;
|
||||
@@ -468,6 +704,28 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
const routineId = routineSelect.value;
|
||||
formData.append("idroutine", routineId);
|
||||
|
||||
const selectedSource = sourceType.value;
|
||||
|
||||
if ((selectedSource === 'API' || selectedSource === 'JSON') && !apiConfigSelect.value) {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: "Please select an API/JSON configuration.",
|
||||
icon: "error",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedSource === 'XLS' && xlsSheetIndex.value === '') {
|
||||
Swal.fire({
|
||||
title: "Error!",
|
||||
text: "Please enter the XLS sheet number.",
|
||||
icon: "error",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
fetch("process_edit_template_xls.php", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
|
||||
@@ -328,3 +328,6 @@
|
||||
2026-03-26 10:57:41 [MoltiplicatorePrezzo] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||
2026-03-26 10:57:56 [AnagraficaCertestObject] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||
2026-03-26 10:58:11 [AnagraficaCertestService] Autenticazione fallita: HTTP 500, Errore cURL: , Risposta: {"title":"Internal Server Error","status":500,"detail":"Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.","instance":"POST /api/authentication/authenticate","errorCode":"63ab532c6"}
|
||||
2026-04-30 15:01:25 - Errore nel recupero dati: HTTP 404, Risposta: {"title":"Not Found","status":404,"detail":"Not Found","instance":"GET /api/odata/Rapporto(2621521)","errorCode":"d25cbd678"}
|
||||
2026-04-30 15:02:04 - Errore nel recupero dati: HTTP 404, Risposta:
|
||||
2026-04-30 15:03:19 - Errore nella richiesta: URL rejected: Malformed input to a URL function
|
||||
|
||||
@@ -59,6 +59,49 @@ function formatDateToExport($value)
|
||||
return null; // Imposta null se non è una data valida
|
||||
}
|
||||
|
||||
// ImportaCommessa con retry: la chiamata è asincrona lato LIMS e a volte
|
||||
// risponde 200 senza importare (StatoCommessaWeb resta "Inviata"/"Nuova").
|
||||
// Riprova con backoff esponenziale finché non passa a "Elaborata".
|
||||
function importaCommessaWithRetry($api, $commessaId, array $payload, $maxRetries = 3, $initialBackoff = 1)
|
||||
{
|
||||
$result = null;
|
||||
$stato = null;
|
||||
$succeeded = false;
|
||||
$log = "";
|
||||
$backoff = $initialBackoff;
|
||||
|
||||
set_time_limit(120); // i backoff non devono far scadere il timeout della richiesta
|
||||
|
||||
for ($attempt = 1; $attempt <= $maxRetries + 1; $attempt++) {
|
||||
try {
|
||||
$result = $api->post("CommessaWeb({$commessaId})/ImportaCommessa", $payload);
|
||||
$stato = $result['StatoCommessaWeb'] ?? null;
|
||||
$log .= "Attempt {$attempt}: HTTP 200, StatoCommessaWeb=" . ($stato ?? 'null') . "\n";
|
||||
} catch (Exception $e) {
|
||||
$stato = null;
|
||||
$log .= "Attempt {$attempt}: EXCEPTION " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
if ($stato === 'Elaborata') {
|
||||
$succeeded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($attempt <= $maxRetries) {
|
||||
$log .= " -> not Elaborata, waiting {$backoff}s before retry\n";
|
||||
sleep($backoff);
|
||||
$backoff *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'succeeded' => $succeeded,
|
||||
'stato' => $stato,
|
||||
'result' => $result,
|
||||
'log' => $log,
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$iddatadb = $_POST['iddatadb'] ?? null;
|
||||
if (!$iddatadb) {
|
||||
@@ -107,11 +150,13 @@ try {
|
||||
|
||||
// 🔹 STEP 3: Fetch Parts (including idmatrice and part id for custom fields)
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id AS part_id, part_number, part_description, material, color, mix, idmatrice, dateexpiry
|
||||
FROM identification_parts
|
||||
WHERE iddatadb = :iddatadb
|
||||
ORDER BY CAST(part_number AS UNSIGNED) ASC, part_number ASC
|
||||
");
|
||||
SELECT id AS part_id, part_number, part_description, material, color, mix, idmatrice, dateexpiry
|
||||
FROM identification_parts
|
||||
WHERE iddatadb = :iddatadb
|
||||
AND part_description IS NOT NULL
|
||||
AND TRIM(part_description) <> ''
|
||||
ORDER BY CAST(part_number AS UNSIGNED) ASC, part_number ASC
|
||||
");
|
||||
$stmt->execute(['iddatadb' => $iddatadb]);
|
||||
$parts = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@@ -510,9 +555,8 @@ try {
|
||||
$writeLog($logFileStep9, $logContentStep9, "STEP 9 - InviaCommessa (commessa={$commessaId})");
|
||||
|
||||
|
||||
// 🔹 STEP 9.5: Importazione da CommessaWeb a Commessa (commentato come richiesto)
|
||||
// 🔹 STEP 9.5: Importazione da CommessaWeb a Commessa (con retry)
|
||||
// Supplier call: POST api/odata/CommessaWeb(XXX)/ImportaCommessa
|
||||
|
||||
$importUserId = (!empty($lims_global_user_id) && is_numeric($lims_global_user_id))
|
||||
? (int) $lims_global_user_id
|
||||
: 285;
|
||||
@@ -520,17 +564,23 @@ try {
|
||||
$importPayload = [
|
||||
"IdUtente" => $importUserId
|
||||
];
|
||||
$importResult = $api->post("CommessaWeb({$commessaId})/ImportaCommessa", $importPayload);
|
||||
|
||||
$importPayloadLog = json_encode($importPayload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
// Logga il POST
|
||||
|
||||
$importOutcome = importaCommessaWithRetry($api, $commessaId, $importPayload);
|
||||
$importResult = $importOutcome['result'];
|
||||
$importStato = $importOutcome['stato'];
|
||||
$importSucceeded = $importOutcome['succeeded'];
|
||||
|
||||
// Logga il POST (tutti i tentativi)
|
||||
$logContentStep91 = "curl --location --request POST '{$apiBaseUrl}CommessaWeb({$commessaId})/ImportaCommessa' \\\n" .
|
||||
"--header 'Content-Type: application/json' \\\n" .
|
||||
"--header 'Authorization: Bearer ••••••' \\\n" .
|
||||
"--data '{$importPayloadLog}'\n\n" .
|
||||
"RESPONSE:\n" . json_encode($importResult, JSON_PRETTY_PRINT);
|
||||
"ATTEMPTS:\n" . $importOutcome['log'] . "\n" .
|
||||
"SUCCEEDED: " . ($importSucceeded ? 'yes' : 'NO') . "\n\n" .
|
||||
"LAST RESPONSE:\n" . json_encode($importResult, JSON_PRETTY_PRINT);
|
||||
$logFileStep91 = $logDir . "commessa_{$commessaId}_importa_step91_" . time() . ".txt";
|
||||
$writeLog($logFileStep91, $logContentStep91, "STEP 9.5 - ImportaCommessa (commessa={$commessaId})");
|
||||
$writeLog($logFileStep91, $logContentStep91, "STEP 9.5 - ImportaCommessa (commessa={$commessaId}, succeeded=" . ($importSucceeded ? '1' : '0') . ")");
|
||||
|
||||
// 🔹 STEP 10: GET di controllo post-PATCH
|
||||
$expand = "CommesseCustomFields(\$expand=CustomField)";
|
||||
@@ -542,7 +592,23 @@ try {
|
||||
"RESPONSE:\n" . json_encode($commessaAfterPatch, JSON_PRETTY_PRINT);
|
||||
$logFileStep10 = $logDir . "commessa_{$commessaId}_get_step10_" . time() . ".txt";
|
||||
$writeLog($logFileStep10, $logContentStep10, "STEP 10 - GET verify (commessa={$commessaId})");
|
||||
// 🔹 STEP 10.1: Save final CodiceCommessa into datadb.commessaweb
|
||||
// After ImportaCommessa, the API returns the final LIMS job code in CodiceCommessa.
|
||||
// Example: CodiceCommessa = 2614795, CodiceCommessaWeb = 26C0103.
|
||||
$finalCodiceCommessa = trim((string)($commessaAfterPatch['CodiceCommessa'] ?? ''));
|
||||
|
||||
if ($finalCodiceCommessa !== '') {
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE datadb
|
||||
SET commessaweb = :commessaweb,
|
||||
status = 'l'
|
||||
WHERE iddatadb = :iddatadb
|
||||
");
|
||||
$stmt->execute([
|
||||
'commessaweb' => substr($finalCodiceCommessa, 0, 30),
|
||||
'iddatadb' => $iddatadb
|
||||
]);
|
||||
}
|
||||
// 🔹 STEP 11: Prepare final response
|
||||
$finalCommessa = [
|
||||
"Cliente" => $clienteId,
|
||||
@@ -557,7 +623,7 @@ try {
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"idcommessaweb" => $commessaId,
|
||||
"commessaweb" => $commessaWebCode,
|
||||
"commessaweb" => $finalCodiceCommessa ?: $commessaWebCode,
|
||||
"commessaWeb" => $finalCommessa,
|
||||
"commessaWebApiResponse" => $commessaWeb, // Incluso per debug
|
||||
"totalCampioni" => count($campioni),
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?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);
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
// Campione hardcoded del tuo esempio
|
||||
$idCampione = 749027;
|
||||
|
||||
// Analisi che stai provando ad assegnare
|
||||
$targetRecordKey = '11;1218320';
|
||||
$targetIdAnalisi = '1218320';
|
||||
|
||||
// Endpoint: recupera il campione espandendo le analisi abilitate
|
||||
$endpoint = "Campione($idCampione)";
|
||||
|
||||
// ATTENZIONE:
|
||||
// nella tua classe normalmente le opzioni vengono trasformate in query string.
|
||||
// Qui serve: ?$expand=AnalisiAbilitate
|
||||
$options = [
|
||||
'$expand' => 'AnalisiAbilitate'
|
||||
];
|
||||
|
||||
// Debug: salva URL usato
|
||||
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||
$query = http_build_query($options);
|
||||
$query = urldecode($query); // rende leggibile $expand invece di %24expand
|
||||
|
||||
$full_url = $base_url . $endpoint . ($query ? '?' . $query : '');
|
||||
file_put_contents(__DIR__ . '/last_url_analisi_abilitate.txt', $full_url . PHP_EOL, FILE_APPEND);
|
||||
|
||||
// Chiamata API
|
||||
$data = $api->get($endpoint, $options);
|
||||
|
||||
// Recupera AnalisiAbilitate dalla risposta
|
||||
$analisiAbilitate = $data['AnalisiAbilitate'] ?? [];
|
||||
|
||||
// Alcune API OData possono restituire collection dentro "value"
|
||||
if (isset($analisiAbilitate['value']) && is_array($analisiAbilitate['value'])) {
|
||||
$analisiAbilitate = $analisiAbilitate['value'];
|
||||
}
|
||||
|
||||
// Cerca se il RecordKey / IdAnalisi che stai usando è effettivamente assegnabile
|
||||
$matches = [];
|
||||
|
||||
foreach ($analisiAbilitate as $analisi) {
|
||||
$recordKey = isset($analisi['RecordKey']) ? (string)$analisi['RecordKey'] : '';
|
||||
$idAnalisi = isset($analisi['IdAnalisi']) ? (string)$analisi['IdAnalisi'] : '';
|
||||
|
||||
if ($recordKey === $targetRecordKey || $idAnalisi === $targetIdAnalisi) {
|
||||
$matches[] = $analisi;
|
||||
}
|
||||
}
|
||||
|
||||
// Output diagnostico
|
||||
$output = [
|
||||
'success' => true,
|
||||
'idCampione' => $idCampione,
|
||||
'request_url' => $full_url,
|
||||
'targetRecordKey' => $targetRecordKey,
|
||||
'targetIdAnalisi' => $targetIdAnalisi,
|
||||
'enabled_analyses_count' => is_array($analisiAbilitate) ? count($analisiAbilitate) : 0,
|
||||
'target_found' => count($matches) > 0,
|
||||
'target_matches' => $matches,
|
||||
'analisi_abilitate' => $analisiAbilitate,
|
||||
'raw_response' => $data
|
||||
];
|
||||
|
||||
// Salva il JSON in locale
|
||||
file_put_contents(
|
||||
__DIR__ . '/analisi_abilitate_campione_749027_response.json',
|
||||
json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
|
||||
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
file_put_contents(
|
||||
__DIR__ . '/error_log_analisi_abilitate.txt',
|
||||
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
http_response_code(500);
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?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);
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
// Campione hardcoded del tuo esempio
|
||||
$idCampione = 749027;
|
||||
|
||||
// Matrice attesa dal log STEP 6
|
||||
$expectedMatriceId = 6430;
|
||||
|
||||
// Endpoint con expand della Matrice
|
||||
$endpoint = "Campione($idCampione)";
|
||||
$options = [
|
||||
'$expand' => 'Matrice'
|
||||
];
|
||||
|
||||
// Debug URL
|
||||
$base_url = 'https://93.43.5.102/limsapi/api/odata/';
|
||||
$query = http_build_query($options);
|
||||
$queryReadable = urldecode($query);
|
||||
|
||||
$full_url = $base_url . $endpoint . ($queryReadable ? '?' . $queryReadable : '');
|
||||
|
||||
file_put_contents(
|
||||
__DIR__ . '/last_url_check_matrice.txt',
|
||||
$full_url . PHP_EOL,
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
// Chiamata API
|
||||
$data = $api->get($endpoint, $options);
|
||||
|
||||
// Recupero Matrice dalla response
|
||||
$matrice = $data['Matrice'] ?? null;
|
||||
|
||||
$actualMatriceId = null;
|
||||
|
||||
if (is_array($matrice)) {
|
||||
// Provo i nomi più probabili
|
||||
$actualMatriceId = $matrice['IdMatrice']
|
||||
?? $matrice['idMatrice']
|
||||
?? $matrice['Id']
|
||||
?? $matrice['ID']
|
||||
?? null;
|
||||
}
|
||||
|
||||
$matrice_ok = ((int)$actualMatriceId === (int)$expectedMatriceId);
|
||||
|
||||
$output = [
|
||||
'success' => true,
|
||||
'idCampione' => $idCampione,
|
||||
'expectedMatriceId' => $expectedMatriceId,
|
||||
'actualMatriceId' => $actualMatriceId,
|
||||
'matrice_ok' => $matrice_ok,
|
||||
'request_url' => $full_url,
|
||||
'matrice' => $matrice,
|
||||
'raw_response' => $data
|
||||
];
|
||||
|
||||
// Salva JSON completo
|
||||
file_put_contents(
|
||||
__DIR__ . '/check_matrice_campione_749027_response.json',
|
||||
json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
|
||||
echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
file_put_contents(
|
||||
__DIR__ . '/error_log_check_matrice.txt',
|
||||
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
http_response_code(500);
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
/**
|
||||
* Uso:
|
||||
* rapporto_full_by_codice.php?codice=2621521
|
||||
* rapporto_full_by_codice.php?codice=2621521&download=1
|
||||
*/
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
$codiceRapporto = trim($_GET['codice'] ?? '');
|
||||
$downloadPdf = isset($_GET['download']) && $_GET['download'] == '1';
|
||||
|
||||
if ($codiceRapporto === '') {
|
||||
throw new Exception("Parametro codice mancante. Usa ?codice=2621521");
|
||||
}
|
||||
|
||||
$debugDir = __DIR__ . '/logs/lims_rapporti/';
|
||||
$pdfDir = __DIR__ . '/pdf/rapporti/';
|
||||
|
||||
if (!is_dir($debugDir)) mkdir($debugDir, 0755, true);
|
||||
if (!is_dir($pdfDir)) mkdir($pdfDir, 0755, true);
|
||||
|
||||
$codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
|
||||
|
||||
/**
|
||||
* STEP 1 - Search IdRapporto
|
||||
*/
|
||||
$searchParams = [
|
||||
'$filter' => "CodiceRapporto eq '{$codiceRapportoSafe}'",
|
||||
'$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
|
||||
'$top' => 1
|
||||
];
|
||||
|
||||
$searchEndpoint = "Rapporto?" . http_build_query($searchParams);
|
||||
$searchData = $api->get($searchEndpoint);
|
||||
|
||||
$rapporti = $searchData['value'] ?? [];
|
||||
|
||||
if (empty($rapporti)) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Nessun rapporto trovato per questo CodiceRapporto.',
|
||||
'codice_rapporto' => $codiceRapporto
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rapportoBase = $rapporti[0];
|
||||
$idRapporto = intval($rapportoBase['IdRapporto'] ?? 0);
|
||||
|
||||
if (!$idRapporto) {
|
||||
throw new Exception("IdRapporto non trovato nella risposta.");
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 2 - Dettaglio Rapporto
|
||||
*/
|
||||
$clienteExpandError = null;
|
||||
$detailEndpoint = "Rapporto({$idRapporto})?\$expand=Cliente,RapportiFiles,CampioniDatiRapporto";
|
||||
|
||||
try {
|
||||
$detailData = $api->get($detailEndpoint);
|
||||
} catch (Exception $e) {
|
||||
$clienteExpandError = $e->getMessage();
|
||||
$detailEndpoint = "Rapporto({$idRapporto})?\$expand=RapportiFiles,CampioniDatiRapporto";
|
||||
$detailData = $api->get($detailEndpoint);
|
||||
}
|
||||
|
||||
file_put_contents(
|
||||
$debugDir . "rapporto_{$codiceRapportoSafe}_light.json",
|
||||
json_encode($detailData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
|
||||
$cliente = $detailData['Cliente'] ?? null;
|
||||
$rapportiFiles = $detailData['RapportiFiles'] ?? [];
|
||||
$campioniDatiRapporto = $detailData['CampioniDatiRapporto'] ?? [];
|
||||
|
||||
// ====================== CLIENTE ======================
|
||||
$clienteInfo = [
|
||||
'found' => is_array($cliente),
|
||||
'id_cliente' => $cliente['IdCliente'] ?? $cliente['Id'] ?? null,
|
||||
'codice_cliente' => $cliente['CodiceCliente'] ?? $cliente['Codice'] ?? null,
|
||||
'nome_cliente' => $cliente['Nominativo'] ?? $cliente['RagioneSociale'] ?? $cliente['Nome'] ?? $cliente['Descrizione'] ?? null,
|
||||
'partita_iva' => $cliente['PartitaIva'] ?? null,
|
||||
'codice_fiscale' => $cliente['CodiceFiscale'] ?? null
|
||||
];
|
||||
|
||||
// ====================== FILE PDF ======================
|
||||
$selectedRapportoFile = null;
|
||||
$idRapportoFile = null;
|
||||
$pdfFileName = $codiceRapporto . '.pdf';
|
||||
$pdfFullPath = $pdfDir . $pdfFileName;
|
||||
|
||||
if (is_array($rapportiFiles) && count($rapportiFiles) > 0) {
|
||||
foreach ($rapportiFiles as $file) {
|
||||
$fileName = $file['FileName'] ?? '';
|
||||
$tipoRapporto = $file['TipoRapporto'] ?? '';
|
||||
$categoria = $file['Categoria'] ?? '';
|
||||
|
||||
if (
|
||||
stripos($fileName, '.pdf') !== false ||
|
||||
stripos($tipoRapporto, 'Rapporto') !== false ||
|
||||
stripos($categoria, 'Rapporti') !== false
|
||||
) {
|
||||
$selectedRapportoFile = $file;
|
||||
$idRapportoFile = $file['IdRapportoFile'] ?? null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$selectedRapportoFile && count($rapportiFiles) > 0) {
|
||||
$selectedRapportoFile = $rapportiFiles[0];
|
||||
$idRapportoFile = $rapportiFiles[0]['IdRapportoFile'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== DOWNLOAD PDF - DEBUG AVANZATO ======================
|
||||
$pdfDownloaded = false;
|
||||
$pdfError = null;
|
||||
$endpointUsato = null;
|
||||
|
||||
if ($downloadPdf && $idRapportoFile) {
|
||||
|
||||
$tentativi = [
|
||||
"RapportoFile({$idRapportoFile})/\$value",
|
||||
"RapportoFile({$idRapportoFile})/Contenuto/\$value",
|
||||
"RapportoFile({$idRapportoFile})/File/\$value",
|
||||
"Rapporto({$idRapporto})/RapportiFiles({$idRapportoFile})/\$value",
|
||||
"Rapporto({$idRapporto})/RapportoFile/\$value",
|
||||
"GetRapportoFile?IdRapportoFile={$idRapportoFile}",
|
||||
"RapportoFile/Download?Id={$idRapportoFile}",
|
||||
"Rapporto/DownloadFile?IdRapporto={$idRapporto}"
|
||||
];
|
||||
|
||||
foreach ($tentativi as $ep) {
|
||||
try {
|
||||
$pdfContent = $api->getRaw($ep);
|
||||
|
||||
if (strlen($pdfContent) > 2000) { // PDF decente deve essere più grande
|
||||
file_put_contents($pdfFullPath, $pdfContent);
|
||||
$pdfDownloaded = true;
|
||||
$endpointUsato = $ep;
|
||||
$pdfError = "SUCCESSO con: " . $ep;
|
||||
break;
|
||||
} else {
|
||||
$pdfError = "Contenuto troppo piccolo con: " . $ep;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$pdfError = "Fallito con " . $ep . " → " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== CAMPIONI + RATING (ripristinato dal tuo originale) ======================
|
||||
$campioniSintesi = [];
|
||||
$ratingFinale = null;
|
||||
$ratingSource = null;
|
||||
$hasIrregolare = false;
|
||||
$hasPositiveResults = false;
|
||||
|
||||
if (is_array($campioniDatiRapporto)) {
|
||||
foreach ($campioniDatiRapporto as $campione) {
|
||||
$giudizioRapporto = $campione['GiudizioRapporto'] ?? null;
|
||||
$giudizioCertificato = $campione['GiudizioCertificato'] ?? null;
|
||||
$esitoGiudizioLMR = $campione['EsitoGiudizioLMR'] ?? null;
|
||||
$esitoGiudizioImpiego = $campione['EsitoGiudizioImpiego'] ?? null;
|
||||
$risultatiIrregolari = ($campione['RisultatiIrregolari'] ?? false) === true;
|
||||
$risultatiPositivi = ($campione['RisultatiPositivi'] ?? false) === true;
|
||||
|
||||
$campioniSintesi[] = [
|
||||
'id_campione_dati_rapporto' => $campione['IdCampioneDatiRapporto'] ?? null,
|
||||
'codice_campione' => $campione['CodiceCampione'] ?? null,
|
||||
'stato_campione' => $campione['StatoCampione'] ?? null,
|
||||
'stato_campione_web' => $campione['StatoCampioneWeb'] ?? null,
|
||||
'matrice' => $campione['Matrice'] ?? null,
|
||||
'macro_matrice' => $campione['MacroMatrice'] ?? null,
|
||||
'giudizio_rapporto' => $giudizioRapporto,
|
||||
'giudizio_certificato' => $giudizioCertificato,
|
||||
'esito_giudizio_lmr' => $esitoGiudizioLMR,
|
||||
'esito_giudizio_impiego' => $esitoGiudizioImpiego,
|
||||
'risultati_positivi' => $risultatiPositivi,
|
||||
'risultati_irregolari' => $risultatiIrregolari
|
||||
];
|
||||
|
||||
if (!$ratingFinale && !empty($giudizioRapporto)) {
|
||||
$ratingFinale = $giudizioRapporto;
|
||||
$ratingSource = 'CampioniDatiRapporto.GiudizioRapporto';
|
||||
}
|
||||
|
||||
if (!$ratingFinale && !empty($giudizioCertificato)) {
|
||||
$ratingFinale = $giudizioCertificato;
|
||||
$ratingSource = 'CampioniDatiRapporto.GiudizioCertificato';
|
||||
}
|
||||
|
||||
if ($risultatiIrregolari) $hasIrregolare = true;
|
||||
if ($risultatiPositivi) $hasPositiveResults = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback rating
|
||||
if (!$ratingFinale) {
|
||||
if ($hasIrregolare) {
|
||||
$ratingFinale = 'Irregolare';
|
||||
$ratingSource = 'RisultatiIrregolari';
|
||||
} elseif ($hasPositiveResults || count($campioniSintesi) > 0) {
|
||||
$ratingFinale = 'Regolare';
|
||||
$ratingSource = 'fallback';
|
||||
}
|
||||
}
|
||||
|
||||
// ====================== RISPOSTA FINALE ======================
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
|
||||
'codice_rapporto' => $codiceRapporto,
|
||||
'id_rapporto' => $idRapporto,
|
||||
|
||||
'search_endpoint' => $searchEndpoint,
|
||||
'detail_endpoint' => $detailEndpoint,
|
||||
'cliente_expand_error' => $clienteExpandError,
|
||||
|
||||
'rapporto_base' => $rapportoBase,
|
||||
'cliente' => $clienteInfo,
|
||||
|
||||
'rating_finale' => $ratingFinale,
|
||||
'rating_source' => $ratingSource,
|
||||
|
||||
'rapporti_files_count' => count($rapportiFiles),
|
||||
'selected_rapporto_file' => $selectedRapportoFile,
|
||||
|
||||
'pdf_file_name' => $pdfFileName,
|
||||
'pdf_path' => $pdfDownloaded ? $pdfFullPath : null,
|
||||
'pdf_downloaded' => $pdfDownloaded,
|
||||
'pdf_error' => $pdfError,
|
||||
|
||||
'campioni_count' => count($campioniSintesi),
|
||||
'campioni_sintesi' => $campioniSintesi,
|
||||
|
||||
'download_requested' => $downloadPdf
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
$commessa = trim($_GET['commessa'] ?? '');
|
||||
|
||||
if ($commessa === '') {
|
||||
throw new Exception("Parametro commessa mancante");
|
||||
}
|
||||
|
||||
$debugDir = __DIR__ . '/logs/lims_rapporto/';
|
||||
if (!is_dir($debugDir)) {
|
||||
mkdir($debugDir, 0755, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 1
|
||||
* Provo a cercare il rapporto con filtri leggeri.
|
||||
* NIENTE expand pesanti.
|
||||
*/
|
||||
$attempts = [];
|
||||
|
||||
$filters = [
|
||||
'CodiceCommessa' => "CodiceCommessa eq '{$commessa}'",
|
||||
'Commessa_CodiceCommessa' => "Commessa/CodiceCommessa eq '{$commessa}'",
|
||||
'Commessa_IdCommessa' => is_numeric($commessa) ? "Commessa/IdCommessa eq {$commessa}" : null,
|
||||
'Codice' => "Codice eq '{$commessa}'"
|
||||
];
|
||||
|
||||
foreach ($filters as $label => $filter) {
|
||||
if (!$filter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$options = [
|
||||
'$filter' => $filter,
|
||||
'$top' => 10
|
||||
];
|
||||
|
||||
$data = $api->get('Rapporto', $options);
|
||||
|
||||
$attempts[$label] = [
|
||||
'success' => true,
|
||||
'filter' => $filter,
|
||||
'records' => isset($data['value']) && is_array($data['value']) ? count($data['value']) : null,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
file_put_contents(
|
||||
$debugDir . "rapporto_{$commessa}_{$label}.json",
|
||||
json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
} catch (Exception $e) {
|
||||
$attempts[$label] = [
|
||||
'success' => false,
|
||||
'filter' => $filter,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* STEP 2
|
||||
* Prendo solo eventuali rapporti trovati.
|
||||
*/
|
||||
$rapportiFound = [];
|
||||
|
||||
foreach ($attempts as $label => $attempt) {
|
||||
if (!$attempt['success']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$items = $attempt['data']['value'] ?? [];
|
||||
|
||||
if (!is_array($items)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
$rapportiFound[] = [
|
||||
'matched_by' => $label,
|
||||
'rapporto' => $item
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'input_commessa' => $commessa,
|
||||
'message' => 'Ricerca leggera su Rapporto completata. Se trovi un rapporto, poi recuperiamo RapportiFiles solo per quello.',
|
||||
'rapporti_found_count' => count($rapportiFound),
|
||||
'rapporti_found' => $rapportiFound,
|
||||
'attempts_summary' => array_map(function ($a) {
|
||||
return [
|
||||
'success' => $a['success'],
|
||||
'filter' => $a['filter'],
|
||||
'records' => $a['records'] ?? null,
|
||||
'error' => $a['error'] ?? null
|
||||
];
|
||||
}, $attempts)
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -2,25 +2,140 @@
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
$rapporto_id = 533329;
|
||||
|
||||
// Costruzione manuale dell'endpoint con espansione annidata
|
||||
$endpoint = "Rapporto($rapporto_id)?\$expand=CampioniDatiRapporto(\$expand=AnalisiDatiRapporto,CustomFieldsDatiRapporto)";
|
||||
// Esempi:
|
||||
// rapporto_by_codice_expand_step.php?codice=2541111&step=base
|
||||
// rapporto_by_codice_expand_step.php?codice=2541111&step=files
|
||||
// rapporto_by_codice_expand_step.php?codice=2541111&step=campioni
|
||||
// rapporto_by_codice_expand_step.php?codice=2541111&step=files_campioni
|
||||
|
||||
// Non passiamo options, già incluso nell'endpoint
|
||||
$data = $api->get($endpoint);
|
||||
$codiceRapporto = trim($_GET['codice'] ?? '');
|
||||
$step = trim($_GET['step'] ?? 'base');
|
||||
|
||||
file_put_contents(__DIR__ . '/rapporto_expanded.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
echo json_encode($data);
|
||||
if ($codiceRapporto === '') {
|
||||
throw new Exception("Parametro codice mancante. Usa ?codice=2541111");
|
||||
}
|
||||
|
||||
$allowedSteps = [
|
||||
'base' => '',
|
||||
'files' => 'RapportiFiles',
|
||||
'allegati' => 'RapportiAllegati',
|
||||
'campioni' => 'CampioniDatiRapporto',
|
||||
'files_campioni' => 'RapportiFiles,CampioniDatiRapporto'
|
||||
];
|
||||
|
||||
if (!array_key_exists($step, $allowedSteps)) {
|
||||
throw new Exception("Step non valido. Usa: " . implode(', ', array_keys($allowedSteps)));
|
||||
}
|
||||
|
||||
// Escape OData per eventuali apostrofi
|
||||
$codiceRapportoSafe = str_replace("'", "''", $codiceRapporto);
|
||||
|
||||
/*
|
||||
* STEP 1 - Trova IdRapporto partendo da CodiceRapporto.
|
||||
* Query leggera, con $select e $top=1.
|
||||
*/
|
||||
$searchParams = [
|
||||
'$filter' => "CodiceRapporto eq '{$codiceRapportoSafe}'",
|
||||
'$select' => 'IdRapporto,CodiceRapporto,Data,Versione,Firmato,DataStampa',
|
||||
'$top' => 1
|
||||
];
|
||||
|
||||
$searchEndpoint = "Rapporto?" . http_build_query($searchParams);
|
||||
|
||||
$searchData = $api->get($searchEndpoint);
|
||||
|
||||
$items = $searchData['value'] ?? [];
|
||||
|
||||
if (!is_array($items) || count($items) === 0) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Nessun rapporto trovato per questo CodiceRapporto.',
|
||||
'codice_rapporto' => $codiceRapporto,
|
||||
'search_endpoint' => $searchEndpoint,
|
||||
'search_data' => $searchData
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rapportoBase = $items[0];
|
||||
$rapportoId = intval($rapportoBase['IdRapporto'] ?? 0);
|
||||
|
||||
if (!$rapportoId) {
|
||||
throw new Exception("IdRapporto non trovato nella risposta.");
|
||||
}
|
||||
|
||||
/*
|
||||
* STEP 2 - Se step=base, restituisco solo la ricerca base.
|
||||
*/
|
||||
if ($step === 'base') {
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'codice_rapporto' => $codiceRapporto,
|
||||
'id_rapporto' => $rapportoId,
|
||||
'step' => $step,
|
||||
'search_endpoint' => $searchEndpoint,
|
||||
'rapporto_base' => $rapportoBase
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
/*
|
||||
* STEP 3 - Espande SOLO il rapporto trovato.
|
||||
*/
|
||||
$expandValue = $allowedSteps[$step];
|
||||
|
||||
$detailParams = [
|
||||
'$expand' => $expandValue
|
||||
];
|
||||
|
||||
$detailEndpoint = "Rapporto({$rapportoId})?" . http_build_query($detailParams);
|
||||
|
||||
file_put_contents(
|
||||
__DIR__ . '/last_rapporto_by_codice_expand_endpoint.txt',
|
||||
'[' . date('Y-m-d H:i:s') . '] SEARCH: ' . $searchEndpoint . PHP_EOL .
|
||||
'[' . date('Y-m-d H:i:s') . '] DETAIL: ' . $detailEndpoint . PHP_EOL,
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
$detailData = $api->get($detailEndpoint);
|
||||
|
||||
file_put_contents(
|
||||
__DIR__ . "/rapporto_codice_{$codiceRapportoSafe}_{$step}.json",
|
||||
json_encode([
|
||||
'search' => $searchData,
|
||||
'detail' => $detailData
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
|
||||
);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'codice_rapporto' => $codiceRapporto,
|
||||
'id_rapporto' => $rapportoId,
|
||||
'step' => $step,
|
||||
'search_endpoint' => $searchEndpoint,
|
||||
'detail_endpoint' => $detailEndpoint,
|
||||
'rapporto_base' => $rapportoBase,
|
||||
'data' => $detailData
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
file_put_contents(__DIR__ . '/error_log.txt', date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL, FILE_APPEND);
|
||||
file_put_contents(
|
||||
__DIR__ . '/error_log.txt',
|
||||
date('Y-m-d H:i:s') . ' - ' . $e->getMessage() . PHP_EOL,
|
||||
FILE_APPEND
|
||||
);
|
||||
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $e->getMessage()]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
+1058
-309
File diff suppressed because it is too large
Load Diff
@@ -10,65 +10,125 @@
|
||||
<title>Template Buttons - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
|
||||
<style>
|
||||
/* Layout flessibile per gestire dimensioni diverse */
|
||||
#templateButtons {
|
||||
/* Main buttons container */
|
||||
.template-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
justify-content: flex-start;
|
||||
/* Allinea a sinistra */
|
||||
padding: 20px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* Definizione delle dimensioni */
|
||||
/* Definizione delle dimensioni */
|
||||
/* Button sizes */
|
||||
.btn-small {
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
min-width: 100px;
|
||||
min-height: 30px;
|
||||
display: flex;
|
||||
/* Aggiunto */
|
||||
min-height: 36px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
/* Aggiunto */
|
||||
align-items: center;
|
||||
/* Aggiunto */
|
||||
gap: 8px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-medium {
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
min-width: 130px;
|
||||
min-height: 45px;
|
||||
display: flex;
|
||||
/* Aggiunto */
|
||||
min-width: 140px;
|
||||
min-height: 48px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
/* Aggiunto */
|
||||
align-items: center;
|
||||
/* Aggiunto */
|
||||
gap: 8px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-large {
|
||||
font-size: 20px;
|
||||
padding: 14px 28px;
|
||||
min-width: 180px;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
/* Aggiunto */
|
||||
min-width: 190px;
|
||||
min-height: 64px;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
/* Aggiunto */
|
||||
align-items: center;
|
||||
/* Aggiunto */
|
||||
gap: 10px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Stile della barra di ricerca */
|
||||
.template-icon {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.btn-large .template-icon {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.btn-small .template-icon {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/* Search box */
|
||||
#searchInput {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
padding: 10px 14px;
|
||||
font-size: 16px;
|
||||
margin-bottom: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 18px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#searchInput:focus {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 0 0 0.15rem rgba(13, 110, 253, 0.15);
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.custom-tabs {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
margin-bottom: 20px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.custom-tabs .nav-link {
|
||||
border: none;
|
||||
border-radius: 10px 10px 0 0;
|
||||
color: #555;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 18px;
|
||||
}
|
||||
|
||||
.custom-tabs .nav-link:hover {
|
||||
background: #f8f9fa;
|
||||
color: #0d6efd;
|
||||
}
|
||||
|
||||
.custom-tabs .nav-link.active {
|
||||
background: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
min-height: 140px;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
color: #6c757d;
|
||||
padding: 10px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -87,14 +147,54 @@
|
||||
<h6 class="mb-0">Active Templates</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Barra di ricerca -->
|
||||
<input type="text" id="searchInput" placeholder="Search template...">
|
||||
<div id="templateButtons"></div>
|
||||
|
||||
<ul class="nav nav-tabs custom-tabs" id="templateTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="xls-tab" data-bs-toggle="tab" data-bs-target="#xls-pane" type="button" role="tab" aria-controls="xls-pane" aria-selected="true">
|
||||
<i class="bx bx-spreadsheet template-icon"></i>
|
||||
XLS
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="api-tab" data-bs-toggle="tab" data-bs-target="#api-pane" type="button" role="tab" aria-controls="api-pane" aria-selected="false">
|
||||
<i class="bx bx-transfer-alt template-icon"></i>
|
||||
JSON/API
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="pdf-tab" data-bs-toggle="tab" data-bs-target="#pdf-pane" type="button" role="tab" aria-controls="pdf-pane" aria-selected="false">
|
||||
<i class="bx bx-file-pdf template-icon"></i>
|
||||
PDF
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="templateTabsContent">
|
||||
<div class="tab-pane fade show active" id="xls-pane" role="tabpanel" aria-labelledby="xls-tab">
|
||||
<div id="templateButtonsXLS" class="template-buttons">
|
||||
<div class="loading-message">Loading XLS templates...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="api-pane" role="tabpanel" aria-labelledby="api-tab">
|
||||
<div id="templateButtonsAPI" class="template-buttons">
|
||||
<div class="loading-message">Loading API templates...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="pdf-pane" role="tabpanel" aria-labelledby="pdf-tab">
|
||||
<div id="templateButtonsPDF" class="template-buttons">
|
||||
<div class="loading-message">Loading PDF templates...</div>
|
||||
</div>
|
||||
</div>
|
||||
</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'); ?>
|
||||
@@ -104,46 +204,137 @@
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const allTemplates = [];
|
||||
|
||||
const containers = {
|
||||
XLS: document.getElementById("templateButtonsXLS"),
|
||||
API: document.getElementById("templateButtonsAPI"),
|
||||
PDF: document.getElementById("templateButtonsPDF")
|
||||
};
|
||||
|
||||
function getSizeClass(buttonSize) {
|
||||
if (buttonSize === "small") return "btn-small";
|
||||
if (buttonSize === "large") return "btn-large";
|
||||
return "btn-medium";
|
||||
}
|
||||
|
||||
function getTemplateIcon(sourceType) {
|
||||
switch ((sourceType || '').toUpperCase()) {
|
||||
case 'XLS':
|
||||
return 'bx bx-spreadsheet';
|
||||
case 'API':
|
||||
return 'bx bx-transfer-alt';
|
||||
case 'PDF':
|
||||
return 'bx bx-file-pdf';
|
||||
default:
|
||||
return 'bx bx-file';
|
||||
}
|
||||
}
|
||||
|
||||
function createButton(template) {
|
||||
const sizeClass = getSizeClass(template.button_size);
|
||||
const sourceType = (template.source_type || '').toUpperCase();
|
||||
const iconClass = getTemplateIcon(sourceType);
|
||||
|
||||
const btn = document.createElement("a");
|
||||
btn.href = `import_xls2.php?id=${template.id}`;
|
||||
btn.className = `btn ${sizeClass}`;
|
||||
btn.style.backgroundColor = template.button_bg_color || '#0d6efd';
|
||||
btn.style.color = template.button_text_color || '#ffffff';
|
||||
btn.setAttribute("data-label", (template.button_label || '').toLowerCase());
|
||||
btn.setAttribute("data-source-type", sourceType);
|
||||
|
||||
btn.innerHTML = `
|
||||
<i class="${iconClass} template-icon"></i>
|
||||
<span>${escapeHtml(template.button_label || 'Unnamed')}</span>
|
||||
`;
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function clearContainers() {
|
||||
Object.values(containers).forEach(container => {
|
||||
container.innerHTML = '';
|
||||
});
|
||||
}
|
||||
|
||||
function renderTemplates(searchValue = '') {
|
||||
clearContainers();
|
||||
|
||||
const grouped = {
|
||||
XLS: [],
|
||||
API: [],
|
||||
PDF: []
|
||||
};
|
||||
|
||||
allTemplates.forEach(template => {
|
||||
const sourceType = (template.source_type || '').toUpperCase();
|
||||
const label = (template.button_label || '').toLowerCase();
|
||||
|
||||
if (searchValue && !label.includes(searchValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (grouped[sourceType]) {
|
||||
grouped[sourceType].push(template);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(grouped).forEach(type => {
|
||||
const container = containers[type];
|
||||
if (!container) return;
|
||||
|
||||
if (grouped[type].length === 0) {
|
||||
container.innerHTML = `<div class="empty-message">No templates found in this section.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
grouped[type].forEach(template => {
|
||||
container.appendChild(createButton(template));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fetch("load_active_templates.php")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (!data.success) {
|
||||
console.error("Error loading templates:", data.message);
|
||||
clearContainers();
|
||||
Object.values(containers).forEach(container => {
|
||||
container.innerHTML = `<div class="empty-message">Error loading templates.</div>`;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let templateButtons = document.getElementById("templateButtons");
|
||||
templateButtons.innerHTML = '';
|
||||
|
||||
data.data.forEach(template => {
|
||||
let sizeClass = "btn-medium"; // Default
|
||||
if (template.button_size === "small") sizeClass = "btn-small";
|
||||
if (template.button_size === "large") sizeClass = "btn-large";
|
||||
|
||||
let btn = document.createElement("a");
|
||||
btn.href = `import_xls2.php?id=${template.id}`;
|
||||
btn.className = `btn ${sizeClass}`;
|
||||
btn.style.backgroundColor = template.button_bg_color;
|
||||
btn.style.color = template.button_text_color;
|
||||
btn.textContent = template.button_label;
|
||||
btn.setAttribute("data-label", template.button_label.toLowerCase()); // Attributo per ricerca
|
||||
|
||||
templateButtons.appendChild(btn);
|
||||
});
|
||||
})
|
||||
.catch(error => console.error("Fetch error:", error));
|
||||
|
||||
// Funzione per la ricerca live
|
||||
document.getElementById("searchInput").addEventListener("input", function() {
|
||||
let searchValue = this.value.toLowerCase();
|
||||
document.querySelectorAll("#templateButtons a").forEach(btn => {
|
||||
let label = btn.getAttribute("data-label");
|
||||
if (label.includes(searchValue)) {
|
||||
btn.style.display = "inline-block";
|
||||
} else {
|
||||
btn.style.display = "none";
|
||||
if (!Array.isArray(data.data)) {
|
||||
clearContainers();
|
||||
Object.values(containers).forEach(container => {
|
||||
container.innerHTML = `<div class="empty-message">Invalid response format.</div>`;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
allTemplates.push(...data.data);
|
||||
renderTemplates();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Fetch error:", error);
|
||||
clearContainers();
|
||||
Object.values(containers).forEach(container => {
|
||||
container.innerHTML = `<div class="empty-message">Fetch error while loading templates.</div>`;
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById("searchInput").addEventListener("input", function() {
|
||||
const searchValue = this.value.toLowerCase().trim();
|
||||
renderTemplates(searchValue);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<?php
|
||||
include('include/headscript.php');
|
||||
|
||||
// ✅ FIX timezone (Rome)
|
||||
ini_set('date.timezone', 'Europe/Rome');
|
||||
date_default_timezone_set('Europe/Rome');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !isset($_POST['template_id']) || !isset($_POST['selected_rows']) || !isset($_POST['filename'])) {
|
||||
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Richiesta non valida"));
|
||||
exit;
|
||||
|
||||
@@ -40,6 +40,22 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
<?php include('cssinclude.php'); ?>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.top-scrollbar {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.25rem;
|
||||
background: #f8f9fa;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.top-scrollbar-inner {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
@@ -151,7 +167,12 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
<div class="d-flex align-items-center">
|
||||
<div>
|
||||
<h6 class="mb-0"><?= htmlspecialchars($template['name']) ?></h6>
|
||||
<small>Template ID: <?= $id ?>, Start Row: <?= $template['header_row'] ?>, Start Column: <?= $template['start_column'] ?></small>
|
||||
<small>
|
||||
Template ID: <?= $id ?>,
|
||||
Sheet Number: <?= (int)($template['xls_sheet_index'] ?? 0) ?>,
|
||||
Start Row: <?= $template['header_row'] ?>,
|
||||
Start Column: <?= $template['start_column'] ?>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -228,8 +249,9 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
const templateId = <?= $id ?>;
|
||||
console.log('Template ID passed to formData:', templateId);
|
||||
formData.append('template_id', templateId);
|
||||
formData.append('header_row', <?= $template['header_row'] ?>);
|
||||
formData.append('start_column', <?= $template['start_column'] ?>);
|
||||
formData.append('header_row', <?= (int)$template['header_row'] ?>);
|
||||
formData.append('start_column', <?= json_encode($template['start_column']) ?>);
|
||||
formData.append('xls_sheet_index', <?= (int)($template['xls_sheet_index'] ?? 0) ?>);
|
||||
|
||||
fetch('process_import_xls2.php', {
|
||||
method: 'POST',
|
||||
@@ -315,8 +337,8 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
<form id="selectRowsForm" action="import_insert.php" method="POST">
|
||||
<input type="hidden" name="template_id" value="${data.template_id}">
|
||||
<input type="hidden" name="columns" value="${encodeURIComponent(JSON.stringify(data.columns))}">
|
||||
<input type="hidden" name="rows" value="${encodeURIComponent(JSON.stringify(data.rows))}">
|
||||
<input type="hidden" name="excelrows" value="${encodeURIComponent(JSON.stringify(data.excel_data.map(r => r.excelrow)))}">
|
||||
<input type="hidden" name="rows" id="selectedRowsData" value="">
|
||||
<input type="hidden" name="excelrows" id="selectedExcelRowsData" value="">
|
||||
<input type="hidden" name="filename" value="${data.filename}">
|
||||
|
||||
<!-- TOP BUTTON -->
|
||||
@@ -324,8 +346,12 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
<button type="submit" class="btn btn-primary" id="proceedButtonTop" disabled>Prosegui</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="table table-striped table-bordered">
|
||||
<div class="top-scrollbar" id="topTableScrollbar">
|
||||
<div class="top-scrollbar-inner" id="topTableScrollbarInner"></div>
|
||||
</div>
|
||||
|
||||
<div class="table-container" id="mainTableContainer">
|
||||
<table class="table table-striped table-bordered" id="importPreviewTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" id="selectAll"> Seleziona</th>
|
||||
@@ -363,6 +389,83 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
`;
|
||||
tableContainer.innerHTML = html;
|
||||
|
||||
const selectRowsForm = document.getElementById('selectRowsForm');
|
||||
|
||||
selectRowsForm.addEventListener('submit', function(e) {
|
||||
const checkedBoxes = Array.from(document.querySelectorAll('.row-checkbox:checked'));
|
||||
|
||||
if (checkedBoxes.length === 0) {
|
||||
e.preventDefault();
|
||||
alert('Seleziona almeno una riga.');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedRows = [];
|
||||
const selectedExcelRows = [];
|
||||
|
||||
checkedBoxes.forEach((cb, newIndex) => {
|
||||
const originalIndex = parseInt(cb.value, 10);
|
||||
|
||||
if (data.rows && data.rows[originalIndex]) {
|
||||
selectedRows.push(data.rows[originalIndex]);
|
||||
}
|
||||
|
||||
if (data.excel_data && data.excel_data[originalIndex]) {
|
||||
selectedExcelRows.push(data.excel_data[originalIndex].excelrow);
|
||||
}
|
||||
|
||||
// Reindex selected_rows so import_insert.php receives only the reduced rows array
|
||||
cb.value = newIndex;
|
||||
});
|
||||
|
||||
document.getElementById('selectedRowsData').value =
|
||||
encodeURIComponent(JSON.stringify(selectedRows));
|
||||
|
||||
document.getElementById('selectedExcelRowsData').value =
|
||||
encodeURIComponent(JSON.stringify(selectedExcelRows));
|
||||
});
|
||||
|
||||
const topTableScrollbar = document.getElementById('topTableScrollbar');
|
||||
const topTableScrollbarInner = document.getElementById('topTableScrollbarInner');
|
||||
const mainTableContainer = document.getElementById('mainTableContainer');
|
||||
const importPreviewTable = document.getElementById('importPreviewTable');
|
||||
|
||||
function updateTopTableScrollbar() {
|
||||
if (!topTableScrollbar || !topTableScrollbarInner || !mainTableContainer || !importPreviewTable) return;
|
||||
|
||||
topTableScrollbarInner.style.width = importPreviewTable.scrollWidth + 'px';
|
||||
|
||||
if (mainTableContainer.scrollWidth > mainTableContainer.clientWidth) {
|
||||
topTableScrollbar.style.display = 'block';
|
||||
} else {
|
||||
topTableScrollbar.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
let syncingTop = false;
|
||||
let syncingBottom = false;
|
||||
|
||||
if (topTableScrollbar && mainTableContainer) {
|
||||
topTableScrollbar.addEventListener('scroll', function() {
|
||||
if (syncingBottom) return;
|
||||
syncingTop = true;
|
||||
mainTableContainer.scrollLeft = topTableScrollbar.scrollLeft;
|
||||
syncingTop = false;
|
||||
});
|
||||
|
||||
mainTableContainer.addEventListener('scroll', function() {
|
||||
if (syncingTop) return;
|
||||
syncingBottom = true;
|
||||
topTableScrollbar.scrollLeft = mainTableContainer.scrollLeft;
|
||||
syncingBottom = false;
|
||||
});
|
||||
}
|
||||
|
||||
updateTopTableScrollbar();
|
||||
setTimeout(updateTopTableScrollbar, 100);
|
||||
setTimeout(updateTopTableScrollbar, 300);
|
||||
window.addEventListener('resize', updateTopTableScrollbar);
|
||||
|
||||
const proceedButtonTop = document.getElementById('proceedButtonTop');
|
||||
const proceedButtonBottom = document.getElementById('proceedButtonBottom');
|
||||
const selectAllCheckbox = document.getElementById('selectAll');
|
||||
@@ -493,4 +596,4 @@ error_log("Loaded template: " . print_r($template, true));
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
+395
-113
@@ -1,10 +1,6 @@
|
||||
<?php
|
||||
include('include/headscript.php');
|
||||
|
||||
// ✅ FIX timezone (Rome)
|
||||
ini_set('date.timezone', 'Europe/Rome');
|
||||
date_default_timezone_set('Europe/Rome');
|
||||
|
||||
$template_id = intval($_GET['id'] ?? 0);
|
||||
if (!$template_id) {
|
||||
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Template ID mancante"));
|
||||
@@ -59,15 +55,22 @@ if (empty($allMappings)) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Trova il campo main_field
|
||||
$mainFieldMapping = null;
|
||||
// Find up to 2 main fields
|
||||
$mainFieldMappings = [];
|
||||
|
||||
foreach ($allMappings as $mapping) {
|
||||
if ($mapping['main_field'] == 1 && $mapping['is_visible_import'] == 1) {
|
||||
$mainFieldMapping = $mapping;
|
||||
if ((string)$mapping['main_field'] === '1' && (int)$mapping['is_visible_import'] === 1) {
|
||||
$mainFieldMappings[] = $mapping;
|
||||
}
|
||||
|
||||
if (count($mainFieldMappings) >= 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Backward compatibility: first main field
|
||||
$mainFieldMapping = $mainFieldMappings[0] ?? null;
|
||||
|
||||
// Recupera l'idclient di default dal template (se presente)
|
||||
$template_stmt = $pdo->prepare("SELECT idclient FROM excel_templates WHERE id = ?");
|
||||
$template_stmt->execute([$template_id]);
|
||||
@@ -95,7 +98,7 @@ $stmt = $pdo->prepare("
|
||||
FROM datadb d
|
||||
LEFT JOIN auth_users u ON d.user_id = u.id
|
||||
{$baseWhere}
|
||||
ORDER BY d.iddatadb DESC
|
||||
ORDER BY d.excelrow ASC, d.iddatadb ASC
|
||||
{$limitClause}
|
||||
");
|
||||
$stmt->execute($baseParams);
|
||||
@@ -202,13 +205,13 @@ foreach ($importedData as $index => $row) {
|
||||
'status' => $row['status'] ?? 'i',
|
||||
'idclient' => $row['idclient'] ?? $default_idclient,
|
||||
'cliente_fornitore_id' => $row['cliente_fornitore_id'] ?? null,
|
||||
'tested_component' => $row['tested_component'] ?? '',
|
||||
'commessaweb' => $row['commessaweb'] ?? null,
|
||||
'user_name' => $row['user_name'] ?? '',
|
||||
'importreferencecode' => $row['importreferencecode'] ?? '',
|
||||
'filename_import' => $row['filename_import'] ?? '',
|
||||
'importdate' => $row['importdate'] ?? '',
|
||||
];
|
||||
|
||||
// Fixed fields
|
||||
$rowObj['fixedFields'] = [];
|
||||
foreach ($fixedFields as $f) {
|
||||
@@ -228,11 +231,18 @@ foreach ($importedData as $index => $row) {
|
||||
$rowObj['details'][(string)$d['mapping_id']] = $d['field_value'] ?? '';
|
||||
}
|
||||
|
||||
// Main field value
|
||||
// Main field values
|
||||
foreach ($mainFieldMappings as $mainMapping) {
|
||||
$mainDetail = array_filter($rowDetails, fn($d) => $d['mapping_id'] == $mainMapping['id']);
|
||||
$mainDetail = reset($mainDetail) ?: ['field_value' => $mainMapping['manual_default'] ?? ''];
|
||||
|
||||
$rowObj['details'][(string)$mainMapping['id']] =
|
||||
$mainDetail['field_value'] ?? $mainMapping['manual_default'] ?? '';
|
||||
}
|
||||
|
||||
// Backward compatibility: first main value
|
||||
if ($mainFieldMapping) {
|
||||
$mainDetail = array_filter($rowDetails, fn($d) => $d['mapping_id'] == $mainFieldMapping['id']);
|
||||
$mainDetail = reset($mainDetail) ?: ['field_value' => $mainFieldMapping['manual_default'] ?? ''];
|
||||
$rowObj['mainFieldValue'] = $mainDetail['field_value'] ?? $mainFieldMapping['manual_default'] ?? '';
|
||||
$rowObj['mainFieldValue'] = $rowObj['details'][(string)$mainFieldMapping['id']] ?? '';
|
||||
}
|
||||
|
||||
$rowObj['_dirty'] = false;
|
||||
@@ -242,16 +252,16 @@ foreach ($importedData as $index => $row) {
|
||||
// Build columns in display order
|
||||
$gridColumns = [];
|
||||
|
||||
// 1. Main field
|
||||
if ($mainFieldMapping) {
|
||||
// 1. Main fields, maximum 2
|
||||
foreach ($mainFieldMappings as $mainMapping) {
|
||||
$gridColumns[] = [
|
||||
'type' => 'main_field',
|
||||
'key' => (string)$mainFieldMapping['id'],
|
||||
'label' => $mainFieldMapping['field_label'],
|
||||
'dataType' => $mainFieldMapping['data_type'],
|
||||
'isManual' => (bool)$mainFieldMapping['is_manual'],
|
||||
'isRequired' => (bool)$mainFieldMapping['is_required'],
|
||||
'fieldId' => $mainFieldMapping['field_id'] ?? null,
|
||||
'key' => (string)$mainMapping['id'],
|
||||
'label' => $mainMapping['field_label'],
|
||||
'dataType' => $mainMapping['data_type'],
|
||||
'isManual' => (bool)$mainMapping['is_manual'],
|
||||
'isRequired' => (bool)$mainMapping['is_required'],
|
||||
'fieldId' => $mainMapping['field_id'] ?? null,
|
||||
'width' => 150,
|
||||
];
|
||||
}
|
||||
@@ -267,7 +277,12 @@ $gridColumns[] = ['type' => 'cliente_fornitore_id', 'key' => 'cliente_fornitore_
|
||||
|
||||
// 5. Auto fields
|
||||
foreach ($allMappings as $mapping) {
|
||||
if (!$mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
||||
if (
|
||||
!$mapping['is_manual']
|
||||
&& $mapping['main_field'] != 1
|
||||
&& $mapping['is_visible_import'] == 1
|
||||
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
||||
) {
|
||||
$gridColumns[] = [
|
||||
'type' => 'detail',
|
||||
'key' => (string)$mapping['id'],
|
||||
@@ -284,7 +299,12 @@ foreach ($allMappings as $mapping) {
|
||||
|
||||
// 6. Manual fields
|
||||
foreach ($allMappings as $mapping) {
|
||||
if ($mapping['is_manual'] && $mapping['main_field'] != 1 && $mapping['is_visible_import'] == 1) {
|
||||
if (
|
||||
$mapping['is_manual']
|
||||
&& $mapping['main_field'] != 1
|
||||
&& $mapping['is_visible_import'] == 1
|
||||
&& trim((string)$mapping['field_label']) !== 'Tested Component:'
|
||||
) {
|
||||
$gridColumns[] = [
|
||||
'type' => 'detail',
|
||||
'key' => (string)$mapping['id'],
|
||||
@@ -336,14 +356,18 @@ $gridMeta = [
|
||||
'slugMapping' => $slugMapping,
|
||||
'timeLabels' => $timeLabels,
|
||||
'columns' => $gridColumns,
|
||||
'mainFieldMapping' => $mainFieldMapping,
|
||||
'mainFieldMapping' => $mainFieldMapping,
|
||||
'mainFieldMappings' => $mainFieldMappings,
|
||||
'totalRows' => count($gridDataArray),
|
||||
];
|
||||
|
||||
?>
|
||||
<script>
|
||||
window.gridData = <?= json_encode($gridDataArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
window.gridData = <?= json_encode($gridDataArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
|
||||
|
||||
// Visible records in the current imported.php page
|
||||
window.visibleIddatadbList = window.gridData.map(row => parseInt(row.iddatadb, 10)).filter(Boolean);
|
||||
</script>
|
||||
|
||||
<!doctype html>
|
||||
@@ -364,20 +388,10 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
input.auto-input,
|
||||
select.auto-input {
|
||||
background-color: #d4edda;
|
||||
}
|
||||
|
||||
input.manual-input,
|
||||
select.manual-input {
|
||||
background-color: #fff3cd;
|
||||
}
|
||||
|
||||
input.required-input,
|
||||
select.required-input {
|
||||
background-color: #f8d7da;
|
||||
}
|
||||
|
||||
|
||||
|
||||
input.required-input,
|
||||
select.required-input {
|
||||
@@ -416,17 +430,9 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
textarea.auto-input {
|
||||
background-color: #d4edda;
|
||||
}
|
||||
|
||||
textarea.manual-input {
|
||||
background-color: #fff3cd;
|
||||
}
|
||||
|
||||
textarea.required-input {
|
||||
background-color: #f8d7da;
|
||||
}
|
||||
|
||||
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
@@ -463,6 +469,21 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.top-scrollbar {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
margin-bottom: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.25rem;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.top-scrollbar-inner {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.grid-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -663,7 +684,33 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.grid-row .grid-header:nth-child(2) {
|
||||
<?php if (isset($mainFieldMappings) && count($mainFieldMappings) >= 2): ?>
|
||||
|
||||
/* Sticky second Main column - only when the template has 2 Main fields */
|
||||
.grid-top .grid-cell:nth-child(3),
|
||||
#gridHeaderContainer .grid-header:nth-child(3),
|
||||
.grid-row .grid-cell:nth-child(3) {
|
||||
position: sticky !important;
|
||||
left: 360px;
|
||||
z-index: 7;
|
||||
background: white;
|
||||
overflow: visible;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#gridHeaderContainer .grid-header:nth-child(3) {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.grid-row:nth-child(even) .grid-cell:nth-child(3) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.grid-row:hover .grid-cell:nth-child(3) {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
<?php endif; ?>.grid-row .grid-header:nth-child(2) {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
@@ -797,7 +844,7 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
#photosModal > .modal-content {
|
||||
#photosModal>.modal-content {
|
||||
background-color: #fff;
|
||||
margin: 5% auto;
|
||||
padding: 20px;
|
||||
@@ -892,9 +939,17 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
}
|
||||
|
||||
@keyframes new-row-pulse {
|
||||
0%, 100% { background-color: transparent; }
|
||||
50% { background-color: #cce5ff; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: #cce5ff;
|
||||
}
|
||||
}
|
||||
|
||||
.row-just-created {
|
||||
animation: new-row-pulse 1s ease-in-out 3;
|
||||
}
|
||||
@@ -1011,11 +1066,7 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.api-fixed-select.required-input:invalid,
|
||||
.api-fixed-select[required]:not([value]):not([data-select2-id]) {
|
||||
background-color: #f8d7da !important;
|
||||
border-color: #dc3545 !important;
|
||||
}
|
||||
|
||||
|
||||
/* ── Pagination bar ── */
|
||||
.pager-bar {
|
||||
@@ -1029,21 +1080,25 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
font-size: 13px;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.pager-rows-per-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.pager-label {
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pager-limit-group {
|
||||
display: inline-flex;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pager-limit-btn {
|
||||
padding: 3px 10px;
|
||||
text-decoration: none;
|
||||
@@ -1052,24 +1107,41 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
transition: background .15s, color .15s;
|
||||
font-weight: 500;
|
||||
}
|
||||
.pager-limit-btn:last-child { border-right: none; }
|
||||
.pager-limit-btn:hover { background: #e9ecef; text-decoration: none; color: #212529; }
|
||||
|
||||
.pager-limit-btn:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.pager-limit-btn:hover {
|
||||
background: #e9ecef;
|
||||
text-decoration: none;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.pager-limit-btn.active {
|
||||
background: #0d6efd;
|
||||
color: #fff;
|
||||
}
|
||||
.pager-limit-btn.active:hover { background: #0b5ed7; color: #fff; }
|
||||
|
||||
.pager-limit-btn.active:hover {
|
||||
background: #0b5ed7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.pager-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pager-info {
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pager-btn, .pager-num {
|
||||
|
||||
.pager-btn,
|
||||
.pager-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -1083,21 +1155,152 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
font-weight: 500;
|
||||
transition: background .15s, color .15s, border-color .15s;
|
||||
}
|
||||
.pager-btn:hover, .pager-num:hover { background: #e9ecef; text-decoration: none; color: #212529; }
|
||||
|
||||
.pager-btn:hover,
|
||||
.pager-num:hover {
|
||||
background: #e9ecef;
|
||||
text-decoration: none;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.pager-num.active {
|
||||
background: #0d6efd;
|
||||
color: #fff;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
|
||||
.pager-btn.disabled {
|
||||
pointer-events: none;
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
.pager-dots {
|
||||
padding: 0 2px;
|
||||
color: #adb5bd;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
FINAL GRID COLORS
|
||||
Schema/customfield fields = green
|
||||
Fixed/standard fields = white
|
||||
Required fields = red border only
|
||||
========================================================= */
|
||||
|
||||
/* Default: all fields white */
|
||||
.grid-container input,
|
||||
.grid-container select,
|
||||
.grid-container textarea,
|
||||
.grid-top input,
|
||||
.grid-top select,
|
||||
.grid-top textarea {
|
||||
background-color: #ffffff !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
/* Schema/customfield fields: green background */
|
||||
.grid-container .schema-field input,
|
||||
.grid-container .schema-field select,
|
||||
.grid-container .schema-field textarea,
|
||||
.grid-top .schema-field input,
|
||||
.grid-top .schema-field select,
|
||||
.grid-top .schema-field textarea {
|
||||
background-color: #dff3e3 !important;
|
||||
}
|
||||
|
||||
/* Fixed and standard fields: white background */
|
||||
.grid-container .fixed-field input,
|
||||
.grid-container .fixed-field select,
|
||||
.grid-container .fixed-field textarea,
|
||||
.grid-container .standard-field input,
|
||||
.grid-container .standard-field select,
|
||||
.grid-container .standard-field textarea,
|
||||
.grid-top .fixed-field input,
|
||||
.grid-top .fixed-field select,
|
||||
.grid-top .fixed-field textarea,
|
||||
.grid-top .standard-field input,
|
||||
.grid-top .standard-field select,
|
||||
.grid-top .standard-field textarea {
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Required fields: red border only */
|
||||
.grid-container .required-field input,
|
||||
.grid-container .required-field select,
|
||||
.grid-container .required-field textarea,
|
||||
.grid-top .required-field input,
|
||||
.grid-top .required-field select,
|
||||
.grid-top .required-field textarea {
|
||||
border: 2px solid #dc3545 !important;
|
||||
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.15) !important;
|
||||
}
|
||||
|
||||
/* Required schema/customfield fields: green + red border */
|
||||
.grid-container .schema-field.required-field input,
|
||||
.grid-container .schema-field.required-field select,
|
||||
.grid-container .schema-field.required-field textarea,
|
||||
.grid-top .schema-field.required-field input,
|
||||
.grid-top .schema-field.required-field select,
|
||||
.grid-top .schema-field.required-field textarea {
|
||||
background-color: #dff3e3 !important;
|
||||
border: 2px solid #dc3545 !important;
|
||||
}
|
||||
|
||||
/* Required fixed/standard fields: white + red border */
|
||||
.grid-container .fixed-field.required-field input,
|
||||
.grid-container .fixed-field.required-field select,
|
||||
.grid-container .fixed-field.required-field textarea,
|
||||
.grid-container .standard-field.required-field input,
|
||||
.grid-container .standard-field.required-field select,
|
||||
.grid-container .standard-field.required-field textarea,
|
||||
.grid-top .fixed-field.required-field input,
|
||||
.grid-top .fixed-field.required-field select,
|
||||
.grid-top .fixed-field.required-field textarea,
|
||||
.grid-top .standard-field.required-field input,
|
||||
.grid-top .standard-field.required-field select,
|
||||
.grid-top .standard-field.required-field textarea {
|
||||
background-color: #ffffff !important;
|
||||
border: 2px solid #dc3545 !important;
|
||||
}
|
||||
|
||||
/* Select2 - schema/customfield fields: green */
|
||||
.grid-container .schema-field .select2-container--default .select2-selection--single,
|
||||
.grid-top .schema-field .select2-container--default .select2-selection--single {
|
||||
background-color: #dff3e3 !important;
|
||||
}
|
||||
|
||||
/* Select2 - fixed/standard fields: white */
|
||||
.grid-container .fixed-field .select2-container--default .select2-selection--single,
|
||||
.grid-container .standard-field .select2-container--default .select2-selection--single,
|
||||
.grid-top .fixed-field .select2-container--default .select2-selection--single,
|
||||
.grid-top .standard-field .select2-container--default .select2-selection--single {
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Select2 - required fields: red border only */
|
||||
.grid-container .required-field .select2-container--default .select2-selection--single,
|
||||
.grid-top .required-field .select2-container--default .select2-selection--single {
|
||||
border: 2px solid #dc3545 !important;
|
||||
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.15) !important;
|
||||
}
|
||||
|
||||
/* Remove old red background from required classes */
|
||||
.grid-container input.required-input,
|
||||
.grid-container select.required-input,
|
||||
.grid-container textarea.required-input,
|
||||
.grid-top input.required-input,
|
||||
.grid-top select.required-input,
|
||||
.grid-top textarea.required-input {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
|
||||
/* Missing required cell: red outline only */
|
||||
.grid-cell.missing-required {
|
||||
background-color: inherit !important;
|
||||
border-right: 1px solid #dee2e6 !important;
|
||||
outline: 2px solid #dc3545 !important;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
</style>
|
||||
<title>Edit Imported Data - <?= htmlspecialchars($titlewebsite, ENT_QUOTES, 'UTF-8'); ?></title>
|
||||
</head>
|
||||
@@ -1112,20 +1315,20 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
<a href="imported.php?id=<?= $template_id ?>" class="btn btn-warning me-2">Imported (i)</a>
|
||||
<a href="tolims.php?id=<?= $template_id ?>" class="btn btn-success">To LIMS (l)</a>
|
||||
<?php if ($importref === ''): ?>
|
||||
<span class="ms-3">
|
||||
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
||||
<input type="checkbox" class="form-check-input" id="showAllUsers" <?= $show_all_users ? 'checked' : '' ?>
|
||||
onchange="window.location.href='imported.php?id=<?= $template_id ?>' + (this.checked ? '&all_users=1' : '')">
|
||||
Show all users
|
||||
</label>
|
||||
</span>
|
||||
<span class="text-muted" style="font-size: 12px;">
|
||||
(<?= $usePagination ? count($importedData) . " of {$totalRows}" : count($importedData) ?> records<?= !$show_all_users ? ' — my records only' : '' ?>)
|
||||
</span>
|
||||
<span class="ms-3">
|
||||
<label class="form-check-label" style="font-size: 13px; cursor: pointer;">
|
||||
<input type="checkbox" class="form-check-input" id="showAllUsers" <?= $show_all_users ? 'checked' : '' ?>
|
||||
onchange="window.location.href='imported.php?id=<?= $template_id ?>' + (this.checked ? '&all_users=1' : '')">
|
||||
Show all users
|
||||
</label>
|
||||
</span>
|
||||
<span class="text-muted" style="font-size: 12px;">
|
||||
(<?= $usePagination ? count($importedData) . " of {$totalRows}" : count($importedData) ?> records<?= !$show_all_users ? ' — my records only' : '' ?>)
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php if ($usePagination): ?>
|
||||
<?php
|
||||
<?php
|
||||
$baseQuery = $_GET;
|
||||
unset($baseQuery['limit'], $baseQuery['page']);
|
||||
$pageQuery = $_GET;
|
||||
@@ -1133,43 +1336,43 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
$pageBase = 'imported.php?' . http_build_query($pageQuery);
|
||||
$fromRow = ($page - 1) * $perPage + 1;
|
||||
$toRow = min($page * $perPage, $totalRows);
|
||||
?>
|
||||
<div class="pager-bar mb-2">
|
||||
<div class="pager-rows-per-page">
|
||||
<span class="pager-label">Rows per page</span>
|
||||
<div class="pager-limit-group">
|
||||
<?php foreach ($allowedLimits as $lim):
|
||||
$isActive = ($perPage === $lim);
|
||||
$url = 'imported.php?' . http_build_query(array_merge($baseQuery, ['limit' => $lim]));
|
||||
?>
|
||||
<a href="<?= htmlspecialchars($url) ?>" class="pager-limit-btn <?= $isActive ? 'active' : '' ?>"><?= $lim ?></a>
|
||||
<?php endforeach; ?>
|
||||
?>
|
||||
<div class="pager-bar mb-2">
|
||||
<div class="pager-rows-per-page">
|
||||
<span class="pager-label">Rows per page</span>
|
||||
<div class="pager-limit-group">
|
||||
<?php foreach ($allowedLimits as $lim):
|
||||
$isActive = ($perPage === $lim);
|
||||
$url = 'imported.php?' . http_build_query(array_merge($baseQuery, ['limit' => $lim]));
|
||||
?>
|
||||
<a href="<?= htmlspecialchars($url) ?>" class="pager-limit-btn <?= $isActive ? 'active' : '' ?>"><?= $lim ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<div class="pager-nav">
|
||||
<span class="pager-info"><?= $fromRow ?>–<?= $toRow ?> of <?= $totalRows ?></span>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="First"><i class="fas fa-angle-double-left"></i></a>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page - 1)) ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="Previous"><i class="fas fa-angle-left"></i></a>
|
||||
<?php
|
||||
$startPage = max(1, $page - 2);
|
||||
$endPage = min($totalPages, $page + 2);
|
||||
if ($startPage > 1): ?>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-num">1</a>
|
||||
<?php if ($startPage > 2): ?><span class="pager-dots">...</span><?php endif; ?>
|
||||
<?php endif;
|
||||
for ($p = $startPage; $p <= $endPage; $p++): ?>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $p) ?>" class="pager-num <?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
|
||||
<?php endfor;
|
||||
if ($endPage < $totalPages): ?>
|
||||
<?php if ($endPage < $totalPages - 1): ?><span class="pager-dots">...</span><?php endif; ?>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-num"><?= $totalPages ?></a>
|
||||
<?php if ($totalPages > 1): ?>
|
||||
<div class="pager-nav">
|
||||
<span class="pager-info"><?= $fromRow ?>–<?= $toRow ?> of <?= $totalRows ?></span>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="First"><i class="fas fa-angle-double-left"></i></a>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page - 1)) ?>" class="pager-btn <?= $page <= 1 ? 'disabled' : '' ?>" title="Previous"><i class="fas fa-angle-left"></i></a>
|
||||
<?php
|
||||
$startPage = max(1, $page - 2);
|
||||
$endPage = min($totalPages, $page + 2);
|
||||
if ($startPage > 1): ?>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=1') ?>" class="pager-num">1</a>
|
||||
<?php if ($startPage > 2): ?><span class="pager-dots">...</span><?php endif; ?>
|
||||
<?php endif;
|
||||
for ($p = $startPage; $p <= $endPage; $p++): ?>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $p) ?>" class="pager-num <?= $p === $page ? 'active' : '' ?>"><?= $p ?></a>
|
||||
<?php endfor;
|
||||
if ($endPage < $totalPages): ?>
|
||||
<?php if ($endPage < $totalPages - 1): ?><span class="pager-dots">...</span><?php endif; ?>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-num"><?= $totalPages ?></a>
|
||||
<?php endif; ?>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page + 1)) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Next"><i class="fas fa-angle-right"></i></a>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Last"><i class="fas fa-angle-double-right"></i></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . ($page + 1)) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Next"><i class="fas fa-angle-right"></i></a>
|
||||
<a href="<?= htmlspecialchars($pageBase . '&page=' . $totalPages) ?>" class="pager-btn <?= $page >= $totalPages ? 'disabled' : '' ?>" title="Last"><i class="fas fa-angle-double-right"></i></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="card radius-10">
|
||||
<div class="card-header">
|
||||
@@ -1179,9 +1382,9 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
<i class="fas fa-cogs"></i> Actions
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<?php if ((Auth::user()->hasRole('Admin'))) : ?>
|
||||
<li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li>
|
||||
<?php endif; ?>
|
||||
|
||||
<li><a class="dropdown-item export-all-lims-btn" href="#"><i class="fas fa-upload" style="color: #eb0b0b;"></i>Export All</a></li>
|
||||
|
||||
<li><a class="dropdown-item save-all-btn" href="#"><i class="fas fa-save" style="color: #28a745;"></i>Save All</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1201,13 +1404,18 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="editForm">
|
||||
<div class="grid-container">
|
||||
<div class="top-scrollbar" id="topScrollbar">
|
||||
<div class="top-scrollbar-inner" id="topScrollbarInner"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid-container" id="gridContainer">
|
||||
<div class="grid-top" id="gridTopContainer"></div>
|
||||
<div class="grid-row" id="gridHeaderContainer" style="font-weight:600;"></div>
|
||||
<div id="gridRowContainer"></div>
|
||||
</div>
|
||||
</form>
|
||||
<div id="partsModalContainer"></div>
|
||||
<div id="analysisModalContainer"></div>
|
||||
<div id="annotationsModalContainer"></div>
|
||||
<?php include 'photos_functions.php'; ?>
|
||||
</div>
|
||||
@@ -1218,7 +1426,12 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
|
||||
<?php include('include/footer.php'); ?>
|
||||
</div>
|
||||
<style>.btn i { margin-top: 0 !important; margin-bottom: 0 !important; }</style>
|
||||
<style>
|
||||
.btn i {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
<?php include('jsinclude.php'); ?>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
|
||||
@@ -1231,6 +1444,75 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
<script src="photos.js"></script>
|
||||
<script src="annotationsModal.js"></script>
|
||||
<script src="partsTable.js"></script>
|
||||
<script src="analysisModal.js"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const topScrollbar = document.getElementById('topScrollbar');
|
||||
const topScrollbarInner = document.getElementById('topScrollbarInner');
|
||||
const gridContainer = document.getElementById('gridContainer');
|
||||
const gridRowContainer = document.getElementById('gridRowContainer');
|
||||
const gridHeaderContainer = document.getElementById('gridHeaderContainer');
|
||||
const gridTopContainer = document.getElementById('gridTopContainer');
|
||||
|
||||
if (!topScrollbar || !topScrollbarInner || !gridContainer) return;
|
||||
|
||||
let syncingFromTop = false;
|
||||
let syncingFromGrid = false;
|
||||
|
||||
function updateTopScrollbarWidth() {
|
||||
const realWidth = Math.max(
|
||||
gridContainer.scrollWidth,
|
||||
gridHeaderContainer ? gridHeaderContainer.scrollWidth : 0,
|
||||
gridTopContainer ? gridTopContainer.scrollWidth : 0,
|
||||
gridRowContainer ? gridRowContainer.scrollWidth : 0
|
||||
);
|
||||
|
||||
topScrollbarInner.style.width = realWidth + 'px';
|
||||
|
||||
if (realWidth > gridContainer.clientWidth) {
|
||||
topScrollbar.style.display = 'block';
|
||||
} else {
|
||||
topScrollbar.style.display = 'none';
|
||||
}
|
||||
|
||||
topScrollbar.scrollLeft = gridContainer.scrollLeft;
|
||||
}
|
||||
|
||||
topScrollbar.addEventListener('scroll', function() {
|
||||
if (syncingFromGrid) return;
|
||||
syncingFromTop = true;
|
||||
gridContainer.scrollLeft = topScrollbar.scrollLeft;
|
||||
syncingFromTop = false;
|
||||
});
|
||||
|
||||
gridContainer.addEventListener('scroll', function() {
|
||||
if (syncingFromTop) return;
|
||||
syncingFromGrid = true;
|
||||
topScrollbar.scrollLeft = gridContainer.scrollLeft;
|
||||
syncingFromGrid = false;
|
||||
});
|
||||
|
||||
window.addEventListener('resize', updateTopScrollbarWidth);
|
||||
|
||||
// Recalculate after JS grid rendering
|
||||
setTimeout(updateTopScrollbarWidth, 100);
|
||||
setTimeout(updateTopScrollbarWidth, 300);
|
||||
setTimeout(updateTopScrollbarWidth, 700);
|
||||
setTimeout(updateTopScrollbarWidth, 1500);
|
||||
|
||||
// Recalculate automatically when rows/header/top controls are rendered or changed
|
||||
const observer = new MutationObserver(updateTopScrollbarWidth);
|
||||
|
||||
if (gridContainer) {
|
||||
observer.observe(gridContainer, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="modal fade" id="exportConfirmModal" tabindex="-1" aria-labelledby="exportConfirmModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
@@ -1394,4 +1676,4 @@ window.gridMeta = <?= json_encode($gridMeta, JSON_UNESCAPED_UNICODE | JSON_UNESC
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -10,6 +10,9 @@ error_reporting(E_ALL | E_STRICT);
|
||||
include('../../extra/auth.php');
|
||||
//require_once __DIR__ . '/extra/auth.php';
|
||||
|
||||
// Laravel bootstrap (loaded by auth.php) forces UTC via config/app.php — re-apply our TZ
|
||||
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||
|
||||
// Here we just check if user is not
|
||||
// logged in, and in that case we redirect
|
||||
// the user to vanguard login page.
|
||||
|
||||
@@ -3,9 +3,20 @@
|
||||
// Retrieve all routines from database
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$stmt = $pdo->prepare("SELECT * FROM routine");
|
||||
$stmt->execute();
|
||||
$routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// Retrieve active API/JSON configurations
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, name, provider_code, api_type, php_class_name
|
||||
FROM api_configurations
|
||||
WHERE is_active = 1
|
||||
ORDER BY name ASC
|
||||
");
|
||||
$stmt->execute();
|
||||
$apiConfigurations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
@@ -40,7 +51,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<li>Template Name</li>
|
||||
<li>Source Type</li>
|
||||
<li>Schema and Client</li>
|
||||
<li>Row Header and Column Header only for XLS templates</li>
|
||||
<li>Row Header, Column Header and Sheet Number only for XLS templates</li>
|
||||
<li>API / JSON Configuration only for API / JSON templates</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,7 +79,8 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<label class="form-label">Source Type *</label>
|
||||
<select name="source_type" id="sourceType" class="form-control" required>
|
||||
<option value="XLS" selected>XLS</option>
|
||||
<option value="API">API</option>
|
||||
<option value="API">API / JSON</option>
|
||||
<option value="PDF">PDF</option>
|
||||
</select>
|
||||
<small class="text-muted">Choose the source used by this template</small>
|
||||
</div>
|
||||
@@ -82,6 +95,58 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
<input type="text" name="start_column" id="startColumn" class="form-control" value="A" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="xlsSheetNumberWrapper">
|
||||
<label class="form-label">XLS Sheet Number</label>
|
||||
<input
|
||||
type="number"
|
||||
name="xls_sheet_index"
|
||||
id="xlsSheetIndex"
|
||||
class="form-control"
|
||||
min="0"
|
||||
value="0">
|
||||
<small class="text-muted">
|
||||
Use 0 for the first sheet, 1 for the second sheet, 2 for the third sheet, and so on.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="apiConfigWrapper" style="display: none;">
|
||||
<label class="form-label">API / JSON Configuration *</label>
|
||||
<select name="api_config_id" id="apiConfigSelect" class="form-control">
|
||||
<option value="">Select an API configuration...</option>
|
||||
|
||||
<?php foreach ($apiConfigurations as $apiConfig): ?>
|
||||
<?php
|
||||
$apiLabelParts = [];
|
||||
|
||||
if (!empty($apiConfig['name'])) {
|
||||
$apiLabelParts[] = $apiConfig['name'];
|
||||
}
|
||||
|
||||
if (!empty($apiConfig['provider_code'])) {
|
||||
$apiLabelParts[] = '[' . $apiConfig['provider_code'] . ']';
|
||||
}
|
||||
|
||||
if (!empty($apiConfig['api_type'])) {
|
||||
$apiLabelParts[] = '(' . $apiConfig['api_type'] . ')';
|
||||
}
|
||||
|
||||
if (!empty($apiConfig['php_class_name'])) {
|
||||
$apiLabelParts[] = '- ' . $apiConfig['php_class_name'];
|
||||
}
|
||||
|
||||
$apiLabel = implode(' ', $apiLabelParts);
|
||||
?>
|
||||
|
||||
<option value="<?php echo (int)$apiConfig['id']; ?>">
|
||||
<?php echo htmlspecialchars($apiLabel, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<small class="text-muted">
|
||||
Select the API/JSON configuration linked to this template.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><?= htmlspecialchars($desctemplate, ENT_QUOTES, 'UTF-8'); ?></label>
|
||||
<textarea name="description" class="form-control"></textarea>
|
||||
@@ -185,10 +250,16 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
const routineAction3 = document.getElementById("routineAction3");
|
||||
|
||||
const sourceType = document.getElementById("sourceType");
|
||||
|
||||
const headerRowWrapper = document.getElementById("headerRowWrapper");
|
||||
const startColumnWrapper = document.getElementById("startColumnWrapper");
|
||||
const xlsSheetNumberWrapper = document.getElementById("xlsSheetNumberWrapper");
|
||||
const apiConfigWrapper = document.getElementById("apiConfigWrapper");
|
||||
|
||||
const headerRow = document.getElementById("headerRow");
|
||||
const startColumn = document.getElementById("startColumn");
|
||||
const xlsSheetIndex = document.getElementById("xlsSheetIndex");
|
||||
const apiConfigSelect = document.getElementById("apiConfigSelect");
|
||||
|
||||
if (!form || !clientLoadingStatus || !schemaLoadingStatus || !routineSelect || !routineDetails) {
|
||||
alert("Errore: Uno o più elementi della pagina non sono stati trovati. Contatta l'amministratore.");
|
||||
@@ -210,27 +281,57 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
allowClear: true
|
||||
});
|
||||
|
||||
$('#apiConfigSelect').select2({
|
||||
placeholder: "Select an API configuration...",
|
||||
allowClear: true
|
||||
});
|
||||
|
||||
function updateSourceFields() {
|
||||
const selectedSource = sourceType.value;
|
||||
|
||||
if (selectedSource === 'API') {
|
||||
headerRowWrapper.style.opacity = '0.6';
|
||||
startColumnWrapper.style.opacity = '0.6';
|
||||
const isXls = selectedSource === 'XLS';
|
||||
const isApiJson = selectedSource === 'API';
|
||||
|
||||
headerRow.required = false;
|
||||
startColumn.required = false;
|
||||
|
||||
headerRow.disabled = true;
|
||||
startColumn.disabled = true;
|
||||
} else {
|
||||
headerRowWrapper.style.opacity = '1';
|
||||
startColumnWrapper.style.opacity = '1';
|
||||
if (isXls) {
|
||||
headerRowWrapper.style.display = 'block';
|
||||
startColumnWrapper.style.display = 'block';
|
||||
xlsSheetNumberWrapper.style.display = 'block';
|
||||
|
||||
headerRow.required = true;
|
||||
startColumn.required = true;
|
||||
xlsSheetIndex.required = true;
|
||||
|
||||
headerRow.disabled = false;
|
||||
startColumn.disabled = false;
|
||||
xlsSheetIndex.disabled = false;
|
||||
|
||||
apiConfigWrapper.style.display = 'none';
|
||||
apiConfigSelect.required = false;
|
||||
apiConfigSelect.disabled = true;
|
||||
$('#apiConfigSelect').val(null).trigger('change');
|
||||
} else {
|
||||
headerRowWrapper.style.display = 'none';
|
||||
startColumnWrapper.style.display = 'none';
|
||||
xlsSheetNumberWrapper.style.display = 'none';
|
||||
|
||||
headerRow.required = false;
|
||||
startColumn.required = false;
|
||||
xlsSheetIndex.required = false;
|
||||
|
||||
headerRow.disabled = true;
|
||||
startColumn.disabled = true;
|
||||
xlsSheetIndex.disabled = true;
|
||||
|
||||
if (isApiJson) {
|
||||
apiConfigWrapper.style.display = 'block';
|
||||
apiConfigSelect.required = true;
|
||||
apiConfigSelect.disabled = false;
|
||||
} else {
|
||||
apiConfigWrapper.style.display = 'none';
|
||||
apiConfigSelect.required = false;
|
||||
apiConfigSelect.disabled = true;
|
||||
$('#apiConfigSelect').val(null).trigger('change');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +362,12 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
data.value.forEach(client => {
|
||||
const nome = client.Nominativo || "Nome non disponibile";
|
||||
const id = client.IdCliente || "ID non disponibile";
|
||||
const option = new Option(`${nome.trim()} (ID: ${id})`, id);
|
||||
|
||||
const codiceCliente = (client.CodiceCliente ?? client.codiceCliente ?? "").toString().trim();
|
||||
const suffix = (codiceCliente.split("_")[1] || "").trim();
|
||||
const shortCode = suffix || (codiceCliente ? codiceCliente.charAt(0) : "--");
|
||||
|
||||
const option = new Option(`${nome.trim()} - ${shortCode} (ID: ${id})`, id);
|
||||
select.add(option);
|
||||
});
|
||||
|
||||
@@ -388,6 +494,28 @@ $routines = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
let formData = new FormData(this);
|
||||
|
||||
const selectedSource = sourceType.value;
|
||||
|
||||
if (selectedSource === 'XLS' && xlsSheetIndex.value === '') {
|
||||
Swal.fire({
|
||||
title: "Errore!",
|
||||
text: "Inserisci il numero del foglio XLS.",
|
||||
icon: "error",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedSource === 'API' && !apiConfigSelect.value) {
|
||||
Swal.fire({
|
||||
title: "Errore!",
|
||||
text: "Seleziona una configurazione API / JSON.",
|
||||
icon: "error",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const clientSelect = document.getElementById("clientSelect");
|
||||
const clientId = clientSelect.value;
|
||||
const selectedClientOption = clientSelect.options[clientSelect.selectedIndex];
|
||||
|
||||
@@ -13,7 +13,10 @@ try {
|
||||
}
|
||||
|
||||
// Recupera solo i template attivi
|
||||
$stmt = $pdo->query("SELECT id, button_label, button_bg_color, button_text_color, button_size FROM excel_templates WHERE status = 'active'");
|
||||
$stmt = $pdo->query("SELECT id, button_label, button_size, button_bg_color, button_text_color, source_type
|
||||
FROM excel_templates
|
||||
WHERE status = 'active'
|
||||
ORDER BY button_label ASC");
|
||||
$templates = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$response["success"] = true;
|
||||
|
||||
@@ -33,9 +33,9 @@ try {
|
||||
if ($extraFieldId) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
p.id, p.iddatadb, p.part_number, p.part_description, p.idmatrice, p.note, p.dateexpiry,
|
||||
cf.value_id AS extra_value_id,
|
||||
cf.value_text AS extra_value_text
|
||||
p.id, p.iddatadb, p.part_number, p.part_description, p.mix, p.idmatrice, p.note, p.dateexpiry,
|
||||
cf.value_id AS extra_value_id,
|
||||
cf.value_text AS extra_value_text
|
||||
FROM identification_parts p
|
||||
LEFT JOIN identification_parts_customfields cf
|
||||
ON cf.part_id = p.id AND cf.field_id = :extraFieldId
|
||||
@@ -48,8 +48,8 @@ try {
|
||||
]);
|
||||
} else {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT id, iddatadb, part_number, part_description, idmatrice, note, dateexpiry,
|
||||
NULL AS extra_value_id, NULL AS extra_value_text
|
||||
SELECT id, iddatadb, part_number, part_description, mix, idmatrice, note, dateexpiry,
|
||||
NULL AS extra_value_id, NULL AS extra_value_text
|
||||
FROM identification_parts
|
||||
WHERE iddatadb = :iddatadb
|
||||
ORDER BY part_number ASC
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,16 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div style="display: flex; align-items: center; gap: 6px; margin-right: auto;">
|
||||
<label for="descriptionColorPickerAnnotations" style="font-size: 0.8rem; margin: 0; white-space: nowrap;">
|
||||
Colore annotazioni:
|
||||
</label>
|
||||
<input
|
||||
type="color"
|
||||
id="descriptionColorPickerAnnotations"
|
||||
value="#000000"
|
||||
style="width: 34px; height: 28px; padding: 1px; border: 1px solid #ccc; border-radius: 4px; cursor: pointer;">
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-sm" id="addDescriptionsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Aggiungi Lista Descrizioni</button>
|
||||
<button type="button" class="btn btn-danger btn-sm" id="removeAnnotationsBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Rimuovi Descrizioni</button>
|
||||
<button type="button" class="btn btn-warning btn-sm" id="undoMarkerBtnAnnotations" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">Undo Marker</button>
|
||||
|
||||
@@ -2,7 +2,24 @@
|
||||
<div class="modal-dialog modal-xl" style="max-width: 95vw !important;">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="partsModalLabel">Parti per TRF: <span id="trfHeader"></span></h5>
|
||||
<h5 class="modal-title" id="partsModalLabel">
|
||||
Parti per TRF:
|
||||
<span id="trfHeader"></span>
|
||||
</h5>
|
||||
|
||||
<div class="ms-auto me-3 d-flex align-items-center gap-2">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="prevPartsRecordBtn" title="Record precedente">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</button>
|
||||
|
||||
<span id="partsRecordCounter" class="text-muted" style="font-size: 12px; min-width: 70px; text-align: center;">
|
||||
-
|
||||
</span>
|
||||
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="nextPartsRecordBtn" title="Record successivo">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@@ -20,16 +37,21 @@
|
||||
</button>
|
||||
|
||||
|
||||
<input type="checkbox" id="showMixParts" name="showMixParts" style="margin-right: 5px; margin-left: 10px;">
|
||||
<label for="showMixParts" style="font-size: 0.9rem; margin-right: 10px;">Mix</label>
|
||||
|
||||
|
||||
<button type="button" class="btn btn-info btn-sm" id="renumberPartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||
Rinumera Parti
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-primary btn-sm" id="clonePartsBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||
<i class="fas fa-clone"></i> Clona Parti
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm ms-2" id="toggleVoiceBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||
<i class="fas fa-microphone"></i> Voce
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm ms-2" id="showHideImageBtn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;">
|
||||
<i class="fas fa-eye-slash" style="font-size: 0.8rem;"></i>
|
||||
<i class="fas fa-image ms-1" style="font-size: 0.8rem;"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Seconda riga: +, M, MacroMatrice, Matrice globale, Propaga -->
|
||||
@@ -42,45 +64,54 @@
|
||||
<select id="global-matrice" class="form-control form-control-sm" style="width: 350px !important; margin-right: 10px;"></select>
|
||||
<button type="button" class="btn btn-primary btn-sm propagate-all-btn" style="padding: 0.1rem 0.5rem; font-size: 0.8rem;"><i class="fas fa-arrow-right"></i> Propaga a tutte</button>
|
||||
</div>
|
||||
<table class="table table-striped table-sm" id="partsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 55px;">Num</th>
|
||||
<th>Descrizione</th>
|
||||
<th style="width: 200px;">Matrice</th>
|
||||
<th style="width: 150px;">
|
||||
<input type="date" class="form-control form-control-sm propagate-date-input" style="width: 130px; margin-left: 5px; display: inline-block;" title="Propaga data a tutte le parti">
|
||||
</th>
|
||||
<th style="width: 200px;">
|
||||
<button type="button" class="btn btn-light btn-sm propagate-note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem; margin-left: 5px;" title="Propaga nota a tutte le parti">
|
||||
<i class="fas fa-sticky-note"></i>
|
||||
</button>
|
||||
Azioni
|
||||
<div class="parts-table-scroll">
|
||||
<table class="table table-striped table-sm" id="partsTable">
|
||||
<colgroup id="partsTableColgroup">
|
||||
<col class="parts-col-num">
|
||||
<col class="parts-col-description">
|
||||
<col class="parts-col-matrice">
|
||||
<col class="parts-col-date">
|
||||
<col class="parts-col-actions">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 55px;">Num</th>
|
||||
<th>Descrizione</th>
|
||||
<th style="width: 200px;">Matrice</th>
|
||||
<th style="width: 150px;">
|
||||
<input type="date" class="form-control form-control-sm propagate-date-input" style="width: 130px; margin-left: 5px; display: inline-block;" title="Propaga data a tutte le parti">
|
||||
</th>
|
||||
<th style="width: 200px;">
|
||||
<button type="button" class="btn btn-light btn-sm propagate-note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem; margin-left: 5px;" title="Propaga nota a tutte le parti">
|
||||
<i class="fas fa-sticky-note"></i>
|
||||
</button>
|
||||
Azioni
|
||||
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="partsTableBody">
|
||||
<tr data-part-id="new">
|
||||
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 55px;"></td>
|
||||
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione"></td>
|
||||
<td>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right"></i></button>
|
||||
<select class="part-matrice form-control form-control-sm" style="width: 150px;"></select>
|
||||
</div>
|
||||
</td>
|
||||
<td><input type="date" class="form-control form-control-sm part-dateexpiry" style="width: 130px;"></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem;" title="Aggiungi/Modifica nota"><i class="fas fa-sticky-note"></i></button>
|
||||
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
|
||||
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash"></i></button>
|
||||
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check"></i></span>
|
||||
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin"></i></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="partsTableBody">
|
||||
<tr data-part-id="new">
|
||||
<td><input type="number" class="form-control form-control-sm part-number" value="1" style="width: 55px;"></td>
|
||||
<td><input type="text" class="form-control form-control-sm part-description" placeholder="Inserisci descrizione"></td>
|
||||
<td>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<button type="button" class="btn btn-primary btn-sm propagate-matrice-btn" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; margin-right: 3px;"><i class="fas fa-arrow-right"></i></button>
|
||||
<select class="part-matrice form-control form-control-sm" style="width: 150px;"></select>
|
||||
</div>
|
||||
</td>
|
||||
<td><input type="date" class="form-control form-control-sm part-dateexpiry" style="width: 130px;"></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-light btn-sm note-btn" style="padding: 0.2rem 0.4rem; font-size: 0.9rem;" title="Aggiungi/Modifica nota"><i class="fas fa-sticky-note"></i></button>
|
||||
<button type="button" class="btn btn-warning btn-sm add-mix-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem;">M+</button>
|
||||
<button type="button" class="btn btn-danger btn-sm remove-row" style="padding: 0.1rem 0.3rem; font-size: 0.8rem; display: none;"><i class="fas fa-trash"></i></button>
|
||||
<span class="save-status text-success" style="display: none; margin-left: 5px;"><i class="fas fa-check"></i></span>
|
||||
<span class="save-loading text-warning" style="display: none; margin-left: 5px;"><i class="fas fa-spinner fa-spin"></i></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h6>Foto del Campione</h6>
|
||||
@@ -288,28 +319,20 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Select delle righe (colonna Matrice) = 150px */
|
||||
/* Select delle righe Matrice: si adatta alla colonna */
|
||||
.part-matrice {
|
||||
width: 300px !important;
|
||||
min-width: 300px !important;
|
||||
max-width: 300px !important;
|
||||
flex: 0 0 300px !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.part-matrice.select2-hidden-accessible+.select2 {
|
||||
width: 300px !important;
|
||||
min-width: 300px !important;
|
||||
max-width: 300px !important;
|
||||
flex: 0 0 300px !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Colonna Descrizione (2ª colonna) = 420px */
|
||||
#partsTable th:nth-child(2),
|
||||
#partsTable td:nth-child(2) {
|
||||
width: 300px !important;
|
||||
min-width: 300px !important;
|
||||
max-width: 300px !important;
|
||||
}
|
||||
|
||||
|
||||
#partsTable .part-number {
|
||||
text-align: center;
|
||||
@@ -340,7 +363,11 @@
|
||||
border: 1px solid #aaa !important;
|
||||
border-radius: 4px !important;
|
||||
background: #fff !important;
|
||||
max-height: 200px !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.select2-container--open .select2-results__options {
|
||||
max-height: 220px !important;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
@@ -514,6 +541,202 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Resizable parts table - stable */
|
||||
.parts-table-scroll {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: visible;
|
||||
contain: inline-size;
|
||||
}
|
||||
|
||||
#partsTable {
|
||||
table-layout: fixed !important;
|
||||
width: max-content !important;
|
||||
min-width: unset !important;
|
||||
}
|
||||
|
||||
#partsTable col.parts-col-num {
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
#partsTable col.parts-col-description {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
#partsTable col.parts-col-matrice {
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
#partsTable col.parts-col-date {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#partsTable col.parts-col-actions {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
#partsTable th,
|
||||
#partsTable td {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#partsTable th {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#partsTable th .parts-resizer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 9px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
z-index: 50;
|
||||
background: rgba(108, 117, 125, 0.12);
|
||||
border-right: 1px solid rgba(108, 117, 125, 0.45);
|
||||
}
|
||||
|
||||
#partsTable th .parts-resizer:hover {
|
||||
background: rgba(108, 117, 125, 0.25);
|
||||
border-right: 2px solid rgba(73, 80, 87, 0.7);
|
||||
}
|
||||
|
||||
#partsTable td {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#partsTable td input,
|
||||
#partsTable td select,
|
||||
#partsTable td .select2-container {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
/* Propagation control for dynamic extra field column */
|
||||
#partsTable th.extra-field-th {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#partsTable th.extra-field-th .extra-propagate-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#partsTable th.extra-field-th .extra-propagate-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 95px;
|
||||
}
|
||||
|
||||
#partsTable th.extra-field-th .extra-propagate-input,
|
||||
#partsTable th.extra-field-th .extra-propagate-select {
|
||||
height: 24px !important;
|
||||
font-size: 0.75rem !important;
|
||||
padding: 0.1rem 0.25rem !important;
|
||||
min-width: 90px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
#partsTable th.extra-field-th .extra-propagate-btn {
|
||||
height: 24px;
|
||||
min-width: 26px;
|
||||
padding: 0.1rem 0.3rem !important;
|
||||
font-size: 0.75rem !important;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
#partsTable th.extra-field-th .select2-container {
|
||||
flex: 1 1 auto;
|
||||
min-width: 90px;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
#partsTable th.extra-field-th .select2-selection--single {
|
||||
height: 24px !important;
|
||||
}
|
||||
|
||||
#partsTable th.extra-field-th .select2-selection__rendered {
|
||||
line-height: 22px !important;
|
||||
font-size: 0.75rem !important;
|
||||
padding-left: 4px !important;
|
||||
}
|
||||
|
||||
#partsTable th.extra-field-th .select2-selection__arrow {
|
||||
height: 22px !important;
|
||||
}
|
||||
|
||||
/* Extra field propagation header - full column width */
|
||||
#partsTable th.extra-field-th {
|
||||
width: auto !important;
|
||||
min-width: 220px !important;
|
||||
max-width: none !important;
|
||||
padding: 0.25rem !important;
|
||||
}
|
||||
|
||||
#partsTable .extra-propagate-wrapper {
|
||||
width: 100% !important;
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#partsTable .extra-propagate-label {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
display: block;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.15;
|
||||
color: #222;
|
||||
white-space: nowrap !important;
|
||||
overflow: visible !important;
|
||||
text-overflow: unset !important;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#partsTable .extra-propagate-control {
|
||||
width: 100% !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#partsTable .extra-propagate-control .extra-propagate-input,
|
||||
#partsTable .extra-propagate-control .extra-propagate-select {
|
||||
flex: 1 1 auto !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
#partsTable .extra-propagate-control .select2-container {
|
||||
flex: 1 1 auto !important;
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
#partsTable .extra-propagate-control .select2-selection {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#partsTable .extra-propagate-btn {
|
||||
flex: 0 0 30px !important;
|
||||
width: 30px !important;
|
||||
height: 24px !important;
|
||||
min-width: 30px !important;
|
||||
padding: 0.1rem 0.25rem !important;
|
||||
}
|
||||
|
||||
/* rosso */
|
||||
</style>
|
||||
@@ -2,93 +2,111 @@
|
||||
* modals_gridData.js — Photos, Parts, Tested Component handlers for gridData pages
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
// ── Photos — use photos.js loadPopupContent (exported to window) ──
|
||||
$(document).on('click', '.photos-btn', function () {
|
||||
const iddatadb = $(this).data('iddatadb') || null;
|
||||
const idquotations = $(this).data('idquotations') || null;
|
||||
const modal = document.getElementById('photosModal');
|
||||
$(document).on("click", ".photos-btn", function () {
|
||||
const iddatadb = $(this).data("iddatadb") || null;
|
||||
const idquotations = $(this).data("idquotations") || null;
|
||||
const modal = document.getElementById("photosModal");
|
||||
if (!modal) return;
|
||||
|
||||
modal.style.display = 'block';
|
||||
modal.style.display = "block";
|
||||
|
||||
if (typeof window.loadPopupContent === 'function') {
|
||||
if (typeof window.loadPopupContent === "function") {
|
||||
window.loadPopupContent(iddatadb, idquotations);
|
||||
}
|
||||
});
|
||||
|
||||
// Close photos modal
|
||||
$(document).on('click', '.close-btn', function () {
|
||||
const modal = document.getElementById('photosModal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
$(document).on("click", ".close-btn", function () {
|
||||
const modal = document.getElementById("photosModal");
|
||||
if (modal) modal.style.display = "none";
|
||||
});
|
||||
|
||||
// Close on backdrop click
|
||||
$(document).on('click', '#photosModal', function (e) {
|
||||
$(document).on("click", "#photosModal", function (e) {
|
||||
if (e.target === this) {
|
||||
this.style.display = 'none';
|
||||
this.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
// ── Parts (matching import_edit2.php behavior) ─────────────────────
|
||||
$(document).on('click', '.parts-btn', function () {
|
||||
const iddatadb = $(this).data('iddatadb') || null;
|
||||
const idquotations = $(this).data('idquotations') || null;
|
||||
$(document).on("click", ".parts-btn", function () {
|
||||
const iddatadb = $(this).data("iddatadb") || null;
|
||||
const idquotations = $(this).data("idquotations") || null;
|
||||
|
||||
$.ajax({
|
||||
url: 'modal_partsTable.php',
|
||||
method: 'GET',
|
||||
url: "modal_partsTable.php",
|
||||
method: "GET",
|
||||
data: { iddatadb: iddatadb },
|
||||
success: function (response) {
|
||||
$('#partsModalContainer').html(response);
|
||||
const modalElement = document.getElementById('partsModal');
|
||||
$("#partsModalContainer").html(response);
|
||||
const modalElement = document.getElementById("partsModal");
|
||||
if (!modalElement) return;
|
||||
|
||||
$("#trfHeader").text(iddatadb || idquotations || '');
|
||||
$("#partsModal").data("iddatadb", iddatadb).data("idquotations", idquotations);
|
||||
$("#trfHeader").text(iddatadb || idquotations || "");
|
||||
|
||||
const visibleIddatadbList = Array.isArray(window.gridData)
|
||||
? window.gridData
|
||||
.map((r) => parseInt(r.iddatadb, 10))
|
||||
.filter((v) => !isNaN(v) && v > 0)
|
||||
: [];
|
||||
|
||||
$("#partsModal")
|
||||
.data("iddatadb", iddatadb)
|
||||
.data("idquotations", idquotations)
|
||||
.data("visible-iddatadb-list", visibleIddatadbList);
|
||||
|
||||
let modal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (!modal) modal = new bootstrap.Modal(modalElement, { backdrop: true, keyboard: true, focus: true });
|
||||
if (!modal)
|
||||
modal = new bootstrap.Modal(modalElement, {
|
||||
backdrop: true,
|
||||
keyboard: true,
|
||||
focus: true,
|
||||
});
|
||||
modal.show();
|
||||
|
||||
if (typeof window.loadParts === 'function') {
|
||||
if (typeof window.loadParts === "function") {
|
||||
window.loadParts(iddatadb, idquotations);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('Error loading parts:', error);
|
||||
}
|
||||
console.error("Error loading parts:", error);
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('hidden.bs.modal', '#partsModal', function () {
|
||||
const modalElement = document.getElementById('partsModal');
|
||||
$(document).on("hidden.bs.modal", "#partsModal", function () {
|
||||
const modalElement = document.getElementById("partsModal");
|
||||
if (modalElement) {
|
||||
const modal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (modal) modal.dispose();
|
||||
}
|
||||
$('#partsModalContainer').empty();
|
||||
$('.modal-backdrop').remove();
|
||||
$('body').removeClass('modal-open').css('padding-right', '');
|
||||
$("#partsModalContainer").empty();
|
||||
$(".modal-backdrop").remove();
|
||||
$("body").removeClass("modal-open").css("padding-right", "");
|
||||
});
|
||||
|
||||
// ── Tested Component quick add ───────────────────────────────────────
|
||||
$(document).on('click', '.add-part-btn', async function () {
|
||||
const iddatadb = $(this).data('iddatadb') || null;
|
||||
const rowIndex = parseInt($(this).data('row'));
|
||||
$(document).on("click", ".add-part-btn", async function () {
|
||||
const iddatadb = $(this).data("iddatadb") || null;
|
||||
const rowIndex = parseInt($(this).data("row"));
|
||||
const row = window.gridData?.[rowIndex];
|
||||
const id = iddatadb || (row ? row.iddatadb : null);
|
||||
if (!id) return;
|
||||
|
||||
const $cell = $(this).closest('.grid-cell, div');
|
||||
const $input = $cell.find('input');
|
||||
const raw = ($input.val() || '').trim();
|
||||
const parts = raw.split('|').map(s => s.trim()).filter(s => s.length > 0);
|
||||
const $cell = $(this).closest(".grid-cell, div");
|
||||
const $input = $cell.find("input");
|
||||
const raw = ($input.val() || "").trim();
|
||||
const parts = raw
|
||||
.split("|")
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s.length > 0);
|
||||
const uniqueParts = [...new Set(parts)];
|
||||
|
||||
if (!uniqueParts.length) {
|
||||
alert('Insert a description first.');
|
||||
alert("Insert a description first.");
|
||||
$input.focus();
|
||||
return;
|
||||
}
|
||||
@@ -96,14 +114,23 @@
|
||||
try {
|
||||
for (const p of uniqueParts) {
|
||||
const formData = new FormData();
|
||||
formData.append('iddatadb', id);
|
||||
formData.append('part_description', p);
|
||||
await fetch('add_part_quick.php', { method: 'POST', body: formData });
|
||||
formData.append("iddatadb", id);
|
||||
formData.append("part_description", p);
|
||||
await fetch("add_part_quick.php", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
}
|
||||
|
||||
if (row) {
|
||||
row.tested_component = raw;
|
||||
row._dirty = true;
|
||||
}
|
||||
|
||||
alert(`Added ${uniqueParts.length} part(s).`);
|
||||
$input.val('');
|
||||
$input.val(raw);
|
||||
} catch (e) {
|
||||
alert('Error: ' + e.message);
|
||||
alert("Error: " + e.message);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -111,44 +138,47 @@
|
||||
let deleteIddatadb = null;
|
||||
let deleteRowIndex = null;
|
||||
|
||||
$(document).on('click', '.delete-btn', function () {
|
||||
deleteIddatadb = $(this).data('iddatadb');
|
||||
deleteRowIndex = parseInt($(this).data('row'));
|
||||
const modalEl = document.getElementById('deleteConfirmModal');
|
||||
$(document).on("click", ".delete-btn", function () {
|
||||
deleteIddatadb = $(this).data("iddatadb");
|
||||
deleteRowIndex = parseInt($(this).data("row"));
|
||||
const modalEl = document.getElementById("deleteConfirmModal");
|
||||
if (modalEl) {
|
||||
document.getElementById('deleteIddatadbText').textContent = deleteIddatadb;
|
||||
document.getElementById("deleteIddatadbText").textContent =
|
||||
deleteIddatadb;
|
||||
new bootstrap.Modal(modalEl).show();
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '#deleteConfirmBtn', async function () {
|
||||
const modalEl = document.getElementById('deleteConfirmModal');
|
||||
$(document).on("click", "#deleteConfirmBtn", async function () {
|
||||
const modalEl = document.getElementById("deleteConfirmModal");
|
||||
const modal = bootstrap.Modal.getInstance(modalEl);
|
||||
if (modal) modal.hide();
|
||||
|
||||
if (!deleteIddatadb) return;
|
||||
|
||||
try {
|
||||
const resp = await fetch('delete_record.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: deleteIddatadb })
|
||||
const resp = await fetch("delete_record.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ id: deleteIddatadb }),
|
||||
});
|
||||
const result = await resp.json();
|
||||
|
||||
if (result.success) {
|
||||
// Remove from gridData
|
||||
const idx = window.gridData.findIndex(r => r.iddatadb === deleteIddatadb);
|
||||
const idx = window.gridData.findIndex(
|
||||
(r) => r.iddatadb === deleteIddatadb,
|
||||
);
|
||||
if (idx >= 0) window.gridData.splice(idx, 1);
|
||||
|
||||
// Re-render
|
||||
const gr = window.gridRenderer;
|
||||
if (gr) gr.renderVisibleRows();
|
||||
} else {
|
||||
alert('Error: ' + result.message);
|
||||
alert("Error: " + result.message);
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error: ' + e.message);
|
||||
alert("Error: " + e.message);
|
||||
}
|
||||
|
||||
deleteIddatadb = null;
|
||||
@@ -156,20 +186,26 @@
|
||||
});
|
||||
|
||||
// ── Add new row ──────────────────────────────────────────────────────
|
||||
$(document).on('click', '#addRowBtn', async function () {
|
||||
$(document).on("click", "#addRowBtn", async function () {
|
||||
const btn = this;
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const templateId = window.gridMeta?.templateId;
|
||||
if (!templateId) { alert('Template ID missing'); return; }
|
||||
if (!templateId) {
|
||||
alert("Template ID missing");
|
||||
return;
|
||||
}
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const importref = urlParams.get('importref') || '';
|
||||
const resp = await fetch('add_record.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ template_id: templateId, importreferencecode: importref })
|
||||
const importref = urlParams.get("importref") || "";
|
||||
const resp = await fetch("add_record.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
template_id: templateId,
|
||||
importreferencecode: importref,
|
||||
}),
|
||||
});
|
||||
const result = await resp.json();
|
||||
|
||||
@@ -177,17 +213,20 @@
|
||||
// Build new row object
|
||||
const newRow = {
|
||||
iddatadb: result.iddatadb,
|
||||
status: 'i',
|
||||
idclient: window.gridMeta?.defaultIdclient || '',
|
||||
status: "i",
|
||||
idclient: window.gridMeta?.defaultIdclient || "",
|
||||
cliente_fornitore_id: null,
|
||||
commessaweb: null,
|
||||
user_name: result.user_name || '',
|
||||
importreferencecode: result.importreferencecode || '',
|
||||
filename_import: '',
|
||||
importdate: new Date().toISOString().slice(0, 19).replace('T', ' '),
|
||||
user_name: result.user_name || "",
|
||||
importreferencecode: result.importreferencecode || "",
|
||||
filename_import: "",
|
||||
importdate: new Date()
|
||||
.toISOString()
|
||||
.slice(0, 19)
|
||||
.replace("T", " "),
|
||||
fixedFields: {},
|
||||
details: {},
|
||||
mainFieldValue: '',
|
||||
mainFieldValue: "",
|
||||
_dirty: false,
|
||||
};
|
||||
|
||||
@@ -199,20 +238,27 @@
|
||||
if (gr) gr.renderVisibleRows();
|
||||
|
||||
// Highlight new row briefly
|
||||
const newGridRow = document.querySelector(`.grid-row[data-id="${result.iddatadb}"]`);
|
||||
const newGridRow = document.querySelector(
|
||||
`.grid-row[data-id="${result.iddatadb}"]`,
|
||||
);
|
||||
if (newGridRow) {
|
||||
newGridRow.classList.add('row-just-created');
|
||||
newGridRow.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
setTimeout(() => newGridRow.classList.remove('row-just-created'), 4000);
|
||||
newGridRow.classList.add("row-just-created");
|
||||
newGridRow.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
});
|
||||
setTimeout(
|
||||
() => newGridRow.classList.remove("row-just-created"),
|
||||
4000,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + (result.message || 'Unknown error'));
|
||||
alert("Error: " + (result.message || "Unknown error"));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error: ' + e.message);
|
||||
alert("Error: " + e.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
+626
-38
@@ -9,6 +9,7 @@ $(document).ready(function () {
|
||||
let quotations = [];
|
||||
let partsExtraField = null; // {field_id, field_label} oppure null
|
||||
let extraFieldOptions = []; // [{id,label}]
|
||||
let isLoadingPartsRecord = false;
|
||||
|
||||
// --- ROW ID helpers: niente più cache impazzita di jQuery .data() ---
|
||||
function getPartId($row) {
|
||||
@@ -107,8 +108,45 @@ $(document).ready(function () {
|
||||
if (!partsExtraField) return "";
|
||||
|
||||
const selectedValueId = $row ? $row.data("extra-value-id") || "" : "";
|
||||
const selectedValueText = $row
|
||||
? $row.data("extra-value-text") || ""
|
||||
: "";
|
||||
|
||||
// SceltaMultipla -> select + hidden value id
|
||||
if (
|
||||
(partsExtraField.data_type || "").toLowerCase() === "sceltamultipla"
|
||||
) {
|
||||
const opts = [`<option value="">Select…</option>`]
|
||||
.concat(
|
||||
extraFieldOptions.map(
|
||||
(o) =>
|
||||
`<option value="${o.id}" ${String(o.id) === String(selectedValueId) ? "selected" : ""}>${o.label}</option>`,
|
||||
),
|
||||
)
|
||||
.join("");
|
||||
|
||||
return `
|
||||
<td class="extra-field-td" style="width:200px;">
|
||||
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
|
||||
<input type="hidden" class="part-extra-value-id" value="${selectedValueId || ""}">
|
||||
<select class="form-control form-control-sm part-extra-select" data-selected="${selectedValueId || ""}">${opts}</select>
|
||||
</td>`;
|
||||
}
|
||||
|
||||
// Testo -> input + hidden field_id
|
||||
return `
|
||||
<td class="extra-field-td" style="width:200px;">
|
||||
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
|
||||
<input type="text" class="form-control form-control-sm part-extra-field" data-type="${partsExtraField.data_type || ""}" value="${selectedValueText || ""}">
|
||||
</td>`;
|
||||
}
|
||||
|
||||
function buildExtraFieldHeaderHtml() {
|
||||
if (!partsExtraField) return "";
|
||||
|
||||
const label = partsExtraField.field_label || "Extra";
|
||||
|
||||
// SceltaMultipla: titolo sopra, select + bottone sotto
|
||||
if (
|
||||
(partsExtraField.data_type || "").toLowerCase() === "sceltamultipla"
|
||||
) {
|
||||
@@ -121,19 +159,38 @@ $(document).ready(function () {
|
||||
.join("");
|
||||
|
||||
return `
|
||||
<td class="extra-field-td" style="width:200px;">
|
||||
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
|
||||
<input type="hidden" class="part-extra-value-id" value="">
|
||||
<select class="form-control form-control-sm part-extra-select" data-selected="${selectedValueId || ""}">${opts}</select>
|
||||
</td>`;
|
||||
<th class="extra-field-th">
|
||||
<div class="extra-propagate-wrapper">
|
||||
<div class="extra-propagate-label" title="${label}">
|
||||
${label}
|
||||
</div>
|
||||
<div class="extra-propagate-control">
|
||||
<select class="form-control form-control-sm extra-propagate-select">
|
||||
${opts}
|
||||
</select>
|
||||
<button type="button" class="btn btn-primary btn-sm extra-propagate-btn" title="Propaga a tutte le parti">
|
||||
<i class="fas fa-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>`;
|
||||
}
|
||||
|
||||
// Testo -> input + hidden field_id
|
||||
// Campo testo: titolo sopra, input + bottone sotto
|
||||
return `
|
||||
<td class="extra-field-td" style="width:200px;">
|
||||
<input type="hidden" class="part-extra-field-id" value="${partsExtraField.field_id}">
|
||||
<input type="text" class="form-control form-control-sm part-extra-field" data-type="${partsExtraField.data_type || ""}">
|
||||
</td>`;
|
||||
<th class="extra-field-th">
|
||||
<div class="extra-propagate-wrapper">
|
||||
<div class="extra-propagate-label" title="${label}">
|
||||
${label}
|
||||
</div>
|
||||
<div class="extra-propagate-control">
|
||||
<input type="text" class="form-control form-control-sm extra-propagate-input" placeholder="Value">
|
||||
<button type="button" class="btn btn-primary btn-sm extra-propagate-btn" title="Propaga a tutte le parti">
|
||||
<i class="fas fa-arrow-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>`;
|
||||
}
|
||||
|
||||
function initializeExtraFieldSelect2($context) {
|
||||
@@ -191,6 +248,79 @@ $(document).ready(function () {
|
||||
saveRow($row);
|
||||
});
|
||||
|
||||
$(document).on("click", ".extra-propagate-btn", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!partsExtraField) return;
|
||||
|
||||
const isSelect =
|
||||
(partsExtraField.data_type || "").toLowerCase() ===
|
||||
"sceltamultipla";
|
||||
|
||||
let propagateValueId = null;
|
||||
let propagateValueText = null;
|
||||
|
||||
if (isSelect) {
|
||||
propagateValueId =
|
||||
$("#partsTable thead .extra-propagate-select").val() || "";
|
||||
|
||||
if (!propagateValueId) {
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-warning temp-alert" role="alert">Seleziona un valore da propagare.</div>',
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(() => {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 3500);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
propagateValueText = (
|
||||
$("#partsTable thead .extra-propagate-input").val() || ""
|
||||
).trim();
|
||||
|
||||
if (!propagateValueText) {
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-warning temp-alert" role="alert">Inserisci un valore da propagare.</div>',
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(() => {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 3500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$("#partsTableBody tr").each(function () {
|
||||
const $row = $(this);
|
||||
|
||||
if (isSelect) {
|
||||
const $select = $row.find(".part-extra-select");
|
||||
|
||||
if ($select.length) {
|
||||
$select.val(propagateValueId).trigger("change.select2");
|
||||
|
||||
$row.data("extra-value-id", propagateValueId);
|
||||
$row.find(".part-extra-value-id").val(propagateValueId);
|
||||
}
|
||||
} else {
|
||||
const $input = $row.find(".part-extra-field");
|
||||
|
||||
if ($input.length) {
|
||||
$input.val(propagateValueText);
|
||||
|
||||
$row.data("extra-value-text", propagateValueText);
|
||||
}
|
||||
}
|
||||
|
||||
saveRow($row);
|
||||
});
|
||||
});
|
||||
|
||||
function applyExtraFieldColumn() {
|
||||
const $theadRow = $("#partsTable thead tr");
|
||||
|
||||
@@ -203,16 +333,17 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
// 3) se non c'è campo extra -> fine
|
||||
if (!partsExtraField) return;
|
||||
if (!partsExtraField) {
|
||||
if (typeof window.applyPartsColumnWidths === "function") {
|
||||
setTimeout(window.applyPartsColumnWidths, 100);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 4) inserisci header prima di "Azioni" (ultima colonna)
|
||||
$theadRow
|
||||
.find("th:last")
|
||||
.before(
|
||||
`<th class="extra-field-th" style="width:200px;">${partsExtraField.field_label}</th>`,
|
||||
);
|
||||
// 4) inserisci header propagabile prima di "Azioni" ultima colonna
|
||||
$theadRow.find("th:last").before(buildExtraFieldHeaderHtml());
|
||||
|
||||
// 5) aggiungi cella a ogni riga già presente (una sola volta)
|
||||
// 5) aggiungi cella a ogni riga già presente
|
||||
$("#partsTableBody tr").each(function () {
|
||||
const $row = $(this);
|
||||
$row.find("td:last").before(buildExtraFieldCellHtml($row));
|
||||
@@ -225,6 +356,33 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
initializeExtraFieldSelect2($("#partsTableBody"));
|
||||
|
||||
// Select2 anche sul campo di propagazione in testata
|
||||
if (
|
||||
(partsExtraField.data_type || "").toLowerCase() === "sceltamultipla"
|
||||
) {
|
||||
const $headerSelect = $(
|
||||
"#partsTable thead .extra-propagate-select",
|
||||
);
|
||||
|
||||
if ($headerSelect.length && typeof $.fn.select2 !== "undefined") {
|
||||
if ($headerSelect.hasClass("select2-hidden-accessible")) {
|
||||
$headerSelect.select2("destroy");
|
||||
}
|
||||
|
||||
$headerSelect.select2({
|
||||
placeholder: "Select…",
|
||||
allowClear: true,
|
||||
width: "100%",
|
||||
dropdownParent: $("#partsModal"),
|
||||
minimumResultsForSearch: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window.applyPartsColumnWidths === "function") {
|
||||
setTimeout(window.applyPartsColumnWidths, 100);
|
||||
}
|
||||
}
|
||||
|
||||
function setPartId($row, id) {
|
||||
@@ -235,6 +393,17 @@ $(document).ready(function () {
|
||||
$row.data("__pid", id);
|
||||
}
|
||||
|
||||
function setRowMix($row, isMix) {
|
||||
const mixValue = isMix === true || isMix === "Y" ? "Y" : "N";
|
||||
$row.attr("data-is-mix", mixValue);
|
||||
$row.data("is-mix", mixValue);
|
||||
}
|
||||
|
||||
function getRowMix($row) {
|
||||
const value = $row.attr("data-is-mix") || $row.data("is-mix");
|
||||
return value === "Y" ? "Y" : "N";
|
||||
}
|
||||
|
||||
// ===================
|
||||
// VOICE RECOGNITION SETUP
|
||||
// ===================
|
||||
@@ -341,21 +510,175 @@ $(document).ready(function () {
|
||||
// MODAL HANDLING
|
||||
// ===================
|
||||
function loadParts(iddatadb, idquotations, callback = null) {
|
||||
isLoadingPartsRecord = true;
|
||||
unsavedChanges = false;
|
||||
|
||||
// Store current modal context
|
||||
$("#partsModal").data("iddatadb", iddatadb || null);
|
||||
$("#partsModal").data("idquotations", idquotations || null);
|
||||
|
||||
// Store the visible record list from the main grid
|
||||
if (Array.isArray(window.visibleIddatadbList)) {
|
||||
$("#partsModal").data(
|
||||
"visible-iddatadb-list",
|
||||
window.visibleIddatadbList,
|
||||
);
|
||||
}
|
||||
|
||||
updatePartsRecordHeader(iddatadb);
|
||||
|
||||
const finishLoading = function () {
|
||||
unsavedChanges = false;
|
||||
isLoadingPartsRecord = false;
|
||||
|
||||
if (callback) callback();
|
||||
};
|
||||
|
||||
if (iddatadb) {
|
||||
loadMacroMatrici();
|
||||
initializeGlobalSelect2();
|
||||
loadPartsExtraField(iddatadb, function () {
|
||||
loadPhoto(iddatadb, idquotations);
|
||||
loadExistingParts(iddatadb, idquotations, callback);
|
||||
loadExistingParts(iddatadb, idquotations, finishLoading);
|
||||
});
|
||||
} else {
|
||||
loadPartsExtraField(iddatadb, function () {
|
||||
loadPhoto(iddatadb, idquotations);
|
||||
loadExistingParts(iddatadb, idquotations, callback);
|
||||
loadExistingParts(iddatadb, idquotations, finishLoading);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===================
|
||||
// PARTS MODAL RECORD NAVIGATION
|
||||
// ===================
|
||||
|
||||
function getVisiblePartsRecordList() {
|
||||
const listFromModal = $("#partsModal").data("visible-iddatadb-list");
|
||||
|
||||
if (Array.isArray(listFromModal) && listFromModal.length > 0) {
|
||||
return listFromModal.map((v) => parseInt(v, 10)).filter(Boolean);
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(window.visibleIddatadbList) &&
|
||||
window.visibleIddatadbList.length > 0
|
||||
) {
|
||||
return window.visibleIddatadbList
|
||||
.map((v) => parseInt(v, 10))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
if (Array.isArray(window.gridData) && window.gridData.length > 0) {
|
||||
return window.gridData
|
||||
.map((row) => parseInt(row.iddatadb, 10))
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function getGridRecordById(iddatadb) {
|
||||
if (!Array.isArray(window.gridData)) return null;
|
||||
|
||||
return (
|
||||
window.gridData.find((row) => {
|
||||
return parseInt(row.iddatadb, 10) === parseInt(iddatadb, 10);
|
||||
}) || null
|
||||
);
|
||||
}
|
||||
|
||||
function getRecordHeaderLabel(iddatadb) {
|
||||
const record = getGridRecordById(iddatadb);
|
||||
|
||||
if (!record) {
|
||||
return iddatadb ? "#" + iddatadb : "";
|
||||
}
|
||||
|
||||
// Prefer main field value if available
|
||||
if (record.mainFieldValue) {
|
||||
return record.mainFieldValue;
|
||||
}
|
||||
|
||||
// Fallbacks
|
||||
if (record.importreferencecode) {
|
||||
return record.importreferencecode;
|
||||
}
|
||||
|
||||
if (record.filename_import) {
|
||||
return record.filename_import;
|
||||
}
|
||||
|
||||
return "#" + iddatadb;
|
||||
}
|
||||
|
||||
function updatePartsRecordHeader(iddatadb) {
|
||||
const list = getVisiblePartsRecordList();
|
||||
const currentId = parseInt(iddatadb, 10);
|
||||
const currentIndex = list.indexOf(currentId);
|
||||
|
||||
$("#trfHeader").text(getRecordHeaderLabel(currentId));
|
||||
|
||||
if (list.length <= 1 || currentIndex === -1) {
|
||||
$("#partsRecordCounter").text("-");
|
||||
$("#prevPartsRecordBtn").prop("disabled", true);
|
||||
$("#nextPartsRecordBtn").prop("disabled", true);
|
||||
return;
|
||||
}
|
||||
|
||||
$("#partsRecordCounter").text(currentIndex + 1 + " / " + list.length);
|
||||
|
||||
$("#prevPartsRecordBtn").prop("disabled", currentIndex <= 0);
|
||||
$("#nextPartsRecordBtn").prop(
|
||||
"disabled",
|
||||
currentIndex >= list.length - 1,
|
||||
);
|
||||
}
|
||||
|
||||
function goToAdjacentPartsRecord(direction) {
|
||||
const list = getVisiblePartsRecordList();
|
||||
const currentId = parseInt($("#partsModal").data("iddatadb"), 10);
|
||||
const currentIndex = list.indexOf(currentId);
|
||||
|
||||
if (currentIndex === -1) return;
|
||||
|
||||
const nextIndex = currentIndex + direction;
|
||||
|
||||
if (nextIndex < 0 || nextIndex >= list.length) return;
|
||||
|
||||
if (
|
||||
!isLoadingPartsRecord &&
|
||||
unsavedChanges &&
|
||||
!confirm(
|
||||
"Hai modifiche non salvate. Vuoi cambiare record senza salvare?",
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextIddatadb = list[nextIndex];
|
||||
|
||||
// Reset local modal state before loading the next record
|
||||
partMatrice = {};
|
||||
unsavedChanges = false;
|
||||
$("#partsTableBody").empty();
|
||||
$("#photoSelectorContainer").empty().hide();
|
||||
$("#samplePhoto").attr("src", "");
|
||||
$(".temp-alert").remove();
|
||||
|
||||
loadParts(nextIddatadb, null);
|
||||
}
|
||||
|
||||
$(document).on("click", "#prevPartsRecordBtn", function (e) {
|
||||
e.preventDefault();
|
||||
goToAdjacentPartsRecord(-1);
|
||||
});
|
||||
|
||||
$(document).on("click", "#nextPartsRecordBtn", function (e) {
|
||||
e.preventDefault();
|
||||
goToAdjacentPartsRecord(1);
|
||||
});
|
||||
|
||||
// EVENTO PER APRIRE IL SECONDO MODALE
|
||||
$(document).on("click", "#openAnnotationsBtn", function () {
|
||||
console.log("Clic su Apri Annotazioni...");
|
||||
@@ -698,7 +1021,7 @@ $(document).ready(function () {
|
||||
const $saveLoading = $row.find(".save-loading");
|
||||
const iddatadb = $("#partsModal").data("iddatadb");
|
||||
const idquotations = $("#partsModal").data("idquotations");
|
||||
const isMix = partDescription.startsWith("Mix") ? "Y" : "N";
|
||||
const isMix = getRowMix($row);
|
||||
|
||||
// EXTRA FIELD (0/1)
|
||||
const extra_field_id = $row.find(".part-extra-field-id").val() || null;
|
||||
@@ -837,11 +1160,7 @@ $(document).ready(function () {
|
||||
|
||||
let $mixRow = $("#partsTableBody tr")
|
||||
.filter(function () {
|
||||
return $(this)
|
||||
.find(".part-description")
|
||||
.val()
|
||||
.trim()
|
||||
.startsWith("Mix");
|
||||
return getRowMix($(this)) === "Y";
|
||||
})
|
||||
.last();
|
||||
|
||||
@@ -897,7 +1216,7 @@ $(document).ready(function () {
|
||||
function addNewRow(nextPartNumber, isMix = false) {
|
||||
const description = isMix ? "Mix" : "";
|
||||
const newRow = `
|
||||
<tr data-part-id="new">
|
||||
<tr data-part-id="new" data-is-mix="${isMix ? "Y" : "N"}">
|
||||
<td><input type="number" class="form-control form-control-sm part-number" value="${nextPartNumber || 1}" style="width: 80px;"></td>
|
||||
<td><input type="text" class="form-control form-control-sm part-description" value="${description}" placeholder="Inserisci descrizione"></td>
|
||||
<td>
|
||||
@@ -918,6 +1237,7 @@ $(document).ready(function () {
|
||||
</tr>`;
|
||||
$("#partsTableBody").append(newRow);
|
||||
const $newRow = $("#partsTableBody tr:last");
|
||||
setRowMix($newRow, isMix ? "Y" : "N");
|
||||
const $select = $newRow.find(".part-matrice");
|
||||
const selectedMacro = $("#macro-matrice-filter").val() || "";
|
||||
|
||||
@@ -925,7 +1245,10 @@ $(document).ready(function () {
|
||||
initializeExtraFieldSelect2($newRow);
|
||||
|
||||
updateRowButtons();
|
||||
markUnsaved();
|
||||
|
||||
if (!isLoadingPartsRecord) {
|
||||
markUnsaved();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================
|
||||
@@ -963,7 +1286,7 @@ $(document).ready(function () {
|
||||
// Raccogli tutti i dati della riga per evitare sovrascritture
|
||||
const partNumber = $row.find(".part-number").val();
|
||||
const partDescription = $row.find(".part-description").val().trim();
|
||||
const mix = partDescription.startsWith("Mix") ? "Y" : "N";
|
||||
const mix = getRowMix($row);
|
||||
const idmatrice = $row.find(".part-matrice").val() || null;
|
||||
const dateexpiry = $row.find(".part-dateexpiry").val() || null;
|
||||
|
||||
@@ -1062,7 +1385,7 @@ $(document).ready(function () {
|
||||
// Raccogli tutti i dati della riga per evitare sovrascritture
|
||||
const partNumber = $row.find(".part-number").val();
|
||||
const partDescription = $row.find(".part-description").val().trim();
|
||||
const mix = partDescription.startsWith("Mix") ? "Y" : "N";
|
||||
const mix = getRowMix($row);
|
||||
const idmatrice = $row.find(".part-matrice").val() || null;
|
||||
const note = $row.data("note") || null;
|
||||
|
||||
@@ -1394,7 +1717,7 @@ $(document).ready(function () {
|
||||
? $("<div>").text(part.note).html()
|
||||
: "";
|
||||
const newRow = `
|
||||
<tr data-part-id="${part.id}" data-note="${escapedNote}">
|
||||
<tr data-part-id="${part.id}" data-note="${escapedNote}" data-is-mix="${part.mix === "Y" ? "Y" : "N"}">
|
||||
<td><input type="number" class="form-control form-control-sm part-number" value="${part.part_number || ""}" style="width: 80px;"></td>
|
||||
<td><input type="text" class="form-control form-control-sm part-description" value="${escapedDescription}" placeholder="Inserisci descrizione"></td>
|
||||
<td>
|
||||
@@ -1418,7 +1741,7 @@ $(document).ready(function () {
|
||||
const $row = $(
|
||||
`#partsTableBody tr[data-part-id="${part.id}"]`,
|
||||
);
|
||||
|
||||
setRowMix($row, part.mix === "Y" ? "Y" : "N");
|
||||
if (
|
||||
part.extra_value_id !== undefined &&
|
||||
part.extra_value_id !== null
|
||||
@@ -1432,6 +1755,19 @@ $(document).ready(function () {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
part.extra_value_text !== undefined &&
|
||||
part.extra_value_text !== null
|
||||
) {
|
||||
$row.data(
|
||||
"extra-value-text",
|
||||
part.extra_value_text,
|
||||
);
|
||||
$row.find(".part-extra-field").val(
|
||||
part.extra_value_text,
|
||||
);
|
||||
}
|
||||
|
||||
initializeExtraFieldSelect2($row);
|
||||
|
||||
const $select = $("#partsTableBody").find(
|
||||
@@ -1595,7 +1931,11 @@ $(document).ready(function () {
|
||||
dataType: "json",
|
||||
delay: 150,
|
||||
data: function (params) {
|
||||
return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
|
||||
return {
|
||||
q: params.term || "",
|
||||
limit: 20,
|
||||
macro: selectedMacro || "",
|
||||
};
|
||||
},
|
||||
processResults: function (data) {
|
||||
return { results: data.results || [] };
|
||||
@@ -1654,7 +1994,11 @@ $(document).ready(function () {
|
||||
dataType: "json",
|
||||
delay: 150,
|
||||
data: function (params) {
|
||||
return { q: params.term || "", limit: 20, macro: selectedMacro || "" };
|
||||
return {
|
||||
q: params.term || "",
|
||||
limit: 20,
|
||||
macro: selectedMacro || "",
|
||||
};
|
||||
},
|
||||
processResults: function (data) {
|
||||
return { results: data.results || [] };
|
||||
@@ -1800,7 +2144,9 @@ $(document).ready(function () {
|
||||
$("#partsTableBody .part-matrice").each(function () {
|
||||
const $target = $(this);
|
||||
if (!$target.find(`option[value="${globalVal}"]`).length) {
|
||||
$target.append(new Option(globalText, globalVal, true, true));
|
||||
$target.append(
|
||||
new Option(globalText, globalVal, true, true),
|
||||
);
|
||||
}
|
||||
$target.val(globalVal).trigger("change");
|
||||
});
|
||||
@@ -1873,7 +2219,7 @@ $(document).ready(function () {
|
||||
id: part.partId,
|
||||
part_number: index + 1,
|
||||
part_description: part.partDescription,
|
||||
mix: part.partDescription.startsWith("Mix") ? "Y" : "N",
|
||||
mix: getRowMix($rows.eq(index)),
|
||||
idmatrice: partMatrice[index + 1] || null,
|
||||
note: part.note,
|
||||
dateexpiry: part.dateexpiry,
|
||||
@@ -1954,6 +2300,8 @@ $(document).ready(function () {
|
||||
});
|
||||
|
||||
function markUnsaved() {
|
||||
if (isLoadingPartsRecord) return;
|
||||
|
||||
if (!unsavedChanges) {
|
||||
unsavedChanges = true;
|
||||
}
|
||||
@@ -1969,7 +2317,109 @@ $(document).ready(function () {
|
||||
".add-row-global, .add-mix-global, .add-mix-row, .remove-row, .propagate-matrice-btn, .propagate-all-btn, .note-btn",
|
||||
markUnsaved,
|
||||
);
|
||||
$(document).on("click", "#clonePartsBtn", function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const sourceIddatadb =
|
||||
parseInt($("#partsModal").data("iddatadb"), 10) || null;
|
||||
const visibleListRaw =
|
||||
$("#partsModal").data("visible-iddatadb-list") || [];
|
||||
|
||||
if (!sourceIddatadb) {
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-danger temp-alert" role="alert">Source record not found.</div>',
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(() => {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
const targetIds = (Array.isArray(visibleListRaw) ? visibleListRaw : [])
|
||||
.map((v) => parseInt(v, 10))
|
||||
.filter((v) => !isNaN(v) && v > 0 && v !== sourceIddatadb);
|
||||
|
||||
if (!targetIds.length) {
|
||||
const errorMsg = $(
|
||||
'<div class="alert alert-warning temp-alert" role="alert">No other visible records available for clone.</div>',
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(() => {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!confirm(
|
||||
`Confermi il clone delle parti del record ${sourceIddatadb} negli altri ${targetIds.length} record visibili?`,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $btn = $(this);
|
||||
const originalHtml = $btn.html();
|
||||
$btn.prop("disabled", true).html(
|
||||
'<i class="fas fa-spinner fa-spin"></i> Clonazione...',
|
||||
);
|
||||
|
||||
$.ajax({
|
||||
url: "clone_parts_to_visible.php",
|
||||
method: "POST",
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
data: JSON.stringify({
|
||||
source_iddatadb: sourceIddatadb,
|
||||
target_iddatadb_list: targetIds,
|
||||
}),
|
||||
success: function (response) {
|
||||
$btn.prop("disabled", false).html(originalHtml);
|
||||
|
||||
if (response.success) {
|
||||
const successMsg = $(
|
||||
`<div class="alert alert-success temp-alert" role="alert">
|
||||
Clone completed. Source parts copied to ${response.cloned_targets || 0} record(s), total cloned parts: ${response.total_cloned_parts || 0}.
|
||||
</div>`,
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(successMsg);
|
||||
setTimeout(() => {
|
||||
successMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
} else {
|
||||
const errorMsg = $(
|
||||
`<div class="alert alert-danger temp-alert" role="alert">Clone error: ${response.message || "Unknown error"}</div>`,
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(() => {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
$btn.prop("disabled", false).html(originalHtml);
|
||||
|
||||
const errorMsg = $(
|
||||
`<div class="alert alert-danger temp-alert" role="alert">Clone error: ${error} (${xhr.status})</div>`,
|
||||
);
|
||||
$("#partsModal .modal-body").prepend(errorMsg);
|
||||
setTimeout(() => {
|
||||
errorMsg.fadeOut(500, function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}, 5000);
|
||||
},
|
||||
});
|
||||
});
|
||||
// Esporta la funzione loadParts per essere usata da import_Edit2.php
|
||||
window.loadParts = loadParts;
|
||||
|
||||
@@ -2126,7 +2576,7 @@ $(document).on("change", ".propagate-date-input", function () {
|
||||
const partId = $row.data("part-id");
|
||||
const partNumber = $row.find(".part-number").val();
|
||||
const partDescription = $row.find(".part-description").val().trim();
|
||||
const mix = partDescription.startsWith("Mix") ? "Y" : "N";
|
||||
const mix = $row.attr("data-is-mix") === "Y" ? "Y" : "N";
|
||||
const idmatrice = $row.find(".part-matrice").val() || null;
|
||||
const note = $row.data("note") || null;
|
||||
|
||||
@@ -2237,7 +2687,7 @@ $(document).on("click", ".save-common-note-btn", function () {
|
||||
const partId = $row.data("part-id");
|
||||
const partNumber = $row.find(".part-number").val();
|
||||
const partDescription = $row.find(".part-description").val().trim();
|
||||
const mix = partDescription.startsWith("Mix") ? "Y" : "N";
|
||||
const mix = $row.attr("data-is-mix") === "Y" ? "Y" : "N";
|
||||
const idmatrice = $row.find(".part-matrice").val() || null;
|
||||
const dateexpiry = $row.find(".part-dateexpiry").val() || null;
|
||||
|
||||
@@ -2351,4 +2801,142 @@ $(document).on("click", "#showHideImageBtn", function () {
|
||||
"<i class='fas fa-eye' style='font-size: 0.8rem;'></i><i class='fas fa-image ms-1' style='font-size: 0.8rem;'></i>",
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof window.applyPartsColumnWidths === "function") {
|
||||
setTimeout(window.applyPartsColumnWidths, 150);
|
||||
}
|
||||
});
|
||||
// ===================
|
||||
// RESIZABLE PARTS TABLE COLUMNS - FIXED COLGROUP VERSION
|
||||
// ===================
|
||||
(function () {
|
||||
// Larghezze default per indice colonna (0-based)
|
||||
const defaultWidths = [55, 320, 360, 150, 220, 230];
|
||||
const savedWidths = [...defaultWidths];
|
||||
|
||||
function getColgroup() {
|
||||
return $("#partsTableColgroup");
|
||||
}
|
||||
|
||||
function syncColgroupToHeaders() {
|
||||
const $table = $("#partsTable");
|
||||
const $ths = $table.find("thead tr:first th");
|
||||
const $colgroup = getColgroup();
|
||||
const thCount = $ths.length;
|
||||
|
||||
// Assicura che ci siano esattamente tante <col> quante <th>
|
||||
while ($colgroup.find("col").length < thCount) {
|
||||
$colgroup.append("<col>");
|
||||
}
|
||||
while ($colgroup.find("col").length > thCount) {
|
||||
$colgroup.find("col:last").remove();
|
||||
}
|
||||
|
||||
// Applica le larghezze salvate
|
||||
$colgroup.find("col").each(function (i) {
|
||||
const w = savedWidths[i] !== undefined ? savedWidths[i] : 150;
|
||||
$(this).css("width", w + "px");
|
||||
});
|
||||
|
||||
// Imposta larghezza totale della tabella = somma colonne (evita reflow)
|
||||
const total = savedWidths.slice(0, thCount).reduce((a, b) => a + b, 0);
|
||||
$table.css("width", total + "px");
|
||||
}
|
||||
|
||||
function applyColumnWidth(colIndex, newWidth) {
|
||||
const w = Math.max(40, Math.round(newWidth));
|
||||
savedWidths[colIndex] = w;
|
||||
|
||||
const $col = getColgroup().find("col").eq(colIndex);
|
||||
if ($col.length) {
|
||||
$col.css("width", w + "px");
|
||||
}
|
||||
|
||||
// Aggiorna larghezza totale tabella senza toccare le altre colonne
|
||||
const thCount = $("#partsTable thead tr:first th").length;
|
||||
const total = savedWidths.slice(0, thCount).reduce((a, b) => a + b, 0);
|
||||
$("#partsTable").css("width", total + "px");
|
||||
}
|
||||
|
||||
function addResizers() {
|
||||
const $table = $("#partsTable");
|
||||
if (!$table.length) return;
|
||||
|
||||
$table.find("thead tr:first th").each(function (colIndex) {
|
||||
const $th = $(this);
|
||||
|
||||
// Salta colonna Num (indice 0) — non ridimensionabile
|
||||
if (colIndex === 0) return;
|
||||
|
||||
// Non aggiungere due volte
|
||||
if ($th.find(".parts-resizer").length) return;
|
||||
|
||||
const $resizer = $("<span class='parts-resizer'></span>");
|
||||
$th.css("position", "relative"); // necessario per il posizionamento assoluto
|
||||
$th.append($resizer);
|
||||
|
||||
$resizer.on("mousedown", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const startX = e.pageX;
|
||||
// Leggi la larghezza ESATTA dalla <col>, non dal <th>
|
||||
const startWidth =
|
||||
savedWidths[colIndex] !== undefined
|
||||
? savedWidths[colIndex]
|
||||
: parseInt(
|
||||
getColgroup()
|
||||
.find("col")
|
||||
.eq(colIndex)
|
||||
.css("width"),
|
||||
10,
|
||||
) || 150;
|
||||
|
||||
$("body")
|
||||
.css("user-select", "none")
|
||||
.css("cursor", "col-resize");
|
||||
|
||||
$(document).on("mousemove.partsResize", function (ev) {
|
||||
const delta = ev.pageX - startX;
|
||||
applyColumnWidth(colIndex, startWidth + delta);
|
||||
});
|
||||
|
||||
$(document).on("mouseup.partsResize", function () {
|
||||
$("body").css("user-select", "").css("cursor", "");
|
||||
$(document).off(".partsResize");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
syncColgroupToHeaders();
|
||||
addResizers();
|
||||
}
|
||||
|
||||
// Init al primo show del modale
|
||||
$(document).on("shown.bs.modal", "#partsModal", function () {
|
||||
setTimeout(init, 120);
|
||||
});
|
||||
|
||||
// Re-init dopo ogni AJAX (nuove righe, caricamenti)
|
||||
$(document).ajaxComplete(function () {
|
||||
if ($("#partsModal").hasClass("show")) {
|
||||
setTimeout(syncColgroupToHeaders, 200);
|
||||
setTimeout(addResizers, 220);
|
||||
}
|
||||
});
|
||||
|
||||
// Re-init dopo aggiunta/rimozione righe
|
||||
$(document).on(
|
||||
"click",
|
||||
".add-row-global, .add-mix-global, .add-mix-row, .remove-row, #renumberPartsBtn, #clonePartsBtn",
|
||||
function () {
|
||||
setTimeout(syncColgroupToHeaders, 120);
|
||||
setTimeout(addResizers, 140);
|
||||
},
|
||||
);
|
||||
|
||||
window.initPartsResizableColumns = init;
|
||||
window.applyPartsColumnWidths = syncColgroupToHeaders;
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
set_time_limit(20);
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
$start = microtime(true);
|
||||
|
||||
// Chiamata minima: 1 solo record, 1 solo campo
|
||||
$data = $api->get('Rapporto', [
|
||||
'$top' => 1,
|
||||
'$select' => 'IdRapporto'
|
||||
]);
|
||||
|
||||
$elapsed = round(microtime(true) - $start, 3);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'elapsed_seconds' => $elapsed,
|
||||
'data' => $data
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -13,8 +13,21 @@ try {
|
||||
$id = intval($_POST['id'] ?? 0);
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$source_type = strtoupper(trim($_POST['source_type'] ?? 'XLS'));
|
||||
$header_row = isset($_POST['header_row']) && $_POST['header_row'] !== '' ? intval($_POST['header_row']) : null;
|
||||
|
||||
$header_row = isset($_POST['header_row']) && $_POST['header_row'] !== ''
|
||||
? intval($_POST['header_row'])
|
||||
: null;
|
||||
|
||||
$start_column = trim($_POST['start_column'] ?? '');
|
||||
|
||||
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
||||
? intval($_POST['xls_sheet_index'])
|
||||
: 0;
|
||||
|
||||
$api_config_id = isset($_POST['api_config_id']) && $_POST['api_config_id'] !== ''
|
||||
? intval($_POST['api_config_id'])
|
||||
: null;
|
||||
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$target_table = trim($_POST['target_table'] ?? 'datadb');
|
||||
$idclient = intval($_POST['client_id'] ?? 0);
|
||||
@@ -27,7 +40,8 @@ try {
|
||||
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
|
||||
$button_label = trim($_POST['button_label'] ?? 'Click Me');
|
||||
|
||||
if (!in_array($source_type, ['XLS', 'API'], true)) {
|
||||
// Allowed source types
|
||||
if (!in_array($source_type, ['XLS', 'API', 'JSON', 'PDF'], true)) {
|
||||
$source_type = 'XLS';
|
||||
}
|
||||
|
||||
@@ -41,18 +55,52 @@ try {
|
||||
if ($header_row === null || $header_row <= 0 || $start_column === '') {
|
||||
throw new Exception("Header Row and Start Column are required for XLS templates.");
|
||||
}
|
||||
|
||||
if ($xls_sheet_index < 0) {
|
||||
throw new Exception("XLS Sheet Number cannot be negative.");
|
||||
}
|
||||
|
||||
$api_config_id = null;
|
||||
}
|
||||
|
||||
// API templates do not require XLS coordinates
|
||||
if ($source_type === 'API') {
|
||||
// API/JSON validation
|
||||
if ($source_type === 'API' || $source_type === 'JSON') {
|
||||
if (empty($api_config_id)) {
|
||||
throw new Exception("API/JSON configuration is required for API or JSON templates.");
|
||||
}
|
||||
|
||||
$header_row = null;
|
||||
$start_column = null;
|
||||
$xls_sheet_index = null;
|
||||
}
|
||||
|
||||
// PDF currently does not require XLS coordinates or API configuration
|
||||
if ($source_type === 'PDF') {
|
||||
$header_row = null;
|
||||
$start_column = null;
|
||||
$xls_sheet_index = null;
|
||||
$api_config_id = null;
|
||||
}
|
||||
|
||||
// Database connection
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
// Optional check: verify API configuration exists and is active
|
||||
if ($api_config_id !== null) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM api_configurations
|
||||
WHERE id = ?
|
||||
AND is_active = 1
|
||||
");
|
||||
$stmt->execute([$api_config_id]);
|
||||
|
||||
if ((int)$stmt->fetchColumn() === 0) {
|
||||
throw new Exception("Selected API/JSON configuration does not exist or is not active.");
|
||||
}
|
||||
}
|
||||
|
||||
// Update template
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE excel_templates
|
||||
@@ -61,6 +109,8 @@ try {
|
||||
source_type = ?,
|
||||
header_row = ?,
|
||||
start_column = ?,
|
||||
xls_sheet_index = ?,
|
||||
api_config_id = ?,
|
||||
description = ?,
|
||||
target_table = ?,
|
||||
idclient = ?,
|
||||
@@ -81,6 +131,8 @@ try {
|
||||
$source_type,
|
||||
$header_row,
|
||||
$start_column,
|
||||
$xls_sheet_index,
|
||||
$api_config_id,
|
||||
$description,
|
||||
$target_table,
|
||||
$idclient,
|
||||
|
||||
@@ -10,6 +10,9 @@ session_start();
|
||||
// Includi PHPSpreadsheet
|
||||
require_once '../../vendor/autoload.php';
|
||||
|
||||
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
|
||||
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||
|
||||
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => ''];
|
||||
|
||||
try {
|
||||
|
||||
@@ -11,17 +11,105 @@ session_start();
|
||||
require_once '../../vendor/autoload.php';
|
||||
require_once __DIR__ . '/class/db-functions.php';
|
||||
|
||||
$response = ['error' => '', 'rows' => [], 'columns' => [], 'template_id' => 0, 'filename' => '', 'apply_routine' => false];
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
|
||||
$response = [
|
||||
'error' => '',
|
||||
'rows' => [],
|
||||
'columns' => [],
|
||||
'template_id' => 0,
|
||||
'filename' => '',
|
||||
'apply_routine' => false
|
||||
];
|
||||
|
||||
/**
|
||||
* Converts a column value to a PhpSpreadsheet 1-based column index.
|
||||
* Accepted values:
|
||||
* - "A" => 1
|
||||
* - "B" => 2
|
||||
* - "AA" => 27
|
||||
* - "1" => 1
|
||||
* - 1 => 1
|
||||
*/
|
||||
function normalizeColumnIndex($value): int
|
||||
{
|
||||
$value = trim((string)$value);
|
||||
|
||||
if ($value === '') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ctype_digit($value)) {
|
||||
return max(1, (int)$value);
|
||||
}
|
||||
|
||||
$value = strtoupper($value);
|
||||
|
||||
if (preg_match('/^[A-Z]+$/', $value)) {
|
||||
return Coordinate::columnIndexFromString($value);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['excel_file'])) {
|
||||
$template_id = isset($_POST['template_id']) ? intval($_POST['template_id']) : 0;
|
||||
$header_row = isset($_POST['header_row']) ? intval($_POST['header_row']) : 1;
|
||||
$start_column = isset($_POST['start_column']) ? intval($_POST['start_column']) : 1;
|
||||
|
||||
if ($template_id <= 0) {
|
||||
throw new Exception("Template ID non valido.");
|
||||
}
|
||||
|
||||
// Connessione al database
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
/*
|
||||
* Recuperiamo i parametri direttamente dal template.
|
||||
* Così non dipendiamo solo dal form e siamo sicuri di usare i dati salvati.
|
||||
*/
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
id,
|
||||
header_row,
|
||||
start_column,
|
||||
xls_sheet_index,
|
||||
idroutine,
|
||||
idclient
|
||||
FROM excel_templates
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$template_id]);
|
||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$template) {
|
||||
throw new Exception("Template non trovato.");
|
||||
}
|
||||
|
||||
$header_row = isset($template['header_row']) && $template['header_row'] !== null
|
||||
? (int)$template['header_row']
|
||||
: 1;
|
||||
|
||||
$start_column_raw = $template['start_column'] ?? 'A';
|
||||
$start_column = normalizeColumnIndex($start_column_raw);
|
||||
|
||||
$xlsSheetIndex = isset($template['xls_sheet_index']) && $template['xls_sheet_index'] !== null
|
||||
? (int)$template['xls_sheet_index']
|
||||
: 0;
|
||||
|
||||
if ($header_row <= 0) {
|
||||
$header_row = 1;
|
||||
}
|
||||
|
||||
if ($xlsSheetIndex < 0) {
|
||||
$xlsSheetIndex = 0;
|
||||
}
|
||||
|
||||
// Debug del template_id ricevuto
|
||||
error_log("Received template_id from POST: " . print_r($_POST['template_id'], true));
|
||||
error_log("Converted template_id: $template_id");
|
||||
error_log("Template XLS settings - header_row: $header_row, start_column_raw: $start_column_raw, start_column_index: $start_column, xls_sheet_index: $xlsSheetIndex");
|
||||
|
||||
$file = $_FILES['excel_file'];
|
||||
$fileError = $file['error'];
|
||||
@@ -38,23 +126,32 @@ try {
|
||||
$originalFilename = basename($file['name']);
|
||||
$newFilename = "{$iduserlogin}-{$timestamp}-{$originalFilename}";
|
||||
$importFolder = __DIR__ . '/imported_trf/';
|
||||
|
||||
if (!file_exists($importFolder)) {
|
||||
mkdir($importFolder, 0777, true);
|
||||
}
|
||||
|
||||
$destination = $importFolder . $newFilename;
|
||||
|
||||
// Sposta il file
|
||||
if (!move_uploaded_file($file['tmp_name'], $destination)) {
|
||||
throw new Exception("Errore durante lo spostamento del file in $destination");
|
||||
}
|
||||
|
||||
error_log("File spostato con successo in: $destination");
|
||||
|
||||
// Connessione al database
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
// Recupera il mapping da template_mapping
|
||||
$stmt = $pdo->prepare("SELECT field_id AS excel_column, field_id AS mysql_column, data_type, is_required, default_value, is_manual FROM template_mapping WHERE template_id = ?");
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
field_id AS excel_column,
|
||||
field_id AS mysql_column,
|
||||
data_type,
|
||||
is_required,
|
||||
default_value,
|
||||
is_manual
|
||||
FROM template_mapping
|
||||
WHERE template_id = ?
|
||||
");
|
||||
$stmt->execute([$template_id]);
|
||||
$mappings = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
@@ -65,19 +162,45 @@ try {
|
||||
$response['error'] = "Nessun mapping trovato per il template con ID $template_id";
|
||||
} else {
|
||||
// Carica il file rinominato con PHPSpreadsheet
|
||||
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($destination);
|
||||
$worksheet = $spreadsheet->getActiveSheet();
|
||||
$spreadsheet = IOFactory::load($destination);
|
||||
|
||||
$sheetCount = $spreadsheet->getSheetCount();
|
||||
$sheetNames = $spreadsheet->getSheetNames();
|
||||
|
||||
if ($sheetCount <= 0) {
|
||||
throw new Exception("Il file XLS non contiene fogli.");
|
||||
}
|
||||
|
||||
if ($xlsSheetIndex >= $sheetCount) {
|
||||
throw new Exception(
|
||||
"Il foglio XLS selezionato non esiste. " .
|
||||
"Sheet Number selezionato: {$xlsSheetIndex}. " .
|
||||
"Fogli disponibili: " . implode(", ", array_map(
|
||||
fn($name, $index) => "{$index}={$name}",
|
||||
$sheetNames,
|
||||
array_keys($sheetNames)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
// Usa il foglio configurato nel template
|
||||
$worksheet = $spreadsheet->getSheet($xlsSheetIndex);
|
||||
$selectedSheetName = $worksheet->getTitle();
|
||||
|
||||
error_log("Selected XLS sheet - index: {$xlsSheetIndex}, name: {$selectedSheetName}");
|
||||
|
||||
$highestRow = $worksheet->getHighestRow();
|
||||
$highestColumn = $worksheet->getHighestColumn();
|
||||
$highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn);
|
||||
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
|
||||
|
||||
$startRow = max(1, $header_row);
|
||||
$startColumn = max(1, $start_column);
|
||||
|
||||
// Advance startColumn to first non-empty cell in header row (match JS behavior)
|
||||
// Advance startColumn to first non-empty cell in header row, matching JS behavior
|
||||
for ($sc = $startColumn; $sc <= $highestColumnIndex; $sc++) {
|
||||
$cl = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($sc);
|
||||
$cl = Coordinate::stringFromColumnIndex($sc);
|
||||
$cv = trim((string)($worksheet->getCell($cl . $header_row)->getCalculatedValue() ?? ''));
|
||||
|
||||
if ($cv !== '') {
|
||||
$startColumn = $sc;
|
||||
break;
|
||||
@@ -85,24 +208,32 @@ try {
|
||||
}
|
||||
|
||||
// Debug dei parametri
|
||||
error_log("Processing - template_id: $template_id, startRow: $startRow, startColumn: $startColumn, highestRow: $highestRow, highestColumn: $highestColumn, highestColumnIndex: $highestColumnIndex");
|
||||
error_log(
|
||||
"Processing - template_id: $template_id, " .
|
||||
"sheetIndex: $xlsSheetIndex, sheetName: $selectedSheetName, " .
|
||||
"startRow: $startRow, startColumn: $startColumn, " .
|
||||
"highestRow: $highestRow, highestColumn: $highestColumn, highestColumnIndex: $highestColumnIndex"
|
||||
);
|
||||
|
||||
// Validazione degli indici
|
||||
if ($startRow > $highestRow) {
|
||||
$response['error'] = "La riga di partenza ($startRow) supera il numero totale di righe ($highestRow).";
|
||||
$response['error'] = "La riga di partenza ($startRow) supera il numero totale di righe ($highestRow) del foglio '$selectedSheetName'.";
|
||||
} elseif ($startColumn > $highestColumnIndex) {
|
||||
$response['error'] = "La colonna di partenza ($startColumn) supera il numero totale di colonne ($highestColumnIndex).";
|
||||
$response['error'] = "La colonna di partenza ($startColumn) supera il numero totale di colonne ($highestColumnIndex) del foglio '$selectedSheetName'.";
|
||||
} else {
|
||||
$excelData = [];
|
||||
|
||||
// Build merge map for header row: physCol -> mergeStartCol
|
||||
$mergeStartMap = [];
|
||||
|
||||
foreach ($worksheet->getMergeCells() as $range) {
|
||||
[$startCell, $endCell] = explode(':', $range);
|
||||
$mStartCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString(preg_replace('/\d+/', '', $startCell));
|
||||
$mEndCol = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString(preg_replace('/\d+/', '', $endCell));
|
||||
|
||||
$mStartCol = Coordinate::columnIndexFromString(preg_replace('/\d+/', '', $startCell));
|
||||
$mEndCol = Coordinate::columnIndexFromString(preg_replace('/\d+/', '', $endCell));
|
||||
$mStartRow = (int)preg_replace('/[A-Z]+/i', '', $startCell);
|
||||
$mEndRow = (int)preg_replace('/[A-Z]+/i', '', $endCell);
|
||||
|
||||
if ($header_row >= $mStartRow && $header_row <= $mEndRow) {
|
||||
for ($c = $mStartCol; $c <= $mEndCol; $c++) {
|
||||
$mergeStartMap[$c] = $mStartCol;
|
||||
@@ -111,12 +242,17 @@ try {
|
||||
}
|
||||
|
||||
// Build logical columns: each merge = one column
|
||||
$logicalCols = []; // array of physical column indices (one per logical column)
|
||||
$logicalCols = []; // array of physical column indices, one per logical column
|
||||
$seen = [];
|
||||
|
||||
for ($col = $startColumn; $col <= $highestColumnIndex; $col++) {
|
||||
if (isset($mergeStartMap[$col])) {
|
||||
$ms = $mergeStartMap[$col];
|
||||
if (in_array($ms, $seen, true)) continue;
|
||||
|
||||
if (in_array($ms, $seen, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seen[] = $ms;
|
||||
$logicalCols[] = $ms;
|
||||
} else {
|
||||
@@ -127,38 +263,48 @@ try {
|
||||
// Build header row using logical columns
|
||||
$headerRowData = [];
|
||||
$logicalNum = 0;
|
||||
|
||||
foreach ($logicalCols as $physCol) {
|
||||
$logicalNum++;
|
||||
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($physCol);
|
||||
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($physCol);
|
||||
$cell = $worksheet->getCell($columnLetter . $header_row);
|
||||
$cellValue = trim((string)($cell ? $cell->getCalculatedValue() : ''));
|
||||
$cellValue = preg_replace('/[\r\n\t]+/', ' ', $cellValue);
|
||||
|
||||
// Empty headers get __empty_N__ to match mapping page
|
||||
$headerRowData[] = ($cellValue !== '') ? $cellValue : '__empty_' . $logicalNum . '__';
|
||||
}
|
||||
|
||||
error_log("Logical headers: " . json_encode($headerRowData));
|
||||
error_log("Logical cols (physical indices): " . json_encode($logicalCols));
|
||||
error_log("Logical cols physical indices: " . json_encode($logicalCols));
|
||||
|
||||
// Find which logical columns have real headers
|
||||
$headerFilledIndices = [];
|
||||
|
||||
foreach ($headerRowData as $idx => $hVal) {
|
||||
if (!str_starts_with($hVal, '__empty_')) $headerFilledIndices[] = $idx;
|
||||
if (!str_starts_with($hVal, '__empty_')) {
|
||||
$headerFilledIndices[] = $idx;
|
||||
}
|
||||
}
|
||||
|
||||
$minFilled = max(1, min(2, count($headerFilledIndices)));
|
||||
|
||||
// Extract data rows using logical columns
|
||||
for ($row = $startRow + 1; $row <= $highestRow; $row++) {
|
||||
$rowData = [];
|
||||
|
||||
foreach ($logicalCols as $physCol) {
|
||||
$columnLetter = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($physCol);
|
||||
$columnLetter = Coordinate::stringFromColumnIndex($physCol);
|
||||
$cell = $worksheet->getCell($columnLetter . $row);
|
||||
$cellValue = $cell ? $cell->getCalculatedValue() : '';
|
||||
|
||||
$rowData[] = $cellValue ?: '';
|
||||
}
|
||||
|
||||
// Count how many header columns have data in this row
|
||||
$filledCount = 0;
|
||||
|
||||
foreach ($headerFilledIndices as $idx) {
|
||||
if (isset($rowData[$idx]) && trim((string)$rowData[$idx]) !== '') {
|
||||
$filledCount++;
|
||||
@@ -166,17 +312,25 @@ try {
|
||||
}
|
||||
|
||||
if ($filledCount >= $minFilled) {
|
||||
$excelData[] = ['data' => $rowData, 'excelrow' => $row];
|
||||
$excelData[] = [
|
||||
'data' => $rowData,
|
||||
'excelrow' => $row
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Recupera routine dal template
|
||||
$stmt = $pdo->prepare("SELECT idroutine, idclient FROM excel_templates WHERE id = ?");
|
||||
$stmt->execute([$template_id]);
|
||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($template && $template['idroutine']) {
|
||||
$stmtRoutine = $pdo->prepare("SELECT idroutine, name, filename, headerrow, instruction FROM routine WHERE idroutine = ?");
|
||||
if ($template && !empty($template['idroutine'])) {
|
||||
$stmtRoutine = $pdo->prepare("
|
||||
SELECT
|
||||
idroutine,
|
||||
name,
|
||||
filename,
|
||||
headerrow,
|
||||
instruction
|
||||
FROM routine
|
||||
WHERE idroutine = ?
|
||||
");
|
||||
$stmtRoutine->execute([$template['idroutine']]);
|
||||
$routineData = $stmtRoutine->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
@@ -188,6 +342,7 @@ try {
|
||||
'filename' => $routineData['filename'] ?? '',
|
||||
'headerrow' => $routineData['headerrow'] ?? $header_row
|
||||
];
|
||||
|
||||
error_log("Routine rilevata per template {$template_id}: " . print_r($routineData, true));
|
||||
} else {
|
||||
error_log("Errore: Nessuna routine trovata per idroutine {$template['idroutine']}");
|
||||
@@ -204,6 +359,8 @@ try {
|
||||
$_SESSION['template_id'] = $template_id;
|
||||
$_SESSION['headers'] = $headerRowData;
|
||||
$_SESSION['mappings'] = $mappings;
|
||||
$_SESSION['xls_sheet_index'] = $xlsSheetIndex;
|
||||
$_SESSION['xls_sheet_name'] = $selectedSheetName;
|
||||
|
||||
// Includi excel_data nella risposta JSON in ogni caso
|
||||
$response['excel_data'] = $excelData;
|
||||
@@ -211,6 +368,8 @@ try {
|
||||
$response['columns'] = $headerRowData;
|
||||
$response['template_id'] = $template_id;
|
||||
$response['filename'] = $newFilename;
|
||||
$response['xls_sheet_index'] = $xlsSheetIndex;
|
||||
$response['xls_sheet_name'] = $selectedSheetName;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -12,22 +12,39 @@ try {
|
||||
// Retrieve and sanitize form data
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
$source_type = strtoupper(trim($_POST['source_type'] ?? 'XLS'));
|
||||
$header_row = isset($_POST['header_row']) && $_POST['header_row'] !== '' ? intval($_POST['header_row']) : null;
|
||||
|
||||
$header_row = isset($_POST['header_row']) && $_POST['header_row'] !== ''
|
||||
? intval($_POST['header_row'])
|
||||
: null;
|
||||
|
||||
$start_column = trim($_POST['start_column'] ?? '');
|
||||
|
||||
$xls_sheet_index = isset($_POST['xls_sheet_index']) && $_POST['xls_sheet_index'] !== ''
|
||||
? intval($_POST['xls_sheet_index'])
|
||||
: 0;
|
||||
|
||||
$api_config_id = isset($_POST['api_config_id']) && $_POST['api_config_id'] !== ''
|
||||
? intval($_POST['api_config_id'])
|
||||
: null;
|
||||
|
||||
$description = trim($_POST['description'] ?? '');
|
||||
$target_table = trim($_POST['target_table'] ?? 'datadb');
|
||||
$idclient = intval($_POST['client_id'] ?? 0);
|
||||
$clientname = trim($_POST['client_name'] ?? '');
|
||||
$idschema = intval($_POST['idschema'] ?? 0);
|
||||
$schemaname = trim($_POST['schemaname'] ?? '');
|
||||
$idroutine = isset($_POST['idroutine']) && $_POST['idroutine'] !== '' ? intval($_POST['idroutine']) : null;
|
||||
$idroutine = isset($_POST['idroutine']) && $_POST['idroutine'] !== ''
|
||||
? intval($_POST['idroutine'])
|
||||
: null;
|
||||
|
||||
$button_size = trim($_POST['button_size'] ?? 'medium');
|
||||
$button_bg_color = trim($_POST['button_bg_color'] ?? '#007bff');
|
||||
$button_text_color = trim($_POST['button_text_color'] ?? '#ffffff');
|
||||
$button_label = trim($_POST['button_label'] ?? 'Click Me');
|
||||
|
||||
// Normalize source type
|
||||
if (!in_array($source_type, ['XLS', 'API'], true)) {
|
||||
// API / JSON is saved as API
|
||||
if (!in_array($source_type, ['XLS', 'API', 'PDF'], true)) {
|
||||
$source_type = 'XLS';
|
||||
}
|
||||
|
||||
@@ -41,26 +58,62 @@ try {
|
||||
if ($header_row === null || $header_row <= 0 || $start_column === '') {
|
||||
throw new Exception("Header Row and Start Column are required for XLS templates.");
|
||||
}
|
||||
|
||||
if ($xls_sheet_index < 0) {
|
||||
throw new Exception("XLS Sheet Number cannot be negative.");
|
||||
}
|
||||
|
||||
$api_config_id = null;
|
||||
}
|
||||
|
||||
// API templates do not require XLS coordinates
|
||||
// API / JSON validation
|
||||
if ($source_type === 'API') {
|
||||
if (empty($api_config_id)) {
|
||||
throw new Exception("API / JSON configuration is required for API / JSON templates.");
|
||||
}
|
||||
|
||||
$header_row = null;
|
||||
$start_column = null;
|
||||
$xls_sheet_index = null;
|
||||
}
|
||||
|
||||
// PDF currently does not require XLS coordinates or API configuration
|
||||
if ($source_type === 'PDF') {
|
||||
$header_row = null;
|
||||
$start_column = null;
|
||||
$xls_sheet_index = null;
|
||||
$api_config_id = null;
|
||||
}
|
||||
|
||||
// Database connection
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
// Optional check: verify API configuration exists and is active
|
||||
if ($api_config_id !== null) {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM api_configurations
|
||||
WHERE id = ?
|
||||
AND is_active = 1
|
||||
");
|
||||
$stmt->execute([$api_config_id]);
|
||||
|
||||
if ((int)$stmt->fetchColumn() === 0) {
|
||||
throw new Exception("Selected API / JSON configuration does not exist or is not active.");
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new template
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO excel_templates
|
||||
INSERT INTO excel_templates
|
||||
(
|
||||
name,
|
||||
source_type,
|
||||
header_row,
|
||||
start_column,
|
||||
xls_sheet_index,
|
||||
api_config_id,
|
||||
description,
|
||||
target_table,
|
||||
idclient,
|
||||
@@ -75,7 +128,13 @@ try {
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
VALUES
|
||||
(
|
||||
?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?, ?,
|
||||
?, ?, ?, ?, ?,
|
||||
NOW(), NOW()
|
||||
)
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
@@ -83,6 +142,8 @@ try {
|
||||
$source_type,
|
||||
$header_row,
|
||||
$start_column,
|
||||
$xls_sheet_index,
|
||||
$api_config_id,
|
||||
$description,
|
||||
$target_table,
|
||||
$idclient,
|
||||
|
||||
+555
-242
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Routine: burberry
|
||||
*
|
||||
* Purpose:
|
||||
* For each imported XLS row:
|
||||
* - read the value from column S
|
||||
* - read the value from column T
|
||||
* - merge the values
|
||||
* - save the final value into column S
|
||||
*
|
||||
* Target:
|
||||
* Column S must be mapped to the destination field in the template mapping.
|
||||
*/
|
||||
|
||||
function applyRoutine(&$excelData, $routineData = [])
|
||||
{
|
||||
/*
|
||||
* This routine does not require external routine data.
|
||||
* Columns are fixed.
|
||||
*
|
||||
* Excel column indexes are zero-based:
|
||||
*
|
||||
* S = 18
|
||||
* T = 19
|
||||
*/
|
||||
$targetColumnIndex = 18; // S
|
||||
|
||||
$columnSIndex = 18; // S
|
||||
$columnTIndex = 19; // T
|
||||
|
||||
foreach ($excelData as $rowIndex => &$row) {
|
||||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||
error_log("Routine burberry: invalid row structure at index {$rowIndex}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueS = trim((string)($row['data'][$columnSIndex] ?? ''));
|
||||
$valueT = trim((string)($row['data'][$columnTIndex] ?? ''));
|
||||
|
||||
/*
|
||||
* Merge values, ignoring empty values.
|
||||
*/
|
||||
$mergedValues = [];
|
||||
|
||||
if ($valueS !== '') {
|
||||
$mergedValues[] = $valueS;
|
||||
}
|
||||
|
||||
if ($valueT !== '') {
|
||||
$mergedValues[] = $valueT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save final value into column S.
|
||||
*/
|
||||
$row['data'][$targetColumnIndex] = implode(' ', $mergedValues);
|
||||
|
||||
error_log(
|
||||
"Routine burberry: row " .
|
||||
($row['excelrow'] ?? $rowIndex) .
|
||||
" generated value in column S: " .
|
||||
$row['data'][$targetColumnIndex]
|
||||
);
|
||||
}
|
||||
|
||||
unset($row);
|
||||
|
||||
error_log("Routine burberry completed.");
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Routine: merge_column_T_and_U_into_T
|
||||
*
|
||||
* Purpose:
|
||||
* For each imported XLS row:
|
||||
* - read the value from column T
|
||||
* - read the value from column U
|
||||
* - merge both values
|
||||
* - save the final value into column T
|
||||
*
|
||||
* Target:
|
||||
* Column T must be mapped to the destination field in the template mapping.
|
||||
*/
|
||||
|
||||
function applyRoutine(&$excelData, $routineData = [])
|
||||
{
|
||||
/*
|
||||
* Excel column indexes are zero-based:
|
||||
*
|
||||
* T = 19
|
||||
* U = 20
|
||||
*/
|
||||
$targetColumnIndex = 19; // T
|
||||
$firstColumnIndex = 19; // T
|
||||
$secondColumnIndex = 20; // U
|
||||
|
||||
foreach ($excelData as $rowIndex => &$row) {
|
||||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||
error_log("Routine merge T+U: invalid row structure at index {$rowIndex}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueT = trim((string)($row['data'][$firstColumnIndex] ?? ''));
|
||||
$valueU = trim((string)($row['data'][$secondColumnIndex] ?? ''));
|
||||
|
||||
/*
|
||||
* Merge values, ignoring empty values.
|
||||
*/
|
||||
$mergedValues = [];
|
||||
|
||||
if ($valueT !== '') {
|
||||
$mergedValues[] = $valueT;
|
||||
}
|
||||
|
||||
if ($valueU !== '') {
|
||||
$mergedValues[] = $valueU;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save final value into column T.
|
||||
*/
|
||||
$row['data'][$targetColumnIndex] = implode(' ', $mergedValues);
|
||||
|
||||
error_log(
|
||||
"Routine merge T+U: row " .
|
||||
($row['excelrow'] ?? $rowIndex) .
|
||||
" generated value in column T: " .
|
||||
$row['data'][$targetColumnIndex]
|
||||
);
|
||||
}
|
||||
|
||||
unset($row);
|
||||
|
||||
error_log("Routine merge T+U completed.");
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Routine: paulshark
|
||||
*
|
||||
* Purpose:
|
||||
* For each imported XLS row:
|
||||
* - read the value from column D
|
||||
* - read the value from column E
|
||||
* - read the value from column J
|
||||
* - merge the values
|
||||
* - save the final value into column D
|
||||
*
|
||||
* Target:
|
||||
* Column D must be mapped to the destination field in the template mapping.
|
||||
*/
|
||||
|
||||
function applyRoutine(&$excelData, $routineData = [])
|
||||
{
|
||||
/*
|
||||
* Excel column indexes are zero-based:
|
||||
*
|
||||
* D = 3
|
||||
* E = 4
|
||||
* J = 9
|
||||
*/
|
||||
$targetColumnIndex = 3; // D
|
||||
|
||||
$columnDIndex = 3; // D
|
||||
$columnEIndex = 4; // E
|
||||
$columnJIndex = 9; // J
|
||||
|
||||
foreach ($excelData as $rowIndex => &$row) {
|
||||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||
error_log("Routine paulshark: invalid row structure at index {$rowIndex}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueD = trim((string)($row['data'][$columnDIndex] ?? ''));
|
||||
$valueE = trim((string)($row['data'][$columnEIndex] ?? ''));
|
||||
$valueJ = trim((string)($row['data'][$columnJIndex] ?? ''));
|
||||
|
||||
/*
|
||||
* Merge values, ignoring empty values.
|
||||
*/
|
||||
$mergedValues = [];
|
||||
|
||||
if ($valueD !== '') {
|
||||
$mergedValues[] = $valueD;
|
||||
}
|
||||
|
||||
if ($valueE !== '') {
|
||||
$mergedValues[] = $valueE;
|
||||
}
|
||||
|
||||
if ($valueJ !== '') {
|
||||
$mergedValues[] = $valueJ;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save final value into column D.
|
||||
*/
|
||||
$row['data'][$targetColumnIndex] = implode(' ', $mergedValues);
|
||||
|
||||
error_log(
|
||||
"Routine paulshark: row " .
|
||||
($row['excelrow'] ?? $rowIndex) .
|
||||
" generated value in column D: " .
|
||||
$row['data'][$targetColumnIndex]
|
||||
);
|
||||
}
|
||||
|
||||
unset($row);
|
||||
|
||||
error_log("Routine paulshark completed.");
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Routine: Richemont Pelletteria
|
||||
*
|
||||
* Purpose:
|
||||
* For each imported XLS row:
|
||||
* - read the value from column D
|
||||
* - read the value from column E
|
||||
* - merge the values
|
||||
* - save the final value into column D
|
||||
*
|
||||
* Target:
|
||||
* Column D must be mapped to the destination field in the template mapping.
|
||||
*/
|
||||
|
||||
function applyRoutine(&$excelData, $routineData = [])
|
||||
{
|
||||
/*
|
||||
* This routine does not require external routine data.
|
||||
* Columns are fixed.
|
||||
*
|
||||
* Excel column indexes are zero-based:
|
||||
*
|
||||
* D = 3
|
||||
* E = 4
|
||||
*/
|
||||
$targetColumnIndex = 3; // D
|
||||
|
||||
$columnDIndex = 3; // D
|
||||
$columnEIndex = 4; // E
|
||||
|
||||
foreach ($excelData as $rowIndex => &$row) {
|
||||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||
error_log("Routine Richemont Pelletteria: invalid row structure at index {$rowIndex}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueD = trim((string)($row['data'][$columnDIndex] ?? ''));
|
||||
$valueE = trim((string)($row['data'][$columnEIndex] ?? ''));
|
||||
|
||||
/*
|
||||
* Merge values, ignoring empty values.
|
||||
*/
|
||||
$mergedValues = [];
|
||||
|
||||
if ($valueD !== '') {
|
||||
$mergedValues[] = $valueD;
|
||||
}
|
||||
|
||||
if ($valueE !== '') {
|
||||
$mergedValues[] = $valueE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Save final value into column D.
|
||||
*/
|
||||
$row['data'][$targetColumnIndex] = implode(' ', $mergedValues);
|
||||
|
||||
error_log(
|
||||
"Routine Richemont Pelletteria: row " .
|
||||
($row['excelrow'] ?? $rowIndex) .
|
||||
" generated value in column D: " .
|
||||
$row['data'][$targetColumnIndex]
|
||||
);
|
||||
}
|
||||
|
||||
unset($row);
|
||||
|
||||
error_log("Routine Richemont Pelletteria completed.");
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Routine: build_field_347_from_x_columns
|
||||
*
|
||||
* Purpose:
|
||||
* For each imported XLS row:
|
||||
* - check columns P to AT
|
||||
* - when a cell contains "x", take the related column title from row 6
|
||||
* - append the free text value from column AU
|
||||
* - save the final comma-separated text into column P
|
||||
*
|
||||
* Target:
|
||||
* Column P must be mapped to field_id 347.
|
||||
*/
|
||||
|
||||
function applyRoutine(&$excelData, $routineData = [])
|
||||
{
|
||||
/*
|
||||
* Excel column indexes are zero-based:
|
||||
*
|
||||
* P = 15
|
||||
* AT = 45
|
||||
* AU = 46
|
||||
*/
|
||||
$targetColumnIndex = 15; // P
|
||||
$startColumnIndex = 15; // P
|
||||
$endColumnIndex = 45; // AT
|
||||
$extraColumnIndex = 46; // AU
|
||||
|
||||
/*
|
||||
* Headers must come from XLS row 6.
|
||||
* Usually they are passed inside $routineData['xls_headers'].
|
||||
*/
|
||||
$headers = $routineData['xls_headers'] ?? [];
|
||||
|
||||
if (empty($headers) || !is_array($headers)) {
|
||||
error_log("Routine field_id 347: missing XLS headers from row 6.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($excelData as $rowIndex => &$row) {
|
||||
if (!isset($row['data']) || !is_array($row['data'])) {
|
||||
error_log("Routine field_id 347: invalid row structure at index {$rowIndex}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
$selectedValues = [];
|
||||
|
||||
/*
|
||||
* Check columns from P to AT.
|
||||
* If the cell contains x, take the related column header.
|
||||
*/
|
||||
for ($columnIndex = $startColumnIndex; $columnIndex <= $endColumnIndex; $columnIndex++) {
|
||||
$cellValue = strtolower(trim((string)($row['data'][$columnIndex] ?? '')));
|
||||
|
||||
if ($cellValue === 'x') {
|
||||
$headerTitle = trim((string)($headers[$columnIndex] ?? ''));
|
||||
|
||||
if ($headerTitle !== '') {
|
||||
$selectedValues[] = $headerTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add free text from column AU.
|
||||
*/
|
||||
$extraText = '';
|
||||
|
||||
if (isset($row['data'][$extraColumnIndex])) {
|
||||
$extraText = trim((string)$row['data'][$extraColumnIndex]);
|
||||
} elseif (isset($row['data']['AU'])) {
|
||||
$extraText = trim((string)$row['data']['AU']);
|
||||
}
|
||||
|
||||
error_log(
|
||||
"Routine field_id 347: row " .
|
||||
($row['excelrow'] ?? $rowIndex) .
|
||||
" AU index {$extraColumnIndex} value: " .
|
||||
print_r($row['data'][$extraColumnIndex] ?? null, true) .
|
||||
" | AU key value: " .
|
||||
print_r($row['data']['AU'] ?? null, true)
|
||||
);
|
||||
|
||||
if ($extraText !== '') {
|
||||
$selectedValues[] = $extraText;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove empty and duplicate values.
|
||||
*/
|
||||
$selectedValues = array_values(array_unique(array_filter($selectedValues, function ($value) {
|
||||
return trim((string)$value) !== '';
|
||||
})));
|
||||
|
||||
/*
|
||||
* Save final value into column P.
|
||||
* Column P must be mapped to field_id 347 in the template mapping.
|
||||
*/
|
||||
$row['data'][$targetColumnIndex] = implode(', ', $selectedValues);
|
||||
|
||||
error_log(
|
||||
"Routine field_id 347: row " .
|
||||
($row['excelrow'] ?? $rowIndex) .
|
||||
" generated value: " .
|
||||
$row['data'][$targetColumnIndex]
|
||||
);
|
||||
}
|
||||
|
||||
unset($row);
|
||||
|
||||
error_log("Routine field_id 347 completed.");
|
||||
}
|
||||
@@ -13,7 +13,7 @@ try {
|
||||
$iddatadb = intval($_POST['iddatadb']);
|
||||
$idclient = isset($_POST['idclient']) ? (is_numeric($_POST['idclient']) ? intval($_POST['idclient']) : null) : null;
|
||||
$clienteFornitoreId = isset($_POST['cliente_fornitore_id']) ? (is_numeric($_POST['cliente_fornitore_id']) ? intval($_POST['cliente_fornitore_id']) : null) : null;
|
||||
|
||||
$testedComponent = isset($_POST['tested_component']) ? trim((string)$_POST['tested_component']) : null;
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
@@ -144,7 +144,11 @@ try {
|
||||
$setParts[] = "cliente_fornitore_id = ?";
|
||||
$params[] = $clienteFornitoreId;
|
||||
}
|
||||
|
||||
// 5a3) tested_component (se presente)
|
||||
if (isset($testedComponent)) {
|
||||
$setParts[] = "tested_component = ?";
|
||||
$params[] = ($testedComponent === '') ? null : $testedComponent;
|
||||
}
|
||||
// 5b) fixed fields dal POST
|
||||
// QUI è il punto chiave: accettiamo SOLO colonne reali (realWhitelist),
|
||||
// ma se dal JS arrivassero ancora key logiche, funzionerebbe uguale
|
||||
|
||||
@@ -16,9 +16,11 @@ if (!$data || !isset($data['id'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$mappingId = $data['id'];
|
||||
$mappingId = (int)$data['id'];
|
||||
$mappingType = $data['mapping_type'] ?? '';
|
||||
|
||||
$excelColumn = $data['excel_column'] ?? null;
|
||||
$jsonNode = $data['json_node'] ?? null;
|
||||
$manualDefault = $data['manual_default'] ?? null;
|
||||
|
||||
$autoValue = $data['auto_value'] ?? 'none';
|
||||
@@ -26,7 +28,7 @@ $tablename = $data['tablename'] ?? '';
|
||||
|
||||
try {
|
||||
// Normalize mapping type
|
||||
$allowedTypes = ['', 'xls', 'manual', 'auto'];
|
||||
$allowedTypes = ['', 'xls', 'json', 'manual', 'auto'];
|
||||
if (!in_array($mappingType, $allowedTypes, true)) {
|
||||
echo json_encode(["success" => false, "message" => "Invalid mapping_type"]);
|
||||
exit;
|
||||
@@ -41,43 +43,67 @@ try {
|
||||
// Decide what to persist based on mapping_type
|
||||
$isManual = 0;
|
||||
$excelToSave = null;
|
||||
$jsonNodeToSave = null;
|
||||
$manualToSave = null;
|
||||
$autoToSave = 'none';
|
||||
|
||||
if ($mappingType === 'xls') {
|
||||
|
||||
$isManual = 0;
|
||||
$excelToSave = $excelColumn ?: null;
|
||||
$jsonNodeToSave = null;
|
||||
$manualToSave = null;
|
||||
$autoToSave = 'none';
|
||||
} elseif ($mappingType === 'json') {
|
||||
|
||||
$isManual = 0;
|
||||
$excelToSave = null;
|
||||
$jsonNodeToSave = $jsonNode ?: null;
|
||||
$manualToSave = null;
|
||||
$autoToSave = 'none';
|
||||
} elseif ($mappingType === 'manual') {
|
||||
|
||||
$isManual = 1;
|
||||
$excelToSave = null;
|
||||
$jsonNodeToSave = null;
|
||||
$manualToSave = $manualDefault;
|
||||
$autoToSave = 'none';
|
||||
} elseif ($mappingType === 'auto') {
|
||||
|
||||
$isManual = 0;
|
||||
$excelToSave = null;
|
||||
$jsonNodeToSave = null;
|
||||
$manualToSave = null;
|
||||
$autoToSave = $autoValue ?: 'none';
|
||||
} else {
|
||||
// reset
|
||||
|
||||
// Reset mapping
|
||||
$isManual = 0;
|
||||
$excelToSave = null;
|
||||
$jsonNodeToSave = null;
|
||||
$manualToSave = null;
|
||||
$autoToSave = 'none';
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE template_mapping
|
||||
SET
|
||||
is_manual = ?,
|
||||
excel_column = ?,
|
||||
manual_default = ?,
|
||||
auto_value = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
UPDATE template_mapping
|
||||
SET
|
||||
is_manual = ?,
|
||||
excel_column = ?,
|
||||
json_node = ?,
|
||||
manual_default = ?,
|
||||
auto_value = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
|
||||
$result = $stmt->execute([$isManual, $excelToSave, $manualToSave, $autoToSave, $mappingId]);
|
||||
$result = $stmt->execute([
|
||||
$isManual,
|
||||
$excelToSave,
|
||||
$jsonNodeToSave,
|
||||
$manualToSave,
|
||||
$autoToSave,
|
||||
$mappingId
|
||||
]);
|
||||
|
||||
if (!$result) {
|
||||
echo json_encode(["success" => false, "message" => "Database update failed"]);
|
||||
@@ -88,15 +114,17 @@ try {
|
||||
"success" => true,
|
||||
"message" => "Mapping updated successfully",
|
||||
"saved" => [
|
||||
"id" => (int)$mappingId,
|
||||
"id" => $mappingId,
|
||||
"mapping_type" => $mappingType,
|
||||
"is_manual" => $isManual,
|
||||
"excel_column" => $excelToSave,
|
||||
"json_node" => $jsonNodeToSave,
|
||||
"manual_default" => $manualToSave,
|
||||
"auto_value" => $autoToSave
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode(["success" => false, "message" => "Error: " . $e->getMessage()]);
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -653,7 +653,7 @@
|
||||
"IdSchemaCustomFields": 163,
|
||||
"ConteggioClienti": 0,
|
||||
"Nome": "Devred",
|
||||
"Descrizione": "Schema creato per cliente DEVRED\r\nGR 18\/03\/2024\r\n\r\n"
|
||||
"Descrizione": "Schema creato per cliente DEVRED\r\nGR 18\/03\/2024 aggiornato il 23\/04\/2026\r\n\r\n"
|
||||
},
|
||||
{
|
||||
"IdSchemaCustomFields": 164,
|
||||
@@ -760,7 +760,7 @@
|
||||
{
|
||||
"IdSchemaCustomFields": 182,
|
||||
"ConteggioClienti": 0,
|
||||
"Nome": "Ralph Lauren - All testing V.6",
|
||||
"Nome": "Ralph Lauren - All testing V.10",
|
||||
"Descrizione": "AGGIORNAMENTO AL 16\/03\/2026 per estrazione fatturazione"
|
||||
},
|
||||
{
|
||||
@@ -876,6 +876,12 @@
|
||||
"ConteggioClienti": 0,
|
||||
"Nome": "ROSSIMODA",
|
||||
"Descrizione": "Per tutti i campioni di ROSSIMODA\r\n\r\n"
|
||||
},
|
||||
{
|
||||
"IdSchemaCustomFields": 202,
|
||||
"ConteggioClienti": 0,
|
||||
"Nome": "LIMS-CIM - MAX MARA",
|
||||
"Descrizione": "Schema per MAX MARA scambio dati Database"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,12 @@
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/class/db-functions.php';
|
||||
include dirname(__DIR__) . '/../extra/auth.php';
|
||||
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
|
||||
|
||||
if (!Auth::check()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
@@ -14,44 +19,95 @@ $q = mb_strtolower(trim($_GET['q'] ?? ''));
|
||||
$limit = max(1, min(50, intval($_GET['limit'] ?? 20)));
|
||||
$id = isset($_GET['id']) ? intval($_GET['id']) : null;
|
||||
|
||||
function formatClientLabel(array $client): string
|
||||
{
|
||||
$name = trim($client['Nominativo'] ?? '');
|
||||
$id = trim((string)($client['IdCliente'] ?? ''));
|
||||
$code = trim((string)($client['CodiceCliente'] ?? ''));
|
||||
|
||||
$parts = explode('_', $code);
|
||||
$suffix = trim($parts[1] ?? '');
|
||||
|
||||
if ($suffix === '' && $code !== '') {
|
||||
$suffix = substr($code, 0, 1);
|
||||
}
|
||||
|
||||
if ($suffix === '') {
|
||||
$suffix = '--';
|
||||
}
|
||||
|
||||
return $name . ' - ' . $suffix . ' (ID: ' . $id . ')';
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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 requesting by specific ID, used 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'] ?? '')]]]);
|
||||
echo json_encode([
|
||||
'results' => [[
|
||||
'id' => $c['IdCliente'],
|
||||
'text' => formatClientLabel($c),
|
||||
'IdCliente' => $c['IdCliente'],
|
||||
'Nominativo' => trim($c['Nominativo'] ?? ''),
|
||||
'CodiceCliente' => trim($c['CodiceCliente'] ?? '')
|
||||
]]
|
||||
]);
|
||||
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;
|
||||
|
||||
if (
|
||||
$q === '' ||
|
||||
mb_strpos(mb_strtolower($name), $q) !== false ||
|
||||
mb_strpos(mb_strtolower($code), $q) !== false
|
||||
) {
|
||||
$results[] = [
|
||||
'id' => $c['IdCliente'],
|
||||
'text' => formatClientLabel($c),
|
||||
'IdCliente' => $c['IdCliente'],
|
||||
'Nominativo' => $name,
|
||||
'CodiceCliente' => $code
|
||||
];
|
||||
|
||||
if (count($results) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/class/db-functions.php';
|
||||
include dirname(__DIR__) . '/../extra/auth.php';
|
||||
if (!Auth::check()) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; }
|
||||
if (!Auth::check()) {
|
||||
http_response_code(401);
|
||||
echo json_encode(['error' => 'Unauthorized']);
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
@@ -13,7 +17,8 @@ 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)));
|
||||
$rawLimit = intval($_GET['limit'] ?? 20);
|
||||
$limit = $rawLimit <= 0 ? 0 : max(1, min(500, $rawLimit));
|
||||
|
||||
if (!$fieldId) {
|
||||
echo json_encode(['results' => []]);
|
||||
@@ -52,7 +57,7 @@ try {
|
||||
$text = $v['Valore'] ?? '';
|
||||
if ($q === '' || mb_strpos(mb_strtolower($text), $q) !== false) {
|
||||
$results[] = ['id' => $v['IdCustomFieldsValue'], 'text' => $text];
|
||||
if (count($results) >= $limit) break;
|
||||
if ($limit > 0 && count($results) >= $limit) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
// Esempio: search_rapporto_min.php?codice=2621716
|
||||
$codice = trim($_GET['codice'] ?? '');
|
||||
|
||||
if ($codice === '') {
|
||||
throw new Exception("Parametro codice mancante. Usa ?codice=2621716");
|
||||
}
|
||||
|
||||
/*
|
||||
* Minimal:
|
||||
* - niente expand
|
||||
* - max 5 record
|
||||
* - pochi campi
|
||||
*
|
||||
* Se il campo Codice non è quello giusto, sotto proviamo anche CodiceRapporto.
|
||||
*/
|
||||
$attempts = [];
|
||||
|
||||
$filters = [
|
||||
'Codice' => "Codice eq '{$codice}'",
|
||||
'CodiceRapporto' => "CodiceRapporto eq '{$codice}'",
|
||||
'Numero' => "Numero eq '{$codice}'"
|
||||
];
|
||||
|
||||
foreach ($filters as $label => $filter) {
|
||||
try {
|
||||
$data = $api->get('Rapporto', [
|
||||
'$filter' => $filter,
|
||||
'$top' => 5,
|
||||
'$select' => 'IdRapporto,Codice,CodiceRapporto,Numero,Data,Versione,Cliente'
|
||||
]);
|
||||
|
||||
$attempts[$label] = [
|
||||
'success' => true,
|
||||
'filter' => $filter,
|
||||
'records' => isset($data['value']) && is_array($data['value']) ? count($data['value']) : null,
|
||||
'data' => $data
|
||||
];
|
||||
|
||||
} catch (Exception $e) {
|
||||
$attempts[$label] = [
|
||||
'success' => false,
|
||||
'filter' => $filter,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'input_codice' => $codice,
|
||||
'attempts' => $attempts
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -181,6 +181,20 @@
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (urlParams.get('cloned') === '1') {
|
||||
Swal.fire({
|
||||
title: "Template cloned",
|
||||
text: "The template was cloned successfully.",
|
||||
icon: "success",
|
||||
confirmButtonText: "OK"
|
||||
});
|
||||
|
||||
const cleanUrl = window.location.pathname;
|
||||
window.history.replaceState({}, document.title, cleanUrl);
|
||||
}
|
||||
$('#xlsTemplatesTable').DataTable({
|
||||
processing: true,
|
||||
serverSide: false,
|
||||
@@ -195,18 +209,24 @@
|
||||
className: "table-actions text-center",
|
||||
render: function(data, type, row) {
|
||||
return `
|
||||
<div class="d-flex justify-content-center gap-1">
|
||||
<a href="edit_template_xls.php?id=${data}" class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bx bx-edit-alt"></i>
|
||||
</a>
|
||||
<a href="mapping_template_xls_scheme2.php?id=${data}" class="btn btn-sm btn-success" title="Mapping">
|
||||
<i class="bx bx-link-alt"></i>
|
||||
</a>
|
||||
<button class="btn btn-sm btn-danger" onclick="confirmDelete(${data})" title="Delete">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
<div class="d-flex justify-content-center gap-1">
|
||||
<a href="edit_template_xls.php?id=${data}" class="btn btn-sm btn-primary" title="Edit">
|
||||
<i class="bx bx-edit-alt"></i>
|
||||
</a>
|
||||
|
||||
<a href="mapping_template_xls_scheme2.php?id=${data}" class="btn btn-sm btn-success" title="Mapping">
|
||||
<i class="bx bx-link-alt"></i>
|
||||
</a>
|
||||
|
||||
<button class="btn btn-sm btn-warning" onclick="confirmClone(${data}, '${String(row.name || '').replace(/'/g, "\\'")}')" title="Clone">
|
||||
<i class="bx bx-copy"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-sm btn-danger" onclick="confirmDelete(${data})" title="Delete">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -318,6 +338,31 @@
|
||||
});
|
||||
}
|
||||
|
||||
function confirmClone(id, templateName) {
|
||||
Swal.fire({
|
||||
title: "Clone template?",
|
||||
html: `
|
||||
<div class="text-start">
|
||||
<p class="mb-2">You are about to clone this template:</p>
|
||||
<strong>${templateName}</strong>
|
||||
<p class="mt-3 mb-0 text-muted">
|
||||
The new template will be created as <strong>Copia di ${templateName}</strong> and all mappings will be copied.
|
||||
</p>
|
||||
</div>
|
||||
`,
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#ffc107",
|
||||
cancelButtonColor: "#6c757d",
|
||||
confirmButtonText: "Yes, clone it",
|
||||
cancelButtonText: "Cancel"
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
window.location.href = `clone_template.php?id=${id}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(document).on("change", ".toggle-status", function() {
|
||||
let templateId = $(this).data("id");
|
||||
let newStatus = $(this).is(":checked") ? "active" : "inactive";
|
||||
|
||||
@@ -8,6 +8,9 @@ ini_set('log_errors', 1);
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
Dotenv\Dotenv::createImmutable(dirname(__DIR__, 2))->safeLoad();
|
||||
date_default_timezone_set($_ENV['APP_TIMEZONE'] ?? 'Europe/Rome');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
|
||||
ini_set('display_errors', '1');
|
||||
error_reporting(E_ALL);
|
||||
set_time_limit(60);
|
||||
|
||||
/**
|
||||
* Uso: get_pdf.php?id=591749
|
||||
*/
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
$idRapportoFile = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
|
||||
if (!$idRapportoFile) {
|
||||
throw new Exception("Parametro id mancante. Usa ?id=591749");
|
||||
}
|
||||
|
||||
// STEP 1: leggi metadati entita
|
||||
$entityData = $api->get("RapportoFile(" . $idRapportoFile . ")");
|
||||
|
||||
$pdfBody = null;
|
||||
$strategyUsed = null;
|
||||
$debugLog = array();
|
||||
|
||||
// Strategia A - campo FileContent base64
|
||||
if (!empty($entityData['FileContent'])) {
|
||||
$decoded = base64_decode($entityData['FileContent'], true);
|
||||
if ($decoded !== false && substr($decoded, 0, 4) === '%PDF') {
|
||||
$pdfBody = $decoded;
|
||||
$strategyUsed = 'A: FileContent base64';
|
||||
}
|
||||
}
|
||||
|
||||
// Strategia B - campo Contenuto base64
|
||||
if (!$pdfBody && !empty($entityData['Contenuto'])) {
|
||||
$decoded = base64_decode($entityData['Contenuto'], true);
|
||||
if ($decoded !== false && substr($decoded, 0, 4) === '%PDF') {
|
||||
$pdfBody = $decoded;
|
||||
$strategyUsed = 'B: Contenuto base64';
|
||||
}
|
||||
}
|
||||
|
||||
// Recupera token e baseUrl dalla classe
|
||||
$token = $api->getToken();
|
||||
$baseUrl = rtrim($api->getBaseUrl(), '/');
|
||||
|
||||
$rawEndpoints = array(
|
||||
'C: $value' => $baseUrl . '/api/odata/RapportoFile(' . $idRapportoFile . ')/$value',
|
||||
'D: FileContent/$value' => $baseUrl . '/api/odata/RapportoFile(' . $idRapportoFile . ')/FileContent/$value',
|
||||
'E: Contenuto/$value' => $baseUrl . '/api/odata/RapportoFile(' . $idRapportoFile . ')/Contenuto/$value',
|
||||
);
|
||||
|
||||
foreach ($rawEndpoints as $label => $url) {
|
||||
if ($pdfBody) break;
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
|
||||
'Authorization: Bearer ' . $token,
|
||||
'Accept: application/pdf, application/octet-stream, */*',
|
||||
));
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||
|
||||
$body = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$ct = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
|
||||
curl_close($ch);
|
||||
|
||||
$debugLog[$label] = array(
|
||||
'url' => $url,
|
||||
'http_code' => $httpCode,
|
||||
'content_type' => $ct,
|
||||
'body_start' => substr((string)$body, 0, 300),
|
||||
);
|
||||
|
||||
if ($httpCode === 200 && is_string($body) && substr($body, 0, 4) === '%PDF') {
|
||||
$pdfBody = $body;
|
||||
$strategyUsed = $label;
|
||||
}
|
||||
}
|
||||
|
||||
// RISPOSTA
|
||||
if ($pdfBody) {
|
||||
$fileName = isset($entityData['FileName']) ? $entityData['FileName'] : 'rapporto_' . $idRapportoFile . '.pdf';
|
||||
header('Content-Type: application/pdf');
|
||||
header('Content-Disposition: inline; filename="' . $fileName . '"');
|
||||
header('Content-Length: ' . strlen($pdfBody));
|
||||
header('Cache-Control: private, max-age=0, must-revalidate');
|
||||
echo $pdfBody;
|
||||
exit;
|
||||
}
|
||||
|
||||
// Nessuna strategia ha funzionato
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'message' => 'Nessuna strategia ha restituito un PDF valido.',
|
||||
'id_rapporto_file' => $idRapportoFile,
|
||||
'entity_fields' => array_keys($entityData ? $entityData : array()),
|
||||
'entity_data' => $entityData,
|
||||
'strategies_debug' => $debugLog,
|
||||
), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
echo json_encode(array(
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
<?php
|
||||
include('include/headscript.php');
|
||||
|
||||
// ✅ FIX timezone (Rome)
|
||||
ini_set('date.timezone', 'Europe/Rome');
|
||||
date_default_timezone_set('Europe/Rome');
|
||||
|
||||
$template_id = intval($_GET['id'] ?? 0);
|
||||
if (!$template_id) {
|
||||
header("Location: xlstemplates_grid.php?status=error&message=" . urlencode("Template ID mancante"));
|
||||
@@ -2016,6 +2012,51 @@ function resolveFixedValue(string $key, $val, array $fixedLookup): string {
|
||||
el.addEventListener('change', () => el.classList.toggle('has-value', el.value.trim() !== ''));
|
||||
});
|
||||
})();
|
||||
|
||||
// ── Column resize ──
|
||||
(function() {
|
||||
const resizers = document.querySelectorAll('.resizer');
|
||||
let currentResizer = null, startX = 0, startWidth = 0, columnIndex = null;
|
||||
|
||||
function resize(e) {
|
||||
if (!currentResizer || columnIndex === null) return;
|
||||
const newWidth = Math.max(80, startWidth + (e.pageX - startX));
|
||||
const sel = '[data-index="' + columnIndex + '"]';
|
||||
const header = document.querySelector('.grid-header' + sel);
|
||||
if (header) header.style.flex = '0 0 ' + newWidth + 'px';
|
||||
const topCell = document.querySelector('.grid-top .grid-cell' + sel);
|
||||
if (topCell) topCell.style.flex = '0 0 ' + newWidth + 'px';
|
||||
document.querySelectorAll('.grid-row .grid-cell' + sel).forEach(function(cell) {
|
||||
cell.style.flex = '0 0 ' + newWidth + 'px';
|
||||
});
|
||||
var filterCell = document.querySelector('#gridFilterRow > .grid-cell:nth-child(' + (parseInt(columnIndex, 10) + 1) + ')');
|
||||
if (filterCell) filterCell.style.flex = '0 0 ' + newWidth + 'px';
|
||||
}
|
||||
|
||||
function stopResize() {
|
||||
if (currentResizer) {
|
||||
document.removeEventListener('mousemove', resize);
|
||||
document.removeEventListener('mouseup', stopResize);
|
||||
currentResizer = null;
|
||||
columnIndex = null;
|
||||
}
|
||||
}
|
||||
|
||||
resizers.forEach(function(resizer) {
|
||||
resizer.addEventListener('mousedown', function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
currentResizer = this;
|
||||
var header = this.closest('.grid-header');
|
||||
if (!header) return;
|
||||
columnIndex = header.getAttribute('data-index');
|
||||
startX = e.pageX;
|
||||
startWidth = header.offsetWidth;
|
||||
document.addEventListener('mousemove', resize);
|
||||
document.addEventListener('mouseup', stopResize);
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once "class/VisualLimsApiClient.class.php";
|
||||
include('include/headscript.php');
|
||||
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
|
||||
try {
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
$commessaId = 577818;
|
||||
|
||||
$endpoint = "CommessaWeb({$commessaId})?\$expand=CommesseCustomFields(\$expand=CustomField)";
|
||||
|
||||
$result = $api->get($endpoint);
|
||||
|
||||
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
"success" => false,
|
||||
"error" => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
require_once dirname(__FILE__) . '/include/headscript.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
try {
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
// Get all schemas currently used in template_mapping
|
||||
$stmtSchemas = $pdo->query("
|
||||
SELECT DISTINCT schema_id
|
||||
FROM template_mapping
|
||||
WHERE schema_id IS NOT NULL
|
||||
AND schema_id > 0
|
||||
ORDER BY schema_id ASC
|
||||
");
|
||||
|
||||
$schemaIds = $stmtSchemas->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($schemaIds)) {
|
||||
throw new Exception('No schema_id found in template_mapping');
|
||||
}
|
||||
|
||||
$stmtUpdate = $pdo->prepare("
|
||||
UPDATE template_mapping
|
||||
SET field_order = ?
|
||||
WHERE schema_id = ?
|
||||
AND field_id = ?
|
||||
");
|
||||
|
||||
$summary = [];
|
||||
$totalUpdated = 0;
|
||||
|
||||
foreach ($schemaIds as $schemaId) {
|
||||
$schemaId = (int)$schemaId;
|
||||
|
||||
$endpoint = "SchemaCustomField($schemaId)?\$expand=SchemiCustomFieldsDettagli(\$expand=CustomField)";
|
||||
$data = $api->get($endpoint);
|
||||
|
||||
if (empty($data['SchemiCustomFieldsDettagli']) || !is_array($data['SchemiCustomFieldsDettagli'])) {
|
||||
$summary[] = [
|
||||
'schema_id' => $schemaId,
|
||||
'success' => false,
|
||||
'message' => 'No SchemiCustomFieldsDettagli found'
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
$schemaUpdated = 0;
|
||||
$notFound = [];
|
||||
|
||||
foreach ($data['SchemiCustomFieldsDettagli'] as $detail) {
|
||||
$order = intval($detail['Ordine'] ?? 9999);
|
||||
$fieldId = intval($detail['CustomField']['IdCustomField'] ?? 0);
|
||||
|
||||
if ($fieldId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stmtUpdate->execute([
|
||||
$order,
|
||||
$schemaId,
|
||||
$fieldId
|
||||
]);
|
||||
|
||||
if ($stmtUpdate->rowCount() > 0) {
|
||||
$schemaUpdated++;
|
||||
$totalUpdated++;
|
||||
} else {
|
||||
$notFound[] = [
|
||||
'field_id' => $fieldId,
|
||||
'order' => $order,
|
||||
'label' => $detail['CustomField']['Titolo'] ?? ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$summary[] = [
|
||||
'schema_id' => $schemaId,
|
||||
'success' => true,
|
||||
'updated' => $schemaUpdated,
|
||||
'not_found_count' => count($notFound),
|
||||
'not_found' => $notFound
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'schemas_processed' => count($schemaIds),
|
||||
'total_updated' => $totalUpdated,
|
||||
'summary' => $summary
|
||||
], JSON_PRETTY_PRINT);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
], JSON_PRETTY_PRINT);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
include('include/headscript.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
try {
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input) {
|
||||
throw new Exception('Invalid JSON payload');
|
||||
}
|
||||
|
||||
$templateId = isset($input['template_id']) ? (int)$input['template_id'] : 0;
|
||||
$apiSampleJson = $input['api_sample_json'] ?? '';
|
||||
$jsonNodes = $input['json_nodes'] ?? '';
|
||||
|
||||
if ($templateId <= 0) {
|
||||
throw new Exception('Invalid template ID');
|
||||
}
|
||||
|
||||
if (trim($apiSampleJson) === '') {
|
||||
throw new Exception('API sample JSON is empty');
|
||||
}
|
||||
|
||||
json_decode($apiSampleJson, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception('Invalid API sample JSON: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
$decodedNodes = json_decode($jsonNodes, true);
|
||||
|
||||
if (!is_array($decodedNodes)) {
|
||||
throw new Exception('Invalid JSON nodes array');
|
||||
}
|
||||
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE excel_templates
|
||||
SET
|
||||
api_sample_json = ?,
|
||||
json_nodes = ?
|
||||
WHERE id = ?
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$apiSampleJson,
|
||||
json_encode($decodedNodes, JSON_UNESCAPED_UNICODE),
|
||||
$templateId
|
||||
]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'nodes_count' => count($decodedNodes)
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -3,14 +3,18 @@ ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
include('include/headscript.php'); // Assumi che questo includa la connessione DB
|
||||
include('include/headscript.php');
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Recupera il payload JSON
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$template_id = intval($data['template_id']);
|
||||
$mapping_id = intval($data['mapping_id']);
|
||||
$value = intval($data['value']);
|
||||
$template_id = intval($data['template_id'] ?? 0);
|
||||
$mapping_id = intval($data['mapping_id'] ?? 0);
|
||||
$value = intval($data['value'] ?? 0);
|
||||
|
||||
// IMPORTANT: main_field may be ENUM('0','1'), so use string values
|
||||
$mainValue = ($value === 1) ? '1' : '0';
|
||||
|
||||
if ($template_id <= 0 || $mapping_id <= 0) {
|
||||
echo json_encode(['success' => false, 'message' => 'Invalid IDs']);
|
||||
@@ -23,19 +27,47 @@ $pdo = $db->getConnection();
|
||||
try {
|
||||
$pdo->beginTransaction();
|
||||
|
||||
if ($value === 1) {
|
||||
// Setta tutti main_field a 0 per questo template
|
||||
$stmt = $pdo->prepare("UPDATE template_mapping SET main_field = 0 WHERE template_id = ?");
|
||||
$stmt->execute([$template_id]);
|
||||
if ($mainValue === '1') {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT COUNT(*)
|
||||
FROM template_mapping
|
||||
WHERE template_id = ?
|
||||
AND main_field = '1'
|
||||
AND id <> ?
|
||||
");
|
||||
$stmt->execute([$template_id, $mapping_id]);
|
||||
$currentMainCount = (int)$stmt->fetchColumn();
|
||||
|
||||
if ($currentMainCount >= 2) {
|
||||
$pdo->rollBack();
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => 'Maximum 2 Main fields allowed',
|
||||
'currentMainCount' => $currentMainCount
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Setta il valore per questo mapping
|
||||
$stmt = $pdo->prepare("UPDATE template_mapping SET main_field = ? WHERE id = ? AND template_id = ?");
|
||||
$stmt->execute([$value, $mapping_id, $template_id]);
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE template_mapping
|
||||
SET main_field = ?
|
||||
WHERE id = ?
|
||||
AND template_id = ?
|
||||
");
|
||||
$stmt->execute([$mainValue, $mapping_id, $template_id]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
echo json_encode(['success' => true]);
|
||||
} catch (Exception $e) {
|
||||
$pdo->rollBack();
|
||||
echo json_encode(['success' => false, 'message' => $e->getMessage()]);
|
||||
if ($pdo->inTransaction()) {
|
||||
$pdo->rollBack();
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
header('Content-Type: application/json');
|
||||
require_once 'class/db-functions.php';
|
||||
|
||||
// Disabilita l'output di errori HTML
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
@@ -13,108 +12,235 @@ try {
|
||||
throw new Exception("Invalid request method.");
|
||||
}
|
||||
|
||||
// Recupera i dati dal body JSON
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
$template_id = isset($input['template_id']) ? intval($input['template_id']) : null;
|
||||
$schemajson = isset($input['schemajson']) ? trim($input['schemajson']) : '';
|
||||
|
||||
// Controllo sui campi obbligatori
|
||||
$template_id = isset($input['template_id']) ? intval($input['template_id']) : null;
|
||||
$schemajson = isset($input['schemajson']) ? trim($input['schemajson']) : '';
|
||||
|
||||
if (empty($template_id) || empty($schemajson)) {
|
||||
throw new Exception("All fields marked with * are required, including schemajson.");
|
||||
}
|
||||
|
||||
// Validazione del JSON
|
||||
$decoded_json = json_decode($schemajson, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new Exception("Invalid JSON format for schemajson.");
|
||||
}
|
||||
|
||||
// Connessione al database
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
// Recupera il target_table e verifica l'esistenza del template
|
||||
$stmt = $pdo->prepare("SELECT target_table FROM excel_templates WHERE id = ?");
|
||||
$stmt->execute([$template_id]);
|
||||
$template = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$template) {
|
||||
throw new Exception("Template not found.");
|
||||
}
|
||||
|
||||
$target_table = $template['target_table'];
|
||||
|
||||
// Inizia una transazione
|
||||
$pdo->beginTransaction();
|
||||
|
||||
// Aggiorna il schemajson in excel_templates
|
||||
$stmt = $pdo->prepare("UPDATE excel_templates
|
||||
SET schemajson = ?, updated_at = NOW()
|
||||
WHERE id = ?");
|
||||
$stmt->execute([$schemajson, $template_id]);
|
||||
|
||||
// Estrai i campi dallo schema
|
||||
$schema_id = $decoded_json['IdSchemaCustomFields'] ?? null;
|
||||
$schema_fields = $decoded_json['SchemiCustomFieldsDettagli'] ?? [];
|
||||
|
||||
if (empty($schema_id)) {
|
||||
throw new Exception("Schema ID not found in schemajson.");
|
||||
}
|
||||
|
||||
if (empty($schema_fields)) {
|
||||
throw new Exception("No fields found in schema.");
|
||||
}
|
||||
|
||||
// Prepara la query per l'inserimento condizionato in template_mapping
|
||||
$insert_stmt = $pdo->prepare("
|
||||
$pdo->beginTransaction();
|
||||
|
||||
/*
|
||||
* 1) Update raw schema JSON in excel_templates
|
||||
*/
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE excel_templates
|
||||
SET schemajson = ?, updated_at = NOW()
|
||||
WHERE id = ?
|
||||
");
|
||||
$stmt->execute([$schemajson, $template_id]);
|
||||
|
||||
/*
|
||||
* Prepare reusable statements
|
||||
*/
|
||||
|
||||
// Check if mapping already exists
|
||||
$checkStmt = $pdo->prepare("
|
||||
SELECT id, data_type
|
||||
FROM template_mapping
|
||||
WHERE template_id = ?
|
||||
AND field_id = ?
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
// Insert new field
|
||||
$insertStmt = $pdo->prepare("
|
||||
INSERT INTO template_mapping (
|
||||
template_id, schema_id, field_id, data_type, is_required, default_value,
|
||||
has_list, length, decimals, min_value, max_value, default_curr_date,
|
||||
tablename, field_label
|
||||
)
|
||||
SELECT :template_id, :schema_id, :field_id, :data_type, :is_required, :default_value,
|
||||
:has_list, :length, :decimals, :min_value, :max_value, :default_curr_date,
|
||||
:tablename, :field_label
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM template_mapping
|
||||
WHERE template_id = :template_id_check AND field_id = :field_id_check
|
||||
template_id,
|
||||
schema_id,
|
||||
field_id,
|
||||
data_type,
|
||||
is_required,
|
||||
default_value,
|
||||
has_list,
|
||||
length,
|
||||
decimals,
|
||||
min_value,
|
||||
max_value,
|
||||
default_curr_date,
|
||||
tablename,
|
||||
field_label
|
||||
) VALUES (
|
||||
:template_id,
|
||||
:schema_id,
|
||||
:field_id,
|
||||
:data_type,
|
||||
:is_required,
|
||||
:default_value,
|
||||
:has_list,
|
||||
:length,
|
||||
:decimals,
|
||||
:min_value,
|
||||
:max_value,
|
||||
:default_curr_date,
|
||||
:tablename,
|
||||
:field_label
|
||||
)
|
||||
");
|
||||
|
||||
// Itera sui campi dello schema e inserisci quelli mancanti
|
||||
// Update existing field WITHOUT clearing manual_default
|
||||
$updateSameTypeStmt = $pdo->prepare("
|
||||
UPDATE template_mapping
|
||||
SET
|
||||
schema_id = :schema_id,
|
||||
data_type = :data_type,
|
||||
is_required = :is_required,
|
||||
default_value = :default_value,
|
||||
has_list = :has_list,
|
||||
length = :length,
|
||||
decimals = :decimals,
|
||||
min_value = :min_value,
|
||||
max_value = :max_value,
|
||||
default_curr_date = :default_curr_date,
|
||||
tablename = :tablename,
|
||||
field_label = :field_label
|
||||
WHERE id = :id
|
||||
");
|
||||
|
||||
// Update existing field AND clear user-entered value if type changed
|
||||
$updateChangedTypeStmt = $pdo->prepare("
|
||||
UPDATE template_mapping
|
||||
SET
|
||||
schema_id = :schema_id,
|
||||
data_type = :data_type,
|
||||
is_required = :is_required,
|
||||
default_value = :default_value,
|
||||
has_list = :has_list,
|
||||
length = :length,
|
||||
decimals = :decimals,
|
||||
min_value = :min_value,
|
||||
max_value = :max_value,
|
||||
default_curr_date = :default_curr_date,
|
||||
tablename = :tablename,
|
||||
field_label = :field_label,
|
||||
|
||||
manual_default = NULL,
|
||||
auto_value = 'none'
|
||||
|
||||
WHERE id = :id
|
||||
");
|
||||
|
||||
$currentFieldIds = [];
|
||||
|
||||
/*
|
||||
* 2) Insert new fields and update existing fields
|
||||
*/
|
||||
foreach ($schema_fields as $field) {
|
||||
$custom_field = $field['CustomField'] ?? [];
|
||||
|
||||
if (empty($custom_field['IdCustomField'])) {
|
||||
continue; // Salta se manca l'ID del campo
|
||||
continue;
|
||||
}
|
||||
|
||||
$insert_stmt->execute([
|
||||
':template_id' => $template_id,
|
||||
':schema_id' => $schema_id,
|
||||
':field_id' => $custom_field['IdCustomField'],
|
||||
':data_type' => $custom_field['Tipo'] ?? 'Testo',
|
||||
':is_required' => $custom_field['ObbligatorioWeb'] ? 1 : 0,
|
||||
':default_value' => $custom_field['ValoreDefault'] ?? null,
|
||||
':has_list' => $custom_field['Elenco'] ? 1 : 0,
|
||||
':length' => $custom_field['Lunghezza'] ?? 0,
|
||||
':decimals' => $custom_field['Decimali'] ?? 0,
|
||||
':min_value' => $custom_field['Minimo'] ?? null,
|
||||
':max_value' => $custom_field['Massimo'] ?? null,
|
||||
':default_curr_date' => $custom_field['DefaultCurrDate'] ? 1 : 0,
|
||||
':tablename' => $target_table,
|
||||
':field_label' => $custom_field['TitoloTraduzione'] ?? '',
|
||||
':template_id_check' => $template_id,
|
||||
':field_id_check' => $custom_field['IdCustomField']
|
||||
]);
|
||||
$fieldId = (int)$custom_field['IdCustomField'];
|
||||
$currentFieldIds[] = $fieldId;
|
||||
|
||||
$newDataType = $custom_field['Tipo'] ?? 'Testo';
|
||||
|
||||
$data = [
|
||||
':schema_id' => $schema_id,
|
||||
':data_type' => $newDataType,
|
||||
':is_required' => !empty($custom_field['ObbligatorioWeb']) ? 1 : 0,
|
||||
':default_value' => $custom_field['ValoreDefault'] ?? null,
|
||||
':has_list' => !empty($custom_field['Elenco']) ? 1 : 0,
|
||||
':length' => $custom_field['Lunghezza'] ?? 0,
|
||||
':decimals' => $custom_field['Decimali'] ?? 0,
|
||||
':min_value' => $custom_field['Minimo'] ?? null,
|
||||
':max_value' => $custom_field['Massimo'] ?? null,
|
||||
':default_curr_date' => !empty($custom_field['DefaultCurrDate']) ? 1 : 0,
|
||||
':tablename' => $target_table,
|
||||
':field_label' => $custom_field['TitoloTraduzione'] ?? ''
|
||||
];
|
||||
|
||||
$checkStmt->execute([$template_id, $fieldId]);
|
||||
$existing = $checkStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$existing) {
|
||||
// New field: insert it
|
||||
$insertData = $data;
|
||||
$insertData[':template_id'] = $template_id;
|
||||
$insertData[':field_id'] = $fieldId;
|
||||
|
||||
$insertStmt->execute($insertData);
|
||||
} else {
|
||||
// Existing field: update schema details
|
||||
$oldDataType = $existing['data_type'] ?? '';
|
||||
|
||||
$updateData = $data;
|
||||
$updateData[':id'] = (int)$existing['id'];
|
||||
|
||||
if ($oldDataType !== $newDataType) {
|
||||
// Type changed: clear manual value because it may no longer be valid
|
||||
$updateChangedTypeStmt->execute($updateData);
|
||||
} else {
|
||||
// Same type: keep manual_default, excel/json mapping, visibility, main field, etc.
|
||||
$updateSameTypeStmt->execute($updateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 3) Remove fields no longer present in the schema
|
||||
*/
|
||||
$currentFieldIds = array_values(array_unique(array_filter($currentFieldIds)));
|
||||
|
||||
if (!empty($currentFieldIds)) {
|
||||
$placeholders = implode(',', array_fill(0, count($currentFieldIds), '?'));
|
||||
|
||||
$deleteParams = array_merge([$template_id], $currentFieldIds);
|
||||
|
||||
$deleteStmt = $pdo->prepare("
|
||||
DELETE FROM template_mapping
|
||||
WHERE template_id = ?
|
||||
AND field_id NOT IN ($placeholders)
|
||||
");
|
||||
$deleteStmt->execute($deleteParams);
|
||||
}
|
||||
|
||||
// Commit della transazione
|
||||
$pdo->commit();
|
||||
|
||||
$response["success"] = true;
|
||||
$response["message"] = "Schema JSON updated and template mappings created successfully!";
|
||||
$response["message"] = "Schema JSON updated, mappings synchronized, removed fields deleted, and changed fields updated successfully.";
|
||||
|
||||
} catch (Exception $e) {
|
||||
if (isset($pdo) && $pdo->inTransaction()) {
|
||||
$pdo->rollback();
|
||||
}
|
||||
|
||||
$response["message"] = $e->getMessage();
|
||||
}
|
||||
|
||||
// Restituisce un JSON per il fetch
|
||||
echo json_encode($response);
|
||||
echo json_encode($response);
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
|
||||
require_once dirname(__FILE__) . '/class/VisualLimsApiClient.class.php';
|
||||
require_once dirname(__FILE__) . '/include/headscript.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
ini_set('display_errors', '0');
|
||||
error_reporting(E_ALL);
|
||||
|
||||
try {
|
||||
$schemaId = isset($_GET['schema_id']) && is_numeric($_GET['schema_id'])
|
||||
? intval($_GET['schema_id'])
|
||||
: 0;
|
||||
|
||||
if ($schemaId <= 0) {
|
||||
throw new Exception('Missing or invalid schema_id');
|
||||
}
|
||||
|
||||
$api = VisualLimsApiClient::getInstance();
|
||||
|
||||
$endpoint = "SchemaCustomField($schemaId)?\$expand=SchemiCustomFieldsDettagli(\$expand=CustomField)";
|
||||
$data = $api->get($endpoint);
|
||||
|
||||
if (empty($data['SchemiCustomFieldsDettagli']) || !is_array($data['SchemiCustomFieldsDettagli'])) {
|
||||
throw new Exception('No SchemiCustomFieldsDettagli found');
|
||||
}
|
||||
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$updated = 0;
|
||||
$notFound = [];
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
UPDATE template_mapping
|
||||
SET field_order = ?
|
||||
WHERE schema_id = ?
|
||||
AND field_id = ?
|
||||
");
|
||||
|
||||
foreach ($data['SchemiCustomFieldsDettagli'] as $detail) {
|
||||
$order = intval($detail['Ordine'] ?? 9999);
|
||||
$fieldId = intval($detail['CustomField']['IdCustomField'] ?? 0);
|
||||
|
||||
if ($fieldId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stmt->execute([
|
||||
$order,
|
||||
$schemaId,
|
||||
$fieldId
|
||||
]);
|
||||
|
||||
if ($stmt->rowCount() > 0) {
|
||||
$updated++;
|
||||
} else {
|
||||
$notFound[] = [
|
||||
'field_id' => $fieldId,
|
||||
'order' => $order,
|
||||
'label' => $detail['CustomField']['Titolo'] ?? ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'schema_id' => $schemaId,
|
||||
'updated' => $updated,
|
||||
'not_found' => $notFound
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
@@ -6,22 +6,57 @@ error_reporting(E_ALL);
|
||||
|
||||
require_once(__DIR__ . '/class/db-functions.php');
|
||||
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
|
||||
if (!$data || !isset($data['template_id'], $data['xls_headers'])) {
|
||||
echo json_encode(["success" => false, "message" => "Invalid or missing data"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$templateId = $data['template_id'];
|
||||
$xlsHeaders = $data['xls_headers'];
|
||||
$headerRow = isset($data['header_row']) ? (int)$data['header_row'] : null;
|
||||
$startColumn = isset($data['start_column']) ? (int)$data['start_column'] : null;
|
||||
|
||||
try {
|
||||
$db = DBHandlerSelect::getInstance();
|
||||
$pdo = $db->getConnection();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input"), true);
|
||||
|
||||
if (!$data || !isset($data['template_id'], $data['xls_headers'])) {
|
||||
echo json_encode(["success" => false, "message" => "Invalid or missing data"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$templateId = (int)$data['template_id'];
|
||||
$xlsHeaders = $data['xls_headers'];
|
||||
|
||||
$headerRow = isset($data['header_row']) && $data['header_row'] !== ''
|
||||
? (int)$data['header_row']
|
||||
: null;
|
||||
|
||||
$startColumn = isset($data['start_column']) && $data['start_column'] !== ''
|
||||
? (int)$data['start_column']
|
||||
: null;
|
||||
|
||||
$xlsSheetIndex = isset($data['xls_sheet_index']) && $data['xls_sheet_index'] !== ''
|
||||
? (int)$data['xls_sheet_index']
|
||||
: null;
|
||||
|
||||
if ($templateId <= 0) {
|
||||
echo json_encode(["success" => false, "message" => "Invalid template ID"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($xlsHeaders === '') {
|
||||
echo json_encode(["success" => false, "message" => "XLS headers cannot be empty"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($headerRow !== null && $headerRow <= 0) {
|
||||
echo json_encode(["success" => false, "message" => "Header row must be greater than 0"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($startColumn !== null && $startColumn <= 0) {
|
||||
echo json_encode(["success" => false, "message" => "Start column must be greater than 0"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($xlsSheetIndex !== null && $xlsSheetIndex < 0) {
|
||||
echo json_encode(["success" => false, "message" => "XLS sheet number cannot be negative"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$sql = "UPDATE excel_templates SET xls_headers = ?";
|
||||
$params = [$xlsHeaders];
|
||||
|
||||
@@ -29,11 +64,18 @@ try {
|
||||
$sql .= ", header_row = ?";
|
||||
$params[] = $headerRow;
|
||||
}
|
||||
|
||||
if ($startColumn !== null) {
|
||||
$sql .= ", start_column = ?";
|
||||
$params[] = $startColumn;
|
||||
}
|
||||
|
||||
if ($xlsSheetIndex !== null) {
|
||||
$sql .= ", xls_sheet_index = ?";
|
||||
$params[] = $xlsSheetIndex;
|
||||
}
|
||||
|
||||
$sql .= ", updated_at = NOW()";
|
||||
$sql .= " WHERE id = ?";
|
||||
$params[] = $templateId;
|
||||
|
||||
@@ -45,8 +87,18 @@ try {
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(["success" => true, "message" => "XLS headers saved successfully"]);
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"message" => "XLS headers saved successfully",
|
||||
"debug" => [
|
||||
"template_id" => $templateId,
|
||||
"header_row" => $headerRow,
|
||||
"start_column" => $startColumn,
|
||||
"xls_sheet_index" => $xlsSheetIndex
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(["success" => false, "message" => "Error: " . $e->getMessage()]);
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
Reference in New Issue
Block a user