Autosave: 20260715-044358
This commit is contained in:
parent
589f83f35c
commit
bb9b9b3670
@ -168,6 +168,8 @@ if (!isset($availableStores[$storeKey])) {
|
|||||||
$storeKey = 'flower';
|
$storeKey = 'flower';
|
||||||
}
|
}
|
||||||
$storeConfig = $availableStores[$storeKey];
|
$storeConfig = $availableStores[$storeKey];
|
||||||
|
$storeLabel = (string) ($storeConfig['label'] ?? strtoupper($storeKey));
|
||||||
|
$usesIncrementalSync = !empty($storeConfig['incremental_sync']);
|
||||||
|
|
||||||
$errorMessage = null;
|
$errorMessage = null;
|
||||||
$noticeMessage = null;
|
$noticeMessage = null;
|
||||||
@ -177,8 +179,7 @@ $visibleOrders = [];
|
|||||||
$modalsHtml = [];
|
$modalsHtml = [];
|
||||||
$totalRows = 0;
|
$totalRows = 0;
|
||||||
$agregadosCount = 0;
|
$agregadosCount = 0;
|
||||||
$tuaniLastProcessedRow = null;
|
$lastProcessedRow = null;
|
||||||
$loadLimit = $storeKey === 'otra_tienda' ? 0 : 100;
|
|
||||||
$startRow = (int) ($storeConfig['startRow'] ?? 5552);
|
$startRow = (int) ($storeConfig['startRow'] ?? 5552);
|
||||||
$catalogoProductos = [];
|
$catalogoProductos = [];
|
||||||
$stats = [
|
$stats = [
|
||||||
@ -205,10 +206,10 @@ try {
|
|||||||
$catalogoProductos = [];
|
$catalogoProductos = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($storeKey === 'otra_tienda') {
|
if ($usesIncrementalSync) {
|
||||||
$syncInfo = drive_test_sync_orders_incremental($pdo, $storeKey, 0);
|
$syncInfo = drive_test_sync_orders_incremental($pdo, $storeKey, 0);
|
||||||
$totalRows = (int) ($syncInfo['drive_rows_total'] ?? 0);
|
$totalRows = (int) ($syncInfo['drive_rows_total'] ?? 0);
|
||||||
$tuaniLastProcessedRow = (int) ($syncInfo['last_processed_row'] ?? 0);
|
$lastProcessedRow = (int) ($syncInfo['last_processed_row'] ?? 0);
|
||||||
$startRow = (int) ($syncInfo['next_start_row'] ?? $startRow);
|
$startRow = (int) ($syncInfo['next_start_row'] ?? $startRow);
|
||||||
|
|
||||||
$orders = drive_test_fetch_orders_from_db($pdo, $storeKey);
|
$orders = drive_test_fetch_orders_from_db($pdo, $storeKey);
|
||||||
@ -220,7 +221,7 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey);
|
$preview = drive_test_fetch_orders(100, $startRow, $storeKey);
|
||||||
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
||||||
$orders = $preview['orders'] ?? [];
|
$orders = $preview['orders'] ?? [];
|
||||||
|
|
||||||
@ -398,6 +399,18 @@ try {
|
|||||||
return $aPendienteLogistica ? -1 : 1;
|
return $aPendienteLogistica ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$aAssigned = cc_test_parse_datetime($a['assigned_at'] ?? null);
|
||||||
|
$bAssigned = cc_test_parse_datetime($b['assigned_at'] ?? null);
|
||||||
|
if ($aAssigned && $bAssigned && $aAssigned != $bAssigned) {
|
||||||
|
return $bAssigned <=> $aAssigned;
|
||||||
|
}
|
||||||
|
if ($aAssigned && !$bAssigned) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!$aAssigned && $bAssigned) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
$aDue = cc_test_parse_datetime($a["proxima_llamada_at"] ?? null);
|
$aDue = cc_test_parse_datetime($a["proxima_llamada_at"] ?? null);
|
||||||
$bDue = cc_test_parse_datetime($b["proxima_llamada_at"] ?? null);
|
$bDue = cc_test_parse_datetime($b["proxima_llamada_at"] ?? null);
|
||||||
if ($aDue && $bDue && $aDue != $bDue) {
|
if ($aDue && $bDue && $aDue != $bDue) {
|
||||||
@ -455,7 +468,7 @@ require_once 'layout_header.php';
|
|||||||
<div class="d-flex flex-wrap gap-2">
|
<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">Drive detectado: <?php echo (int) $totalRows; ?> filas</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">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: distribución desde fila <?php echo (int) ($storeConfig['startRow'] ?? $startRow); ?> · último procesado <?php echo (int) $tuaniLastProcessedRow; ?><?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"><?php if ($usesIncrementalSync && $lastProcessedRow !== null): ?><?php echo htmlspecialchars(mb_strtoupper($storeLabel)); ?>: distribución desde fila <?php echo (int) ($storeConfig['startRow'] ?? $startRow); ?> · último procesado <?php echo (int) $lastProcessedRow; ?><?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>
|
<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>
|
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -274,9 +274,15 @@ try {
|
|||||||
|
|
||||||
$user_id = $_SESSION['user_id'];
|
$user_id = $_SESSION['user_id'];
|
||||||
$user_role = $_SESSION['user_role'] ?? 'Asesor';
|
$user_role = $_SESSION['user_role'] ?? 'Asesor';
|
||||||
|
$is_admin = in_array($user_role, ['Administrador', 'admin'], true);
|
||||||
|
|
||||||
$search_query = trim($_GET['q'] ?? '');
|
$search_query = trim($_GET['q'] ?? '');
|
||||||
|
|
||||||
|
if (!$is_admin) {
|
||||||
|
http_response_code(403);
|
||||||
|
exit('Acceso denegado.');
|
||||||
|
}
|
||||||
|
|
||||||
// Exportar solo los pedidos con fecha de entrega "mañana"
|
// Exportar solo los pedidos con fecha de entrega "mañana"
|
||||||
$date_condition = "DATE(p.fecha_entrega) = DATE_ADD(CURDATE(), INTERVAL 1 DAY)";
|
$date_condition = "DATE(p.fecha_entrega) = DATE_ADD(CURDATE(), INTERVAL 1 DAY)";
|
||||||
|
|
||||||
|
|||||||
@ -169,6 +169,8 @@ if (!isset($availableStores[$storeKey])) {
|
|||||||
$storeKey = 'flower';
|
$storeKey = 'flower';
|
||||||
}
|
}
|
||||||
$storeConfig = $availableStores[$storeKey];
|
$storeConfig = $availableStores[$storeKey];
|
||||||
|
$storeLabel = (string) ($storeConfig['label'] ?? strtoupper($storeKey));
|
||||||
|
$usesIncrementalSync = !empty($storeConfig['incremental_sync']);
|
||||||
|
|
||||||
$errorMessage = null;
|
$errorMessage = null;
|
||||||
$noticeMessage = null;
|
$noticeMessage = null;
|
||||||
@ -179,8 +181,7 @@ $visibleOrders = [];
|
|||||||
$modalsHtml = [];
|
$modalsHtml = [];
|
||||||
$totalRows = 0;
|
$totalRows = 0;
|
||||||
$agregadosCount = 0;
|
$agregadosCount = 0;
|
||||||
$tuaniLastProcessedRow = null;
|
$lastProcessedRow = null;
|
||||||
$loadLimit = $storeKey === 'otra_tienda' ? 0 : 100;
|
|
||||||
|
|
||||||
$cupoObjetivoPorAsesora = 10;
|
$cupoObjetivoPorAsesora = 10;
|
||||||
$advisorOpenCounts = [];
|
$advisorOpenCounts = [];
|
||||||
@ -233,10 +234,10 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($storeKey === 'otra_tienda') {
|
if ($usesIncrementalSync) {
|
||||||
$syncInfo = drive_test_sync_orders_incremental($pdo, $storeKey, 0);
|
$syncInfo = drive_test_sync_orders_incremental($pdo, $storeKey, 0);
|
||||||
$totalRows = (int) ($syncInfo['drive_rows_total'] ?? 0);
|
$totalRows = (int) ($syncInfo['drive_rows_total'] ?? 0);
|
||||||
$tuaniLastProcessedRow = (int) ($syncInfo['last_processed_row'] ?? 0);
|
$lastProcessedRow = (int) ($syncInfo['last_processed_row'] ?? 0);
|
||||||
$startRow = (int) ($syncInfo['next_start_row'] ?? $startRow);
|
$startRow = (int) ($syncInfo['next_start_row'] ?? $startRow);
|
||||||
|
|
||||||
$orders = drive_test_fetch_orders_from_db($pdo, $storeKey);
|
$orders = drive_test_fetch_orders_from_db($pdo, $storeKey);
|
||||||
@ -248,7 +249,7 @@ try {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$preview = drive_test_fetch_orders($loadLimit, $startRow, $storeKey);
|
$preview = drive_test_fetch_orders(100, $startRow, $storeKey);
|
||||||
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
$totalRows = (int) ($preview['total_rows'] ?? 0);
|
||||||
$orders = $preview['orders'] ?? [];
|
$orders = $preview['orders'] ?? [];
|
||||||
|
|
||||||
@ -311,6 +312,108 @@ try {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'bulk_assign_assessor') {
|
||||||
|
if (empty($assessors)) {
|
||||||
|
throw new RuntimeException('No hay asesoras configuradas en el sistema.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetValue = trim((string) ($_POST['target_assessor'] ?? ''));
|
||||||
|
$targetKey = trim(mb_strtoupper($targetValue));
|
||||||
|
$targetKey = preg_replace('/\s+/', ' ', $targetKey) ?? $targetKey;
|
||||||
|
$selectedAssessor = $targetKey !== '' ? ($assessors[$targetKey] ?? null) : null;
|
||||||
|
if (!$selectedAssessor) {
|
||||||
|
throw new RuntimeException('Selecciona la asesora para la asignación rápida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetUserId = (int) ($selectedAssessor['id'] ?? 0);
|
||||||
|
if ($targetUserId <= 0) {
|
||||||
|
throw new RuntimeException('La asesora seleccionada no es válida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$availableSourceKeys = array_fill_keys(array_column($orders, 'source_key'), true);
|
||||||
|
$sourceKeys = [];
|
||||||
|
foreach ((array) ($_POST['source_keys'] ?? []) as $sourceKeyRaw) {
|
||||||
|
$sourceKey = trim((string) $sourceKeyRaw);
|
||||||
|
if ($sourceKey === '' || !preg_match('/^[a-f0-9]{40}$/', $sourceKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($availableSourceKeys[$sourceKey])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sourceKeys[$sourceKey] = $sourceKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($sourceKeys)) {
|
||||||
|
throw new RuntimeException('Selecciona al menos un pedido válido para la asignación rápida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$placeholders = implode(',', array_fill(0, count($sourceKeys), '?'));
|
||||||
|
$stmtCurrent = $pdo->prepare("SELECT source_key, user_id FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
|
||||||
|
$stmtCurrent->execute(array_values($sourceKeys));
|
||||||
|
|
||||||
|
$currentAssignments = [];
|
||||||
|
while ($row = $stmtCurrent->fetch(PDO::FETCH_ASSOC)) {
|
||||||
|
$currentAssignments[(string) ($row['source_key'] ?? '')] = isset($row['user_id']) && (int) $row['user_id'] > 0
|
||||||
|
? (int) $row['user_id']
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$assignedCount = 0;
|
||||||
|
$sameAssessorCount = 0;
|
||||||
|
$lockedCount = 0;
|
||||||
|
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
try {
|
||||||
|
foreach ($sourceKeys as $sourceKey) {
|
||||||
|
$currentUserId = $currentAssignments[$sourceKey] ?? null;
|
||||||
|
|
||||||
|
if ($currentUserId !== null && (int) $currentUserId !== $targetUserId) {
|
||||||
|
$lockedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($currentUserId !== null && (int) $currentUserId === $targetUserId) {
|
||||||
|
$sameAssessorCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cc_test_upsert_assignee($pdo, $sourceKey, $targetUserId);
|
||||||
|
$assignedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
if ($pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
$messageParts = [];
|
||||||
|
if ($assignedCount > 0) {
|
||||||
|
$messageParts[] = 'Asignación rápida lista: ' . $assignedCount . ' pedido' . ($assignedCount === 1 ? '' : 's') . ' asignado' . ($assignedCount === 1 ? '' : 's') . ' a ' . $selectedAssessor['label'] . '.';
|
||||||
|
}
|
||||||
|
if ($sameAssessorCount > 0) {
|
||||||
|
$messageParts[] = $sameAssessorCount . ' ya estaba' . ($sameAssessorCount === 1 ? '' : 'n') . ' con esa misma asesora.';
|
||||||
|
}
|
||||||
|
if ($lockedCount > 0) {
|
||||||
|
$messageParts[] = $lockedCount . ' ya estaba' . ($lockedCount === 1 ? '' : 'n') . ' asignado' . ($lockedCount === 1 ? '' : 's') . ' a otra asesora y se dejó igual.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$noticeMessage = !empty($messageParts)
|
||||||
|
? implode(' ', $messageParts)
|
||||||
|
: 'No hubo cambios en la asignación rápida.';
|
||||||
|
|
||||||
|
$tracking = drive_test_fetch_tracking($pdo, array_column($orders, 'source_key'));
|
||||||
|
$orders = drive_test_merge_tracking($orders, $tracking);
|
||||||
|
$orders = array_values(array_filter($orders, static function (array $order): bool {
|
||||||
|
return empty($order['eliminado']);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'auto_assign_cupo') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'auto_assign_cupo') {
|
||||||
$openStates = cc_test_open_states();
|
$openStates = cc_test_open_states();
|
||||||
|
|
||||||
@ -671,6 +774,70 @@ try {
|
|||||||
require_once 'layout_header.php';
|
require_once 'layout_header.php';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.cc-quick-select-btn {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-table tbody td {
|
||||||
|
padding-top: .5rem;
|
||||||
|
padding-bottom: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-table .small {
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-row.is-selected td:first-child {
|
||||||
|
box-shadow: inset 4px 0 0 #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-assign-form {
|
||||||
|
padding: .5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-assign-row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: .35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-assign-row .form-select {
|
||||||
|
min-width: 11rem;
|
||||||
|
flex: 1 1 11rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-assign-submit {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-actions {
|
||||||
|
gap: .35rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-callcenter-action-btn {
|
||||||
|
padding: .2rem .55rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
font-size: .78rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.cc-callcenter-table tbody td {
|
||||||
|
padding-top: .42rem;
|
||||||
|
padding-bottom: .42rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<main class="container-fluid py-4">
|
<main class="container-fluid py-4">
|
||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
|
<div class="d-flex flex-column flex-xl-row justify-content-between align-items-xl-center gap-3">
|
||||||
@ -692,7 +859,7 @@ require_once 'layout_header.php';
|
|||||||
<div class="d-flex flex-wrap gap-2">
|
<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">Drive detectado: <?php echo (int) $totalRows; ?> filas</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">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: distribución desde fila <?php echo (int) ($storeConfig['startRow'] ?? $startRow); ?> · último procesado <?php echo (int) $tuaniLastProcessedRow; ?><?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"><?php if ($usesIncrementalSync && $lastProcessedRow !== null): ?><?php echo htmlspecialchars(mb_strtoupper($storeLabel)); ?>: distribución desde fila <?php echo (int) ($storeConfig['startRow'] ?? $startRow); ?> · último procesado <?php echo (int) $lastProcessedRow; ?><?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>
|
<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>
|
<a href="test_importar_drive.php?store=<?php echo htmlspecialchars($storeKey); ?>" class="btn btn-outline-primary btn-sm">Ver vista previa Drive</a>
|
||||||
</div>
|
</div>
|
||||||
@ -882,22 +1049,45 @@ require_once 'layout_header.php';
|
|||||||
<span class="badge bg-light text-dark border"><?php echo count($visibleOrders); ?> pedidos en esta bandeja</span>
|
<span class="badge bg-light text-dark border"><?php echo count($visibleOrders); ?> pedidos en esta bandeja</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
|
<div class="border-bottom bg-light-subtle px-3 py-3">
|
||||||
|
<form method="post" id="bulkAssignForm" class="d-flex flex-column flex-xl-row align-items-xl-center gap-2 gap-xl-3">
|
||||||
|
<input type="hidden" name="action" value="bulk_assign_assessor">
|
||||||
|
<div id="bulkAssignSourceKeys"></div>
|
||||||
|
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||||
|
<span class="badge text-bg-primary rounded-pill px-3 py-2"><span id="bulkSelectedCount">0</span> seleccionados</span>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" id="toggleSelectAllOrders"<?php echo empty($visibleOrders) ? ' disabled' : ''; ?>>Marcar visibles</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="clearSelectedOrders" disabled>Limpiar</button>
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted">Usa el botón de la izquierda de cada pedido y asigna varios de una sola vez.</div>
|
||||||
|
<div class="d-flex flex-column flex-sm-row gap-2 ms-xl-auto">
|
||||||
|
<select name="target_assessor" class="form-select form-select-sm" style="min-width: 220px;"<?php echo empty($assessors) ? ' disabled' : ''; ?>>
|
||||||
|
<option value="">Elegir asesora</option>
|
||||||
|
<?php foreach ($assessors as $assessorKey => $assessor): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($assessorKey); ?>"><?php echo htmlspecialchars($assessor['label']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary" id="bulkAssignSubmit" disabled>Asignar seleccionados</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="small text-muted mt-2">Si un pedido ya está tomado por otra asesora, el sistema lo deja igual y te avisa.</div>
|
||||||
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover align-middle mb-0">
|
<table class="table table-hover align-middle mb-0 cc-callcenter-table">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th class="text-center" style="width: 64px;">Sel.</th>
|
||||||
|
<th class="text-start" style="min-width: 16rem;">Asignación / acciones</th>
|
||||||
<th>N° Pedido</th>
|
<th>N° Pedido</th>
|
||||||
<th>Cliente</th>
|
<th>Cliente</th>
|
||||||
<th>Ubicación editable</th>
|
<th>Ubicación editable</th>
|
||||||
<th>Pedido</th>
|
<th>Pedido</th>
|
||||||
<th>Gestión</th>
|
<th>Gestión</th>
|
||||||
<th class="text-center">Acciones</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if (empty($visibleOrders)): ?>
|
<?php if (empty($visibleOrders)): ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" class="text-center py-5 text-muted">
|
<td colspan="7" class="text-center py-5 text-muted">
|
||||||
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
|
<i class="bi bi-inbox fs-2 d-block mb-2"></i>
|
||||||
No hay pedidos en esta bandeja por ahora.
|
No hay pedidos en esta bandeja por ahora.
|
||||||
</td>
|
</td>
|
||||||
@ -950,6 +1140,123 @@ require_once 'layout_header.php';
|
|||||||
ob_start();
|
ob_start();
|
||||||
?>
|
?>
|
||||||
<tr class="cc-callcenter-row" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>" style="<?php echo htmlspecialchars(cc_test_row_background_style((string) ($order['estado'] ?? 'POR LLAMAR'))); ?>">
|
<tr class="cc-callcenter-row" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>" style="<?php echo htmlspecialchars(cc_test_row_background_style((string) ($order['estado'] ?? 'POR LLAMAR'))); ?>">
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-outline-primary cc-quick-select-btn js-quick-select-btn"
|
||||||
|
data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>"
|
||||||
|
aria-pressed="false"
|
||||||
|
aria-label="Seleccionar pedido <?php echo htmlspecialchars(cc_test_order_label($order)); ?>"
|
||||||
|
title="Seleccionar este pedido para asignación rápida"
|
||||||
|
>
|
||||||
|
<i class="bi bi-plus-lg"></i>
|
||||||
|
</button>
|
||||||
|
<input type="checkbox" class="d-none js-bulk-order-checkbox" value="<?php echo htmlspecialchars($order['source_key']); ?>" data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle" style="min-width: 16rem;">
|
||||||
|
<?php if (in_array($_SESSION['user_role'] ?? '', ['Administrador', 'admin'], true)): ?>
|
||||||
|
<?php
|
||||||
|
$orderUserId = $order['user_id'] ?? null;
|
||||||
|
$orderUserIdNorm = $orderUserId !== null ? ((int) $orderUserId > 0 ? (int) $orderUserId : null) : null;
|
||||||
|
$unassigned = $orderUserIdNorm === null;
|
||||||
|
$assignedAssessorKey = null;
|
||||||
|
if (!$unassigned) {
|
||||||
|
foreach ($assessors as $assessorKey => $assessor) {
|
||||||
|
if ((int) ($assessor['id'] ?? 0) === (int) $orderUserIdNorm) {
|
||||||
|
$assignedAssessorKey = (string) $assessorKey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$assignFormClass = 'border rounded-3 p-1 text-start shadow-sm cc-callcenter-assign-form';
|
||||||
|
if ($unassigned) {
|
||||||
|
$assignFormClass .= ' bg-light';
|
||||||
|
} elseif ($assignedAssessorKey === 'KARINA') {
|
||||||
|
$assignFormClass .= ' bg-primary-subtle border-primary';
|
||||||
|
} elseif ($assignedAssessorKey === 'ESTEFANYA') {
|
||||||
|
$assignFormClass .= ' bg-success-subtle border-success';
|
||||||
|
} elseif ($assignedAssessorKey === 'CARMEN') {
|
||||||
|
$assignFormClass .= ' bg-warning-subtle border-warning';
|
||||||
|
} else {
|
||||||
|
$assignFormClass .= ' bg-secondary-subtle border-secondary';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($unassigned) {
|
||||||
|
$selectedAssessorKeyForSelect = !empty($recommendedAssessorKey) ? (string) $recommendedAssessorKey : '';
|
||||||
|
} else {
|
||||||
|
$selectedAssessorKeyForSelect = $assignedAssessorKey !== null ? (string) $assignedAssessorKey : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$buttonLabel = $unassigned
|
||||||
|
? 'Asignar pedido'
|
||||||
|
: ($assignedAssessorKey !== null ? 'Guardar asignación' : 'Desasignar');
|
||||||
|
?>
|
||||||
|
|
||||||
|
<form method="post" class="<?php echo $assignFormClass; ?>">
|
||||||
|
<input type="hidden" name="action" value="assign_assessor">
|
||||||
|
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center gap-2 mb-1">
|
||||||
|
<div class="small text-uppercase text-muted fw-semibold">Asignar a asesora</div>
|
||||||
|
<?php if ($unassigned): ?>
|
||||||
|
<span class="badge bg-light text-dark border">Sin asignar</span>
|
||||||
|
<?php elseif ($assignedAssessorKey === 'KARINA'): ?>
|
||||||
|
<span class="badge bg-primary-subtle text-primary-emphasis border">KARINA</span>
|
||||||
|
<?php elseif ($assignedAssessorKey === 'ESTEFANYA'): ?>
|
||||||
|
<span class="badge bg-success-subtle text-success-emphasis border">ESTEFANYA</span>
|
||||||
|
<?php elseif ($assignedAssessorKey === 'CARMEN'): ?>
|
||||||
|
<span class="badge bg-warning-subtle text-warning-emphasis border">CARMEN</span>
|
||||||
|
<?php else: ?>
|
||||||
|
<span class="badge bg-secondary-subtle text-secondary-emphasis border">Asignado</span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cc-callcenter-assign-row">
|
||||||
|
<select name="target_assessor" class="form-select form-select-sm">
|
||||||
|
<option value=""<?php echo ($selectedAssessorKeyForSelect === '') ? ' selected' : ''; ?>>Sin asignar</option>
|
||||||
|
|
||||||
|
<?php if ($unassigned): ?>
|
||||||
|
<?php foreach ($assessors as $assessorKey => $assessor): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars($assessorKey); ?>"<?php echo ($selectedAssessorKeyForSelect === $assessorKey) ? ' selected' : ''; ?>><?php echo htmlspecialchars($assessor['label']); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php elseif ($assignedAssessorKey !== null): ?>
|
||||||
|
<option value="<?php echo htmlspecialchars((string) $assignedAssessorKey); ?>" selected><?php echo htmlspecialchars((string) ($assessors[$assignedAssessorKey]['label'] ?? $assignedAssessorKey)); ?></option>
|
||||||
|
<?php endif; ?>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary cc-callcenter-assign-submit cc-callcenter-action-btn"><?php echo htmlspecialchars((string) $buttonLabel); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php endif; ?>
|
||||||
|
<div class="d-flex flex-column gap-1 align-items-stretch cc-callcenter-actions<?php echo in_array($_SESSION['user_role'] ?? '', ['Administrador', 'admin'], true) ? ' mt-1' : ''; ?>">
|
||||||
|
<?php if (!empty($order['telefono_url'])): ?>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-sm btn-primary cc-callcenter-action-btn"
|
||||||
|
data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>"
|
||||||
|
data-modal-id="<?php echo htmlspecialchars($modalId); ?>"
|
||||||
|
data-phone="<?php echo htmlspecialchars((string) ($order['celular'] ?? '')); ?>"
|
||||||
|
data-order-label="<?php echo htmlspecialchars(cc_test_order_label($order)); ?>"
|
||||||
|
data-client-name="<?php echo htmlspecialchars(cc_test_display_value($order['nombre'], 'Cliente sin nombre')); ?>"
|
||||||
|
onclick="return registrarLlamada(event, this)">
|
||||||
|
<i class="bi bi-telephone-outbound"></i> Llamar / Gestionar
|
||||||
|
</button>
|
||||||
|
<?php else: ?>
|
||||||
|
<button type="button" class="btn btn-sm btn-primary cc-callcenter-action-btn text-nowrap" disabled>Sin teléfono</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if (!empty($order['whatsapp_url'])): ?>
|
||||||
|
<a href="<?php echo htmlspecialchars($order['whatsapp_url']); ?>" target="_blank" rel="noopener" class="btn btn-sm btn-outline-success cc-callcenter-action-btn">
|
||||||
|
<i class="bi bi-whatsapp"></i> WhatsApp
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-dark cc-callcenter-action-btn" data-bs-toggle="modal" data-bs-target="#<?php echo htmlspecialchars($modalId); ?>">
|
||||||
|
<i class="bi bi-sliders"></i> Gestionar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php if ($followupDayInfo !== null): ?>
|
<?php if ($followupDayInfo !== null): ?>
|
||||||
<div class="small text-uppercase fw-semibold <?php echo htmlspecialchars($followupDayInfo['text_class']); ?> mb-1"><?php echo htmlspecialchars($followupDayInfo['display_label']); ?></div>
|
<div class="small text-uppercase fw-semibold <?php echo htmlspecialchars($followupDayInfo['text_class']); ?> mb-1"><?php echo htmlspecialchars($followupDayInfo['display_label']); ?></div>
|
||||||
@ -1015,106 +1322,6 @@ require_once 'layout_header.php';
|
|||||||
<div class="small text-muted">Última gestión: <?php echo htmlspecialchars(cc_test_format_datetime($order['ultima_gestion_at'] ?? ($order['seguimiento_actualizado'] ?? null), 'Aún no gestionado')); ?></div>
|
<div class="small text-muted">Última gestión: <?php echo htmlspecialchars(cc_test_format_datetime($order['ultima_gestion_at'] ?? ($order['seguimiento_actualizado'] ?? null), 'Aún no gestionado')); ?></div>
|
||||||
<div class="small text-muted mt-1">Nota: <?php echo htmlspecialchars(cc_test_display_value($order['nota_seguimiento'], 'Sin nota interna')); ?></div>
|
<div class="small text-muted mt-1">Nota: <?php echo htmlspecialchars(cc_test_display_value($order['nota_seguimiento'], 'Sin nota interna')); ?></div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
|
||||||
<div class="d-flex flex-column gap-2 align-items-stretch">
|
|
||||||
<?php if (in_array($_SESSION['user_role'] ?? '', ['Administrador', 'admin'], true)): ?>
|
|
||||||
<?php
|
|
||||||
$orderUserId = $order['user_id'] ?? null;
|
|
||||||
$orderUserIdNorm = $orderUserId !== null ? ((int) $orderUserId > 0 ? (int) $orderUserId : null) : null;
|
|
||||||
$unassigned = $orderUserIdNorm === null;
|
|
||||||
$assignedAssessorKey = null;
|
|
||||||
if (!$unassigned) {
|
|
||||||
foreach ($assessors as $assessorKey => $assessor) {
|
|
||||||
if ((int) ($assessor['id'] ?? 0) === (int) $orderUserIdNorm) {
|
|
||||||
$assignedAssessorKey = (string) $assessorKey;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$assignFormClass = 'border rounded-3 p-2 text-start shadow-sm';
|
|
||||||
if ($unassigned) {
|
|
||||||
$assignFormClass .= ' bg-light';
|
|
||||||
} elseif ($assignedAssessorKey === 'KARINA') {
|
|
||||||
$assignFormClass .= ' bg-primary-subtle border-primary';
|
|
||||||
} elseif ($assignedAssessorKey === 'ESTEFANYA') {
|
|
||||||
$assignFormClass .= ' bg-success-subtle border-success';
|
|
||||||
} elseif ($assignedAssessorKey === 'CARMEN') {
|
|
||||||
$assignFormClass .= ' bg-warning-subtle border-warning';
|
|
||||||
} else {
|
|
||||||
$assignFormClass .= ' bg-secondary-subtle border-secondary';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($unassigned) {
|
|
||||||
$selectedAssessorKeyForSelect = !empty($recommendedAssessorKey) ? (string) $recommendedAssessorKey : '';
|
|
||||||
} else {
|
|
||||||
$selectedAssessorKeyForSelect = $assignedAssessorKey !== null ? (string) $assignedAssessorKey : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$buttonLabel = $unassigned
|
|
||||||
? 'Asignar pedido'
|
|
||||||
: ($assignedAssessorKey !== null ? 'Guardar asignación' : 'Desasignar');
|
|
||||||
?>
|
|
||||||
|
|
||||||
<form method="post" class="<?php echo $assignFormClass; ?>">
|
|
||||||
<input type="hidden" name="action" value="assign_assessor">
|
|
||||||
<input type="hidden" name="source_key" value="<?php echo htmlspecialchars($order['source_key']); ?>">
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center gap-2 mb-1">
|
|
||||||
<div class="small text-uppercase text-muted fw-semibold">Asignar a asesora</div>
|
|
||||||
<?php if ($unassigned): ?>
|
|
||||||
<span class="badge bg-light text-dark border">Sin asignar</span>
|
|
||||||
<?php elseif ($assignedAssessorKey === 'KARINA'): ?>
|
|
||||||
<span class="badge bg-primary-subtle text-primary-emphasis border">KARINA</span>
|
|
||||||
<?php elseif ($assignedAssessorKey === 'ESTEFANYA'): ?>
|
|
||||||
<span class="badge bg-success-subtle text-success-emphasis border">ESTEFANYA</span>
|
|
||||||
<?php elseif ($assignedAssessorKey === 'CARMEN'): ?>
|
|
||||||
<span class="badge bg-warning-subtle text-warning-emphasis border">CARMEN</span>
|
|
||||||
<?php else: ?>
|
|
||||||
<span class="badge bg-secondary-subtle text-secondary-emphasis border">Asignado</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<select name="target_assessor" class="form-select form-select-sm mb-2">
|
|
||||||
<option value=""<?php echo ($selectedAssessorKeyForSelect === '') ? ' selected' : ''; ?>>Sin asignar</option>
|
|
||||||
|
|
||||||
<?php if ($unassigned): ?>
|
|
||||||
<?php foreach ($assessors as $assessorKey => $assessor): ?>
|
|
||||||
<option value="<?php echo htmlspecialchars($assessorKey); ?>"<?php echo ($selectedAssessorKeyForSelect === $assessorKey) ? ' selected' : ''; ?>><?php echo htmlspecialchars($assessor['label']); ?></option>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
<?php elseif ($assignedAssessorKey !== null): ?>
|
|
||||||
<option value="<?php echo htmlspecialchars((string) $assignedAssessorKey); ?>" selected><?php echo htmlspecialchars((string) ($assessors[$assignedAssessorKey]['label'] ?? $assignedAssessorKey)); ?></option>
|
|
||||||
<?php endif; ?>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-sm btn-primary w-100"><?php echo htmlspecialchars((string) $buttonLabel); ?></button>
|
|
||||||
</form>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (!empty($order['telefono_url'])): ?>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-sm btn-primary"
|
|
||||||
data-source-key="<?php echo htmlspecialchars($order['source_key']); ?>"
|
|
||||||
data-modal-id="<?php echo htmlspecialchars($modalId); ?>"
|
|
||||||
data-phone="<?php echo htmlspecialchars((string) ($order['celular'] ?? '')); ?>"
|
|
||||||
data-order-label="<?php echo htmlspecialchars(cc_test_order_label($order)); ?>"
|
|
||||||
data-client-name="<?php echo htmlspecialchars(cc_test_display_value($order['nombre'], 'Cliente sin nombre')); ?>"
|
|
||||||
onclick="return registrarLlamada(event, this)">
|
|
||||||
<i class="bi bi-telephone-outbound"></i> Llamar / Gestionar
|
|
||||||
</button>
|
|
||||||
<?php else: ?>
|
|
||||||
<button type="button" class="btn btn-sm btn-primary" disabled>Sin teléfono</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (!empty($order['whatsapp_url'])): ?>
|
|
||||||
<a href="<?php echo htmlspecialchars($order['whatsapp_url']); ?>" target="_blank" rel="noopener" class="btn btn-sm btn-outline-success">
|
|
||||||
<i class="bi bi-whatsapp"></i> WhatsApp
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
<button type="button" class="btn btn-sm btn-outline-dark" data-bs-toggle="modal" data-bs-target="#<?php echo htmlspecialchars($modalId); ?>">
|
|
||||||
<i class="bi bi-sliders"></i> Gestionar
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php $rowHtml = ob_get_clean(); echo $rowHtml; ?>
|
<?php $rowHtml = ob_get_clean(); echo $rowHtml; ?>
|
||||||
|
|
||||||
@ -2238,6 +2445,142 @@ if (airDroidCopyButton) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bulkAssignForm = document.getElementById('bulkAssignForm');
|
||||||
|
const bulkAssignSourceKeys = document.getElementById('bulkAssignSourceKeys');
|
||||||
|
const bulkSelectedCount = document.getElementById('bulkSelectedCount');
|
||||||
|
const bulkAssignSubmit = document.getElementById('bulkAssignSubmit');
|
||||||
|
const toggleSelectAllOrders = document.getElementById('toggleSelectAllOrders');
|
||||||
|
const clearSelectedOrders = document.getElementById('clearSelectedOrders');
|
||||||
|
const bulkOrderCheckboxes = Array.from(document.querySelectorAll('.js-bulk-order-checkbox'));
|
||||||
|
const quickSelectButtons = Array.from(document.querySelectorAll('.js-quick-select-btn'));
|
||||||
|
const bulkAssessorSelect = bulkAssignForm ? bulkAssignForm.querySelector('select[name="target_assessor"]') : null;
|
||||||
|
|
||||||
|
function getSelectedBulkOrderCheckboxes() {
|
||||||
|
return bulkOrderCheckboxes.filter(function (checkbox) {
|
||||||
|
return checkbox.checked;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setQuickSelectButtonState(button, selected) {
|
||||||
|
if (!button) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.classList.toggle('btn-primary', selected);
|
||||||
|
button.classList.toggle('btn-outline-primary', !selected);
|
||||||
|
button.setAttribute('aria-pressed', selected ? 'true' : 'false');
|
||||||
|
button.setAttribute('title', selected ? 'Quitar de la selección rápida' : 'Seleccionar este pedido para asignación rápida');
|
||||||
|
button.innerHTML = selected ? '<i class="bi bi-check-lg"></i>' : '<i class="bi bi-plus-lg"></i>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncBulkAssignSelectionUi() {
|
||||||
|
const selectedCheckboxes = getSelectedBulkOrderCheckboxes();
|
||||||
|
|
||||||
|
if (bulkSelectedCount) {
|
||||||
|
bulkSelectedCount.textContent = String(selectedCheckboxes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bulkAssignSourceKeys) {
|
||||||
|
bulkAssignSourceKeys.innerHTML = '';
|
||||||
|
selectedCheckboxes.forEach(function (checkbox) {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'hidden';
|
||||||
|
input.name = 'source_keys[]';
|
||||||
|
input.value = checkbox.value;
|
||||||
|
bulkAssignSourceKeys.appendChild(input);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkOrderCheckboxes.forEach(function (checkbox) {
|
||||||
|
const row = checkbox.closest('tr');
|
||||||
|
const button = checkbox.parentElement ? checkbox.parentElement.querySelector('.js-quick-select-btn') : null;
|
||||||
|
if (row) {
|
||||||
|
row.classList.toggle('is-selected', checkbox.checked);
|
||||||
|
}
|
||||||
|
setQuickSelectButtonState(button, checkbox.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
const anySelected = selectedCheckboxes.length > 0;
|
||||||
|
const canSubmit = anySelected && !!bulkAssessorSelect && !bulkAssessorSelect.disabled && bulkAssessorSelect.value !== '';
|
||||||
|
if (bulkAssignSubmit) {
|
||||||
|
bulkAssignSubmit.disabled = !canSubmit;
|
||||||
|
}
|
||||||
|
if (clearSelectedOrders) {
|
||||||
|
clearSelectedOrders.disabled = !anySelected;
|
||||||
|
}
|
||||||
|
if (toggleSelectAllOrders) {
|
||||||
|
const allSelected = bulkOrderCheckboxes.length > 0 && selectedCheckboxes.length === bulkOrderCheckboxes.length;
|
||||||
|
toggleSelectAllOrders.textContent = allSelected ? 'Desmarcar visibles' : 'Marcar visibles';
|
||||||
|
toggleSelectAllOrders.disabled = bulkOrderCheckboxes.length === 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quickSelectButtons.forEach(function (button) {
|
||||||
|
button.addEventListener('click', function () {
|
||||||
|
const sourceKey = this.dataset.sourceKey || '';
|
||||||
|
const checkbox = document.querySelector('.js-bulk-order-checkbox[data-source-key="' + sourceKey + '"]');
|
||||||
|
if (!checkbox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkbox.checked = !checkbox.checked;
|
||||||
|
syncBulkAssignSelectionUi();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
bulkOrderCheckboxes.forEach(function (checkbox) {
|
||||||
|
checkbox.addEventListener('change', syncBulkAssignSelectionUi);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bulkAssessorSelect) {
|
||||||
|
bulkAssessorSelect.addEventListener('change', syncBulkAssignSelectionUi);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toggleSelectAllOrders) {
|
||||||
|
toggleSelectAllOrders.addEventListener('click', function () {
|
||||||
|
const shouldSelectAll = bulkOrderCheckboxes.some(function (checkbox) {
|
||||||
|
return !checkbox.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
bulkOrderCheckboxes.forEach(function (checkbox) {
|
||||||
|
checkbox.checked = shouldSelectAll;
|
||||||
|
});
|
||||||
|
|
||||||
|
syncBulkAssignSelectionUi();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearSelectedOrders) {
|
||||||
|
clearSelectedOrders.addEventListener('click', function () {
|
||||||
|
bulkOrderCheckboxes.forEach(function (checkbox) {
|
||||||
|
checkbox.checked = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
syncBulkAssignSelectionUi();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bulkAssignForm) {
|
||||||
|
bulkAssignForm.addEventListener('submit', function (event) {
|
||||||
|
const selectedCheckboxes = getSelectedBulkOrderCheckboxes();
|
||||||
|
const assessorSelect = this.querySelector('select[name="target_assessor"]');
|
||||||
|
|
||||||
|
if (selectedCheckboxes.length === 0) {
|
||||||
|
event.preventDefault();
|
||||||
|
alert('Primero selecciona al menos un pedido.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!assessorSelect || !assessorSelect.value) {
|
||||||
|
event.preventDefault();
|
||||||
|
alert('Elige la asesora para hacer la asignación rápida.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
syncBulkAssignSelectionUi();
|
||||||
|
}
|
||||||
|
|
||||||
window.setInterval(function () {
|
window.setInterval(function () {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 600000);
|
}, 600000);
|
||||||
|
|||||||
@ -30,6 +30,7 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
|||||||
`estado` VARCHAR(40) NOT NULL DEFAULT 'POR LLAMAR',
|
`estado` VARCHAR(40) NOT NULL DEFAULT 'POR LLAMAR',
|
||||||
`nota_seguimiento` TEXT NULL,
|
`nota_seguimiento` TEXT NULL,
|
||||||
`user_id` INT NULL,
|
`user_id` INT NULL,
|
||||||
|
`assigned_at` DATETIME NULL,
|
||||||
`direccion` TEXT NULL,
|
`direccion` TEXT NULL,
|
||||||
`referencia` TEXT NULL,
|
`referencia` TEXT NULL,
|
||||||
`agencia` VARCHAR(80) NULL,
|
`agencia` VARCHAR(80) NULL,
|
||||||
@ -78,6 +79,7 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
|||||||
KEY `idx_callcenter_test_tracking_proxima` (`proxima_llamada_at`)
|
KEY `idx_callcenter_test_tracking_proxima` (`proxima_llamada_at`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
|
||||||
|
|
||||||
|
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'assigned_at', 'DATETIME NULL AFTER `user_id`');
|
||||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'direccion', 'TEXT NULL AFTER `user_id`');
|
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'direccion', 'TEXT NULL AFTER `user_id`');
|
||||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'referencia', 'TEXT NULL AFTER `direccion`');
|
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'referencia', 'TEXT NULL AFTER `direccion`');
|
||||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'agencia', 'VARCHAR(80) NULL AFTER `referencia`');
|
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'agencia', 'VARCHAR(80) NULL AFTER `referencia`');
|
||||||
@ -119,6 +121,11 @@ function cc_test_ensure_tracking_table(PDO $pdo): void
|
|||||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'pedido_rotulado_subido_por', 'INT NULL AFTER `pedido_rotulado_subido_at`');
|
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'pedido_rotulado_subido_por', 'INT NULL AFTER `pedido_rotulado_subido_at`');
|
||||||
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ultima_gestion_at', 'DATETIME NULL AFTER `ruta_contraentrega_subido_por`');
|
cc_test_ensure_column($pdo, 'callcenter_test_tracking', 'ultima_gestion_at', 'DATETIME NULL AFTER `ruta_contraentrega_subido_por`');
|
||||||
|
|
||||||
|
$stmtAssignedBackfill = $pdo->query("SELECT id FROM callcenter_test_tracking WHERE assigned_at IS NULL AND user_id IS NOT NULL LIMIT 1");
|
||||||
|
if ($stmtAssignedBackfill && $stmtAssignedBackfill->fetchColumn()) {
|
||||||
|
$pdo->exec("UPDATE callcenter_test_tracking SET assigned_at = created_at, updated_at = updated_at WHERE assigned_at IS NULL AND user_id IS NOT NULL");
|
||||||
|
}
|
||||||
|
|
||||||
$checked = true;
|
$checked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,18 +222,27 @@ function cc_test_upsert_assignee(PDO $pdo, string $sourceKey, ?int $userId): voi
|
|||||||
{
|
{
|
||||||
cc_test_ensure_tracking_table($pdo);
|
cc_test_ensure_tracking_table($pdo);
|
||||||
|
|
||||||
$stmt = $pdo->prepare("INSERT INTO callcenter_test_tracking (source_key, user_id, ultima_gestion_at)
|
$assignedAt = $userId === null ? null : (new DateTimeImmutable('now'))->format('Y-m-d H:i:s');
|
||||||
VALUES (:source_key, :user_id, CURRENT_TIMESTAMP)
|
|
||||||
|
$stmt = $pdo->prepare("INSERT INTO callcenter_test_tracking (source_key, user_id, assigned_at, ultima_gestion_at)
|
||||||
|
VALUES (:source_key, :user_id, :assigned_at, CURRENT_TIMESTAMP)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
user_id = VALUES(user_id),
|
user_id = VALUES(user_id),
|
||||||
|
assigned_at = CASE
|
||||||
|
WHEN VALUES(user_id) IS NULL THEN NULL
|
||||||
|
WHEN assigned_at IS NULL OR user_id IS NULL OR user_id <> VALUES(user_id) THEN VALUES(assigned_at)
|
||||||
|
ELSE assigned_at
|
||||||
|
END,
|
||||||
ultima_gestion_at = VALUES(ultima_gestion_at),
|
ultima_gestion_at = VALUES(ultima_gestion_at),
|
||||||
updated_at = CURRENT_TIMESTAMP");
|
updated_at = CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
$stmt->bindValue(':source_key', $sourceKey, PDO::PARAM_STR);
|
$stmt->bindValue(':source_key', $sourceKey, PDO::PARAM_STR);
|
||||||
if ($userId === null) {
|
if ($userId === null) {
|
||||||
$stmt->bindValue(':user_id', null, PDO::PARAM_NULL);
|
$stmt->bindValue(':user_id', null, PDO::PARAM_NULL);
|
||||||
|
$stmt->bindValue(':assigned_at', null, PDO::PARAM_NULL);
|
||||||
} else {
|
} else {
|
||||||
$stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
|
$stmt->bindValue(':user_id', $userId, PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':assigned_at', $assignedAt, PDO::PARAM_STR);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|||||||
@ -18,8 +18,10 @@ function drive_test_available_stores(): array
|
|||||||
'label' => 'Flower',
|
'label' => 'Flower',
|
||||||
'spreadsheet_id' => '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c',
|
'spreadsheet_id' => '1SSmQuR9quxeQbMKNMDkRe8-n1gU7WuEfsFaJ3WKFO-c',
|
||||||
'sheet_gid' => null,
|
'sheet_gid' => null,
|
||||||
'startRow' => 5552,
|
'startRow' => 7796, // Pedido #37431
|
||||||
'enforce_db_start_row' => false,
|
'min_codigo_number' => 37431,
|
||||||
|
'enforce_db_start_row' => true,
|
||||||
|
'incremental_sync' => true,
|
||||||
],
|
],
|
||||||
// Segunda tienda (nuevo link que pasaste).
|
// Segunda tienda (nuevo link que pasaste).
|
||||||
'otra_tienda' => [
|
'otra_tienda' => [
|
||||||
@ -28,6 +30,7 @@ function drive_test_available_stores(): array
|
|||||||
'sheet_gid' => 1523126328,
|
'sheet_gid' => 1523126328,
|
||||||
'startRow' => 6804,
|
'startRow' => 6804,
|
||||||
'enforce_db_start_row' => true,
|
'enforce_db_start_row' => true,
|
||||||
|
'incremental_sync' => true,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -94,6 +97,16 @@ function drive_test_get_cell(array $row, array $indexes, array $aliases, string
|
|||||||
return $default;
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function drive_test_extract_codigo_number(?string $codigo): ?int
|
||||||
|
{
|
||||||
|
$digits = preg_replace('/\D+/', '', trim((string) $codigo));
|
||||||
|
if ($digits === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int) $digits;
|
||||||
|
}
|
||||||
|
|
||||||
function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $storeKey = 'flower'): array
|
function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $storeKey = 'flower'): array
|
||||||
{
|
{
|
||||||
$storeConfig = drive_test_get_store_config($storeKey);
|
$storeConfig = drive_test_get_store_config($storeKey);
|
||||||
@ -157,6 +170,7 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $
|
|||||||
$previewRows = array_reverse($previewRows, true);
|
$previewRows = array_reverse($previewRows, true);
|
||||||
|
|
||||||
$orders = [];
|
$orders = [];
|
||||||
|
$minCodigoNumber = isset($storeConfig['min_codigo_number']) ? (int) ($storeConfig['min_codigo_number'] ?? 0) : 0;
|
||||||
|
|
||||||
foreach ($previewRows as $rowIndex => $row) {
|
foreach ($previewRows as $rowIndex => $row) {
|
||||||
$codigo = trim((string) ($row[0] ?? ''));
|
$codigo = trim((string) ($row[0] ?? ''));
|
||||||
@ -177,6 +191,11 @@ function drive_test_fetch_orders(int $limit = 10, int $startRow = 5552, string $
|
|||||||
$referencia = drive_test_get_cell($row, $headerIndexes, ['REFERENCIA']);
|
$referencia = drive_test_get_cell($row, $headerIndexes, ['REFERENCIA']);
|
||||||
$distrito = drive_test_get_cell($row, $headerIndexes, ['DISTRITO']);
|
$distrito = drive_test_get_cell($row, $headerIndexes, ['DISTRITO']);
|
||||||
$sourceRow = (int) $rowIndex + 2;
|
$sourceRow = (int) $rowIndex + 2;
|
||||||
|
$codigoNumber = drive_test_extract_codigo_number($codigo);
|
||||||
|
|
||||||
|
if ($minCodigoNumber > 0 && ($codigoNumber === null || $codigoNumber < $minCodigoNumber)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Importante: para 'flower' mantenemos EXACTAMENTE el algoritmo anterior
|
// Importante: para 'flower' mantenemos EXACTAMENTE el algoritmo anterior
|
||||||
// para no romper el historial/tracking ya existente.
|
// para no romper el historial/tracking ya existente.
|
||||||
@ -229,7 +248,7 @@ function drive_test_fetch_tracking(PDO $pdo, array $sourceKeys): array
|
|||||||
cc_test_ensure_tracking_table($pdo);
|
cc_test_ensure_tracking_table($pdo);
|
||||||
|
|
||||||
$placeholders = implode(',', array_fill(0, count($sourceKeys), '?'));
|
$placeholders = implode(',', array_fill(0, count($sourceKeys), '?'));
|
||||||
$stmt = $pdo->prepare("SELECT source_key, estado, nota_seguimiento, user_id, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, numero_cuenta_sede_id, numero_cuenta_dni, observaciones, coordenadas, producto, cantidad, precio, monto_adelantado, confirmacion_producto, confirmacion_cantidad, confirmacion_precio, confirmacion_producto_extra, confirmacion_cantidad_extra, confirmacion_precio_extra, proxima_llamada_at, fecha_entrega_programada, numero_cuenta_enviado_at, promo_final_evidencia_path, promo_final_evidencia_subido_at, promo_final_evidencia_subido_por, cancelado_evidencia_path, cancelado_evidencia_subido_at, cancelado_evidencia_subido_por, eliminado_at, eliminado_por, ruta_contraentrega_pedido_id, ruta_contraentrega_subido_at, ruta_contraentrega_subido_por, pedido_rotulado_pedido_id, pedido_rotulado_subido_at, pedido_rotulado_subido_por, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
|
$stmt = $pdo->prepare("SELECT source_key, estado, nota_seguimiento, user_id, assigned_at, direccion, referencia, agencia, sede_agencia, sede, ciudad, distrito, dni, numero_cuenta_sede_id, numero_cuenta_dni, observaciones, coordenadas, producto, cantidad, precio, monto_adelantado, confirmacion_producto, confirmacion_cantidad, confirmacion_precio, confirmacion_producto_extra, confirmacion_cantidad_extra, confirmacion_precio_extra, proxima_llamada_at, fecha_entrega_programada, numero_cuenta_enviado_at, promo_final_evidencia_path, promo_final_evidencia_subido_at, promo_final_evidencia_subido_por, cancelado_evidencia_path, cancelado_evidencia_subido_at, cancelado_evidencia_subido_por, eliminado_at, eliminado_por, ruta_contraentrega_pedido_id, ruta_contraentrega_subido_at, ruta_contraentrega_subido_por, pedido_rotulado_pedido_id, pedido_rotulado_subido_at, pedido_rotulado_subido_por, ultima_gestion_at, updated_at FROM callcenter_test_tracking WHERE source_key IN ($placeholders)");
|
||||||
$stmt->execute($sourceKeys);
|
$stmt->execute($sourceKeys);
|
||||||
|
|
||||||
$tracking = [];
|
$tracking = [];
|
||||||
@ -251,6 +270,7 @@ function drive_test_merge_tracking(array $orders, array $tracking): array
|
|||||||
$userId = array_key_exists('user_id', (array) $current) ? $current['user_id'] : null;
|
$userId = array_key_exists('user_id', (array) $current) ? $current['user_id'] : null;
|
||||||
$order['user_id'] = $userId !== null ? ((int) $userId > 0 ? (int) $userId : null) : null;
|
$order['user_id'] = $userId !== null ? ((int) $userId > 0 ? (int) $userId : null) : null;
|
||||||
$order['seguimiento_actualizado'] = $current['updated_at'] ?? null;
|
$order['seguimiento_actualizado'] = $current['updated_at'] ?? null;
|
||||||
|
$order['assigned_at'] = $current['assigned_at'] ?? null;
|
||||||
$order['proxima_llamada_at'] = $current['proxima_llamada_at'] ?? null;
|
$order['proxima_llamada_at'] = $current['proxima_llamada_at'] ?? null;
|
||||||
$order['fecha_entrega_programada'] = $current['fecha_entrega_programada'] ?? null;
|
$order['fecha_entrega_programada'] = $current['fecha_entrega_programada'] ?? null;
|
||||||
$order['numero_cuenta_enviado_at'] = $current['numero_cuenta_enviado_at'] ?? null;
|
$order['numero_cuenta_enviado_at'] = $current['numero_cuenta_enviado_at'] ?? null;
|
||||||
@ -628,12 +648,21 @@ function drive_test_fetch_orders_from_db(PDO $pdo, string $storeKey, ?bool $only
|
|||||||
$stmt->execute($params);
|
$stmt->execute($params);
|
||||||
|
|
||||||
$orders = [];
|
$orders = [];
|
||||||
|
$minCodigoNumber = isset($storeConfig['min_codigo_number']) ? (int) ($storeConfig['min_codigo_number'] ?? 0) : 0;
|
||||||
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
|
||||||
|
$isAgregado = (int) ($row['is_agregado'] ?? 0);
|
||||||
|
$codigo = (string) ($row['codigo'] ?? '');
|
||||||
|
$codigoNumber = drive_test_extract_codigo_number($codigo);
|
||||||
|
|
||||||
|
if ($minCodigoNumber > 0 && $isAgregado !== 1 && ($codigoNumber === null || $codigoNumber < $minCodigoNumber)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$orders[] = [
|
$orders[] = [
|
||||||
'id' => (int) ($row['id'] ?? 0),
|
'id' => (int) ($row['id'] ?? 0),
|
||||||
'is_agregado' => (int) ($row['is_agregado'] ?? 0),
|
'is_agregado' => $isAgregado,
|
||||||
'source_key' => (string) ($row['source_key'] ?? ''),
|
'source_key' => (string) ($row['source_key'] ?? ''),
|
||||||
'codigo' => (string) ($row['codigo'] ?? ''),
|
'codigo' => $codigo,
|
||||||
'import_id' => (string) ($row['import_id'] ?? ''),
|
'import_id' => (string) ($row['import_id'] ?? ''),
|
||||||
'source_row' => isset($row['source_row']) && (int) $row['source_row'] > 0 ? (int) $row['source_row'] : null,
|
'source_row' => isset($row['source_row']) && (int) $row['source_row'] > 0 ? (int) $row['source_row'] : null,
|
||||||
'drive_imported_at' => (string) ($row['drive_imported_at'] ?? ''),
|
'drive_imported_at' => (string) ($row['drive_imported_at'] ?? ''),
|
||||||
|
|||||||
@ -88,6 +88,7 @@ ensureTipoPaqueteEnumDefinition($pdo);
|
|||||||
|
|
||||||
$user_id = $_SESSION['user_id'];
|
$user_id = $_SESSION['user_id'];
|
||||||
$user_role = $_SESSION['user_role'] ?? 'Asesor';
|
$user_role = $_SESSION['user_role'] ?? 'Asesor';
|
||||||
|
$is_admin = in_array($user_role, ['Administrador', 'admin'], true);
|
||||||
|
|
||||||
// Fetch years for the filter
|
// Fetch years for the filter
|
||||||
$years_query = "SELECT DISTINCT YEAR(created_at) as year FROM pedidos";
|
$years_query = "SELECT DISTINCT YEAR(created_at) as year FROM pedidos";
|
||||||
@ -337,6 +338,7 @@ include 'layout_header.php';
|
|||||||
AGREGAR PEDIDOS CONTRAENTREGA
|
AGREGAR PEDIDOS CONTRAENTREGA
|
||||||
</button>
|
</button>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if ($is_admin): ?>
|
||||||
<?php
|
<?php
|
||||||
$excelParams = array_filter([
|
$excelParams = array_filter([
|
||||||
'q' => $search_query,
|
'q' => $search_query,
|
||||||
@ -348,6 +350,7 @@ include 'layout_header.php';
|
|||||||
<a href="<?php echo htmlspecialchars($excelUrl); ?>" class="btn btn-success">
|
<a href="<?php echo htmlspecialchars($excelUrl); ?>" class="btn btn-success">
|
||||||
<i class="fas fa-file-excel me-1"></i> Descargar Excel (Mañana)
|
<i class="fas fa-file-excel me-1"></i> Descargar Excel (Mañana)
|
||||||
</a>
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user