extrac prices pdfco

This commit is contained in:
Claudio 2025-11-17 15:05:10 +01:00
parent 6b65b31932
commit 1804605cad
16 changed files with 1131 additions and 330 deletions

View File

@ -108,9 +108,9 @@ class LoginController extends Controller
// Reindirizza in base al ruolo // Reindirizza in base al ruolo
if ($user->hasRole('Admin')) { if ($user->hasRole('Admin')) {
return redirect()->to('userarea/import_dashboard.php'); return redirect()->to('userarea/import_xls.php');
} elseif ($user->hasRole('User')) { } elseif ($user->hasRole('User')) {
return redirect()->to('userarea/import_dashboard.php'); return redirect()->to('userarea/import_xls.php');
} }
// Se il ruolo non è specificato, reindirizza alla home predefinita // Se il ruolo non è specificato, reindirizza alla home predefinita

View File

@ -47,6 +47,7 @@
"smalot/pdfparser": "^2.12", "smalot/pdfparser": "^2.12",
"socialiteproviders/microsoft": "^4.7", "socialiteproviders/microsoft": "^4.7",
"spatie/laravel-query-builder": "^5.0", "spatie/laravel-query-builder": "^5.0",
"spatie/pdf-to-text": "^1.54",
"vanguardapp/activity-log": "^6.0", "vanguardapp/activity-log": "^6.0",
"vanguardapp/announcements": "^6.0", "vanguardapp/announcements": "^6.0",
"vanguardapp/plugins": "^6.0", "vanguardapp/plugins": "^6.0",

