Autosave: 20260710-045546
This commit is contained in:
parent
36dde6307a
commit
3c0975c71c
@ -73,6 +73,10 @@ function cc_test_display_value(?string $value, string $fallback = 'No registrado
|
||||
|
||||
function cc_test_order_label(array $order): string
|
||||
{
|
||||
if (!empty($order['is_agregado'])) {
|
||||
return 'AGREGADOS';
|
||||
}
|
||||
|
||||
$codigo = trim((string) ($order['codigo'] ?? ''));
|
||||
if ($codigo !== '') {
|
||||
return '#' . ltrim($codigo, '#');
|
||||
@ -98,16 +102,40 @@ function cc_test_badge_class(string $estado): string
|
||||
|
||||
function cc_test_order_time(array $order): int
|
||||
{
|
||||
foreach (['proxima_llamada_at', 'numero_cuenta_enviado_at', 'ultima_gestion_at', 'seguimiento_actualizado', 'import_id'] as $field) {
|
||||
foreach (['proxima_llamada_at', 'numero_cuenta_enviado_at', 'ultima_gestion_at', 'seguimiento_actualizado'] as $field) {
|
||||
$date = cc_test_parse_datetime($order[$field] ?? null);
|
||||
if ($date) {
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
// Fallback recency for Drive imports where import_id is not a datetime (e.g. "#35898").
|
||||
return cc_test_import_time($order);
|
||||
}
|
||||
|
||||
function cc_test_import_time(array $order): int
|
||||
{
|
||||
if (!empty($order['is_agregado'])) {
|
||||
return (int) ($order['id'] ?? 0);
|
||||
}
|
||||
|
||||
$importRaw = trim((string) ($order['import_id'] ?? ''));
|
||||
if ($importRaw !== '') {
|
||||
$date = cc_test_parse_datetime($importRaw);
|
||||
if ($date) {
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
|
||||
$digits = preg_replace('/\D+/', '', $importRaw);
|
||||
if ($digits !== '') {
|
||||
return (int) $digits;
|
||||
}
|
||||
}
|
||||
|
||||
$codigoRaw = trim((string) ($order['codigo'] ?? ''));
|
||||
$digits = preg_replace('/\D+/', '', $codigoRaw);
|
||||
return $digits !== '' ? (int) $digits : 0;
|
||||
}
|
||||
function cc_test_followup_semaforo(array $order): ?array
|
||||
{
|
||||
if (cc_test_normalize_state((string) ($order['estado'] ?? '')) !== 'SE ENVIO NUMERO DE CUENTA') {
|
||||
@ -146,6 +174,8 @@ $orders = [];
|
||||
$visibleOrders = [];
|
||||
$modalsHtml = [];
|
||||
$totalRows = 0;
|
||||
$agregadosCount = 0;
|
||||
$tuaniLastProcessedRow = null;
|
||||
$loadLimit = $storeKey === 'otra_tienda' ? 0 : 100;
|
||||
$startRow = (int) ($storeConfig['startRow'] ?? 5552);
|
||||
$catalogoProductos = [];
|
||||
@ -172,9 +202,31 @@ try {
|
||||
$catalogoProductos = [];
|
||||
}
|
||||
|
||||
$preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey);
|
||||
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
||||
$orders = $preview['orders'] ?? [];
|
||||
if ($storeKey === 'otra_tienda') {
|
||||
$syncInfo = drive_test_sync_orders_incremental($pdo, $storeKey, 0);
|
||||
$totalRows = (int) ($syncInfo['drive_rows_total'] ?? 0);
|
||||
$tuaniLastProcessedRow = (int) ($syncInfo['last_processed_row'] ?? 0);
|
||||
$startRow = (int) ($syncInfo['next_start_row'] ?? $startRow);
|
||||
|
||||
$orders = drive_test_fetch_orders_from_db($pdo, $storeKey);
|
||||
|
||||
$agregadosCount = 0;
|
||||
foreach ($orders as $o) {
|
||||
if (!empty($o['is_agregado'])) {
|
||||
$agregadosCount++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey);
|
||||
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
||||
$orders = $preview['orders'] ?? [];
|
||||
|
||||
$agregadosOrders = drive_test_fetch_orders_from_db($pdo, $storeKey, true);
|
||||
$agregadosCount = count($agregadosOrders);
|
||||
if (!empty($agregadosOrders)) {
|
||||
$orders = array_merge($orders, $agregadosOrders);
|
||||
}
|
||||
}
|
||||
|
||||
$tracking = drive_test_fetch_tracking($pdo, array_column($orders, 'source_key'));
|
||||
$orders = drive_test_merge_tracking($orders, $tracking);
|
||||
@ -289,9 +341,22 @@ try {
|
||||
}));
|
||||
|
||||
usort($visibleOrders, static function (array $a, array $b) use ($view): int {
|
||||
if ($view === 'pendientes_hoy') {
|
||||
$aDue = cc_test_parse_datetime($a['proxima_llamada_at'] ?? null);
|
||||
$bDue = cc_test_parse_datetime($b['proxima_llamada_at'] ?? null);
|
||||
$aAgregado = !empty($a['is_agregado']);
|
||||
$bAgregado = !empty($b['is_agregado']);
|
||||
if ($aAgregado !== $bAgregado) {
|
||||
return $aAgregado ? 1 : -1;
|
||||
}
|
||||
|
||||
$aImport = cc_test_import_time($a);
|
||||
$bImport = cc_test_import_time($b);
|
||||
|
||||
if ($aImport !== $bImport) {
|
||||
return $bImport <=> $aImport;
|
||||
}
|
||||
|
||||
if ($view === "pendientes_hoy") {
|
||||
$aDue = cc_test_parse_datetime($a["proxima_llamada_at"] ?? null);
|
||||
$bDue = cc_test_parse_datetime($b["proxima_llamada_at"] ?? null);
|
||||
if ($aDue && $bDue && $aDue != $bDue) {
|
||||
return $aDue <=> $bDue;
|
||||
}
|
||||
@ -334,7 +399,8 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Drive detectado: <?php echo (int) $totalRows; ?> filas</span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Extracción: desde fila <?php echo (int) $startRow; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Agregados: <?php echo (int) $agregadosCount; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2"><?php if ($storeKey === 'otra_tienda' && $tuaniLastProcessedRow !== null): ?>TUANI: último procesado <?php echo (int) $tuaniLastProcessedRow; ?> · próxima desde <?php echo (int) $startRow; ?><?php else: ?>Extracción: desde fila <?php echo (int) $startRow; ?><?php endif; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Auto actualización: cada 10 min</span>
|
||||
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
-- 088_add_store_key_and_drive_test_checkpoints.sql
|
||||
-- Adds store_key to callcenter_test_orders (so we can filter per Drive store)
|
||||
-- and creates a checkpoint table to support incremental Drive imports for TUANI.
|
||||
|
||||
ALTER TABLE callcenter_test_orders
|
||||
ADD COLUMN IF NOT EXISTS store_key VARCHAR(50) NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS drive_test_import_checkpoints (
|
||||
store_key VARCHAR(50) NOT NULL,
|
||||
last_processed_row INT NOT NULL DEFAULT 0,
|
||||
last_sync_at DATETIME NULL,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (store_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
CREATE INDEX idx_callcenter_test_orders_store_key ON callcenter_test_orders (store_key);
|
||||
@ -0,0 +1,3 @@
|
||||
-- 089_expand_callcenter_test_orders_dni_drive.sql
|
||||
ALTER TABLE callcenter_test_orders
|
||||
MODIFY COLUMN dni_drive TEXT NULL;
|
||||
@ -0,0 +1,4 @@
|
||||
-- 090_expand_callcenter_test_orders_dni_drive_longtext.sql
|
||||
-- Ensures callcenter_test_orders.dni_drive can store very long values coming from Google Sheets.
|
||||
ALTER TABLE callcenter_test_orders
|
||||
MODIFY COLUMN dni_drive LONGTEXT NULL;
|
||||
54
download_agregados_pedidos_template.php
Normal file
54
download_agregados_pedidos_template.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'db/config.php';
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use Shuchkin\SimpleXLSXGen;
|
||||
|
||||
$headers = [
|
||||
'NOMBRE',
|
||||
'CELULAR',
|
||||
'DNI',
|
||||
'DIRECCION',
|
||||
'REFERENCIA',
|
||||
'SEDE',
|
||||
'CIUDAD',
|
||||
'DISTRITO',
|
||||
'PRODUCTO',
|
||||
'CANTIDAD',
|
||||
'PRECIO',
|
||||
'PAIS',
|
||||
'COORDENADAS',
|
||||
'METODO',
|
||||
'OBSERVACIONES'
|
||||
];
|
||||
|
||||
$data = [
|
||||
$headers,
|
||||
[
|
||||
'Ej: Juan Pérez',
|
||||
'999999999',
|
||||
'12345678',
|
||||
'Av. Siempre Viva 123',
|
||||
'Referencia (opcional)',
|
||||
'Sede / ID',
|
||||
'Ciudad',
|
||||
'Distrito',
|
||||
'Producto',
|
||||
'1',
|
||||
'10.50',
|
||||
'Perú',
|
||||
'lat,long',
|
||||
'Contraentrega',
|
||||
'Observaciones (opcional)'
|
||||
]
|
||||
];
|
||||
|
||||
$filename = 'plantilla_agregados_pedidos.xlsx';
|
||||
SimpleXLSXGen::fromArray($data, 'Agregados')->downloadAs($filename);
|
||||
@ -20,7 +20,7 @@ try {
|
||||
p.producto,
|
||||
p.cantidad
|
||||
FROM pedidos p
|
||||
WHERE p.estado = 'ROTULADO 📦'
|
||||
WHERE (p.estado = 'ROTULADO 📦' OR p.estado = 'PENDIENTE')
|
||||
ORDER BY p.id DESC
|
||||
";
|
||||
$stmt = $pdo->prepare($query);
|
||||
|
||||
@ -13,11 +13,11 @@ try {
|
||||
$pdo = db();
|
||||
$type = $_GET['type'] ?? 'all';
|
||||
|
||||
// Filtramos solo los pedidos que están en estado 'ROTULADO 📦' y son de la agencia 'SHALOM'
|
||||
// Filtramos los pedidos ROTULADO 📦 y PENDIENTE (listados como 'Pedidos Rotulados') y de la agencia 'SHALOM'
|
||||
$query = "
|
||||
SELECT p.dni_cliente, p.celular, p.sede_envio, p.agencia, p.producto, p.cantidad
|
||||
FROM pedidos p
|
||||
WHERE p.estado = 'ROTULADO 📦'
|
||||
WHERE (p.estado = 'ROTULADO 📦' OR p.estado = 'PENDIENTE')
|
||||
AND p.agencia LIKE '%SHALOM%'
|
||||
";
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ session_start();
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/callcenter_test_helpers.php';
|
||||
require_once 'includes/drive_test_orders.php';
|
||||
require_once 'includes/agregados_excel_import.php';
|
||||
require_once 'includes/contraentrega_cobertura.php';
|
||||
|
||||
$provinciasPorDepartamentoContraentrega = contraentregaProvinciasPorDepartamento();
|
||||
@ -73,6 +74,10 @@ function cc_test_display_value(?string $value, string $fallback = 'No registrado
|
||||
|
||||
function cc_test_order_label(array $order): string
|
||||
{
|
||||
if (!empty($order['is_agregado'])) {
|
||||
return 'AGREGADOS';
|
||||
}
|
||||
|
||||
$codigo = trim((string) ($order['codigo'] ?? ''));
|
||||
if ($codigo !== '') {
|
||||
return '#' . ltrim($codigo, '#');
|
||||
@ -98,16 +103,40 @@ function cc_test_badge_class(string $estado): string
|
||||
|
||||
function cc_test_order_time(array $order): int
|
||||
{
|
||||
foreach (['proxima_llamada_at', 'numero_cuenta_enviado_at', 'ultima_gestion_at', 'seguimiento_actualizado', 'import_id'] as $field) {
|
||||
foreach (['proxima_llamada_at', 'numero_cuenta_enviado_at', 'ultima_gestion_at', 'seguimiento_actualizado'] as $field) {
|
||||
$date = cc_test_parse_datetime($order[$field] ?? null);
|
||||
if ($date) {
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
// Fallback recency for Drive imports where import_id is not a datetime (e.g. "#35898").
|
||||
return cc_test_import_time($order);
|
||||
}
|
||||
|
||||
function cc_test_import_time(array $order): int
|
||||
{
|
||||
if (!empty($order['is_agregado'])) {
|
||||
return (int) ($order['id'] ?? 0);
|
||||
}
|
||||
|
||||
$importRaw = trim((string) ($order['import_id'] ?? ''));
|
||||
if ($importRaw !== '') {
|
||||
$date = cc_test_parse_datetime($importRaw);
|
||||
if ($date) {
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
|
||||
$digits = preg_replace('/\D+/', '', $importRaw);
|
||||
if ($digits !== '') {
|
||||
return (int) $digits;
|
||||
}
|
||||
}
|
||||
|
||||
$codigoRaw = trim((string) ($order['codigo'] ?? ''));
|
||||
$digits = preg_replace('/\D+/', '', $codigoRaw);
|
||||
return $digits !== '' ? (int) $digits : 0;
|
||||
}
|
||||
function cc_test_followup_semaforo(array $order): ?array
|
||||
{
|
||||
if (cc_test_normalize_state((string) ($order['estado'] ?? '')) !== 'SE ENVIO NUMERO DE CUENTA') {
|
||||
@ -141,11 +170,14 @@ $storeConfig = $availableStores[$storeKey];
|
||||
|
||||
$errorMessage = null;
|
||||
$noticeMessage = null;
|
||||
$uploadErrorMessage = null;
|
||||
$assessors = [];
|
||||
$orders = [];
|
||||
$visibleOrders = [];
|
||||
$modalsHtml = [];
|
||||
$totalRows = 0;
|
||||
$agregadosCount = 0;
|
||||
$tuaniLastProcessedRow = null;
|
||||
$loadLimit = $storeKey === 'otra_tienda' ? 0 : 100;
|
||||
|
||||
$cupoObjetivoPorAsesora = 10;
|
||||
@ -177,9 +209,52 @@ try {
|
||||
$catalogoProductos = [];
|
||||
}
|
||||
|
||||
$preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey);
|
||||
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
||||
$orders = $preview['orders'] ?? [];
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'upload_agregados_excel') {
|
||||
try {
|
||||
$file = $_FILES['agregados_excel'] ?? null;
|
||||
if (!is_array($file)) {
|
||||
throw new RuntimeException('No se encontró el archivo de Excel.');
|
||||
}
|
||||
$maxSize = 10 * 1024 * 1024; // 10 MB
|
||||
if ((int) ($file['size'] ?? 0) > $maxSize) {
|
||||
throw new RuntimeException('El archivo es demasiado grande. Máximo 10 MB.');
|
||||
}
|
||||
|
||||
$res = cc_agregados_import_from_uploaded_file($pdo, $storeKey, $file);
|
||||
$inserted = (int) ($res['inserted'] ?? 0);
|
||||
$skipped = (int) ($res['skipped'] ?? 0);
|
||||
$noticeMessage = 'Importación completada: ' . $inserted . ' pedidos agregados.' . ($skipped > 0 ? ' Filas ignoradas: ' . $skipped . '.' : '');
|
||||
$uploadErrorMessage = null;
|
||||
} catch (Throwable $e) {
|
||||
$uploadErrorMessage = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($storeKey === 'otra_tienda') {
|
||||
$syncInfo = drive_test_sync_orders_incremental($pdo, $storeKey, 0);
|
||||
$totalRows = (int) ($syncInfo['drive_rows_total'] ?? 0);
|
||||
$tuaniLastProcessedRow = (int) ($syncInfo['last_processed_row'] ?? 0);
|
||||
$startRow = (int) ($syncInfo['next_start_row'] ?? $startRow);
|
||||
|
||||
$orders = drive_test_fetch_orders_from_db($pdo, $storeKey);
|
||||
|
||||
$agregadosCount = 0;
|
||||
foreach ($orders as $o) {
|
||||
if (!empty($o['is_agregado'])) {
|
||||
$agregadosCount++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey);
|
||||
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
||||
$orders = $preview['orders'] ?? [];
|
||||
|
||||
$agregadosOrders = drive_test_fetch_orders_from_db($pdo, $storeKey, true);
|
||||
$agregadosCount = count($agregadosOrders);
|
||||
if (!empty($agregadosOrders)) {
|
||||
$orders = array_merge($orders, $agregadosOrders);
|
||||
}
|
||||
}
|
||||
|
||||
$tracking = drive_test_fetch_tracking($pdo, array_column($orders, 'source_key'));
|
||||
$orders = drive_test_merge_tracking($orders, $tracking);
|
||||
@ -465,9 +540,22 @@ try {
|
||||
}));
|
||||
|
||||
usort($visibleOrders, static function (array $a, array $b) use ($view): int {
|
||||
if ($view === 'pendientes_hoy') {
|
||||
$aDue = cc_test_parse_datetime($a['proxima_llamada_at'] ?? null);
|
||||
$bDue = cc_test_parse_datetime($b['proxima_llamada_at'] ?? null);
|
||||
$aAgregado = !empty($a['is_agregado']);
|
||||
$bAgregado = !empty($b['is_agregado']);
|
||||
if ($aAgregado !== $bAgregado) {
|
||||
return $aAgregado ? 1 : -1;
|
||||
}
|
||||
|
||||
$aImport = cc_test_import_time($a);
|
||||
$bImport = cc_test_import_time($b);
|
||||
|
||||
if ($aImport !== $bImport) {
|
||||
return $bImport <=> $aImport;
|
||||
}
|
||||
|
||||
if ($view === "pendientes_hoy") {
|
||||
$aDue = cc_test_parse_datetime($a["proxima_llamada_at"] ?? null);
|
||||
$bDue = cc_test_parse_datetime($b["proxima_llamada_at"] ?? null);
|
||||
if ($aDue && $bDue && $aDue != $bDue) {
|
||||
return $aDue <=> $bDue;
|
||||
}
|
||||
@ -508,7 +596,8 @@ require_once 'layout_header.php';
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Drive detectado: <?php echo (int) $totalRows; ?> filas</span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Extracción: desde fila <?php echo (int) $startRow; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Agregados: <?php echo (int) $agregadosCount; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2"><?php if ($storeKey === 'otra_tienda' && $tuaniLastProcessedRow !== null): ?>TUANI: último procesado <?php echo (int) $tuaniLastProcessedRow; ?> · próxima desde <?php echo (int) $startRow; ?><?php else: ?>Extracción: desde fila <?php echo (int) $startRow; ?><?php endif; ?></span>
|
||||
<span class="badge rounded-pill text-bg-light border px-3 py-2">Auto actualización: cada 10 min</span>
|
||||
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
||||
</div>
|
||||
@ -526,6 +615,47 @@ require_once 'layout_header.php';
|
||||
<?php echo htmlspecialchars($errorMessage); ?>
|
||||
</section>
|
||||
<?php else: ?>
|
||||
<section class="mb-4">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3">
|
||||
<div>
|
||||
<h2 class="h5 fw-bold mb-1">
|
||||
<i class="bi bi-upload text-primary"></i> Agregar pedidos por Excel
|
||||
</h2>
|
||||
<div class="text-muted small">
|
||||
Para: <strong><?php echo $storeKey === 'otra_tienda' ? 'TUANI' : 'Flower'; ?></strong>.
|
||||
Los pedidos cargados se muestran como <strong>AGREGADOS</strong> y no afectan la secuencia de Drive.
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<a href="download_agregados_pedidos_template.php" class="btn btn-outline-primary btn-sm">Descargar plantilla Excel</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-100 w-lg-auto">
|
||||
<form method="post" enctype="multipart/form-data" class="d-flex flex-column gap-2">
|
||||
<input type="hidden" name="action" value="upload_agregados_excel">
|
||||
<input
|
||||
type="file"
|
||||
name="agregados_excel"
|
||||
class="form-control form-control-sm"
|
||||
accept=".xlsx,.csv"
|
||||
required
|
||||
>
|
||||
<button type="submit" class="btn btn-primary btn-sm w-100">Importar pedidos</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($uploadErrorMessage)): ?>
|
||||
<div class="alert alert-danger mt-3 mb-0" role="alert">
|
||||
<?php echo htmlspecialchars($uploadErrorMessage); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="row g-3 mb-4">
|
||||
<div class="col-md-6 col-xl-2">
|
||||
<a href="?view=pendientes_hoy&store=<?php echo htmlspecialchars($storeKey); ?>" class="text-decoration-none">
|
||||
|
||||
@ -25,7 +25,7 @@ $is_today = isset($_GET['today']);
|
||||
|
||||
$sql = "SELECT p.*
|
||||
FROM pedidos p
|
||||
WHERE p.estado = 'ROTULADO 📦'";
|
||||
WHERE (p.estado = 'ROTULADO 📦' OR p.estado = 'PENDIENTE')";
|
||||
$params = [];
|
||||
|
||||
if ($user_role === 'Asesor') {
|
||||
@ -197,7 +197,7 @@ $label_generated_date = date('d/m');
|
||||
|
||||
<?php if (empty($pedidos)): ?>
|
||||
<div style="text-align: center; margin-top: 50px;">
|
||||
<h3>No hay pedidos rotulados para mostrar.</h3>
|
||||
<h3>No hay pedidos rotulados o pendientes para mostrar (ROTULADO 📦 / PENDIENTE).</h3>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
488
includes/agregados_excel_import.php
Normal file
488
includes/agregados_excel_import.php
Normal file
@ -0,0 +1,488 @@
|
||||
<?php
|
||||
|
||||
// Importación de pedidos "Agregados" desde Excel (.xlsx) o CSV.
|
||||
// Objetivo: cargar pedidos manuales sin romper la secuencia de pedidos provenientes de Drive.
|
||||
|
||||
function cc_agregados_null_if_empty(mixed $value): mixed
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
return $value === '' ? null : $value;
|
||||
}
|
||||
|
||||
return $value === '' ? null : $value;
|
||||
}
|
||||
|
||||
function cc_agregados_col_letters_to_index(string $letters): int
|
||||
{
|
||||
$letters = strtoupper(trim($letters));
|
||||
if ($letters === '') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
$len = strlen($letters);
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
$ch = $letters[$i];
|
||||
if ($ch < 'A' || $ch > 'Z') {
|
||||
continue;
|
||||
}
|
||||
$index = $index * 26 + (ord($ch) - ord('A') + 1);
|
||||
}
|
||||
|
||||
return max(0, $index - 1);
|
||||
}
|
||||
|
||||
function cc_agregados_normalize_header(string $label): string
|
||||
{
|
||||
$label = trim($label);
|
||||
if ($label === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$ascii = @iconv('UTF-8', 'ASCII//TRANSLIT', $label);
|
||||
if ($ascii !== false && $ascii !== '') {
|
||||
$label = $ascii;
|
||||
}
|
||||
|
||||
$label = mb_strtoupper($label);
|
||||
$label = preg_replace('/[^A-Z0-9]+/', '', $label);
|
||||
|
||||
return (string) ($label ?? '');
|
||||
}
|
||||
|
||||
function cc_agregados_guess_field_from_header(string $normalizedHeader): ?string
|
||||
{
|
||||
if ($normalizedHeader === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$h = $normalizedHeader;
|
||||
|
||||
if (str_contains($h, 'NOMBRE') || str_contains($h, 'CLIENTE')) {
|
||||
return 'nombre';
|
||||
}
|
||||
if (str_contains($h, 'CELULAR') || str_contains($h, 'TELEF')) {
|
||||
return 'celular';
|
||||
}
|
||||
if (str_contains($h, 'DNI')) {
|
||||
return 'dni';
|
||||
}
|
||||
if (str_contains($h, 'DIREC')) {
|
||||
return 'direccion';
|
||||
}
|
||||
if (str_contains($h, 'REFER')) {
|
||||
return 'referencia';
|
||||
}
|
||||
if (str_contains($h, 'SEDE')) {
|
||||
return 'sede';
|
||||
}
|
||||
if (str_contains($h, 'CIUDAD')) {
|
||||
return 'ciudad';
|
||||
}
|
||||
if (str_contains($h, 'DISTRIT')) {
|
||||
return 'distrito';
|
||||
}
|
||||
if (str_contains($h, 'PRODUCT')) {
|
||||
return 'producto';
|
||||
}
|
||||
if (str_contains($h, 'CANT')) {
|
||||
return 'cantidad';
|
||||
}
|
||||
if (str_contains($h, 'PRECIO')) {
|
||||
return 'precio';
|
||||
}
|
||||
if (str_contains($h, 'PAIS')) {
|
||||
return 'pais';
|
||||
}
|
||||
if (str_contains($h, 'COORD')) {
|
||||
return 'coordenadas';
|
||||
}
|
||||
if (str_contains($h, 'METODO')) {
|
||||
return 'metodo';
|
||||
}
|
||||
if (str_contains($h, 'OBSERV')) {
|
||||
return 'observaciones';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function cc_agregados_read_csv_rows(string $filePath): array
|
||||
{
|
||||
$handle = fopen($filePath, 'rb');
|
||||
if (!$handle) {
|
||||
throw new RuntimeException('No se pudo leer el archivo CSV.');
|
||||
}
|
||||
|
||||
$firstLine = fgets($handle);
|
||||
if ($firstLine === false) {
|
||||
fclose($handle);
|
||||
return [];
|
||||
}
|
||||
|
||||
$delimiter = ';';
|
||||
$commaCount = substr_count($firstLine, ',');
|
||||
$semiCount = substr_count($firstLine, ';');
|
||||
$delimiter = $semiCount >= $commaCount ? ';' : ',';
|
||||
|
||||
rewind($handle);
|
||||
|
||||
$rows = [];
|
||||
$rowNum = 1;
|
||||
while (($line = fgetcsv($handle, 0, $delimiter)) !== false) {
|
||||
// fgetcsv puede devolver nulls.
|
||||
$clean = [];
|
||||
foreach ($line as $cell) {
|
||||
$clean[] = $cell === null ? '' : (string) $cell;
|
||||
}
|
||||
$rows[$rowNum] = $clean;
|
||||
$rowNum++;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
function cc_agregados_read_xlsx_rows(string $filePath): array
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$openResult = $zip->open($filePath);
|
||||
if ($openResult !== true) {
|
||||
throw new RuntimeException('No se pudo abrir el archivo XLSX.');
|
||||
}
|
||||
|
||||
$sharedStrings = [];
|
||||
$sharedName = 'xl/sharedStrings.xml';
|
||||
if ($zip->locateName($sharedName) !== false) {
|
||||
$sharedXml = $zip->getFromName($sharedName);
|
||||
if ($sharedXml) {
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadXML($sharedXml);
|
||||
$xpath = new DOMXPath($doc);
|
||||
|
||||
foreach ($xpath->query("//*[local-name()='si']") as $siNode) {
|
||||
$parts = [];
|
||||
foreach ($xpath->query(".//*[local-name()='t']", $siNode) as $tNode) {
|
||||
$parts[] = (string) $tNode->textContent;
|
||||
}
|
||||
$sharedStrings[] = implode('', $parts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Elegimos la primera hoja.
|
||||
$sheetName = null;
|
||||
if ($zip->locateName('xl/worksheets/sheet1.xml') !== false) {
|
||||
$sheetName = 'xl/worksheets/sheet1.xml';
|
||||
} else {
|
||||
$candidates = [];
|
||||
for ($i = 0; $i < $zip->numFiles; $i++) {
|
||||
$name = $zip->getNameIndex($i);
|
||||
if (preg_match('#^xl/worksheets/sheet\d+\.xml$#', $name)) {
|
||||
$candidates[] = $name;
|
||||
}
|
||||
}
|
||||
sort($candidates);
|
||||
$sheetName = $candidates[0] ?? null;
|
||||
}
|
||||
|
||||
if (!$sheetName) {
|
||||
throw new RuntimeException('No se encontró una hoja en el archivo XLSX.');
|
||||
}
|
||||
|
||||
$sheetXml = $zip->getFromName($sheetName);
|
||||
if (!$sheetXml) {
|
||||
throw new RuntimeException('No se pudo leer la hoja del archivo XLSX.');
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->loadXML($sheetXml);
|
||||
$xpath = new DOMXPath($doc);
|
||||
|
||||
$rows = [];
|
||||
$rowNodes = $xpath->query("//*[local-name()='sheetData']/*[local-name()='row']");
|
||||
foreach ($rowNodes as $rowNode) {
|
||||
$rowNumRaw = $rowNode->getAttribute('r');
|
||||
$rowNum = (int) $rowNumRaw;
|
||||
if ($rowNum <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($xpath->query("*[local-name()='c']", $rowNode) as $cNode) {
|
||||
/** @var DOMElement $cNode */
|
||||
$cellRef = $cNode->getAttribute('r');
|
||||
if ($cellRef === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$colLetters = preg_replace('/\d+/', '', $cellRef);
|
||||
$colIndex = cc_agregados_col_letters_to_index($colLetters);
|
||||
if ($colIndex < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cellType = $cNode->getAttribute('t');
|
||||
$value = '';
|
||||
|
||||
if ($cellType === 's') {
|
||||
$v = (string) $xpath->evaluate("string(./*[local-name()='v'])", $cNode);
|
||||
$idx = (int) $v;
|
||||
$value = $sharedStrings[$idx] ?? '';
|
||||
} elseif ($cellType === 'inlineStr') {
|
||||
$parts = [];
|
||||
foreach ($xpath->query("./*[local-name()='is']/*[local-name()='t']", $cNode) as $tNode) {
|
||||
$parts[] = (string) $tNode->textContent;
|
||||
}
|
||||
$value = implode('', $parts);
|
||||
} else {
|
||||
$value = (string) $xpath->evaluate("string(./*[local-name()='v'])", $cNode);
|
||||
}
|
||||
|
||||
$rows[$rowNum][$colIndex] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
function cc_agregados_normalize_phone(string $value): string
|
||||
{
|
||||
$value = trim($value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return preg_replace('/\D+/', '', $value);
|
||||
}
|
||||
|
||||
function cc_agregados_import_from_uploaded_file(PDO $pdo, string $storeKey, array $uploadedFile): array
|
||||
{
|
||||
if (!isset($uploadedFile['error']) || (int) $uploadedFile['error'] !== UPLOAD_ERR_OK) {
|
||||
$err = (int) ($uploadedFile['error'] ?? -1);
|
||||
throw new RuntimeException('Error al subir el archivo. Código: ' . $err);
|
||||
}
|
||||
|
||||
$tmpPath = (string) ($uploadedFile['tmp_name'] ?? '');
|
||||
if ($tmpPath === '' || !file_exists($tmpPath)) {
|
||||
throw new RuntimeException('El archivo temporal no está disponible.');
|
||||
}
|
||||
|
||||
$originalName = (string) ($uploadedFile['name'] ?? '');
|
||||
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
|
||||
|
||||
// Ensure schema.
|
||||
drive_test_ensure_orders_table($pdo);
|
||||
|
||||
$rowsByIndex = [];
|
||||
if ($ext === 'xlsx') {
|
||||
$rowsByIndex = cc_agregados_read_xlsx_rows($tmpPath);
|
||||
} elseif ($ext === 'csv') {
|
||||
$rowsByIndex = cc_agregados_read_csv_rows($tmpPath);
|
||||
} else {
|
||||
throw new RuntimeException('Formato no soportado. Solo .xlsx o .csv');
|
||||
}
|
||||
|
||||
if (empty($rowsByIndex)) {
|
||||
throw new RuntimeException('El archivo está vacío o no se pudo leer.');
|
||||
}
|
||||
|
||||
// Cabecera: intentamos fila 1.
|
||||
$rowNumbers = array_keys($rowsByIndex);
|
||||
sort($rowNumbers);
|
||||
$headerRowNum = $rowNumbers[0] ?? 1;
|
||||
if (isset($rowsByIndex[1])) {
|
||||
$headerRowNum = 1;
|
||||
}
|
||||
|
||||
$headerRow = $rowsByIndex[$headerRowNum] ?? [];
|
||||
if (!is_array($headerRow) || empty($headerRow)) {
|
||||
throw new RuntimeException('No se encontró la fila de encabezados.');
|
||||
}
|
||||
|
||||
// Column index => field key
|
||||
$colToField = [];
|
||||
foreach ($headerRow as $colIndex => $headerValue) {
|
||||
$headerValue = trim((string) $headerValue);
|
||||
if ($headerValue === '') {
|
||||
continue;
|
||||
}
|
||||
$normalized = cc_agregados_normalize_header($headerValue);
|
||||
$field = cc_agregados_guess_field_from_header($normalized);
|
||||
if ($field !== null) {
|
||||
$colToField[(int) $colIndex] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($colToField)) {
|
||||
throw new RuntimeException('No se reconocieron encabezados. Revisa la plantilla de Excel.');
|
||||
}
|
||||
|
||||
$maxRowNum = max($rowNumbers);
|
||||
|
||||
$now = date('Y-m-d H:i:s');
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO callcenter_test_orders (
|
||||
store_key,
|
||||
source_key,
|
||||
codigo,
|
||||
import_id,
|
||||
drive_imported_at,
|
||||
is_agregado,
|
||||
nombre,
|
||||
direccion_drive,
|
||||
referencia_drive,
|
||||
sede_drive,
|
||||
ciudad_drive,
|
||||
distrito_drive,
|
||||
dni_drive,
|
||||
observaciones_drive,
|
||||
celular,
|
||||
producto,
|
||||
cantidad,
|
||||
precio,
|
||||
pais,
|
||||
coordenadas,
|
||||
metodo
|
||||
) VALUES (
|
||||
:store_key,
|
||||
:source_key,
|
||||
:codigo,
|
||||
:import_id,
|
||||
:drive_imported_at,
|
||||
:is_agregado,
|
||||
:nombre,
|
||||
:direccion_drive,
|
||||
:referencia_drive,
|
||||
:sede_drive,
|
||||
:ciudad_drive,
|
||||
:distrito_drive,
|
||||
:dni_drive,
|
||||
:observaciones_drive,
|
||||
:celular,
|
||||
:producto,
|
||||
:cantidad,
|
||||
:precio,
|
||||
:pais,
|
||||
:coordenadas,
|
||||
:metodo
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
store_key = VALUES(store_key),
|
||||
codigo = VALUES(codigo),
|
||||
import_id = VALUES(import_id),
|
||||
drive_imported_at = VALUES(drive_imported_at),
|
||||
is_agregado = VALUES(is_agregado),
|
||||
nombre = VALUES(nombre),
|
||||
direccion_drive = VALUES(direccion_drive),
|
||||
referencia_drive = VALUES(referencia_drive),
|
||||
sede_drive = VALUES(sede_drive),
|
||||
ciudad_drive = VALUES(ciudad_drive),
|
||||
distrito_drive = VALUES(distrito_drive),
|
||||
dni_drive = VALUES(dni_drive),
|
||||
observaciones_drive = VALUES(observaciones_drive),
|
||||
celular = VALUES(celular),
|
||||
producto = VALUES(producto),
|
||||
cantidad = VALUES(cantidad),
|
||||
precio = VALUES(precio),
|
||||
pais = VALUES(pais),
|
||||
coordenadas = VALUES(coordenadas),
|
||||
metodo = VALUES(metodo),
|
||||
last_seen_at = CURRENT_TIMESTAMP"
|
||||
);
|
||||
|
||||
$inserted = 0;
|
||||
$skipped = 0;
|
||||
$rowProcessed = 0;
|
||||
|
||||
for ($rowNum = $headerRowNum + 1; $rowNum <= $maxRowNum; $rowNum++) {
|
||||
if (!isset($rowsByIndex[$rowNum])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rowProcessed++;
|
||||
|
||||
$data = [];
|
||||
foreach ($colToField as $colIndex => $field) {
|
||||
$val = $rowsByIndex[$rowNum][$colIndex] ?? '';
|
||||
$val = trim((string) $val);
|
||||
$data[$field] = $val;
|
||||
}
|
||||
|
||||
$nombre = $data['nombre'] ?? '';
|
||||
$celularRaw = $data['celular'] ?? '';
|
||||
$celular = cc_agregados_normalize_phone($celularRaw);
|
||||
$producto = $data['producto'] ?? '';
|
||||
|
||||
$isEmptyRow = ($nombre === '' && $celular === '' && $producto === '');
|
||||
if ($isEmptyRow) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$dni = cc_agregados_null_if_empty($data['dni'] ?? '');
|
||||
$direccion = cc_agregados_null_if_empty($data['direccion'] ?? '');
|
||||
$referencia = cc_agregados_null_if_empty($data['referencia'] ?? '');
|
||||
$sede = cc_agregados_null_if_empty($data['sede'] ?? '');
|
||||
$ciudad = cc_agregados_null_if_empty($data['ciudad'] ?? '');
|
||||
$distrito = cc_agregados_null_if_empty($data['distrito'] ?? '');
|
||||
$observaciones = cc_agregados_null_if_empty($data['observaciones'] ?? '');
|
||||
$cantidad = cc_agregados_null_if_empty($data['cantidad'] ?? '');
|
||||
$precio = cc_agregados_null_if_empty($data['precio'] ?? '');
|
||||
$pais = cc_agregados_null_if_empty($data['pais'] ?? '');
|
||||
$coordenadas = cc_agregados_null_if_empty($data['coordenadas'] ?? '');
|
||||
$metodo = cc_agregados_null_if_empty($data['metodo'] ?? '');
|
||||
|
||||
$sourceKeyBase = implode('|', [
|
||||
$storeKey,
|
||||
(string) $nombre,
|
||||
(string) $celular,
|
||||
(string) ($dni ?? ''),
|
||||
(string) $producto,
|
||||
(string) ($cantidad ?? ''),
|
||||
(string) ($precio ?? ''),
|
||||
(string) ($direccion ?? ''),
|
||||
(string) ($referencia ?? ''),
|
||||
(string) ($sede ?? ''),
|
||||
(string) ($ciudad ?? ''),
|
||||
(string) ($distrito ?? ''),
|
||||
(string) ($observaciones ?? ''),
|
||||
(string) ($metodo ?? ''),
|
||||
]);
|
||||
|
||||
$sourceKey = sha1('manual|' . $sourceKeyBase);
|
||||
|
||||
$stmt->execute([
|
||||
':store_key' => $storeKey,
|
||||
':source_key' => $sourceKey,
|
||||
':codigo' => null,
|
||||
':import_id' => null,
|
||||
':drive_imported_at' => $now,
|
||||
':is_agregado' => 1,
|
||||
':nombre' => cc_agregados_null_if_empty($nombre),
|
||||
':direccion_drive' => $direccion,
|
||||
':referencia_drive' => $referencia,
|
||||
':sede_drive' => $sede,
|
||||
':ciudad_drive' => $ciudad,
|
||||
':distrito_drive' => $distrito,
|
||||
':dni_drive' => $dni,
|
||||
':observaciones_drive' => $observaciones,
|
||||
':celular' => $celular !== '' ? $celular : null,
|
||||
':producto' => cc_agregados_null_if_empty($producto),
|
||||
':cantidad' => $cantidad,
|
||||
':precio' => $precio,
|
||||
':pais' => $pais,
|
||||
':coordenadas' => $coordenadas,
|
||||
':metodo' => $metodo,
|
||||
]);
|
||||
|
||||
$inserted++;
|
||||
}
|
||||
|
||||
return [
|
||||
'inserted' => $inserted,
|
||||
'skipped' => $skipped,
|
||||
'rows_processed' => $rowProcessed,
|
||||
];
|
||||
}
|
||||
@ -169,7 +169,7 @@ function cc_test_ensure_schema(PDO $pdo): void
|
||||
`sede_drive` VARCHAR(120) NULL,
|
||||
`ciudad_drive` VARCHAR(120) NULL,
|
||||
`distrito_drive` VARCHAR(120) NULL,
|
||||
`dni_drive` VARCHAR(40) NULL,
|
||||
`dni_drive` LONGTEXT NULL,
|
||||
`observaciones_drive` TEXT NULL,
|
||||
`celular` VARCHAR(40) DEFAULT NULL,
|
||||
`producto` TEXT NULL,
|
||||
@ -185,6 +185,26 @@ function cc_test_ensure_schema(PDO $pdo): void
|
||||
KEY `idx_callcenter_test_orders_last_seen_at` (`last_seen_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
|
||||
// Asegurar que `dni_drive` soporte valores grandes (evita SQLSTATE[22001] por truncamiento).
|
||||
try {
|
||||
$colStmt = $pdo->prepare("
|
||||
SELECT DATA_TYPE
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'callcenter_test_orders'
|
||||
AND COLUMN_NAME = 'dni_drive'
|
||||
LIMIT 1
|
||||
");
|
||||
$colStmt->execute();
|
||||
$dataType = strtolower((string) $colStmt->fetchColumn());
|
||||
|
||||
if ($dataType !== 'longtext') {
|
||||
$pdo->exec("ALTER TABLE `callcenter_test_orders` MODIFY COLUMN `dni_drive` LONGTEXT NULL");
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
error_log('cc_test_ensure_schema: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `callcenter_test_tracking` (
|
||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`source_key` CHAR(40) NOT NULL,
|
||||
|
||||
@ -141,7 +141,11 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $
|
||||
$dataRows = array_slice($values, 1);
|
||||
|
||||
$startIndex = max(0, $startRow - 2);
|
||||
if ($startIndex > 0 && $startIndex < count($dataRows)) {
|
||||
$totalDataRows = count($dataRows);
|
||||
|
||||
if ($startIndex >= $totalDataRows) {
|
||||
$dataRows = [];
|
||||
} elseif ($startIndex > 0) {
|
||||
$dataRows = array_slice($dataRows, $startIndex);
|
||||
}
|
||||
|
||||
@ -263,3 +267,340 @@ function drive_test_merge_tracking(array $orders, array $tracking): array
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
|
||||
function drive_test_parse_datetime_mysql(?string $value): ?string
|
||||
{
|
||||
$value = trim((string) $value);
|
||||
if ($value === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
$formats = [
|
||||
"Y-m-d\\TH:i:sP",
|
||||
"Y-m-d\\TH:i:s",
|
||||
"Y-m-d\\TH:i",
|
||||
"Y-m-d H:i:s",
|
||||
"Y-m-d H:i",
|
||||
DateTimeInterface::ATOM,
|
||||
"Y-m-d",
|
||||
];
|
||||
|
||||
foreach ($formats as $format) {
|
||||
try {
|
||||
$date = DateTimeImmutable::createFromFormat($format, $value);
|
||||
if ($date instanceof DateTimeImmutable) {
|
||||
return $date->format("Y-m-d H:i:s");
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
// ignore and try next format
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return (new DateTimeImmutable($value))->format("Y-m-d H:i:s");
|
||||
} catch (Throwable $exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function drive_test_ensure_orders_table(PDO $pdo): void
|
||||
{
|
||||
static $checked = false;
|
||||
if ($checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `callcenter_test_orders` (
|
||||
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
`store_key` VARCHAR(50) NULL,
|
||||
`source_key` CHAR(40) NOT NULL,
|
||||
`codigo` VARCHAR(80) DEFAULT NULL,
|
||||
`import_id` VARCHAR(120) DEFAULT NULL,
|
||||
`is_agregado` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`drive_imported_at` DATETIME NULL,
|
||||
`nombre` VARCHAR(255) DEFAULT NULL,
|
||||
`direccion_drive` TEXT NULL,
|
||||
`referencia_drive` TEXT NULL,
|
||||
`sede_drive` VARCHAR(120) NULL,
|
||||
`ciudad_drive` VARCHAR(120) NULL,
|
||||
`distrito_drive` VARCHAR(120) NULL,
|
||||
`dni_drive` LONGTEXT NULL,
|
||||
`observaciones_drive` TEXT NULL,
|
||||
`celular` VARCHAR(40) DEFAULT NULL,
|
||||
`producto` TEXT NULL,
|
||||
`cantidad` VARCHAR(60) NULL,
|
||||
`precio` VARCHAR(60) NULL,
|
||||
`pais` VARCHAR(80) NULL,
|
||||
`coordenadas` VARCHAR(255) NULL,
|
||||
`metodo` VARCHAR(120) NULL,
|
||||
`first_seen_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`last_seen_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY `uniq_callcenter_test_orders_source_key` (`source_key`),
|
||||
KEY `idx_callcenter_test_orders_drive_imported_at` (`drive_imported_at`),
|
||||
KEY `idx_callcenter_test_orders_last_seen_at` (`last_seen_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
|
||||
// In case the existing table was created before we introduced store_key.
|
||||
cc_test_ensure_column($pdo, "callcenter_test_orders", "store_key", "VARCHAR(50) NULL");
|
||||
|
||||
// Asegura columna para pedidos cargados manualmente (Agregados)
|
||||
cc_test_ensure_column($pdo, "callcenter_test_orders", "is_agregado", "TINYINT(1) NOT NULL DEFAULT 0");
|
||||
|
||||
// Asegurar que `dni_drive` soporte valores grandes desde Google Sheets.
|
||||
try {
|
||||
$colStmt = $pdo->prepare("
|
||||
SELECT DATA_TYPE
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'callcenter_test_orders'
|
||||
AND COLUMN_NAME = 'dni_drive'
|
||||
LIMIT 1
|
||||
");
|
||||
$colStmt->execute();
|
||||
$dataType = strtolower((string) $colStmt->fetchColumn());
|
||||
|
||||
if ($dataType !== 'longtext') {
|
||||
$pdo->exec("ALTER TABLE `callcenter_test_orders` MODIFY COLUMN `dni_drive` LONGTEXT NULL");
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
// No bloqueamos el panel si por alguna razón no se puede ajustar el tipo.
|
||||
error_log('drive_test_ensure_orders_table: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
$checked = true;
|
||||
}
|
||||
|
||||
function drive_test_ensure_import_checkpoints_table(PDO $pdo): void
|
||||
{
|
||||
static $checked = false;
|
||||
if ($checked) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pdo->exec("CREATE TABLE IF NOT EXISTS `drive_test_import_checkpoints` (
|
||||
`store_key` VARCHAR(50) NOT NULL,
|
||||
`last_processed_row` INT NOT NULL DEFAULT 0,
|
||||
`last_sync_at` DATETIME NULL,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`store_key`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||
|
||||
$checked = true;
|
||||
}
|
||||
|
||||
function drive_test_sync_orders_incremental(PDO $pdo, string $storeKey, int $overlapRows = 50): array
|
||||
{
|
||||
$storeKey = mb_strtolower(trim($storeKey));
|
||||
$storeConfig = drive_test_get_store_config($storeKey);
|
||||
$baselineStartRow = (int) ($storeConfig["startRow"] ?? 5552);
|
||||
|
||||
drive_test_ensure_orders_table($pdo);
|
||||
drive_test_ensure_import_checkpoints_table($pdo);
|
||||
|
||||
$stmtCheckpoint = $pdo->prepare("SELECT last_processed_row FROM drive_test_import_checkpoints WHERE store_key = ? LIMIT 1");
|
||||
$stmtCheckpoint->execute([$storeKey]);
|
||||
$checkpointRow = (int) ($stmtCheckpoint->fetchColumn() ?? 0);
|
||||
|
||||
$overlapRows = max(0, $overlapRows);
|
||||
$effectiveStartRow = $checkpointRow > 0
|
||||
? max($baselineStartRow, $checkpointRow - $overlapRows + 1)
|
||||
: $baselineStartRow;
|
||||
|
||||
$preview = drive_test_fetch_orders(0, $effectiveStartRow, $storeKey);
|
||||
$driveOrders = $preview["orders"] ?? [];
|
||||
$driveTotalRows = (int) ($preview["total_rows"] ?? 0);
|
||||
|
||||
$sheetLastRow = ($effectiveStartRow - 1) + $driveTotalRows;
|
||||
$newCheckpointRow = max($checkpointRow, (int) $sheetLastRow);
|
||||
|
||||
if (!empty($driveOrders)) {
|
||||
$stmtUpsert = $pdo->prepare(
|
||||
"INSERT INTO callcenter_test_orders (
|
||||
store_key,
|
||||
source_key,
|
||||
codigo,
|
||||
import_id,
|
||||
drive_imported_at,
|
||||
nombre,
|
||||
direccion_drive,
|
||||
referencia_drive,
|
||||
sede_drive,
|
||||
ciudad_drive,
|
||||
distrito_drive,
|
||||
dni_drive,
|
||||
observaciones_drive,
|
||||
celular,
|
||||
producto,
|
||||
cantidad,
|
||||
precio,
|
||||
pais,
|
||||
coordenadas,
|
||||
metodo
|
||||
) VALUES (
|
||||
:store_key,
|
||||
:source_key,
|
||||
:codigo,
|
||||
:import_id,
|
||||
:drive_imported_at,
|
||||
:nombre,
|
||||
:direccion_drive,
|
||||
:referencia_drive,
|
||||
:sede_drive,
|
||||
:ciudad_drive,
|
||||
:distrito_drive,
|
||||
:dni_drive,
|
||||
:observaciones_drive,
|
||||
:celular,
|
||||
:producto,
|
||||
:cantidad,
|
||||
:precio,
|
||||
:pais,
|
||||
:coordenadas,
|
||||
:metodo
|
||||
) ON DUPLICATE KEY UPDATE
|
||||
store_key = VALUES(store_key),
|
||||
codigo = VALUES(codigo),
|
||||
import_id = VALUES(import_id),
|
||||
is_agregado = 0,
|
||||
drive_imported_at = VALUES(drive_imported_at),
|
||||
nombre = VALUES(nombre),
|
||||
direccion_drive = VALUES(direccion_drive),
|
||||
referencia_drive = VALUES(referencia_drive),
|
||||
sede_drive = VALUES(sede_drive),
|
||||
ciudad_drive = VALUES(ciudad_drive),
|
||||
distrito_drive = VALUES(distrito_drive),
|
||||
dni_drive = VALUES(dni_drive),
|
||||
observaciones_drive = VALUES(observaciones_drive),
|
||||
celular = VALUES(celular),
|
||||
producto = VALUES(producto),
|
||||
cantidad = VALUES(cantidad),
|
||||
precio = VALUES(precio),
|
||||
pais = VALUES(pais),
|
||||
coordenadas = VALUES(coordenadas),
|
||||
metodo = VALUES(metodo),
|
||||
last_seen_at = CURRENT_TIMESTAMP"
|
||||
);
|
||||
|
||||
foreach ($driveOrders as $order) {
|
||||
$driveImportedAt = drive_test_parse_datetime_mysql($order["import_id"] ?? null);
|
||||
|
||||
$stmtUpsert->execute([
|
||||
":store_key" => $storeKey,
|
||||
":source_key" => $order["source_key"] ?? "",
|
||||
":codigo" => ($order["codigo"] ?? "") !== "" ? ($order["codigo"] ?? null) : null,
|
||||
":import_id" => ($order["import_id"] ?? "") !== "" ? ($order["import_id"] ?? null) : null,
|
||||
":drive_imported_at" => $driveImportedAt,
|
||||
":nombre" => ($order["nombre"] ?? "") !== "" ? ($order["nombre"] ?? null) : null,
|
||||
":direccion_drive" => ($order["direccion"] ?? "") !== "" ? ($order["direccion"] ?? null) : null,
|
||||
":referencia_drive" => ($order["referencia"] ?? "") !== "" ? ($order["referencia"] ?? null) : null,
|
||||
":sede_drive" => ($order["sede"] ?? "") !== "" ? ($order["sede"] ?? null) : null,
|
||||
":ciudad_drive" => ($order["ciudad"] ?? "") !== "" ? ($order["ciudad"] ?? null) : null,
|
||||
":distrito_drive" => ($order["distrito"] ?? "") !== "" ? ($order["distrito"] ?? null) : null,
|
||||
":dni_drive" => ($order["dni"] ?? "") !== "" ? ($order["dni"] ?? null) : null,
|
||||
":observaciones_drive" => ($order["observaciones"] ?? "") !== "" ? ($order["observaciones"] ?? null) : null,
|
||||
":celular" => ($order["celular"] ?? "") !== "" ? ($order["celular"] ?? null) : null,
|
||||
":producto" => ($order["producto"] ?? "") !== "" ? ($order["producto"] ?? null) : null,
|
||||
":cantidad" => ($order["cantidad"] ?? "") !== "" ? ($order["cantidad"] ?? null) : null,
|
||||
":precio" => ($order["precio"] ?? "") !== "" ? ($order["precio"] ?? null) : null,
|
||||
":pais" => ($order["pais"] ?? "") !== "" ? ($order["pais"] ?? null) : null,
|
||||
":coordenadas" => ($order["coordenadas"] ?? "") !== "" ? ($order["coordenadas"] ?? null) : null,
|
||||
":metodo" => ($order["metodo"] ?? "") !== "" ? ($order["metodo"] ?? null) : null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$stmtCheckpointUpsert = $pdo->prepare(
|
||||
"INSERT INTO drive_test_import_checkpoints (store_key, last_processed_row, last_sync_at)
|
||||
VALUES (:store_key, :last_processed_row, CURRENT_TIMESTAMP)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
last_processed_row = VALUES(last_processed_row),
|
||||
last_sync_at = CURRENT_TIMESTAMP"
|
||||
);
|
||||
$stmtCheckpointUpsert->execute([
|
||||
":store_key" => $storeKey,
|
||||
":last_processed_row" => $newCheckpointRow,
|
||||
]);
|
||||
|
||||
return [
|
||||
"checkpoint_was" => $checkpointRow,
|
||||
"last_processed_row" => $newCheckpointRow,
|
||||
"next_start_row" => $newCheckpointRow + 1,
|
||||
"effective_start_row" => $effectiveStartRow,
|
||||
"drive_rows_total" => $driveTotalRows,
|
||||
"orders_synced_count" => count($driveOrders),
|
||||
];
|
||||
}
|
||||
|
||||
function drive_test_fetch_orders_from_db(PDO $pdo, string $storeKey, ?bool $onlyAgregados = null): array
|
||||
{
|
||||
$storeKey = mb_strtolower(trim($storeKey));
|
||||
drive_test_ensure_orders_table($pdo);
|
||||
|
||||
$where = "WHERE store_key = ?";
|
||||
$params = [$storeKey];
|
||||
|
||||
if ($onlyAgregados === true) {
|
||||
$where .= " AND is_agregado = 1";
|
||||
} elseif ($onlyAgregados === false) {
|
||||
$where .= " AND (is_agregado = 0 OR is_agregado IS NULL)";
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT
|
||||
id,
|
||||
is_agregado,
|
||||
source_key,
|
||||
codigo,
|
||||
import_id,
|
||||
nombre,
|
||||
celular,
|
||||
producto,
|
||||
cantidad,
|
||||
precio,
|
||||
pais,
|
||||
coordenadas,
|
||||
metodo,
|
||||
direccion_drive,
|
||||
referencia_drive,
|
||||
sede_drive,
|
||||
ciudad_drive,
|
||||
distrito_drive,
|
||||
dni_drive,
|
||||
observaciones_drive
|
||||
FROM callcenter_test_orders
|
||||
$where
|
||||
ORDER BY id DESC"
|
||||
);
|
||||
$stmt->execute($params);
|
||||
|
||||
$orders = [];
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||
$orders[] = [
|
||||
"id" => (int) ($row["id"] ?? 0),
|
||||
"is_agregado" => (int) ($row["is_agregado"] ?? 0),
|
||||
"source_key" => (string) ($row["source_key"] ?? ""),
|
||||
"codigo" => (string) ($row["codigo"] ?? ""),
|
||||
"import_id" => (string) ($row["import_id"] ?? ""),
|
||||
"nombre" => (string) ($row["nombre"] ?? ""),
|
||||
"direccion" => (string) ($row["direccion_drive"] ?? ""),
|
||||
"referencia" => (string) ($row["referencia_drive"] ?? ""),
|
||||
"agencia" => "",
|
||||
"sede_agencia" => "",
|
||||
"distrito" => (string) ($row["distrito_drive"] ?? ""),
|
||||
"celular" => (string) ($row["celular"] ?? ""),
|
||||
"producto" => (string) ($row["producto"] ?? ""),
|
||||
"cantidad" => (string) ($row["cantidad"] ?? ""),
|
||||
"precio" => (string) ($row["precio"] ?? ""),
|
||||
"pais" => (string) ($row["pais"] ?? ""),
|
||||
"coordenadas" => (string) ($row["coordenadas"] ?? ""),
|
||||
"ciudad" => (string) ($row["ciudad_drive"] ?? ""),
|
||||
"metodo" => (string) ($row["metodo"] ?? ""),
|
||||
"sede" => (string) ($row["sede_drive"] ?? ""),
|
||||
"dni" => (string) ($row["dni_drive"] ?? ""),
|
||||
"observaciones" => (string) ($row["observaciones_drive"] ?? ""),
|
||||
];
|
||||
}
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user