Autosave: 20260521-170036
BIN
assets/uploads/vouchers/6a0cb59413a36-281.png
Normal file
|
After Width: | Height: | Size: 281 KiB |
BIN
assets/uploads/vouchers/6a0cb61281766-8001.png
Normal file
|
After Width: | Height: | Size: 357 KiB |
BIN
assets/uploads/vouchers/6a0cb6efd02ce-1206.png
Normal file
|
After Width: | Height: | Size: 376 KiB |
|
After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 205 KiB |
BIN
assets/uploads/vouchers/6a0cd343e95c3-Screenshot_351.png
Normal file
|
After Width: | Height: | Size: 297 KiB |
BIN
assets/uploads/vouchers/6a0cdc850a961-1231.png
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
assets/uploads/vouchers/6a0cdd89d472f-143.png
Normal file
|
After Width: | Height: | Size: 407 KiB |
BIN
assets/uploads/vouchers/6a0cde19499f5-3005.png
Normal file
|
After Width: | Height: | Size: 480 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 239 KiB |
BIN
assets/uploads/vouchers/6a0dc4219a3a8-457.png
Normal file
|
After Width: | Height: | Size: 283 KiB |
BIN
assets/uploads/vouchers/6a0dc8d914bd5-920.png
Normal file
|
After Width: | Height: | Size: 407 KiB |
BIN
assets/uploads/vouchers/6a0dcadf26392-NUEVO.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
assets/uploads/vouchers/6a0dcca6cab8b-Screenshot_5.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
assets/uploads/vouchers/6a0dcd3d0b0b2-464.png
Normal file
|
After Width: | Height: | Size: 342 KiB |
BIN
assets/uploads/vouchers/6a0dcf417eed2-Screenshot_6.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
assets/uploads/vouchers/6a0e0610b0f7d-Screenshot_352.png
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
assets/uploads/vouchers/6a0f12416120f-Screenshot_353.png
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
assets/uploads/vouchers/6a0f129b5035b-Screenshot_353.png
Normal file
|
After Width: | Height: | Size: 238 KiB |
BIN
assets/uploads/vouchers/6a0f1833a9f4e-157.png
Normal file
|
After Width: | Height: | Size: 273 KiB |
|
After Width: | Height: | Size: 323 KiB |
2
db/migrations/015_add_nota_adicional_to_pedidos.sql
Normal file
@ -0,0 +1,2 @@
|
||||
-- Migration: Add nota_adicional column to pedidos table
|
||||
ALTER TABLE pedidos ADD COLUMN nota_adicional TEXT NULL AFTER clave;
|
||||
@ -15,69 +15,82 @@ try {
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
$generated_codes = [];
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$producto_id = $_POST['producto_id'] ?? null;
|
||||
$cantidad = isset($_POST['cantidad']) ? (int)$_POST['cantidad'] : 0;
|
||||
$generated_data = []; // Store both code and product name
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['productos'])) {
|
||||
$productos_input = $_POST['productos'];
|
||||
$cantidades_input = $_POST['cantidades'];
|
||||
|
||||
if ($producto_id && $cantidad > 0 && $cantidad <= 1000) {
|
||||
$db = db();
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
$db = db();
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
|
||||
$total_generated = 0;
|
||||
|
||||
// 1. Lock the product row and get current counter and code base
|
||||
$stmt = $db->prepare("SELECT codigo_base, sku, contador_etiquetas FROM products WHERE id = ? FOR UPDATE");
|
||||
$stmt->execute([$producto_id]);
|
||||
$product_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
foreach ($productos_input as $index => $producto_id) {
|
||||
$cantidad = isset($cantidades_input[$index]) ? (int)$cantidades_input[$index] : 0;
|
||||
|
||||
if (!$product_data) {
|
||||
throw new Exception("Producto no encontrado.");
|
||||
}
|
||||
if ($producto_id && $cantidad > 0 && $cantidad <= 1000) {
|
||||
// 1. Lock the product row and get current counter and code base
|
||||
$stmt = $db->prepare("SELECT nombre, codigo_base, sku, contador_etiquetas FROM products WHERE id = ? FOR UPDATE");
|
||||
$stmt->execute([$producto_id]);
|
||||
$product_data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$codigo_base = $product_data['codigo_base'];
|
||||
$contador_actual = (int)$product_data['contador_etiquetas'];
|
||||
if (!$product_data) {
|
||||
continue; // Skip if product not found
|
||||
}
|
||||
|
||||
// If codigo_base is missing, try to use SKU
|
||||
if (empty($codigo_base)) {
|
||||
$codigo_base = $product_data['sku'];
|
||||
|
||||
// If SKU is also missing, abort
|
||||
$product_name = $product_data['nombre'];
|
||||
$codigo_base = $product_data['codigo_base'];
|
||||
$contador_actual = (int)$product_data['contador_etiquetas'];
|
||||
|
||||
// If codigo_base is missing, try to use SKU
|
||||
if (empty($codigo_base)) {
|
||||
throw new Exception("El producto no tiene un SKU o Código Base definido. Por favor, edite el producto y añada un SKU.");
|
||||
$codigo_base = $product_data['sku'];
|
||||
|
||||
// If SKU is also missing, skip or throw error
|
||||
if (empty($codigo_base)) {
|
||||
throw new Exception("El producto '$product_name' no tiene un SKU o Código Base definido.");
|
||||
}
|
||||
|
||||
// Persist the SKU as the codigo_base for future use
|
||||
$update_base_stmt = $db->prepare("UPDATE products SET codigo_base = ? WHERE id = ?");
|
||||
$update_base_stmt->execute([$codigo_base, $producto_id]);
|
||||
}
|
||||
|
||||
// Prepare statement for insertion into unidades_inventario
|
||||
$insert_stmt = $db->prepare("INSERT INTO unidades_inventario (codigo_unico, producto_id) VALUES (?, ?)");
|
||||
|
||||
// 2. Generate new codes
|
||||
for ($i = 1; $i <= $cantidad; $i++) {
|
||||
$nuevo_contador = $contador_actual + $i;
|
||||
$numero_formateado = str_pad($nuevo_contador, 4, '0', STR_PAD_LEFT);
|
||||
$unique_code = $codigo_base . '-' . $numero_formateado;
|
||||
|
||||
$insert_stmt->execute([$unique_code, $producto_id]);
|
||||
$generated_data[] = [
|
||||
'code' => $unique_code,
|
||||
'product_name' => $product_name
|
||||
];
|
||||
$total_generated++;
|
||||
}
|
||||
|
||||
// Persist the SKU as the codigo_base for future use
|
||||
$update_base_stmt = $db->prepare("UPDATE products SET codigo_base = ? WHERE id = ?");
|
||||
$update_base_stmt->execute([$codigo_base, $producto_id]);
|
||||
// 3. Update the counter in the products table
|
||||
$nuevo_total_contador = $contador_actual + $cantidad;
|
||||
$update_stmt = $db->prepare("UPDATE products SET contador_etiquetas = ? WHERE id = ?");
|
||||
$update_stmt->execute([$nuevo_total_contador, $producto_id]);
|
||||
}
|
||||
|
||||
// Prepare statement for insertion into unidades_inventario
|
||||
$insert_stmt = $db->prepare("INSERT INTO unidades_inventario (codigo_unico, producto_id) VALUES (?, ?)");
|
||||
|
||||
// 2. Generate new codes
|
||||
for ($i = 1; $i <= $cantidad; $i++) {
|
||||
$nuevo_contador = $contador_actual + $i;
|
||||
$numero_formateado = str_pad($nuevo_contador, 4, '0', STR_PAD_LEFT);
|
||||
$unique_code = $codigo_base . '-' . $numero_formateado;
|
||||
|
||||
$insert_stmt->execute([$unique_code, $producto_id]);
|
||||
$generated_codes[] = $unique_code;
|
||||
}
|
||||
|
||||
// 3. Update the counter in the products table
|
||||
$nuevo_total_contador = $contador_actual + $cantidad;
|
||||
$update_stmt = $db->prepare("UPDATE products SET contador_etiquetas = ? WHERE id = ?");
|
||||
$update_stmt->execute([$nuevo_total_contador, $producto_id]);
|
||||
|
||||
$db->commit();
|
||||
$_SESSION['success_message'] = 'Se generaron ' . count($generated_codes) . ' códigos exitosamente.';
|
||||
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
$_SESSION['error_message'] = 'Error al generar los códigos: ' . $e->getMessage();
|
||||
}
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'Por favor, seleccione un producto y especifique una cantidad válida (entre 1 y 1000).';
|
||||
|
||||
$db->commit();
|
||||
if ($total_generated > 0) {
|
||||
$_SESSION['success_message'] = 'Se generaron ' . $total_generated . ' códigos exitosamente.';
|
||||
} else {
|
||||
$_SESSION['error_message'] = 'No se generó ningún código. Verifique los datos ingresados.';
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
if ($db->inTransaction()) $db->rollBack();
|
||||
$_SESSION['error_message'] = 'Error al generar los códigos: ' . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,51 +112,91 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
unset($_SESSION['error_message']); // Clear the message
|
||||
}
|
||||
?>
|
||||
<form action="generar_etiquetas.php" method="POST">
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="producto_id" class="form-label">Producto</label>
|
||||
<select class="form-select" id="producto_id" name="producto_id" required>
|
||||
<option value="">Seleccione un producto</option>
|
||||
<?php foreach ($products as $product): ?>
|
||||
<option value="<?php echo htmlspecialchars($product['id']); ?>">
|
||||
<?php echo htmlspecialchars($product['nombre']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<form action="generar_etiquetas.php" method="POST" id="labelsForm">
|
||||
<div id="productsContainer">
|
||||
<div class="row product-row mb-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Producto</label>
|
||||
<select class="form-select" name="productos[]" required>
|
||||
<option value="">Seleccione un producto</option>
|
||||
<?php foreach ($products as $product): ?>
|
||||
<option value="<?php echo htmlspecialchars($product['id']); ?>">
|
||||
<?php echo htmlspecialchars($product['nombre']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Cantidad de Etiquetas</label>
|
||||
<input type="number" class="form-control" name="cantidades[]" min="1" max="1000" required>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end">
|
||||
<button type="button" class="btn btn-danger w-100 remove-product" style="display: none;">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="cantidad" class="form-label">Cantidad de Etiquetas</label>
|
||||
<input type="number" class="form-control" id="cantidad" name="cantidad" min="1" max="1000" required>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<button type="button" id="addProduct" class="btn btn-secondary">
|
||||
<i class="fas fa-plus"></i> Agregar otro producto
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-2 mb-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100">Generar</button>
|
||||
<div class="col-md-6 text-end">
|
||||
<button type="submit" class="btn btn-primary px-5">Generar Todas</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const container = document.getElementById('productsContainer');
|
||||
const addButton = document.getElementById('addProduct');
|
||||
|
||||
addButton.addEventListener('click', function() {
|
||||
const firstRow = container.querySelector('.product-row');
|
||||
const newRow = firstRow.cloneNode(true);
|
||||
|
||||
// Clear inputs
|
||||
newRow.querySelector('select').value = '';
|
||||
newRow.querySelector('input').value = '';
|
||||
|
||||
// Show remove button
|
||||
const removeBtn = newRow.querySelector('.remove-product');
|
||||
removeBtn.style.display = 'block';
|
||||
|
||||
removeBtn.addEventListener('click', function() {
|
||||
newRow.remove();
|
||||
});
|
||||
|
||||
container.appendChild(newRow);
|
||||
});
|
||||
|
||||
// Initial remove button logic for the first row (if needed, though hidden by default)
|
||||
container.querySelectorAll('.remove-product').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
if (container.querySelectorAll('.product-row').length > 1) {
|
||||
btn.closest('.product-row').remove();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
if (!empty($generated_codes)) {
|
||||
if (!empty($generated_data)) {
|
||||
?>
|
||||
<div class="card mt-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h3 class="card-title mb-0">Códigos Generados</h3>
|
||||
<form action="imprimir_etiquetas.php" method="POST" target="_blank" class="m-0">
|
||||
<?php
|
||||
// Get product name to pass to the export script
|
||||
$product_name = '';
|
||||
foreach ($products as $p) {
|
||||
if ($p['id'] == $producto_id) {
|
||||
$product_name = $p['nombre'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<input type="hidden" name="product_name" value="<?php echo htmlspecialchars($product_name); ?>">
|
||||
<?php foreach ($generated_codes as $code): ?>
|
||||
<input type="hidden" name="codes[]" value="<?php echo htmlspecialchars($code); ?>">
|
||||
<?php foreach ($generated_data as $data): ?>
|
||||
<input type="hidden" name="codes[]" value="<?php echo htmlspecialchars($data['code']); ?>">
|
||||
<input type="hidden" name="product_names[]" value="<?php echo htmlspecialchars($data['product_name']); ?>">
|
||||
<?php endforeach; ?>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-file-excel"></i> Exportar a Excel
|
||||
@ -153,12 +206,13 @@ if (!empty($generated_codes)) {
|
||||
<div class="card-body">
|
||||
<p>Estos son los códigos generados. Puedes escanearlos con una app en tu teléfono para probar que funcionan.</p>
|
||||
<div class="row mt-4">
|
||||
<?php foreach ($generated_codes as $code): ?>
|
||||
<?php foreach ($generated_data as $data): ?>
|
||||
<div class="col-lg-4 col-md-6 text-center mb-4">
|
||||
<div class="barcode-container" style="min-height: 70px;">
|
||||
<img src="https://barcode.tec-it.com/barcode.ashx?data=<?php echo urlencode($code); ?>&code=Code128" alt="Barcode for <?php echo htmlspecialchars($code); ?>" style="max-height: 50px;">
|
||||
<img src="https://barcode.tec-it.com/barcode.ashx?data=<?php echo urlencode($data['code']); ?>&code=Code128" alt="Barcode for <?php echo htmlspecialchars($data['code']); ?>" style="max-height: 50px;">
|
||||
</div>
|
||||
<code><?php echo htmlspecialchars($code); ?></code>
|
||||
<code><?php echo htmlspecialchars($data['code']); ?></code><br>
|
||||
<small class="text-muted"><?php echo htmlspecialchars($data['product_name']); ?></small>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
@ -1,38 +1,69 @@
|
||||
<?php
|
||||
// Receive data from the form
|
||||
// Limpiar cualquier salida previa para evitar corrupción del archivo binario
|
||||
if (ob_get_level()) ob_end_clean();
|
||||
|
||||
// Recibir datos del formulario
|
||||
$codes = $_POST['codes'] ?? [];
|
||||
$product_name = $_POST['product_name'] ?? 'Producto Desconocido';
|
||||
$product_names = $_POST['product_names'] ?? [];
|
||||
$single_product_name = $_POST['product_name'] ?? 'Varios_Productos';
|
||||
|
||||
if (empty($codes)) {
|
||||
die("No se proporcionaron códigos para exportar.");
|
||||
}
|
||||
|
||||
// 1. Set HTTP headers to trigger file download
|
||||
$filename = "etiquetas_" . str_replace(' ', '_', $product_name) . "_" . date("Y-m-d") . ".csv";
|
||||
header('Content-Type: text/csv; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
$display_name = count($product_names) > 0 ? "etiquetas_multiples" : str_replace(' ', '_', $single_product_name);
|
||||
$filename = $display_name . "_" . date("Y-m-d") . ".xls";
|
||||
|
||||
// 2. Create a file pointer connected to the output stream
|
||||
$output = fopen('php://output', 'w');
|
||||
|
||||
// 3. Add a UTF-8 BOM to ensure Excel opens it correctly
|
||||
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
|
||||
|
||||
// 4. Write the header row
|
||||
$header = ['Nombre del producto', 'Código único', 'Código de barra (usar fuente)'];
|
||||
fputcsv($output, $header, ';');
|
||||
|
||||
// 5. Write the data rows
|
||||
foreach ($codes as $code) {
|
||||
$row = [
|
||||
$product_name,
|
||||
$code,
|
||||
$code // The same code, to be formatted as a barcode in Excel
|
||||
];
|
||||
fputcsv($output, $row, ';');
|
||||
// Funciones para generar el formato binario BIFF5 (Excel 97-2003)
|
||||
function xlsBOF() {
|
||||
echo pack("ssssss", 0x809, 0x8, 0x0, 0x10, 0x0, 0x0);
|
||||
}
|
||||
|
||||
// 6. Close the file pointer
|
||||
fclose($output);
|
||||
function xlsEOF() {
|
||||
echo pack("ss", 0x0A, 0x00);
|
||||
}
|
||||
|
||||
function xlsWriteLabel($Row, $Col, $Value) {
|
||||
$L = strlen($Value);
|
||||
echo pack("ssssss", 0x204, 8 + $L, $Row, $Col, 0x0, $L);
|
||||
echo $Value;
|
||||
}
|
||||
|
||||
// Añadir registro de Codepage para que Excel reconozca el juego de caracteres Windows-1252
|
||||
function xlsCodepage() {
|
||||
echo pack("sss", 0x0042, 0x0002, 0x04E4); // 1252 (Windows Latin 1)
|
||||
}
|
||||
|
||||
// Configurar cabeceras para descarga de archivo binario XLS (Excel 97-2003)
|
||||
header('Content-Type: application/vnd.ms-excel');
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
header('Cache-Control: max-age=0');
|
||||
header('Pragma: public');
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
|
||||
// Iniciar el archivo Excel
|
||||
xlsBOF();
|
||||
xlsCodepage();
|
||||
|
||||
// Escribir cabeceras de columnas
|
||||
xlsWriteLabel(0, 0, mb_convert_encoding("Nombre del producto", 'Windows-1252', 'UTF-8'));
|
||||
xlsWriteLabel(0, 1, mb_convert_encoding("Codigo unico", 'Windows-1252', 'UTF-8'));
|
||||
xlsWriteLabel(0, 2, mb_convert_encoding("Codigo de barra (usar fuente)", 'Windows-1252', 'UTF-8'));
|
||||
|
||||
// Escribir los datos
|
||||
$row = 1;
|
||||
foreach ($codes as $index => $code) {
|
||||
$p_name = isset($product_names[$index]) ? $product_names[$index] : $single_product_name;
|
||||
|
||||
// Convertir a Windows-1252 para compatibilidad con el formato BIFF antiguo
|
||||
$p_name_encoded = mb_convert_encoding($p_name, 'Windows-1252', 'UTF-8');
|
||||
|
||||
xlsWriteLabel($row, 0, $p_name_encoded);
|
||||
xlsWriteLabel($row, 1, (string)$code);
|
||||
xlsWriteLabel($row, 2, (string)$code);
|
||||
$row++;
|
||||
}
|
||||
|
||||
// Finalizar el archivo Excel
|
||||
xlsEOF();
|
||||
exit;
|
||||
@ -348,6 +348,11 @@ include 'layout_header.php';
|
||||
<textarea class="form-control" id="notas" name="notas" rows="3"><?php echo htmlspecialchars($pedido['notas']); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="nota_adicional" class="form-label">Nota Adicional (Dedicatorias, etc.)</label>
|
||||
<textarea class="form-control" id="nota_adicional" name="nota_adicional" rows="3"><?php echo htmlspecialchars($pedido['nota_adicional'] ?? ''); ?></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="observacion" class="form-label">Observación (Verificación de Pago)</label>
|
||||
<textarea class="form-control" id="observacion" name="observacion" rows="2"><?php echo htmlspecialchars($pedido['observacion'] ?? ''); ?></textarea>
|
||||
|
||||
60
pedidos.php
@ -166,6 +166,7 @@ include 'layout_header.php';
|
||||
<th>Nº De Orden</th>
|
||||
<th>Codigo De Orden</th>
|
||||
<th>CLAVE</th>
|
||||
<th>NOTA ADICIONAL</th>
|
||||
<th>Estado</th>
|
||||
<?php if ($user_role !== 'Asesor'): ?><th>Asesor</th><?php endif; ?>
|
||||
<th>Fecha Creación</th>
|
||||
@ -197,6 +198,9 @@ include 'layout_header.php';
|
||||
<td class="<?php echo $isEditableClave; ?>" data-id="<?php echo $pedido['id']; ?>" data-field="clave">
|
||||
<?php echo $canSeeClave ? htmlspecialchars($pedido['clave'] ?? 'N/A') : '<i class="fas fa-eye-slash text-muted" title="Suba el número de operación y seleccione el banco para ver la clave"></i> <span class="text-muted" style="font-size: 0.8rem;">Oculto</span>'; ?>
|
||||
</td>
|
||||
<td class="editable-dblclick" data-id="<?php echo $pedido['id']; ?>" data-field="nota_adicional">
|
||||
<?php echo htmlspecialchars($pedido['nota_adicional'] ?? 'N/A'); ?>
|
||||
</td>
|
||||
<td><span class="badge" style="<?php echo getStatusStyle($pedido['estado']); ?>"><?php echo ($pedido['estado'] == 'Gestion') ? 'GESTIONES ⚙️' : htmlspecialchars($pedido['estado']); ?></span></td>
|
||||
<?php if ($user_role !== 'Asesor'): ?><td><?php echo htmlspecialchars($pedido['asesor_nombre'] ?? 'N/A'); ?></td><?php endif; ?>
|
||||
<td><?php echo htmlspecialchars($pedido['created_at']); ?></td>
|
||||
@ -306,5 +310,61 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
table.addEventListener('dblclick', function(e) {
|
||||
if (e.target && e.target.classList.contains('editable-dblclick')) {
|
||||
const cell = e.target;
|
||||
if (cell.querySelector('input')) {
|
||||
return; // Already in edit mode
|
||||
}
|
||||
|
||||
const originalContent = cell.textContent.trim();
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.className = 'form-control form-control-sm';
|
||||
input.value = originalContent === 'N/A' ? '' : originalContent;
|
||||
|
||||
cell.innerHTML = '';
|
||||
cell.appendChild(input);
|
||||
input.focus();
|
||||
|
||||
const saveChanges = function() {
|
||||
const newValue = input.value.trim();
|
||||
const pedidoId = cell.dataset.id;
|
||||
const field = cell.dataset.field;
|
||||
|
||||
// Restore original content visually
|
||||
cell.textContent = newValue === '' ? 'N/A' : newValue;
|
||||
|
||||
// Send data to server
|
||||
fetch('update_pedido_field.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ id: pedidoId, field: field, value: newValue })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
console.error('Error:', data.error);
|
||||
cell.textContent = originalContent;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fetch Error:', error);
|
||||
cell.textContent = originalContent;
|
||||
});
|
||||
};
|
||||
|
||||
input.addEventListener('blur', saveChanges);
|
||||
input.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
input.blur();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -82,6 +82,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$banco = trim($_POST['banco'] ?? '');
|
||||
$estado = $_POST['estado'];
|
||||
$notas = trim($_POST['notas']);
|
||||
$nota_adicional = trim($_POST['nota_adicional'] ?? '');
|
||||
$observacion = trim($_POST['observacion'] ?? '');
|
||||
$descargo = trim($_POST['descargo'] ?? '');
|
||||
|
||||
@ -153,6 +154,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
'monto_debe' => $monto_debe,
|
||||
'estado' => $estado,
|
||||
'notas' => $notas,
|
||||
'nota_adicional' => $nota_adicional,
|
||||
'observacion' => $observacion,
|
||||
'descargo' => $descargo,
|
||||
'voucher_adelanto_path' => $voucher_adelanto_path,
|
||||
@ -183,6 +185,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
"monto_debe = :monto_debe",
|
||||
"estado = :estado",
|
||||
"notas = :notas",
|
||||
"nota_adicional = :nota_adicional",
|
||||
"observacion = :observacion",
|
||||
"descargo = :descargo",
|
||||
"voucher_adelanto_path = :voucher_adelanto_path",
|
||||
@ -237,8 +240,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
// INSERT: The advisor is the user creating the order.
|
||||
$params['asesor_id'] = $_SESSION['user_id'];
|
||||
|
||||
$columns_sql = "dni_cliente, nombre_completo, celular, agencia, sede_envio, codigo_rastreo, codigo_tracking, clave, pendientes, producto, cantidad, monto_total, monto_adelantado, numero_operacion, banco, monto_debe, estado, asesor_id, notas, observacion, descargo, voucher_adelanto_path, voucher_restante_path";
|
||||
$values_sql = ":dni_cliente, :nombre_completo, :celular, :agencia, :sede_envio, :codigo_rastreo, :codigo_tracking, :clave, :pendientes, :producto, :cantidad, :monto_total, :monto_adelantado, :numero_operacion, :banco, :monto_debe, :estado, :asesor_id, :notas, :observacion, :descargo, :voucher_adelanto_path, :voucher_restante_path";
|
||||
$columns_sql = "dni_cliente, nombre_completo, celular, agencia, sede_envio, codigo_rastreo, codigo_tracking, clave, pendientes, producto, cantidad, monto_total, monto_adelantado, numero_operacion, banco, monto_debe, estado, asesor_id, notas, nota_adicional, observacion, descargo, voucher_adelanto_path, voucher_restante_path";
|
||||
$values_sql = ":dni_cliente, :nombre_completo, :celular, :agencia, :sede_envio, :codigo_rastreo, :codigo_tracking, :clave, :pendientes, :producto, :cantidad, :monto_total, :monto_adelantado, :numero_operacion, :banco, :monto_debe, :estado, :asesor_id, :notas, :nota_adicional, :observacion, :descargo, :voucher_adelanto_path, :voucher_restante_path";
|
||||
|
||||
$completed_states = ['Completado', 'COMPLETADO ✅'];
|
||||
if (in_array($estado, $completed_states)) {
|
||||
|
||||
@ -21,7 +21,7 @@ $field = $data['field'];
|
||||
$value = $data['value'];
|
||||
|
||||
// Whitelist allowed fields for security
|
||||
$allowed_fields = ['numero_operacion', 'banco', 'clave', 'fecha_recojo', 'observacion', 'descargo'];
|
||||
$allowed_fields = ['numero_operacion', 'banco', 'clave', 'fecha_recojo', 'observacion', 'descargo', 'notas', 'nota_adicional'];
|
||||
if (!in_array($field, $allowed_fields)) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Campo no permitido.']);
|
||||
|
||||