60
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "97382155de516648df8ab1f1c50ab1d5", "content-hash": "d55364bafa22e40dedff454d54442d59",
"packages": [ "packages": [
{ {
"name": "akaunting/laravel-setting", "name": "akaunting/laravel-setting",
@ -5290,6 +5290,64 @@
], ],
"time": "2024-05-10T08:19:35+00:00" "time": "2024-05-10T08:19:35+00:00"
}, },
{
"name": "spatie/pdf-to-text",
"version": "1.54.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/pdf-to-text.git",
"reference": "ad2a792c7e1e68f1bc9b038dc73cdcf760595fbb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/pdf-to-text/zipball/ad2a792c7e1e68f1bc9b038dc73cdcf760595fbb",
"reference": "ad2a792c7e1e68f1bc9b038dc73cdcf760595fbb",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0",
"symfony/process": "^4.0|^5.0|^6.0|^7.0"
},
"require-dev": {
"pestphp/pest-plugin-laravel": "^1.3",
"phpunit/phpunit": "^9.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\PdfToText\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Extract text from a pdf",
"homepage": "https://github.com/spatie/pdf-to-text",
"keywords": [
"pdf-to-text",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/pdf-to-text/issues",
"source": "https://github.com/spatie/pdf-to-text/tree/1.54.1"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
}
],
"time": "2025-06-13T15:23:19+00:00"
},
{ {
"name": "symfony/clock", "name": "symfony/clock",
"version": "v7.1.1", "version": "v7.1.1",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1,73 @@
<?php
require_once __DIR__ . "/../../vendor/autoload.php";
use Spatie\PdfToText\Pdf;
// ---------------------------------------------------------
// 1) Percorso PDF
// ---------------------------------------------------------
$pdfFile = __DIR__ . "/listino.pdf";
if (!file_exists($pdfFile)) {
die(json_encode(["error" => "PDF non trovato: $pdfFile"]));
}
// ---------------------------------------------------------
// 2) Estrazione testo usando Poppler
// ---------------------------------------------------------
$poppler = 'C:\poppler\Library\bin\pdftotext.exe';
try {
$text = Pdf::getText($pdfFile, $poppler);
} catch (Exception $e) {
die(json_encode(["error" => $e->getMessage()]));
}
// ---------------------------------------------------------
// 3) Normalizzazione
// ---------------------------------------------------------
$text = preg_replace('/[ ]{2,}/', ' ', $text);
$text = str_replace("\t", " ", $text);
// ---------------------------------------------------------
// 4) REGEX PER IL LISTINO CASSINA
// ---------------------------------------------------------
$regex = '/(\d{3}\s+\d{2,3}\s*[A-Z]?)\s+([^0-9]+?)\s+((?:\d{1,2}[.,]\d{2,3}\s+)+)/';
preg_match_all($regex, $text, $matches, PREG_SET_ORDER);
$results = [];
foreach ($matches as $m) {
$codice = trim($m[1]);
$descr = trim($m[2]);
$priceChunk = trim($m[3]);
// Trova tutti i prezzi
preg_match_all('/\d{1,2}[.,]\d{2,3}/', $priceChunk, $nums);
foreach ($nums[0] as $i => $val) {
// 10,80 → 10.80
$val = str_replace(',', '.', $val);
$results[] = [
"codice" => $codice,
"descrizione" => $descr,
"colonna" => "COL_" . ($i + 1),
"prezzo" => floatval($val)
];
}
}
// ---------------------------------------------------------
// 5) Output JSON
// ---------------------------------------------------------
header("Content-Type: application/json");
echo json_encode([
"items" => count($results),
"data" => $results
], JSON_PRETTY_PRINT);

View File

@ -0,0 +1,218 @@
<?php
header("Content-Type: application/json; charset=utf-8");
// ---------------------------------------------------------
// 1) CONFIGURAZIONE
// ---------------------------------------------------------
$apiKey = "info@claudiosironi.com_Qfh02D7sAvi2tcx3ZchHpusNaBquCKhJw81fEnkHe2ersQDVOex4IokhCCzaFAz1";
$fileToken = "filetoken://37bb07a8561409d281b32bcd023ff3dc33de92a8f65aae5af7";
// opzionale: passare un token via GET
if (isset($_GET['token']) && $_GET['token'] !== "") {
$fileToken = $_GET['token'];
}
// ---------------------------------------------------------
// 2) PDF.CO → JSON2
// ---------------------------------------------------------
$endpoint = "https://api.pdf.co/v1/pdf/convert/to/json2";
$payload = [
"url" => $fileToken,
"inline" => true,
"detectTables" => true,
"pages" => ""
];
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"x-api-key: $apiKey"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (!$response) {
echo json_encode(["error" => "Errore CURL: " . curl_error($ch)]);
curl_close($ch);
exit;
}
curl_close($ch);
$json = json_decode($response, true);
if (!isset($json["body"]["document"]["page"]["row"])) {
echo json_encode([
"error" => true,
"message" => "JSON PDF.co non contiene tabelle utili",
"raw" => $json
]);
exit;
}
$rows = $json["body"]["document"]["page"]["row"];
// ---------------------------------------------------------
// 3) FUNZIONI UTILITARIE
// ---------------------------------------------------------
// Formato A 291 02F
function isCodeTypeA($txt)
{
return preg_match('/^\d{3}\s+[0-9A-Z]{2,3}$/', $txt);
}
// Estrai blocchi tipo "499 3A 14 5.475"
function extractTypeCBlocks($line)
{
preg_match_all(
'/(\d{3})\s+([0-9A-Z]{1,3})\s+(\d{2})\s+(\d{1,2}\.\d{3})/',
$line,
$m,
PREG_SET_ORDER
);
$out = [];
foreach ($m as $b) {
$out[] = [
"codice" => $b[1],
"variante" => $b[2],
"dimensione" => $b[3],
"prezzo" => floatval(str_replace(".", "", $b[4]))
];
}
return $out;
}
// ---------------------------------------------------------
// 4) PARSER UNICO (GESTISCE TUTTI I FORMATI)
// ---------------------------------------------------------
$items = [];
$currentCode = null;
$currentDesc = "";
$currentPrices = [];
foreach ($rows as $row) {
// Ricostruisco la riga
$line = "";
foreach ($row["column"] as $col) {
if (!isset($col["text"]["text"])) continue;
$t = trim($col["text"]["text"]);
if ($t !== "") $line .= " " . $t;
}
$line = trim($line);
if ($line === "") continue;
// ---------------------------------------------------------
// 4A — FORMATO CASSINA A (FIX prezzi concatenati)
// ---------------------------------------------------------
if (preg_match('/^(\d{3}\s+[0-9A-Z]{2,3})\s+(.*)$/', $line, $mA)) {
$codice = trim($mA[1]);
$resto = trim($mA[2]);
// Estrae TUTTI i prezzi concatenati (es. "10,808.9906.460...")
preg_match_all('/\d{1,2}[.,]\d{2,3}/', $resto, $matchesPrezzi);
if (count($matchesPrezzi[0]) >= 2) {
// Descrizione SENZA prezzi
$descr = trim(preg_replace('/\d{1,2}[.,]\d{2,3}/', '', $resto));
// Conversione valori
$vals = array_map(function ($v) {
return floatval(str_replace(",", ".", str_replace(".", "", $v)));
}, $matchesPrezzi[0]);
$items[] = [
"type" => "A",
"codice" => $codice,
"descrizione" => $descr,
"prezzi" => $vals
];
continue;
}
}
// ---------------------------------------------------------
// 4B — FORMATO VECCHIO CASSINA (003 BC cromata … 8 prezzi)
// ---------------------------------------------------------
if (preg_match(
'/^(\d{3}\s+[A-Z]{1,3})\s+([A-Za-zÀ-ù\s]+?)\s+((?:\d{1,2}[.,]\d{2,3}\s*){8,})$/',
$line,
$mB
)) {
$codice = trim($mB[1]);
$descr = trim($mB[2]);
$valuesString = trim($mB[3]);
preg_match_all('/\d{1,2}[.,]\d{2,3}/', $valuesString, $vv);
$vals = array_map(function ($v) {
return floatval(str_replace(",", ".", str_replace(".", "", $v)));
}, $vv[0]);
$categorie = ["Z", "Y", "X", "O", "L", "F/COL", "E/COM", "ALTRO"];
$prezziMappati = [];
foreach ($vals as $i => $v) {
$key = $categorie[$i] ?? ("COL_" . ($i + 1));
$prezziMappati[$key] = $v;
}
$items[] = [
"type" => "B1",
"codice" => $codice,
"descrizione" => $descr,
"prezzi" => $prezziMappati
];
continue;
}
// ---------------------------------------------------------
// 4C — FORMATO LISTINO2 (499 3A 14 5.475 ripetuti)
// ---------------------------------------------------------
$blocks = extractTypeCBlocks($line);
if (!empty($blocks)) {
$desc = $line;
foreach ($blocks as $b) {
$pattern = sprintf(
'/%s\s+%s\s+%s\s+\d{1,2}\.\d{3}/',
$b["codice"],
$b["variante"],
$b["dimensione"]
);
$desc = preg_replace($pattern, "", $desc);
}
$desc = trim($desc);
$items[] = [
"type" => "C",
"descrizione" => $desc,
"varianti" => $blocks
];
continue;
}
}
// ---------------------------------------------------------
// 5) OUTPUT FINALE
// ---------------------------------------------------------
echo json_encode([
"status" => "ok",
"total" => count($items),
"items" => $items
], JSON_PRETTY_PRINT);

View File

@ -0,0 +1,137 @@
<?php
header("Content-Type: application/json; charset=utf-8");
/******************************************************
* CONFIG
******************************************************/
$apiKey = "info@claudiosironi.com_Qfh02D7sAvi2tcx3ZchHpusNaBquCKhJw81fEnkHe2ersQDVOex4IokhCCzaFAz1";
$fileToken = "filetoken://61a780917907f86a340290d22c449357dc68950e9066bd67b2";
// Se passi ?token=xxx usa quello
if (!empty($_GET['token'])) {
$fileToken = $_GET['token'];
}
/******************************************************
* 1) ESTRAGGO CSV (solo pagine con tabelle vere)
******************************************************/
$endpointCsv = "https://api.pdf.co/v1/pdf/convert/to/csv";
$payloadCsv = [
"url" => $fileToken,
"inline" => true,
"detectTables" => true,
"pages" => "all",
"extractColumnBy" => "vertical_lines",
"csvSeparator" => ";"
];
$ch = curl_init($endpointCsv);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"x-api-key: $apiKey"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadCsv));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$csvResponse = curl_exec($ch);
curl_close($ch);
$csvJson = json_decode($csvResponse, true);
/******************************************************
* 2) ESTRAGGO ANCHE TESTO (per pagine NON tabellari)
******************************************************/
$endpointText = "https://api.pdf.co/v1/pdf/convert/to/text";
$payloadText = [
"url" => $fileToken,
"inline" => true,
"pages" => "all"
];
$ch = curl_init($endpointText);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"x-api-key: $apiKey"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payloadText));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$textResponse = curl_exec($ch);
curl_close($ch);
$textJson = json_decode($textResponse, true);
/******************************************************
* 3) PARSER CSV TABELLE FORMATO A
******************************************************/
$formatoA = [];
if (isset($csvJson["csv"])) {
$rows = explode("\n", $csvJson["csv"]);
foreach ($rows as $r) {
$cols = str_getcsv($r, ";");
if (count($cols) < 2) continue;
if (!preg_match('/^\d{3}\s+[0-9A-Z]{2,3}$/', trim($cols[0]))) continue;
$formatoA[] = [
"codice" => trim($cols[0]),
"descrizione" => trim($cols[1]),
"prezzi" => array_map(fn($x) => is_numeric(str_replace(",", ".", $x))
? floatval(str_replace(",", ".", $x))
: null, array_slice($cols, 2))
];
}
}
/******************************************************
* 4) PARSER TESTUALE FORMATO B (pagina tipo 499)
******************************************************/
$formatoB = [];
if (isset($textJson["body"])) {
$text = $textJson["body"];
// pattern codice tipo “499 3A 14”
$codeRegex = '/\b(\d{3}\s+[A-Z0-9]{1,2}\s+\d{2})\b/';
// pattern prezzo “5.475” o “5475” o “6.085”
$priceRegex = '/\b\d{1,2}\.?\d{3}\b/';
// Cerca combinazioni CODICE + PREZZO ripetute sulla stessa riga
$linee = explode("\n", $text);
foreach ($linee as $line) {
if (!preg_match_all($codeRegex, $line, $codici)) continue;
if (!preg_match_all($priceRegex, $line, $prezzi)) continue;
$codici = $codici[1];
$prezzi = $prezzi[0];
// Se quantità corrispondono → coppie 1:1
if (count($codici) == count($prezzi)) {
for ($i = 0; $i < count($codici); $i++) {
$formatoB[] = [
"codice" => $codici[$i],
"prezzo" => floatval(str_replace(".", "", $prezzi[$i]))
];
}
}
}
}
/******************************************************
* 5) OUTPUT
******************************************************/
echo json_encode([
"status" => "ok",
"formatoA" => $formatoA,
"formatoB" => $formatoB,
], JSON_PRETTY_PRINT);

View File

@ -45,139 +45,140 @@ $pdo = $db->getConnection();
</head> </head>
<body> <body>
<?php include('include/navbar.php'); ?> <div class="wrapper">
<div class="dashboard-main-wrapper wrapper"> <?php include('include/navbar.php'); ?>
<?php include('include/topbar.php'); ?> <div class="dashboard-main-wrapper wrapper">
<?php include('include/topbar.php'); ?>
<div class="dashboard-body"> <div class="dashboard-body">
<div class="card shadow-sm"> <div class="card shadow-sm">
<div class="card-body"> <div class="card-body">
<div class="d-flex flex-wrap justify-content-between align-items-center mb-3"> <div class="d-flex flex-wrap justify-content-between align-items-center mb-3">
<h4 class="mb-0">Imported Data</h4> <h4 class="mb-0">Imported Data</h4>
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<label for="clientFilter" class="form-label mb-0 me-2">Filter by Client:</label> <label for="clientFilter" class="form-label mb-0 me-2">Filter by Client:</label>
<select id="clientFilter" class="form-select form-select-sm" style="width: 220px;"> <select id="clientFilter" class="form-select form-select-sm" style="width: 220px;">
<option value="">All Clients</option> <option value="">All Clients</option>
<?php <?php
// carichiamo i clienti dal DB // carichiamo i clienti dal DB
$clients = $pdo->query("SELECT idclient, client_name FROM clients ORDER BY client_name")->fetchAll(PDO::FETCH_ASSOC); $clients = $pdo->query("SELECT idclient, client_name FROM clients ORDER BY client_name")->fetchAll(PDO::FETCH_ASSOC);
foreach ($clients as $cl) { foreach ($clients as $cl) {
echo '<option value="' . htmlspecialchars($cl['idclient']) . '">' . htmlspecialchars($cl['client_name']) . '</option>'; echo '<option value="' . htmlspecialchars($cl['idclient']) . '">' . htmlspecialchars($cl['client_name']) . '</option>';
} }
?> ?>
</select> </select>
</div>
</div> </div>
</div> <div class="d-flex justify-content-between align-items-center mb-2">
<div class="d-flex justify-content-between align-items-center mb-2"> <div>
<div> <button id="updatePricesBtn" class="btn btn-warning btn-sm" style="display:none;">
<button id="updatePricesBtn" class="btn btn-warning btn-sm" style="display:none;"> <i class="fas fa-sync-alt"></i> Aggiorna Prezzi
<i class="fas fa-sync-alt"></i> Aggiorna Prezzi </button>
</button> </div>
</div> </div>
</div>
<div class="table-responsive"> <div class="table-responsive">
<table id="importTable" class="table table-striped table-bordered w-100"> <table id="importTable" class="table table-striped table-bordered w-100">
<thead> <thead>
<tr> <tr>
<th></th> <!-- Checkbox column --> <th></th> <!-- Checkbox column -->
<th>ID</th> <th>ID</th>
<th>Collection</th> <th>Collection</th>
<th>Category</th> <th>Category</th>
<th>Model</th> <th>Model</th>
<th>Model Description</th> <th>Model Description</th>
<th>Mod.Code</th> <th>Mod.Code</th>
<th>Description</th> <th>Description</th>
<th>Finish.Code</th> <th>Finish.Code</th>
<th>Finishing Description</th> <th>Finishing Description</th>
<th>Available Qty</th> <th>Available Qty</th>
<th>WH</th> <th>WH</th>
<th>Price</th> <th>Price</th>
<th>Actions</th> <th>Actions</th>
</tr> </tr>
</thead> </thead>
</table> </table>
</div>
</div> </div>
</div> </div>
</div> </div>
<?php include('include/footer.php'); ?>
</div> </div>
<?php include('include/footer.php'); ?> <?php include('jsinclude.php'); ?>
</div> <script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
<?php include('jsinclude.php'); ?> <script>
<script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script> $(document).ready(function() {
<script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script> let table = $('#importTable').DataTable({
ajax: {
<script> url: 'import_list_data.php',
$(document).ready(function() { dataSrc: ''
let table = $('#importTable').DataTable({
ajax: {
url: 'import_list_data.php',
dataSrc: ''
},
columns: [{
data: null,
orderable: false,
searchable: false,
className: 'text-center',
render: function() {
return '<input type="checkbox" class="row-check" />';
}
}, },
{ columns: [{
data: 'id' data: null,
}, orderable: false,
{ searchable: false,
data: 'collection', className: 'text-center',
className: 'editable' render: function() {
}, return '<input type="checkbox" class="row-check" />';
{ }
data: 'category', },
className: 'noedit text-muted' {
}, data: 'id'
{ },
data: 'model', {
className: 'editable' data: 'collection',
}, className: 'editable'
{ },
data: 'model_description', {
className: 'editable' data: 'category',
}, className: 'noedit text-muted'
{ },
data: 'mod_code', {
className: 'text-muted' data: 'model',
}, className: 'editable'
{ },
data: 'description', {
className: 'editable' data: 'model_description',
}, className: 'editable'
{ },
data: 'finish_code', {
className: 'editable' data: 'mod_code',
}, className: 'text-muted'
{ },
data: 'finishing_description', {
className: 'editable' data: 'description',
}, className: 'editable'
{ },
data: 'available_qty', {
className: 'editable' data: 'finish_code',
}, className: 'editable'
{ },
data: 'wh', {
className: 'editable' data: 'finishing_description',
}, className: 'editable'
{ },
data: 'price', {
className: 'editable' data: 'available_qty',
}, className: 'editable'
{ },
data: null, {
orderable: false, data: 'wh',
render: function(data, type, row) { className: 'editable'
return ` },
{
data: 'price',
className: 'editable'
},
{
data: null,
orderable: false,
render: function(data, type, row) {
return `
<button class="btn btn-success btn-sm action-btn save-row" data-id="${row.id}"> <button class="btn btn-success btn-sm action-btn save-row" data-id="${row.id}">
<i class="fas fa-save"></i> <i class="fas fa-save"></i>
</button> </button>
@ -185,228 +186,239 @@ $pdo = $db->getConnection();
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button> </button>
`; `;
}
} }
} ],
], pageLength: 25,
pageLength: 25, order: [
order: [ [1, 'desc']
[1, 'desc'] ],
], responsive: true,
responsive: true, autoWidth: false
autoWidth: false });
});
// === Filtro per Client === // === Filtro per Client ===
$('#clientFilter').on('change', function() { $('#clientFilter').on('change', function() {
const selectedClient = $(this).val(); const selectedClient = $(this).val();
// Ricarica i dati passando il parametro idclient // Ricarica i dati passando il parametro idclient
table.ajax.url('import_list_data.php' + (selectedClient ? '?idclient=' + selectedClient : '')).load(); table.ajax.url('import_list_data.php' + (selectedClient ? '?idclient=' + selectedClient : '')).load();
}); });
// === Inline Editing === // === Inline Editing ===
$('#importTable tbody').on('click', 'td.editable', function() { $('#importTable tbody').on('click', 'td.editable', function() {
const cell = table.cell(this); const cell = table.cell(this);
const rowData = table.row(cell.index().row).data(); const rowData = table.row(cell.index().row).data();
const colName = table.column(cell.index().column).dataSrc(); const colName = table.column(cell.index().column).dataSrc();
const originalValue = rowData[colName] ?? ''; const originalValue = rowData[colName] ?? '';
// Evita doppia apertura // Evita doppia apertura
if ($(this).find('input').length > 0) return; if ($(this).find('input').length > 0) return;
const input = $('<input type="text" class="form-control form-control-sm"/>') const input = $('<input type="text" class="form-control form-control-sm"/>')
.val(originalValue) .val(originalValue)
.css({ .css({
'min-width': '120px', 'min-width': '120px',
'height': '24px', 'height': '24px',
'padding': '0 4px' 'padding': '0 4px'
});
$(this).html(input);
input.focus();
// Gestione conferma o uscita
input.on('blur keydown', function(e) {
if (e.type === 'blur' || e.key === 'Enter') {
const newValue = $(this).val();
// Aggiorna in memoria e interfaccia
rowData[colName] = newValue;
table.row(cell.index().row).data(rowData).draw(false);
const td = $(table.cell(cell.index()).node());
if (newValue !== originalValue) {
td.addClass('modified'); // evidenzia modificato
} else {
td.removeClass('modified');
}
}
});
});
// === Salvataggio singola riga ===
$('#importTable').on('click', '.save-row', function() {
const rowData = table.row($(this).closest('tr')).data();
$.ajax({
url: 'import_list_save.php',
type: 'POST',
data: {
row: JSON.stringify(rowData)
},
success: function(res) {
Swal.close();
// Se il server restituisce già JSON, non serve JSON.parse()
let json = (typeof res === 'object') ? res : null;
if (!json) {
try {
json = JSON.parse(res);
} catch (e) {
console.error("Invalid JSON from server:", res);
Swal.fire('Errore', 'Risposta non valida dal server.', 'error');
return;
}
}
if (json.status === 'ok') {
Swal.fire({
title: 'Successo',
text: `Aggiornati ${json.updated} prezzi.`,
icon: 'success',
timer: 2000,
showConfirmButton: false
});
table.ajax.reload(null, false); // 🔁 aggiorna tabella
} else {
console.error("Errore backend:", json);
Swal.fire('Errore', json.message || 'Errore sconosciuto.', 'error');
}
},
error: function(xhr, status, err) {
Swal.fire('Errore', 'Errore di rete o server non raggiungibile.', 'error');
console.error("AJAX error:", status, err, xhr.responseText);
}
});
});
// === Eliminazione con conferma ===
$('#importTable').on('click', '.delete-row', function() {
const id = $(this).data('id');
Swal.fire({
title: 'Are you sure?',
text: 'This record will be permanently deleted.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, delete it',
cancelButtonText: 'Cancel'
}).then(result => {
if (result.isConfirmed) {
$.ajax({
url: 'import_list_delete.php',
type: 'POST',
data: {
id: id
},
success: function(res) {
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Error', res, 'error');
return;
}
if (json.status === 'ok') {
Swal.fire('Deleted', 'Row removed successfully.', 'success');
table.ajax.reload(null, false);
} else {
Swal.fire('Error', json.message, 'error');
}
},
error: function() {
Swal.fire('Error', 'Request failed.', 'error');
}
});
}
});
});
// === Gestione selezione righe e pulsante Aggiorna Prezzi ===
$('#importTable').on('change', '.row-check', function() {
const checkedCount = $('#importTable .row-check:checked').length;
$('#updatePricesBtn').toggle(checkedCount > 0);
});
// === Aggiornamento prezzi con ChatPDF ===
$('#updatePricesBtn').on('click', function() {
const selectedRows = [];
$('#importTable .row-check:checked').each(function() {
const row = table.row($(this).closest('tr')).data();
if (row) selectedRows.push(row);
}); });
$(this).html(input); if (selectedRows.length === 0) {
input.focus(); Swal.fire('Notice', 'Seleziona almeno una riga.', 'info');
return;
// Gestione conferma o uscita
input.on('blur keydown', function(e) {
if (e.type === 'blur' || e.key === 'Enter') {
const newValue = $(this).val();
// Aggiorna in memoria e interfaccia
rowData[colName] = newValue;
table.row(cell.index().row).data(rowData).draw(false);
const td = $(table.cell(cell.index()).node());
if (newValue !== originalValue) {
td.addClass('modified'); // evidenzia modificato
} else {
td.removeClass('modified');
}
} }
});
});
// === Salvataggio singola riga === const clientId = $('#clientFilter').val();
$('#importTable').on('click', '.save-row', function() { if (!clientId) {
const rowData = table.row($(this).closest('tr')).data(); Swal.fire('Notice', 'Seleziona prima un cliente.', 'info');
$.ajax({ return;
url: 'import_list_save.php', }
type: 'POST',
data: {
row: JSON.stringify(rowData)
},
success: function(res) {
Swal.close();
// Se il server restituisce già JSON, non serve JSON.parse() Swal.fire({
let json = (typeof res === 'object') ? res : null; title: 'Aggiornare i prezzi?',
if (!json) { text: `Verranno interrogati i prezzi per ${selectedRows.length} prodotti.`,
try { icon: 'question',
json = JSON.parse(res); showCancelButton: true,
} catch (e) { confirmButtonText: 'Procedi',
console.error("Invalid JSON from server:", res); cancelButtonText: 'Annulla'
Swal.fire('Errore', 'Risposta non valida dal server.', 'error'); }).then(result => {
return; if (result.isConfirmed) {
}
}
if (json.status === 'ok') {
Swal.fire({ Swal.fire({
title: 'Successo', title: 'Aggiornamento in corso...',
text: `Aggiornati ${json.updated} prezzi.`, allowOutsideClick: false,
icon: 'success', didOpen: () => Swal.showLoading()
timer: 2000, });
showConfirmButton: false
$.ajax({
url: 'update_prices_chatpdf.php',
type: 'POST',
data: {
rows: JSON.stringify(selectedRows),
idclient: clientId
},
success: function(res) {
Swal.close();
// Se il server ha già restituito un oggetto JSON, usiamolo direttamente
let json = (typeof res === "object") ? res : null;
// Se invece è una stringa JSON, parse
if (!json) {
try {
json = JSON.parse(res);
} catch (e) {
console.error("JSON parse error:", res);
Swal.fire('Errore', 'Risposta non valida dal server.', 'error');
return;
}
}
// Ora siamo sicuri che json è un oggetto valido
if (json.status === 'ok') {
Swal.fire('Successo', `Aggiornati ${json.updated} prezzi.`, 'success');
table.ajax.reload(null, false);
} else {
Swal.fire('Errore', json.message || 'Errore sconosciuto.', 'error');
}
},
error: function() {
Swal.fire('Errore', 'Aggiornamento fallito.', 'error');
}
}); });
table.ajax.reload(null, false); // 🔁 aggiorna tabella
} else {
console.error("Errore backend:", json);
Swal.fire('Errore', json.message || 'Errore sconosciuto.', 'error');
} }
}, });
error: function(xhr, status, err) {
Swal.fire('Errore', 'Errore di rete o server non raggiungibile.', 'error');
console.error("AJAX error:", status, err, xhr.responseText);
}
});
});
// === Eliminazione con conferma ===
$('#importTable').on('click', '.delete-row', function() {
const id = $(this).data('id');
Swal.fire({
title: 'Are you sure?',
text: 'This record will be permanently deleted.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Yes, delete it',
cancelButtonText: 'Cancel'
}).then(result => {
if (result.isConfirmed) {
$.ajax({
url: 'import_list_delete.php',
type: 'POST',
data: {
id: id
},
success: function(res) {
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Error', res, 'error');
return;
}
if (json.status === 'ok') {
Swal.fire('Deleted', 'Row removed successfully.', 'success');
table.ajax.reload(null, false);
} else {
Swal.fire('Error', json.message, 'error');
}
},
error: function() {
Swal.fire('Error', 'Request failed.', 'error');
}
});
}
});
});
// === Gestione selezione righe e pulsante Aggiorna Prezzi ===
$('#importTable').on('change', '.row-check', function() {
const checkedCount = $('#importTable .row-check:checked').length;
$('#updatePricesBtn').toggle(checkedCount > 0);
});
// === Aggiornamento prezzi con ChatPDF ===
$('#updatePricesBtn').on('click', function() {
const selectedRows = [];
$('#importTable .row-check:checked').each(function() {
const row = table.row($(this).closest('tr')).data();
if (row) selectedRows.push(row);
}); });
if (selectedRows.length === 0) {
Swal.fire('Notice', 'Seleziona almeno una riga.', 'info');
return;
}
const clientId = $('#clientFilter').val();
if (!clientId) {
Swal.fire('Notice', 'Seleziona prima un cliente.', 'info');
return;
}
Swal.fire({
title: 'Aggiornare i prezzi?',
text: `Verranno interrogati i prezzi per ${selectedRows.length} prodotti.`,
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Procedi',
cancelButtonText: 'Annulla'
}).then(result => {
if (result.isConfirmed) {
Swal.fire({
title: 'Aggiornamento in corso...',
allowOutsideClick: false,
didOpen: () => Swal.showLoading()
});
$.ajax({
url: 'update_prices_chatpdf.php',
type: 'POST',
data: {
rows: JSON.stringify(selectedRows),
idclient: clientId
},
success: function(res) {
Swal.close();
let json;
try {
json = JSON.parse(res);
} catch (e) {
Swal.fire('Errore', res, 'error');
return;
}
if (json.status === 'ok') {
Swal.fire('Successo', `Aggiornati ${json.updated} prezzi.`, 'success');
table.ajax.reload(null, false);
} else {
Swal.fire('Errore', json.message, 'error');
}
},
error: function() {
Swal.fire('Errore', 'Aggiornamento fallito.', 'error');
}
});
}
});
}); });
</script>
</div> <!-- end wrapper -->
});
</script>
</body> </body>
</html> </html>

View File

@ -22,9 +22,10 @@
<ul> <ul>
<!-- <li> <a href="index.php"><i class='bx bx-radio-circle'></i>Default</a> <!-- <li> <a href="index.php"><i class='bx bx-radio-circle'></i>Default</a>
</li> --> </li> -->
<li> <a href="import_dashboard.php"><i class='bx bx-radio-circle'></i>XLS Import</a> <li> <a href="import_xls.php"><i class='bx bx-radio-circle'></i>XLS Import</a>
</li>
<li> <a href="import_list.php"><i class='bx bx-radio-circle'></i>Imported LIST</a>
</li> </li>
</ul> </ul>
</li> </li>

217
public/userarea/jsonraw.php Normal file
View File

@ -0,0 +1,217 @@
<?php
header("Content-Type: application/json; charset=utf-8");
// ---------------------------------------------------------
// 1) CONFIGURAZIONE
// ---------------------------------------------------------
$apiKey = "info@claudiosironi.com_Qfh02D7sAvi2tcx3ZchHpusNaBquCKhJw81fEnkHe2ersQDVOex4IokhCCzaFAz1";
$fileToken = "filetoken://61a780917907f86a340290d22c449357dc68950e9066bd67b2";
// opzionale: passare un token via GET
if (isset($_GET['token']) && $_GET['token'] !== "") {
$fileToken = $_GET['token'];
}
// ---------------------------------------------------------
// 2) PDF.CO → JSON2 (TUTTE LE PAGINE)
// ---------------------------------------------------------
$endpoint = "https://api.pdf.co/v1/pdf/convert/to/json2";
$payload = [
"url" => $fileToken,
"inline" => true,
"detectTables" => true,
"pages" => "all"
];
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"x-api-key: $apiKey"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (!$response) {
echo json_encode(["error" => "Errore CURL: " . curl_error($ch)]);
exit;
}
curl_close($ch);
$json = json_decode($response, true);
// ---------------------------------------------------------
// 3) VALIDAZIONE
// ---------------------------------------------------------
if (!isset($json["body"]["document"]["page"])) {
echo json_encode([
"error" => true,
"message" => "PDF.co JSON senza pagine valide",
"raw" => $json
]);
exit;
}
$pages = $json["body"]["document"]["page"];
// ---------------------------------------------------------
// 4) FUNZIONI UTILI
// ---------------------------------------------------------
// Formato A 291 02F
function isCodeTypeA($txt)
{
return preg_match('/^\d{3}\s+[0-9A-Z]{2,3}$/', $txt);
}
// Formato C blocchi ripetuti (499 3A 14 5.475)
function extractTypeCBlocks($line)
{
preg_match_all(
'/(\d{3})\s+([0-9A-Z]{1,3})\s+(\d{2})\s+(\d{1,3}\.\d{3})/',
$line,
$m,
PREG_SET_ORDER
);
$out = [];
foreach ($m as $b) {
$out[] = [
"codice" => $b[1],
"variante" => $b[2],
"dimensione" => $b[3],
"prezzo" => floatval(str_replace(".", "", $b[4]))
];
}
return $out;
}
// ---------------------------------------------------------
// 5) PARSER PER UNA SINGOLA RIGA (ricostruita)
// ---------------------------------------------------------
function parseLine($line)
{
// FORMATO A
if (preg_match('/^(\d{3}\s+[0-9A-Z]{2,3})\s+(.*)$/', $line, $mA)) {
$codice = trim($mA[1]);
$resto = trim($mA[2]);
preg_match_all('/\d{1,3}[.,]\d{2,3}/', $resto, $matchesPrezzi);
if (count($matchesPrezzi[0]) >= 2) {
$descr = trim(preg_replace('/\d{1,3}[.,]\d{2,3}/', '', $resto));
$vals = array_map(function ($v) {
return floatval(str_replace(",", ".", str_replace(".", "", $v)));
}, $matchesPrezzi[0]);
return [
"type" => "A",
"codice" => $codice,
"descrizione" => $descr,
"prezzi" => $vals
];
}
}
// FORMATO B1
if (preg_match(
'/^(\d{3}\s+[A-Z]{1,3})\s+([A-Za-zÀ-ù0-9\s]+?)\s+((?:\d{1,3}[.,]\d{2,3}\s*){4,})$/',
$line,
$mB
)) {
$codice = trim($mB[1]);
$descr = trim($mB[2]);
$valuesString = trim($mB[3]);
preg_match_all('/\d{1,3}[.,]\d{2,3}/', $valuesString, $vv);
$vals = array_map(function ($v) {
return floatval(str_replace(",", ".", str_replace(".", "", $v)));
}, $vv[0]);
return [
"type" => "B1",
"codice" => $codice,
"descrizione" => $descr,
"prezzi" => $vals
];
}
// FORMATO C (ripetuto in riga)
$blocks = extractTypeCBlocks($line);
if (!empty($blocks)) {
$desc = $line;
foreach ($blocks as $b) {
$pattern = sprintf(
'/%s\s+%s\s+%s\s+\d{1,3}\.\d{3}/',
$b["codice"],
$b["variante"],
$b["dimensione"]
);
$desc = preg_replace($pattern, "", $desc);
}
$desc = trim($desc);
return [
"type" => "C",
"descrizione" => $desc,
"varianti" => $blocks
];
}
// Nessun match
return null;
}
// ---------------------------------------------------------
// 6) PROCESSAMENTO DI TUTTE LE PAGINE
// ---------------------------------------------------------
$items = [];
foreach ($pages as $p) {
if (!isset($p["row"])) continue;
// Ricostruisco tutte le vere righe (“flat lines”)
$flatLines = [];
foreach ($p["row"] as $row) {
$line = "";
foreach ($row["column"] as $col) {
if (!empty($col["text"]["text"])) {
$line .= " " . trim($col["text"]["text"]);
}
}
$line = trim($line);
if ($line !== "") {
$flatLines[] = $line;
}
}
// Applico i parser su ogni riga ricostruita
foreach ($flatLines as $l) {
$parsed = parseLine($l);
if ($parsed !== null) {
$items[] = $parsed;
}
}
}
// ---------------------------------------------------------
// 7) OUTPUT FINALE
// ---------------------------------------------------------
echo json_encode([
"status" => "ok",
"total" => count($items),
"items" => $items
], JSON_PRETTY_PRINT);

BIN
public/userarea/listino.pdf Normal file

Binary file not shown.

View File

@ -6660,6 +6660,10 @@ return array(
'Spatie\\LaravelPackageTools\\Exceptions\\InvalidPackage' => $vendorDir . '/spatie/laravel-package-tools/src/Exceptions/InvalidPackage.php', 'Spatie\\LaravelPackageTools\\Exceptions\\InvalidPackage' => $vendorDir . '/spatie/laravel-package-tools/src/Exceptions/InvalidPackage.php',
'Spatie\\LaravelPackageTools\\Package' => $vendorDir . '/spatie/laravel-package-tools/src/Package.php', 'Spatie\\LaravelPackageTools\\Package' => $vendorDir . '/spatie/laravel-package-tools/src/Package.php',
'Spatie\\LaravelPackageTools\\PackageServiceProvider' => $vendorDir . '/spatie/laravel-package-tools/src/PackageServiceProvider.php', 'Spatie\\LaravelPackageTools\\PackageServiceProvider' => $vendorDir . '/spatie/laravel-package-tools/src/PackageServiceProvider.php',
'Spatie\\PdfToText\\Exceptions\\BinaryNotFoundException' => $vendorDir . '/spatie/pdf-to-text/src/Exceptions/BinaryNotFoundException.php',
'Spatie\\PdfToText\\Exceptions\\CouldNotExtractText' => $vendorDir . '/spatie/pdf-to-text/src/Exceptions/CouldNotExtractText.php',
'Spatie\\PdfToText\\Exceptions\\PdfNotFound' => $vendorDir . '/spatie/pdf-to-text/src/Exceptions/PdfNotFound.php',
'Spatie\\PdfToText\\Pdf' => $vendorDir . '/spatie/pdf-to-text/src/Pdf.php',
'Spatie\\QueryBuilder\\AllowedFilter' => $vendorDir . '/spatie/laravel-query-builder/src/AllowedFilter.php', 'Spatie\\QueryBuilder\\AllowedFilter' => $vendorDir . '/spatie/laravel-query-builder/src/AllowedFilter.php',
'Spatie\\QueryBuilder\\AllowedInclude' => $vendorDir . '/spatie/laravel-query-builder/src/AllowedInclude.php', 'Spatie\\QueryBuilder\\AllowedInclude' => $vendorDir . '/spatie/laravel-query-builder/src/AllowedInclude.php',
'Spatie\\QueryBuilder\\AllowedSort' => $vendorDir . '/spatie/laravel-query-builder/src/AllowedSort.php', 'Spatie\\QueryBuilder\\AllowedSort' => $vendorDir . '/spatie/laravel-query-builder/src/AllowedSort.php',

View File

@ -54,6 +54,7 @@ return array(
'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'), 'Symfony\\Component\\Clock\\' => array($vendorDir . '/symfony/clock'),
'Spatie\\QueryBuilder\\Database\\Factories\\' => array($vendorDir . '/spatie/laravel-query-builder/database/factories'), 'Spatie\\QueryBuilder\\Database\\Factories\\' => array($vendorDir . '/spatie/laravel-query-builder/database/factories'),
'Spatie\\QueryBuilder\\' => array($vendorDir . '/spatie/laravel-query-builder/src'), 'Spatie\\QueryBuilder\\' => array($vendorDir . '/spatie/laravel-query-builder/src'),
'Spatie\\PdfToText\\' => array($vendorDir . '/spatie/pdf-to-text/src'),
'Spatie\\LaravelPackageTools\\' => array($vendorDir . '/spatie/laravel-package-tools/src'), 'Spatie\\LaravelPackageTools\\' => array($vendorDir . '/spatie/laravel-package-tools/src'),
'Spatie\\LaravelIgnition\\' => array($vendorDir . '/spatie/laravel-ignition/src', $vendorDir . '/spatie/error-solutions/legacy/laravel-ignition'), 'Spatie\\LaravelIgnition\\' => array($vendorDir . '/spatie/laravel-ignition/src', $vendorDir . '/spatie/error-solutions/legacy/laravel-ignition'),
'Spatie\\Ignition\\' => array($vendorDir . '/spatie/ignition/src', $vendorDir . '/spatie/error-solutions/legacy/ignition'), 'Spatie\\Ignition\\' => array($vendorDir . '/spatie/ignition/src', $vendorDir . '/spatie/error-solutions/legacy/ignition'),

View File

@ -114,6 +114,7 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'Symfony\\Component\\Clock\\' => 24, 'Symfony\\Component\\Clock\\' => 24,
'Spatie\\QueryBuilder\\Database\\Factories\\' => 39, 'Spatie\\QueryBuilder\\Database\\Factories\\' => 39,
'Spatie\\QueryBuilder\\' => 20, 'Spatie\\QueryBuilder\\' => 20,
'Spatie\\PdfToText\\' => 17,
'Spatie\\LaravelPackageTools\\' => 27, 'Spatie\\LaravelPackageTools\\' => 27,
'Spatie\\LaravelIgnition\\' => 23, 'Spatie\\LaravelIgnition\\' => 23,
'Spatie\\Ignition\\' => 16, 'Spatie\\Ignition\\' => 16,
@ -440,6 +441,10 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
array ( array (
0 => __DIR__ . '/..' . '/spatie/laravel-query-builder/src', 0 => __DIR__ . '/..' . '/spatie/laravel-query-builder/src',
), ),
'Spatie\\PdfToText\\' =>
array (
0 => __DIR__ . '/..' . '/spatie/pdf-to-text/src',
),
'Spatie\\LaravelPackageTools\\' => 'Spatie\\LaravelPackageTools\\' =>
array ( array (
0 => __DIR__ . '/..' . '/spatie/laravel-package-tools/src', 0 => __DIR__ . '/..' . '/spatie/laravel-package-tools/src',
@ -7483,6 +7488,10 @@ class ComposerStaticInitc91cd9c5b1e6a9e8573a14b799ea9342
'Spatie\\LaravelPackageTools\\Exceptions\\InvalidPackage' => __DIR__ . '/..' . '/spatie/laravel-package-tools/src/Exceptions/InvalidPackage.php', 'Spatie\\LaravelPackageTools\\Exceptions\\InvalidPackage' => __DIR__ . '/..' . '/spatie/laravel-package-tools/src/Exceptions/InvalidPackage.php',
'Spatie\\LaravelPackageTools\\Package' => __DIR__ . '/..' . '/spatie/laravel-package-tools/src/Package.php', 'Spatie\\LaravelPackageTools\\Package' => __DIR__ . '/..' . '/spatie/laravel-package-tools/src/Package.php',
'Spatie\\LaravelPackageTools\\PackageServiceProvider' => __DIR__ . '/..' . '/spatie/laravel-package-tools/src/PackageServiceProvider.php', 'Spatie\\LaravelPackageTools\\PackageServiceProvider' => __DIR__ . '/..' . '/spatie/laravel-package-tools/src/PackageServiceProvider.php',
'Spatie\\PdfToText\\Exceptions\\BinaryNotFoundException' => __DIR__ . '/..' . '/spatie/pdf-to-text/src/Exceptions/BinaryNotFoundException.php',
'Spatie\\PdfToText\\Exceptions\\CouldNotExtractText' => __DIR__ . '/..' . '/spatie/pdf-to-text/src/Exceptions/CouldNotExtractText.php',
'Spatie\\PdfToText\\Exceptions\\PdfNotFound' => __DIR__ . '/..' . '/spatie/pdf-to-text/src/Exceptions/PdfNotFound.php',
'Spatie\\PdfToText\\Pdf' => __DIR__ . '/..' . '/spatie/pdf-to-text/src/Pdf.php',
'Spatie\\QueryBuilder\\AllowedFilter' => __DIR__ . '/..' . '/spatie/laravel-query-builder/src/AllowedFilter.php', 'Spatie\\QueryBuilder\\AllowedFilter' => __DIR__ . '/..' . '/spatie/laravel-query-builder/src/AllowedFilter.php',
'Spatie\\QueryBuilder\\AllowedInclude' => __DIR__ . '/..' . '/spatie/laravel-query-builder/src/AllowedInclude.php', 'Spatie\\QueryBuilder\\AllowedInclude' => __DIR__ . '/..' . '/spatie/laravel-query-builder/src/AllowedInclude.php',
'Spatie\\QueryBuilder\\AllowedSort' => __DIR__ . '/..' . '/spatie/laravel-query-builder/src/AllowedSort.php', 'Spatie\\QueryBuilder\\AllowedSort' => __DIR__ . '/..' . '/spatie/laravel-query-builder/src/AllowedSort.php',

View File

@ -8827,6 +8827,67 @@
], ],
"install-path": "../spatie/laravel-query-builder" "install-path": "../spatie/laravel-query-builder"
}, },
{
"name": "spatie/pdf-to-text",
"version": "1.54.1",
"version_normalized": "1.54.1.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/pdf-to-text.git",
"reference": "ad2a792c7e1e68f1bc9b038dc73cdcf760595fbb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/pdf-to-text/zipball/ad2a792c7e1e68f1bc9b038dc73cdcf760595fbb",
"reference": "ad2a792c7e1e68f1bc9b038dc73cdcf760595fbb",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0",
"symfony/process": "^4.0|^5.0|^6.0|^7.0"
},
"require-dev": {
"pestphp/pest-plugin-laravel": "^1.3",
"phpunit/phpunit": "^9.5"
},
"time": "2025-06-13T15:23:19+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Spatie\\PdfToText\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Extract text from a pdf",
"homepage": "https://github.com/spatie/pdf-to-text",
"keywords": [
"pdf-to-text",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/pdf-to-text/issues",
"source": "https://github.com/spatie/pdf-to-text/tree/1.54.1"
},
"funding": [
{
"url": "https://spatie.be/open-source/support-us",
"type": "custom"
}
],
"install-path": "../spatie/pdf-to-text"
},
{ {
"name": "symfony/clock", "name": "symfony/clock",
"version": "v7.1.1", "version": "v7.1.1",

View File

@ -3,7 +3,7 @@
'name' => 'loshmis/vanguard', 'name' => 'loshmis/vanguard',
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => 'e75be99e43b28088315b19f86eee2e6869c7c844', 'reference' => '6b65b31932e62bc55f5c539e7856ca8eb8440782',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -688,7 +688,7 @@
'loshmis/vanguard' => array( 'loshmis/vanguard' => array(
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => 'e75be99e43b28088315b19f86eee2e6869c7c844', 'reference' => '6b65b31932e62bc55f5c539e7856ca8eb8440782',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@ -1391,6 +1391,15 @@
0 => '*', 0 => '*',
), ),
), ),
'spatie/pdf-to-text' => array(
'pretty_version' => '1.54.1',
'version' => '1.54.1.0',
'reference' => 'ad2a792c7e1e68f1bc9b038dc73cdcf760595fbb',
'type' => 'library',
'install_path' => __DIR__ . '/../spatie/pdf-to-text',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/clock' => array( 'symfony/clock' => array(
'pretty_version' => 'v7.1.1', 'pretty_version' => 'v7.1.1',
'version' => '7.1.1.0', 'version' => '7.1.1.0',