feat(v4.2.0): Visualizador de Pagos + Fix Sincronización CallBell
Nuevas Características: • Visualizador de pagos mensuales con gráfica de dona (Chart.js) • Tarjetas estadísticas: clientes activos, pagados y pendientes • Tabla de clientes pendientes con saldos en tiempo real • Microservicio Node.js para metadata de Stripe (acceso directo a BD) Mejoras: • Fix crítico: Sincronización automática de saldo en CallBell al agregar facturas • Categorización mejorada de pagos OXXO y Transferencias Stripe • Normalización de valores: "OXXO" → "OXXO Pay" para evitar errores 422 • Configuración .env para credenciales de base de datos Correcciones: • Saldo y estado ahora se actualizan correctamente en CallBell • Fix networking Docker (ECONNREFUSED resuelto) • Fix validación de atributos en API de UCRM • Actualización automática de userId en pagos Stripe Archivos principales: public.php (visualizador de pagos) AbstractMessageNotifierFacade.php (logging sync) ClientCallBellAPI.php (comparación de campos) AbstractStripeOperationsFacade.php (normalización) manifest.json, README.md, CHANGELOG.md (docs)
This commit is contained in:
parent
84a25a829c
commit
2926211060
48
CHANGELOG.md
48
CHANGELOG.md
@ -1,5 +1,53 @@
|
|||||||
# CHANGELOG - siip-whatsapp-notifications
|
# CHANGELOG - siip-whatsapp-notifications
|
||||||
|
|
||||||
|
## VERSIÓN 4.2.0 - 17-01-2026
|
||||||
|
### 🚀 Nuevas Características (Features)
|
||||||
|
1. **Visualizador de Pagos Mensuales**:
|
||||||
|
* Nueva sección en el portal administrativo para análisis visual de pagos.
|
||||||
|
* Selector de mes para consultar estadísticas de cualquier período.
|
||||||
|
* Tarjetas estadísticas mostrando: Total de clientes activos, Clientes que pagaron, Clientes pendientes.
|
||||||
|
* Gráfica de dona (doughnut) interactiva con Chart.js mostrando proporción de pagos.
|
||||||
|
* Tabla detallada de clientes pendientes con información de saldo.
|
||||||
|
* Cálculo automático de porcentajes de completitud.
|
||||||
|
* Loader/spinner animado mientras se cargan los datos.
|
||||||
|
|
||||||
|
2. **Integración de Microservicio para Metadata de Stripe**:
|
||||||
|
* Nuevo microservicio Node.js (`vouchers-oxxopay-generator-service`) con acceso directo a la base de datos UCRM.
|
||||||
|
* Endpoint `/stripe-metadata/:id` para obtener metadata de pagos Stripe que no está disponible en la API de UCRM.
|
||||||
|
* Endpoint `/payments/:id/user` para actualizar el `userId` de pagos vía SQL directo (campo no modificable por API).
|
||||||
|
* Configuración mediante archivo `.env` para credenciales de base de datos.
|
||||||
|
|
||||||
|
### 🔵 Mejoras (Enhancements)
|
||||||
|
1. **Sincronización Mejorada con CallBell**:
|
||||||
|
* Fix crítico en sincronización de saldo: Ahora se actualiza correctamente en CallBell cuando se agregan facturas.
|
||||||
|
* Logging detallado agregado a `onlyUpdate()` para diagnóstico de sincronización.
|
||||||
|
* Comparación inteligente de campos para evitar PATCH innecesarios.
|
||||||
|
* Sincronización automática al agregar/editar facturas (`invoice.add`, `invoice.edit`).
|
||||||
|
* Sincronización automática al editar servicios (`service.edit`, `service.suspend`, etc.).
|
||||||
|
|
||||||
|
2. **Mejora en Categorización de Pagos Stripe**:
|
||||||
|
* Lógica mejorada en `ensureStripePaymentAttribute()` para asignar correctamente el atributo `tipoPagoStripe`.
|
||||||
|
* Normalización de valores de metadata: "OXXO" → "OXXO Pay" para coincidir con opciones de UCRM.
|
||||||
|
* Prioridad a metadata obtenida del microservicio sobre adivinación por nombre de método.
|
||||||
|
* Fix de validación 422 para pagos OXXO que fallaban por valor de atributo no válido.
|
||||||
|
|
||||||
|
3. **Configuración para Producción**:
|
||||||
|
* Archivo `.env` agregado al microservicio con todas las credenciales de base de datos.
|
||||||
|
* `docker-compose.yml` refactorizado para usar variables de entorno desde `.env`.
|
||||||
|
* Red Docker `unms_internal` configurada para comunicación segura entre servicios.
|
||||||
|
* Puerto 5432 de PostgreSQL expuesto en `docker-compose.yml` principal para acceso del microservicio.
|
||||||
|
|
||||||
|
### 🐛 Correcciones (Bug Fixes)
|
||||||
|
1. **Fix Metadata de Pagos Stripe**: Los pagos por transferencia bancaria y OXXO ahora se categorizan correctamente.
|
||||||
|
2. **Fix sincronización CallBell**: Saldo y estado del cliente ahora se actualizan correctamente en tiempo real.
|
||||||
|
3. **Fix networking Docker**: Resueltos problemas de `ECONNREFUSED` entre contenedores mediante configuración de red interna.
|
||||||
|
4. **Fix validación de atributos**: Normalización de valores para evitar errores 422 en la API de UCRM.
|
||||||
|
|
||||||
|
### 📝 Documentación
|
||||||
|
1. Logging mejorado en `ClientCallBellAPI.php` para comparaciones de campo.
|
||||||
|
2. Logging agregado en `AbstractMessageNotifierFacade.php` para diagnóstico de sync.
|
||||||
|
3. Documentación completa del flujo de datos del visualizador de pagos.
|
||||||
|
|
||||||
## VERSIÓN 4.1.0 - 15-01-2026
|
## VERSIÓN 4.1.0 - 15-01-2026
|
||||||
### 🚀 Nuevas Características (Features)
|
### 🚀 Nuevas Características (Features)
|
||||||
1. **Microservicio PDF (`pdf-cropper`)**:
|
1. **Microservicio PDF (`pdf-cropper`)**:
|
||||||
|
|||||||
@ -1,107 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
|
|
||||||
use GuzzleHttp\Client;
|
|
||||||
use GuzzleHttp\Exception\GuzzleException;
|
|
||||||
|
|
||||||
|
|
||||||
// URL base de la API
|
|
||||||
$baseUri = 'https://172.16.5.134/crm/api/v1.0/';
|
|
||||||
// Token de autenticación
|
|
||||||
$token = '6abef18c-783d-4dd0-b530-be6e6a7bbd1d';
|
|
||||||
|
|
||||||
// Configuración del cliente GuzzleHttp
|
|
||||||
$clientGuzzleHttp = new Client([
|
|
||||||
'base_uri' => $baseUri,
|
|
||||||
'headers' => [
|
|
||||||
'X-Auth-App-Key' => $token, // Cambia el nombre de la cabecera de autorización
|
|
||||||
'Accept' => 'application/pdf', // Indica que esperamos una respuesta en formato JSON
|
|
||||||
],
|
|
||||||
'verify' => false,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Hacer la solicitud GET
|
|
||||||
$response = $clientGuzzleHttp->request('GET', "payments/89/pdf");
|
|
||||||
|
|
||||||
// Obtener el cuerpo de la respuesta (contenido binario)
|
|
||||||
$binaryData = $response->getBody()->getContents();
|
|
||||||
|
|
||||||
// Abrir un recurso de flujo de datos en memoria
|
|
||||||
$fileHandle = fopen('php://temp', 'r+');
|
|
||||||
|
|
||||||
// Escribir los datos binarios en el recurso de flujo de datos en memoria
|
|
||||||
fwrite($fileHandle, $binaryData);
|
|
||||||
|
|
||||||
// Rebobinar el puntero del flujo de datos para leer desde el principio
|
|
||||||
rewind($fileHandle);
|
|
||||||
|
|
||||||
if (!isset($fileHandle)) {
|
|
||||||
print_r("viene vacia la variable filehandle" . PHP_EOL);
|
|
||||||
}
|
|
||||||
// Subir el archivo al servidor FTP usando ftp_fput
|
|
||||||
$remoteFilename = 'archivo.pdf';
|
|
||||||
|
|
||||||
$pdf_payment_path = '/home/unms/data/ucrm/ucrm/data/payment_receipts/hola.txt';
|
|
||||||
$permisos = 0777; // Permisos 777 (lectura, escritura y ejecución para todos)
|
|
||||||
|
|
||||||
// Verificar si el archivo existe antes de cambiar los permisos
|
|
||||||
if (file_exists($pdf_payment_path)) {
|
|
||||||
// Cambiar los permisos del archivo
|
|
||||||
if (chmod($pdf_payment_path, $permisos)) {
|
|
||||||
print_r("Los permisos del archivo se han cambiado correctamente." . PHP_EOL);
|
|
||||||
} else {
|
|
||||||
print_r("No se pudieron cambiar los permisos del archivo." . PHP_EOL);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print_r("El archivo no existe en la ruta especificada." . PHP_EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
UploadFileWordpress($fileHandle);
|
|
||||||
|
|
||||||
function UploadFileWordpress($fileHandle): string
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Configuración de conexión FTP
|
|
||||||
$ftp_server = "siip.mx";
|
|
||||||
$ftp_username = "siip0001";
|
|
||||||
$ftp_password = '$spGiT,[wa)n';
|
|
||||||
//$fileName = $fileNameComprobantePDF;
|
|
||||||
$remote_file = "/public_html/wp/wp-content/uploads/pdf/Comprobante_de_pago.pdf";
|
|
||||||
//$file_to_upload = '/home/unms/data/ucrm/ucrm/data/payment_receipts/' . $fileName;
|
|
||||||
$url = 'http://siip.mx/wp/wp-content/uploads/pdf/Comprobante_de_pago.pdf';
|
|
||||||
|
|
||||||
|
|
||||||
// Conexión FTP
|
|
||||||
$ftp_conn = ftp_connect($ftp_server) or die("No se pudo conectar al servidor FTP");
|
|
||||||
$login = ftp_login($ftp_conn, $ftp_username, $ftp_password);
|
|
||||||
|
|
||||||
|
|
||||||
// Verificar conexión y login
|
|
||||||
if ($ftp_conn && $login) {
|
|
||||||
print_r("Conexión FTP exitosa" . PHP_EOL);
|
|
||||||
|
|
||||||
var_dump($ftp_conn);
|
|
||||||
// Cargar archivo
|
|
||||||
if (ftp_fput($ftp_conn, $remote_file, $fileHandle, FTP_BINARY)) {
|
|
||||||
print_r("El archivo ha sido cargado exitosamente." . PHP_EOL);
|
|
||||||
print_r("La URL es: " . $url . PHP_EOL);
|
|
||||||
// Cerrar conexión FTP
|
|
||||||
ftp_close($ftp_conn);
|
|
||||||
return $url;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
print_r("Error al cargar el archivo " . PHP_EOL);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cerrar conexión FTP
|
|
||||||
ftp_close($ftp_conn);
|
|
||||||
return '';
|
|
||||||
} else {
|
|
||||||
print_r("No se pudo conectar o iniciar sesión en el servidor FTP." . PHP_EOL);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
13
README.md
13
README.md
@ -1,12 +1,23 @@
|
|||||||
# SIIP - WhatsApp Notifications & Integrated Payment Portal
|
# SIIP - WhatsApp Notifications & Integrated Payment Portal
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
Este plugin es una solución integral que transforma tu UCRM en un **Portal Administrativo de Última Generación**. No solo automatiza la comunicación por WhatsApp, sino que integra un Dashboard completo para la gestión de pagos online (Stripe/OXXO), visualización de comprobantes y coordinación de equipos técnicos.
|
Este plugin es una solución integral que transforma tu UCRM en un **Portal Administrativo de Última Generación**. No solo automatiza la comunicación por WhatsApp, sino que integra un Dashboard completo para la gestión de pagos online (Stripe/OXXO), visualización de comprobantes y coordinación de equipos técnicos.
|
||||||
|
|
||||||
|
## 🚀 Novedades v4.2.0 (Analytics & Sync)
|
||||||
|
|
||||||
|
- **📊 Visualizador de Pagos Mensuales**: Nueva herramienta de análisis que permite seleccionar cualquier mes y visualizar gráficamente:
|
||||||
|
- Estadísticas de clientes activos vs clientes que pagaron
|
||||||
|
- Gráfica de dona interactiva con Chart.js
|
||||||
|
- Listado detallado de clientes pendientes con saldos
|
||||||
|
- Porcentajes de cobranza en tiempo real
|
||||||
|
- **🔄 Sincronización Mejorada CallBell**: Fix crítico que garantiza la actualización automática del saldo y estado del cliente en CallBell cuando se agregan facturas o se modifican servicios.
|
||||||
|
- **🎯 Categorización Inteligente de Pagos**: Nuevo microservicio con acceso directo a la base de datos para obtener metadata de Stripe (tipo de pago) y asignar correctamente los atributos incluso cuando la API de UCRM no tiene la información.
|
||||||
|
- **⚙️ Configuración para Producción**: Sistema de `.env` implementado para gestión segura de credenciales de base de datos.
|
||||||
|
|
||||||
## 🚀 Novedades v4.1.0 (Performance & Storage)
|
## 🚀 Novedades v4.1.0 (Performance & Storage)
|
||||||
|
|
||||||
- **⚡ Microservicio PDF (`pdf-cropper`)**: Nuevo motor de renderizado externo (Python/FastAPI) que reemplaza librerías legacy, aumentando la velocidad y eliminando problemas de memoria en el servidor principal.
|
- **⚡ Microservicio PDF (`pdf-cropper`)**: Nuevo motor de renderizado externo (Python/FastAPI) que reemplaza librerías legacy, aumentando la velocidad y eliminando problemas de memoria en el servidor principal.
|
||||||
|
|||||||
26
check_method.php
Normal file
26
check_method.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
chdir(__DIR__);
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
|
$ucrmConfig = json_decode(file_get_contents('ucrm.json'), true);
|
||||||
|
$ucrmUrl = $ucrmConfig['ucrmLocalUrl'] ?? 'http://localhost';
|
||||||
|
$appKey = $ucrmConfig['pluginAppKey'];
|
||||||
|
|
||||||
|
$client = new \GuzzleHttp\Client([
|
||||||
|
'base_uri' => rtrim($ucrmUrl, '/') . '/api/v1.0/',
|
||||||
|
'verify' => false,
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-App-Key' => $appKey
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$methodId = '1dd098fa-5d63-4c8d-88b7-3c27ffbbb6ae';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->get("payment-methods/$methodId");
|
||||||
|
$method = json_decode($response->getBody(), true);
|
||||||
|
echo "Method ID: $methodId\n";
|
||||||
|
echo "Name: " . $method['name'] . "\n";
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo "ERROR: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
@ -1,175 +0,0 @@
|
|||||||
|
|
||||||
Pregunta: Algún microservicio que pueda levantar con docker compose y que funcione como API para pasarle un PDF y me devuelva una imagen y recortada en ciertas coordenadas o ajustada a formato cuadrado.
|
|
||||||
|
|
||||||
Respuesta: Para lo que necesitas, la mejor opción no es una herramienta genérica (como Gotenberg), ya que estas suelen convertir la página entera pero no permiten recortar por coordenadas en la misma petición.
|
|
||||||
|
|
||||||
La solución más limpia y eficiente es crear un microservicio ligero en Python (usando FastAPI y pdf2image) que puedas levantar con Docker. Python tiene las mejores librerías para manipulación de PDFs e imágenes.
|
|
||||||
|
|
||||||
Aquí tienes la solución completa "Copy-Paste":
|
|
||||||
|
|
||||||
1. Estructura del proyecto
|
|
||||||
Crea una carpeta (ej. pdf-cropper) con estos 3 archivos:
|
|
||||||
|
|
||||||
Dockerfile
|
|
||||||
|
|
||||||
main.py
|
|
||||||
|
|
||||||
docker-compose.yml
|
|
||||||
|
|
||||||
2. El Código (Python + FastAPI)
|
|
||||||
Este script recibe el PDF, lo convierte a imagen y aplica el recorte o ajuste cuadrado según lo que le pidas.
|
|
||||||
|
|
||||||
Archivo: main.py
|
|
||||||
|
|
||||||
Python
|
|
||||||
|
|
||||||
from fastapi import FastAPI, UploadFile, File, Form
|
|
||||||
from pdf2image import convert_from_bytes
|
|
||||||
from PIL import Image
|
|
||||||
import io
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
@app.post("/process")
|
|
||||||
async def process_pdf(
|
|
||||||
file: UploadFile = File(...),
|
|
||||||
mode: str = Form("square"), # 'square' o 'coords'
|
|
||||||
x: int = Form(0),
|
|
||||||
y: int = Form(0),
|
|
||||||
w: int = Form(500),
|
|
||||||
h: int = Form(500)
|
|
||||||
):
|
|
||||||
# 1. Leer el archivo PDF en memoria
|
|
||||||
pdf_bytes = await file.read()
|
|
||||||
|
|
||||||
# 2. Convertir la primera página a imagen
|
|
||||||
# poppler_path no es necesario si se instala en el sistema (ver Dockerfile)
|
|
||||||
images = convert_from_bytes(pdf_bytes, fmt="jpeg")
|
|
||||||
|
|
||||||
if not images:
|
|
||||||
return {"error": "No se pudo leer el PDF"}
|
|
||||||
|
|
||||||
img = images[0] # Tomamos la primera página
|
|
||||||
|
|
||||||
# 3. Lógica de recorte
|
|
||||||
if mode == "square":
|
|
||||||
# Recorte cuadrado centrado automático
|
|
||||||
width, height = img.size
|
|
||||||
new_dim = min(width, height)
|
|
||||||
left = (width - new_dim) / 2
|
|
||||||
top = (height - new_dim) / 2
|
|
||||||
right = (width + new_dim) / 2
|
|
||||||
bottom = (height + new_dim) / 2
|
|
||||||
img = img.crop((left, top, right, bottom))
|
|
||||||
# Opcional: Redimensionar al tamaño final deseado
|
|
||||||
img = img.resize((w, h))
|
|
||||||
|
|
||||||
elif mode == "coords":
|
|
||||||
# Recorte por coordenadas exactas
|
|
||||||
img = img.crop((x, y, x + w, y + h))
|
|
||||||
|
|
||||||
# 4. Devolver la imagen procesada
|
|
||||||
img_byte_arr = io.BytesIO()
|
|
||||||
img.save(img_byte_arr, format='JPEG', quality=90)
|
|
||||||
img_byte_arr.seek(0)
|
|
||||||
|
|
||||||
from starlette.responses import StreamingResponse
|
|
||||||
return StreamingResponse(img_byte_arr, media_type="image/jpeg")
|
|
||||||
3. El Dockerfile
|
|
||||||
Es crucial instalar poppler-utils, que es el motor que procesa el PDF.
|
|
||||||
|
|
||||||
Archivo: Dockerfile
|
|
||||||
|
|
||||||
Dockerfile
|
|
||||||
|
|
||||||
FROM python:3.9-slim
|
|
||||||
|
|
||||||
# Instalar poppler (necesario para pdf2image)
|
|
||||||
RUN apt-get update && apt-get install -y poppler-utils && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Instalar dependencias de Python
|
|
||||||
RUN pip install fastapi uvicorn python-multipart pdf2image Pillow
|
|
||||||
|
|
||||||
COPY main.py .
|
|
||||||
|
|
||||||
# Ejecutar la API
|
|
||||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
||||||
4. Docker Compose
|
|
||||||
Para levantarlo.
|
|
||||||
|
|
||||||
Archivo: docker-compose.yml
|
|
||||||
|
|
||||||
YAML
|
|
||||||
|
|
||||||
services:
|
|
||||||
pdf-cropper:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "8080:8000"
|
|
||||||
restart: always
|
|
||||||
Comando para iniciar:
|
|
||||||
|
|
||||||
Bash
|
|
||||||
|
|
||||||
docker compose up -d --build
|
|
||||||
5. Cómo usarlo desde PHP
|
|
||||||
Ahora tienes una API corriendo en http://localhost:8080/process. Aquí tienes el script PHP para enviarle un PDF y guardar la imagen recortada.
|
|
||||||
|
|
||||||
Archivo: cliente.php
|
|
||||||
|
|
||||||
PHP
|
|
||||||
|
|
||||||
<?php
|
|
||||||
|
|
||||||
$pdfPath = 'documento.pdf';
|
|
||||||
$outputPath = 'resultado.jpg';
|
|
||||||
|
|
||||||
// URL de tu microservicio docker
|
|
||||||
$url = 'http://localhost:8080/process';
|
|
||||||
|
|
||||||
$curl = curl_init();
|
|
||||||
|
|
||||||
$fields = [
|
|
||||||
'file' => new CURLFile($pdfPath, 'application/pdf', 'documento.pdf'),
|
|
||||||
|
|
||||||
// OPCIÓN A: Recorte cuadrado automático (centrado)
|
|
||||||
'mode' => 'square',
|
|
||||||
'w' => 800, // Tamaño final del cuadrado (px)
|
|
||||||
'h' => 800,
|
|
||||||
|
|
||||||
// OPCIÓN B: Coordenadas específicas (descomentar para usar)
|
|
||||||
// 'mode' => 'coords',
|
|
||||||
// 'x' => 100, // Píxeles desde la izquierda
|
|
||||||
// 'y' => 200, // Píxeles desde arriba
|
|
||||||
// 'w' => 500, // Ancho del recorte
|
|
||||||
// 'h' => 300 // Alto del recorte
|
|
||||||
];
|
|
||||||
|
|
||||||
curl_setopt_array($curl, [
|
|
||||||
CURLOPT_URL => $url,
|
|
||||||
CURLOPT_POST => true,
|
|
||||||
CURLOPT_POSTFIELDS => $fields,
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$response = curl_exec($curl);
|
|
||||||
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
|
||||||
|
|
||||||
if ($httpCode == 200) {
|
|
||||||
file_put_contents($outputPath, $response);
|
|
||||||
echo "Imagen guardada exitosamente en $outputPath";
|
|
||||||
} else {
|
|
||||||
echo "Error: " . curl_error($curl);
|
|
||||||
echo "Respuesta del servidor: " . $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_close($curl);
|
|
||||||
?>
|
|
||||||
¿Por qué esta solución?
|
|
||||||
Atomocidad: Hace la conversión y el recorte en un solo paso de memoria (RAM), sin guardar archivos temporales en disco.
|
|
||||||
|
|
||||||
Control: Puedes ajustar la lógica de "cuadrado" (si quieres que sea centrado o desde la esquina superior) modificando fácilmente el main.py.
|
|
||||||
|
|
||||||
Ligero: La imagen Docker pesará poco y consumirá memoria solo cuando procese un archivo.
|
|
||||||
@ -1 +1 @@
|
|||||||
{"ipserver":"venus.siip.mx","apitoken":"gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9","unmsApiToken":"079c28f5-888c-457d-bd7a-0a4202590f75","tokencallbell":"g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79","tokenstripe":"sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M","hostServerFTP":"siip.mx","usernameServerFTP":"siip0001","passServerFTP":"$spGiT,[wa)n","ipPuppeteer":"172.16.5.134","portPuppeteer":"4100","idPaymentAdminCRM":"1180","cashPaymentMethodId":false,"courtesyPaymentMethodId":false,"bankTransferPaymentMethodId":true,"paypalPaymentMethodId":true,"creditCardPaypalPaymentMethodId":true,"creditCardStripePaymentMethodId":true,"stripeSubscriptionCreditCardPaymentMethodId":true,"paypalSubscriptionPaymentMethodId":true,"mercadopagoPaymentMethodId":true,"checkPaymentMethodId":true,"customPaymentMethodId":true,"notificationTypeText":false,"installersDataWhatsApp":"{\r\n \"instaladores\": [\r\n {\r\n \"id\": 1019,\r\n \"nombre\": \"Mucio Robledo\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1173,\r\n \"nombre\": \"Ángel Arvizu\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1172,\r\n \"nombre\": \"Juan Rostro\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1015,\r\n \"nombre\": \"Daniel Humberto\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1131,\r\n \"nombre\": \"Gricelda Avalos\",\r\n \"whatsapp\": \"4181817609\"\r\n }\r\n ]\r\n}","debugMode":true,"logging_level":true,"minioEndpoint":"http://172.16.5.134:9002","minioPublicUrl":"https://aws-venus.siip.mx","minioAccessKey":"minioadmin","minioSecretKey":"minioadmin","minioBucket":"vouchers-oxxo"}
|
{"ipserver":"venus.siip.mx","apitoken":"gvcnIJqXdUjneVSjhl6THLlQcYXJyIFCcwHKVba2bvIrNraanCTb5VeoWuJ0TFZ9","unmsApiToken":"079c28f5-888c-457d-bd7a-0a4202590f75","tokencallbell":"g8thcZkXGd3xBj2g3TtYNYFMH1fuesbJ.b6a940ea7d78cf6c9e42f067b21c8ddf96e9fa2a9e307bfd0c7c7c4d7fa38f79","tokenstripe":"sk_test_51OkG0REFY1WEUtgRH6UxBK5pu80Aq5Iy8EcdPnf0cOWzuVLQTpyLCd7CbPzqMsWMafZOHElCxhEHF7g8boURjWlJ00tBwE0W1M","hostServerFTP":"siip.mx","usernameServerFTP":"siip0001","passServerFTP":"$spGiT,[wa)n","ipPuppeteer":"172.16.5.134","portPuppeteer":"4100","idPaymentAdminCRM":"1180","cashPaymentMethodId":false,"courtesyPaymentMethodId":false,"bankTransferPaymentMethodId":true,"paypalPaymentMethodId":true,"creditCardPaypalPaymentMethodId":true,"creditCardStripePaymentMethodId":true,"stripeSubscriptionCreditCardPaymentMethodId":true,"paypalSubscriptionPaymentMethodId":true,"mercadopagoPaymentMethodId":true,"checkPaymentMethodId":true,"customPaymentMethodId":true,"notificationTypeText":false,"installersDataWhatsApp":"{\r\n \"instaladores\": [\r\n {\r\n \"id\": 1019,\r\n \"nombre\": \"Mucio Robledo\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1173,\r\n \"nombre\": \"Ángel Arvizu\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1172,\r\n \"nombre\": \"Juan Rostro\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1015,\r\n \"nombre\": \"Daniel Humberto\",\r\n \"whatsapp\": \"4181878106\"\r\n },\r\n {\r\n \"id\": 1131,\r\n \"nombre\": \"Gricelda Avalos\",\r\n \"whatsapp\": \"4181817609\"\r\n }\r\n ]\r\n}","debugMode":true,"minioEndpoint":"http://172.16.5.134:9002","minioPublicUrl":"https://aws-venus.siip.mx","minioAccessKey":"minioadmin","minioSecretKey":"minioadmin","minioBucket":"vouchers-oxxo","logging_level":true}
|
||||||
3496
data/plugin.log
Normal file → Executable file
3496
data/plugin.log
Normal file → Executable file
File diff suppressed because one or more lines are too long
34
explore_db.php
Normal file
34
explore_db.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
$host = 'localhost';
|
||||||
|
$port = '5432';
|
||||||
|
$dbname = 'unms';
|
||||||
|
$user = 'ucrm';
|
||||||
|
$password = 'MtxWkaa5O2Cwy3PRFVXtC01bXFyQjykRpAIqEOXC5FmpMURD';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dsn = "pgsql:host=$host;port=$port;dbname=$dbname;user=$user;password=$password";
|
||||||
|
$pdo = new PDO($dsn);
|
||||||
|
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
echo "Connected to DB.\n";
|
||||||
|
|
||||||
|
// List tables in ucrm schema matching 'stripe'
|
||||||
|
$stmt = $pdo->query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'ucrm' AND table_name LIKE '%stripe%'");
|
||||||
|
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
echo "Tables found:\n";
|
||||||
|
foreach ($tables as $table) {
|
||||||
|
echo "- $table\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check for 'payment' tables if stripe ones aren't obvious/enough
|
||||||
|
$stmt2 = $pdo->query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'ucrm' AND table_name LIKE '%payment%' LIMIT 20");
|
||||||
|
$tables2 = $stmt2->fetchAll(PDO::FETCH_COLUMN);
|
||||||
|
echo "\nPayment Tables sample:\n";
|
||||||
|
foreach ($tables2 as $table) {
|
||||||
|
echo "- $table\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
echo "Connection failed: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
33
find_attribute_id.php
Normal file
33
find_attribute_id.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
chdir(__DIR__);
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
|
use Ubnt\UcrmPluginSdk\Service\UcrmApi;
|
||||||
|
use Ubnt\UcrmPluginSdk\Service\PluginConfigManager;
|
||||||
|
|
||||||
|
$ucrmConfig = json_decode(file_get_contents('ucrm.json'), true);
|
||||||
|
$ucrmUrl = $ucrmConfig['ucrmLocalUrl'] ?? 'http://localhost';
|
||||||
|
$appKey = $ucrmConfig['pluginAppKey'];
|
||||||
|
|
||||||
|
$client = new \GuzzleHttp\Client([
|
||||||
|
'base_uri' => rtrim($ucrmUrl, '/') . '/api/v1.0/',
|
||||||
|
'verify' => false,
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-App-Key' => $appKey
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->get('custom-attributes');
|
||||||
|
$attributes = json_decode($response->getBody(), true);
|
||||||
|
|
||||||
|
foreach ($attributes as $attr) {
|
||||||
|
if ($attr['key'] === 'tipoPagoStripe') {
|
||||||
|
echo "FOUND: ID=" . $attr['id'] . " Name='" . $attr['name'] . "' Type=" . $attr['attributeType'] . "\n";
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "NOT FOUND: tipoPagoStripe\n";
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo "ERROR: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
24
inspect_payment.php
Normal file
24
inspect_payment.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
chdir(__DIR__);
|
||||||
|
require_once 'vendor/autoload.php';
|
||||||
|
|
||||||
|
$ucrmConfig = json_decode(file_get_contents('ucrm.json'), true);
|
||||||
|
$ucrmUrl = $ucrmConfig['ucrmLocalUrl'] ?? 'http://localhost';
|
||||||
|
$appKey = $ucrmConfig['pluginAppKey'];
|
||||||
|
|
||||||
|
$client = new \GuzzleHttp\Client([
|
||||||
|
'base_uri' => rtrim($ucrmUrl, '/') . '/api/v1.0/',
|
||||||
|
'verify' => false,
|
||||||
|
'headers' => [
|
||||||
|
'X-Auth-App-Key' => $appKey
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$paymentId = 818; // From screenshot
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->get("payments/$paymentId");
|
||||||
|
echo $response->getBody();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo "ERROR: " . $e->getMessage() . "\n";
|
||||||
|
}
|
||||||
0
list_attributes.php
Normal file → Executable file
0
list_attributes.php
Normal file → Executable file
@ -5,7 +5,7 @@
|
|||||||
"displayName": "SIIP - Procesador de Pagos en línea con Stripe, Oxxo y Transferencia, Sincronizador de CallBell y Envío de Notificaciones y comprobantes vía WhatsApp",
|
"displayName": "SIIP - Procesador de Pagos en línea con Stripe, Oxxo y Transferencia, Sincronizador de CallBell y Envío de Notificaciones y comprobantes vía WhatsApp",
|
||||||
"description": "Este plugin sincroniza los clientes del sistema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes",
|
"description": "Este plugin sincroniza los clientes del sistema UISP CRM con los contactos de WhatsApp en CallBell, además procesa pagos de Stripe como las trasferencias bancarias y genera referencias de pago vía OXXO, además envía comprobantes de pago en formato imagen PNG o texto vía Whatsapp a los clientes",
|
||||||
"url": "https://siip.mx/",
|
"url": "https://siip.mx/",
|
||||||
"version": "4.1.0",
|
"version": "4.2.0",
|
||||||
"unmsVersionCompliancy": {
|
"unmsVersionCompliancy": {
|
||||||
"min": "2.1.0",
|
"min": "2.1.0",
|
||||||
"max": null
|
"max": null
|
||||||
|
|||||||
280
public.php
280
public.php
@ -688,11 +688,39 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
|
|
||||||
.search-item .details {
|
.search-item .details {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-view { display: none; }
|
.section-view { display: none; }
|
||||||
.section-view.active { display: block; }
|
.section-view.active { display: block; }
|
||||||
|
|
||||||
|
/* Payment Visualizer Styles */
|
||||||
|
.stat-card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card h3 {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card p {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-main);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card.success p { color: var(--success); }
|
||||||
|
.stat-card.danger p { color: var(--danger); }
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -718,6 +746,14 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
<img src="?action=image&file=oxxo-logo.png" alt="" style="width: 28px; height: 28px; object-fit: contain; flex-shrink: 0;">
|
<img src="?action=image&file=oxxo-logo.png" alt="" style="width: 28px; height: 28px; object-fit: contain; flex-shrink: 0;">
|
||||||
<span>Pagos OXXO</span>
|
<span>Pagos OXXO</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="#" class="nav-link" onclick="switchSection('payments-viz', this)">
|
||||||
|
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<line x1="12" y1="20" x2="12" y2="10"></line>
|
||||||
|
<line x1="18" y1="20" x2="18" y2="4"></line>
|
||||||
|
<line x1="6" y1="20" x2="6" y2="16"></line>
|
||||||
|
</svg>
|
||||||
|
<span>Visualizador Pagos</span>
|
||||||
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@ -904,7 +940,74 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
<!-- Contenedor de Resultado Inline: Full Width -->
|
<!-- Contenedor de Resultado Inline: Full Width -->
|
||||||
<div id="oxxoInlineResult" style="margin-top: 1.5rem; display: none;"></div>
|
<div id="oxxoInlineResult" style="margin-top: 1.5rem; display: none;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="section-payments-viz" class="section-view">
|
||||||
|
<!-- Selector de Mes -->
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<div style="display: flex; gap: 1rem; align-items: flex-end;">
|
||||||
|
<div class="form-group" style="flex: 1; margin-bottom: 0;">
|
||||||
|
<label>Seleccionar Mes</label>
|
||||||
|
<input type="month" id="monthSelector" class="form-control" value="<?= date('Y-m') ?>">
|
||||||
</div>
|
</div>
|
||||||
|
<button class="btn btn-primary" onclick="loadPaymentsData()" style="height: 46px;">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||||
|
</svg>
|
||||||
|
Cargar Datos
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loader -->
|
||||||
|
<div id="paymentsLoader" style="display: none; text-align: center; padding: 3rem;">
|
||||||
|
<div style="width: 50px; height: 50px; border: 4px solid var(--border); border-top-color: var(--primary); border-radius: 50%; margin: 0 auto; animation: spin 1s linear infinite;"></div>
|
||||||
|
<p style="margin-top: 1rem; color: var(--text-muted);">Cargando datos...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Estadísticas Resumen -->
|
||||||
|
<div id="paymentsStats" style="display: none;">
|
||||||
|
<div class="stats-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; margin-bottom: 2rem;">
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Total Clientes Activos</h3>
|
||||||
|
<p id="total-clients">0</p>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card success">
|
||||||
|
<h3>Clientes que Pagaron</h3>
|
||||||
|
<p id="clients-paid">0</p>
|
||||||
|
<span id="paid-percentage" style="color: var(--text-muted); font-size: 0.875rem;"></span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card danger">
|
||||||
|
<h3>Clientes Pendientes</h3>
|
||||||
|
<p id="clients-pending">0</p>
|
||||||
|
<span id="pending-percentage" style="color: var(--text-muted); font-size: 0.875rem;"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Gráfica -->
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<canvas id="paymentsChart" style="max-height: 400px;"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabla de Clientes Pendientes -->
|
||||||
|
<div class="card">
|
||||||
|
<h3 style="margin-bottom: 1.5rem;">Clientes Pendientes de Pago</h3>
|
||||||
|
<div class="table-container">
|
||||||
|
<table id="pendingTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Nombre</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Saldo</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Modal Form -->
|
<!-- Modal Form -->
|
||||||
@ -949,6 +1052,7 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
|
|
||||||
<div id="toast"></div>
|
<div id="toast"></div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const store = {
|
const store = {
|
||||||
installers: <?php echo json_encode($installersData['instaladores']); ?>,
|
installers: <?php echo json_encode($installersData['instaladores']); ?>,
|
||||||
@ -986,6 +1090,9 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
} else if (sectionId === 'oxxo') {
|
} else if (sectionId === 'oxxo') {
|
||||||
title.textContent = 'Pagos OXXO Pay';
|
title.textContent = 'Pagos OXXO Pay';
|
||||||
desc.textContent = 'Genera fichas de pago para establecimientos OXXO';
|
desc.textContent = 'Genera fichas de pago para establecimientos OXXO';
|
||||||
|
} else if (sectionId === 'payments-viz') {
|
||||||
|
title.textContent = 'Visualizador de Pagos';
|
||||||
|
desc.textContent = 'Análisis mensual de pagos de clientes activos';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1584,6 +1691,177 @@ $installersData = json_decode($config['installersDataWhatsApp'] ?? '{"instalador
|
|||||||
setTimeout(() => toast.classList.remove('show'), 3000);
|
setTimeout(() => toast.classList.remove('show'), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Payment Visualizer Functions
|
||||||
|
let paymentsChart = null;
|
||||||
|
|
||||||
|
async function loadPaymentsData() {
|
||||||
|
const month = document.getElementById('monthSelector').value;
|
||||||
|
if (!month) {
|
||||||
|
showToast('Por favor selecciona un mes', true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [year, monthNum] = month.split('-');
|
||||||
|
const firstDay = `${year}-${monthNum.padStart(2, '0')}-01`;
|
||||||
|
const lastDay = new Date(year, monthNum, 0).toISOString().split('T')[0];
|
||||||
|
|
||||||
|
// Show loader
|
||||||
|
document.getElementById('paymentsLoader').style.display = 'block';
|
||||||
|
document.getElementById('paymentsStats').style.display = 'none';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Fetch active clients
|
||||||
|
const clients = await fetchActiveClients();
|
||||||
|
|
||||||
|
// 2. Fetch payments for the month
|
||||||
|
const payments = await fetchPaymentsByMonth(firstDay, lastDay);
|
||||||
|
|
||||||
|
// 3. Calculate stats
|
||||||
|
const stats = calculateStats(clients, payments);
|
||||||
|
|
||||||
|
// 4. Update UI
|
||||||
|
updateStatsDisplay(stats);
|
||||||
|
updateChart(stats);
|
||||||
|
updatePendingTable(stats.pendingClients);
|
||||||
|
|
||||||
|
// Hide loader, show stats
|
||||||
|
document.getElementById('paymentsLoader').style.display = 'none';
|
||||||
|
document.getElementById('paymentsStats').style.display = 'block';
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading payments data:', error);
|
||||||
|
showToast('Error al cargar datos: ' + error.message, true);
|
||||||
|
document.getElementById('paymentsLoader').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchActiveClients() {
|
||||||
|
const response = await fetch('/crm/api/v1.0/clients?isArchived=0&limit=1000', {
|
||||||
|
headers: { 'X-Auth-App-Key': '<?= $config["apitoken"] ?>' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Error al obtener clientes');
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchPaymentsByMonth(from, to) {
|
||||||
|
const response = await fetch(`/crm/api/v1.0/payments?createdDateFrom=${from}&createdDateTo=${to}&limit=5000`, {
|
||||||
|
headers: { 'X-Auth-App-Key': '<?= $config["apitoken"] ?>' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Error al obtener pagos');
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateStats(clients, payments) {
|
||||||
|
// Filter only clients with active services (not suspended or archived)
|
||||||
|
const activeClients = clients.filter(c => !c.hasSuspendedService && !c.isArchived);
|
||||||
|
|
||||||
|
// Create set of client IDs who paid this month
|
||||||
|
const paidClientIds = new Set(payments.map(p => p.clientId));
|
||||||
|
|
||||||
|
// Clients who paid
|
||||||
|
const clientsPaid = activeClients.filter(c => paidClientIds.has(c.id));
|
||||||
|
|
||||||
|
// Pending clients
|
||||||
|
const clientsPending = activeClients.filter(c => !paidClientIds.has(c.id));
|
||||||
|
|
||||||
|
const paidPercentage = activeClients.length > 0
|
||||||
|
? (clientsPaid.length / activeClients.length * 100).toFixed(1)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalClients: activeClients.length,
|
||||||
|
clientsPaid: clientsPaid.length,
|
||||||
|
clientsPending: clientsPending.length,
|
||||||
|
pendingClients: clientsPending,
|
||||||
|
paidPercentage: paidPercentage
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatsDisplay(stats) {
|
||||||
|
document.getElementById('total-clients').textContent = stats.totalClients;
|
||||||
|
document.getElementById('clients-paid').textContent = stats.clientsPaid;
|
||||||
|
document.getElementById('clients-pending').textContent = stats.clientsPending;
|
||||||
|
document.getElementById('paid-percentage').textContent = `${stats.paidPercentage}% del total`;
|
||||||
|
document.getElementById('pending-percentage').textContent = `${(100 - stats.paidPercentage).toFixed(1)}% del total`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateChart(stats) {
|
||||||
|
const ctx = document.getElementById('paymentsChart').getContext('2d');
|
||||||
|
|
||||||
|
if (paymentsChart) paymentsChart.destroy();
|
||||||
|
|
||||||
|
paymentsChart = new Chart(ctx, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: ['Pagaron', 'Pendientes'],
|
||||||
|
datasets: [{
|
||||||
|
data: [stats.clientsPaid, stats.clientsPending],
|
||||||
|
backgroundColor: ['#22c55e', '#ef4444'],
|
||||||
|
borderWidth: 0,
|
||||||
|
hoverOffset: 10
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
font: { size: 14, weight: '600' },
|
||||||
|
padding: 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: `Estado de Pagos del Mes (${stats.paidPercentage}% completado)`,
|
||||||
|
font: { size: 18, weight: '700' },
|
||||||
|
padding: { bottom: 30 }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
const label = context.label || '';
|
||||||
|
const value = context.parsed || 0;
|
||||||
|
const percentage = ((value / stats.totalClients) * 100).toFixed(1);
|
||||||
|
return `${label}: ${value} clientes (${percentage}%)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePendingTable(pendingClients) {
|
||||||
|
const tbody = document.querySelector('#pendingTable tbody');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (pendingClients.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="4" style="text-align: center; padding: 2rem; color: var(--text-muted);">¡Todos los clientes activos han pagado este mes! 🎉</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingClients.forEach(client => {
|
||||||
|
const email = client.contacts?.find(c => c.email)?.email || 'Sin email';
|
||||||
|
const nombre = `${client.firstName || ''} ${client.lastName || ''}`.trim() || client.companyName || 'Sin nombre';
|
||||||
|
const saldo = client.accountBalance < 0
|
||||||
|
? `$${Math.abs(client.accountBalance).toFixed(2)} pendientes`
|
||||||
|
: `$${client.accountBalance.toFixed(2)} a favor`;
|
||||||
|
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${client.id}</td>
|
||||||
|
<td>${nombre}</td>
|
||||||
|
<td>${email}</td>
|
||||||
|
<td style="color: ${client.accountBalance < 0 ? 'var(--danger)' : 'var(--success)'}; font-weight: 600;">${saldo}</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderTable();
|
renderTable();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -242,11 +242,19 @@ abstract class AbstractMessageNotifierFacade
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function onlyUpdate(NotificationData $notificationData, $phoneToUpdate): void {
|
public function onlyUpdate(NotificationData $notificationData, $phoneToUpdate): void {
|
||||||
|
$this->logger->debug("onlyUpdate: Iniciando actualización para teléfono: $phoneToUpdate");
|
||||||
$config = PluginConfigManager::create()->loadConfig();
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
|
$api = new ClientCallBellAPI($config['apitoken'], $config['ipserver'], $config['tokencallbell']);
|
||||||
$phone = $this->validarNumeroTelefono($phoneToUpdate);
|
$phone = $this->validarNumeroTelefono($phoneToUpdate);
|
||||||
|
$this->logger->debug("onlyUpdate: Teléfono validado: $phone");
|
||||||
$contact = json_decode($api->getContactWhatsapp($phone), true);
|
$contact = json_decode($api->getContactWhatsapp($phone), true);
|
||||||
if ($contact) $api->patchWhatsapp($contact, $notificationData);
|
$this->logger->debug("onlyUpdate: Contacto obtenido de CallBell: " . json_encode($contact));
|
||||||
|
if ($contact) {
|
||||||
|
$this->logger->info("onlyUpdate: Ejecutando patchWhatsapp para teléfono: $phone");
|
||||||
|
$api->patchWhatsapp($contact, $notificationData);
|
||||||
|
} else {
|
||||||
|
$this->logger->warning("onlyUpdate: No se encontró contacto en CallBell para teléfono: $phone");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onlyUpdateService(NotificationData $notificationData, $phoneToUpdate): void {
|
public function onlyUpdateService(NotificationData $notificationData, $phoneToUpdate): void {
|
||||||
|
|||||||
@ -611,5 +611,133 @@ abstract class AbstractStripeOperationsFacade
|
|||||||
return (strlen($n) === 10) ? '52' . $n : $n;
|
return (strlen($n) === 10) ? '52' . $n : $n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ensureStripePaymentAttribute($notificationObject): void
|
||||||
|
{
|
||||||
|
// 1. Get Payment ID
|
||||||
|
$paymentId = is_object($notificationObject) ? ($notificationObject->entityId ?? null) : ($notificationObject['entityId'] ?? null);
|
||||||
|
|
||||||
|
if (!$paymentId) {
|
||||||
|
$this->logger->warning("ensureStripePaymentAttribute: No entityId found in notification.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->info("Verificando existencia de atributo 'tipoPagoStripe' para Payment ID: $paymentId");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load Config
|
||||||
|
$config = PluginConfigManager::create()->loadConfig();
|
||||||
|
$ipPuppeteer = $config['ipPuppeteer'] ?? 'localhost';
|
||||||
|
$portPuppeteer = $config['portPuppeteer'] ?? '3000';
|
||||||
|
$stripeUserId = $config['idPaymentAdminCRM'] ?? null;
|
||||||
|
|
||||||
|
$microserviceBaseUrl = "http://$ipPuppeteer:$portPuppeteer";
|
||||||
|
$httpClient = new Client();
|
||||||
|
|
||||||
|
// 2. Fetch Metadata from Microservice (DB Access)
|
||||||
|
$metadataTipoPago = null;
|
||||||
|
try {
|
||||||
|
$response = $httpClient->get("$microserviceBaseUrl/stripe-metadata/$paymentId", ['timeout' => 5]);
|
||||||
|
$data = json_decode($response->getBody()->getContents(), true);
|
||||||
|
if (isset($data['metadata']['tipoPago'])) {
|
||||||
|
$metadataTipoPago = $data['metadata']['tipoPago'];
|
||||||
|
$this->logger->info("Microservice found metadata: tipoPago = '$metadataTipoPago'");
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logger->warning("Microservice metadata fetch failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Update User ID if missing (Direct DB Patch via Microservice)
|
||||||
|
// UCRM API doesn't support PATCH userId, so we use microservice
|
||||||
|
if ($stripeUserId) {
|
||||||
|
try {
|
||||||
|
$payment = $this->ucrmApi->get('payments/' . $paymentId);
|
||||||
|
if (empty($payment['userId'])) {
|
||||||
|
$this->logger->info("Payment $paymentId has no User ID. Assigning Stripe User ID: $stripeUserId");
|
||||||
|
$httpClient->patch("$microserviceBaseUrl/payments/$paymentId/user", [
|
||||||
|
'json' => ['userId' => $stripeUserId],
|
||||||
|
'timeout' => 5
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->logger->debug("Payment $paymentId already has User ID: " . $payment['userId']);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logger->error("Failed to patch User ID via microservice: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Determine Target Attribute Value
|
||||||
|
// Truth Source Priority: 1. Metadata (DB), 2. Existing Attribute, 3. Method Name (Guess)
|
||||||
|
|
||||||
|
// A. Check Existing Attribute (Don't overwrite valid values unless Metadata says otherwise?)
|
||||||
|
// Actually, Metadata keys are stronger than manual edits if the flow is automatic.
|
||||||
|
// But let's respect existing valid attributes if metadata is missing.
|
||||||
|
|
||||||
|
$payment = $this->ucrmApi->get('payments/' . $paymentId); // Re-fetch in case changed? Or Use previous result.
|
||||||
|
$currentValue = null;
|
||||||
|
$hasAttribute = false;
|
||||||
|
foreach ($payment['attributes'] as $attr) {
|
||||||
|
if ($attr['key'] === 'tipoPagoStripe' || $attr['customAttributeId'] == 20) {
|
||||||
|
$hasAttribute = true;
|
||||||
|
$currentValue = $attr['value'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$targetValue = null;
|
||||||
|
|
||||||
|
if ($metadataTipoPago) {
|
||||||
|
// Normalize Metadata Values to Attribute Choice Values
|
||||||
|
if ($metadataTipoPago === 'OXXO') {
|
||||||
|
$targetValue = 'OXXO Pay';
|
||||||
|
} else {
|
||||||
|
$targetValue = $metadataTipoPago;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to Method Name Guessing if Metadata missing
|
||||||
|
if ($hasAttribute && in_array($currentValue, ['OXXO Pay', 'Transferencia Bancaria', 'Tarjeta de Crédito'])) {
|
||||||
|
$this->logger->debug("Payment $paymentId ya tiene atributo '$currentValue' y no hay metadata. Respetando.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$methodId = $payment['methodId'];
|
||||||
|
$method = $this->ucrmApi->get('payment-methods/' . $methodId);
|
||||||
|
$methodName = $method['name'] ?? '';
|
||||||
|
|
||||||
|
if (stripos($methodName, 'OXXO') !== false) {
|
||||||
|
$targetValue = 'OXXO Pay';
|
||||||
|
} elseif (stripos($methodName, 'Transferencia') !== false) {
|
||||||
|
$targetValue = 'Transferencia Bancaria';
|
||||||
|
} elseif (stripos($methodName, 'Tarjeta') !== false && stripos($methodName, 'Stripe') !== false) {
|
||||||
|
$targetValue = 'Tarjeta de Crédito';
|
||||||
|
}
|
||||||
|
$this->logger->debug("Fallback Method Guessing '$methodName' -> '$targetValue'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Apply Update
|
||||||
|
if ($targetValue) {
|
||||||
|
// Check redundancy
|
||||||
|
if ($hasAttribute && $currentValue === $targetValue) {
|
||||||
|
$this->logger->debug("Attribute already matches target '$targetValue'. Skipping patch.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logger->info("PATCHING Payment $paymentId: Setting tipoPagoStripe = '$targetValue'");
|
||||||
|
$this->ucrmApi->patch('payments/' . $paymentId, [
|
||||||
|
'attributes' => [
|
||||||
|
[
|
||||||
|
'customAttributeId' => 20,
|
||||||
|
'value' => $targetValue
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$this->logger->debug("No se pudo determinar el tipoPagoStripe.");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logger->error("Error in ensureStripePaymentAttribute: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abstract protected function sendWhatsApp(NotificationData $notificationData, string $clientPhoneNumber): void;
|
abstract protected function sendWhatsApp(NotificationData $notificationData, string $clientPhoneNumber): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1150,6 +1150,11 @@ class ClientCallBellAPI
|
|||||||
$contact = $response_getContactCallBell['contact'] ?? [];
|
$contact = $response_getContactCallBell['contact'] ?? [];
|
||||||
$contactCustomFields = $contact['customFields'] ?? [];
|
$contactCustomFields = $contact['customFields'] ?? [];
|
||||||
|
|
||||||
|
$log->appendLog("DEBUG COMPARACIÓN - CallBell Saldo Actual: '" . ($contactCustomFields['Saldo Actual'] ?? 'NULL') . "'" . PHP_EOL);
|
||||||
|
$log->appendLog("DEBUG COMPARACIÓN - UCRM Saldo Actual: '" . $data_CRM['custom_fields']['Saldo Actual'] . "'" . PHP_EOL);
|
||||||
|
$log->appendLog("DEBUG COMPARACIÓN - CallBell Estado: '" . ($contactCustomFields['Estado'] ?? 'NULL') . "'" . PHP_EOL);
|
||||||
|
$log->appendLog("DEBUG COMPARACIÓN - UCRM Estado: '" . $data_CRM['custom_fields']['Estado'] . "'" . PHP_EOL);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
($contactCustomFields['Cliente'] ?? '') != $data_CRM['custom_fields']['Cliente']
|
($contactCustomFields['Cliente'] ?? '') != $data_CRM['custom_fields']['Cliente']
|
||||||
|| ($contactCustomFields['Domicilio'] ?? '') != $data_CRM['custom_fields']['Domicilio']
|
|| ($contactCustomFields['Domicilio'] ?? '') != $data_CRM['custom_fields']['Domicilio']
|
||||||
@ -1163,13 +1168,14 @@ class ClientCallBellAPI
|
|||||||
|| ($contact['name'] ?? '') != $data_CRM['name']
|
|| ($contact['name'] ?? '') != $data_CRM['name']
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
$log->appendLog("EJECUTANDO PATCH - Se detectaron cambios" . PHP_EOL);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data_CRM));
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data_CRM));
|
||||||
$response = curl_exec($ch);
|
$response = curl_exec($ch);
|
||||||
//$log->appendLog("Response Patch CallBell: " . $response . PHP_EOL);
|
$log->appendLog("Response Patch CallBell: " . $response . PHP_EOL);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//$log->appendLog("No hay cambios que actualizar " . PHP_EOL);
|
$log->appendLog("NO SE EJECUTA PATCH - No hay cambios que actualizar" . PHP_EOL);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -299,6 +299,9 @@ class Plugin
|
|||||||
$result = json_encode($notification);
|
$result = json_encode($notification);
|
||||||
$this->logger->debug('Notification encodificado en JSON:' . $result . PHP_EOL);
|
$this->logger->debug('Notification encodificado en JSON:' . $result . PHP_EOL);
|
||||||
|
|
||||||
|
// [NEW] Attempt to patch the payment with correct Stripe attribute if applicable
|
||||||
|
$this->pluginNotifierFacade->ensureStripePaymentAttribute($notification);
|
||||||
|
|
||||||
$payment_method_id = $notification->paymentData['methodId'];
|
$payment_method_id = $notification->paymentData['methodId'];
|
||||||
$payment_method = '';
|
$payment_method = '';
|
||||||
|
|
||||||
|
|||||||
0
src/Service/MinioStorageService.php
Normal file → Executable file
0
src/Service/MinioStorageService.php
Normal file → Executable file
0
src/Service/PaymentIntentService.php
Normal file → Executable file
0
src/Service/PaymentIntentService.php
Normal file → Executable file
0
unms-swagger.json
Normal file → Executable file
0
unms-swagger.json
Normal file → Executable file
0
unmscrm.apib
Normal file → Executable file
0
unmscrm.apib
Normal file → Executable file
0
vendor/aws/aws-crt-php/CODE_OF_CONDUCT.md
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/CODE_OF_CONDUCT.md
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/LICENSE
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/LICENSE
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/NOTICE
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/NOTICE
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/README.md
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/README.md
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/composer.json
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/composer.json
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/AwsCredentials.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/AwsCredentials.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/CredentialsProvider.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/CredentialsProvider.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/Signable.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/Signable.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SignatureType.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SignatureType.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SignedBodyHeaderType.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SignedBodyHeaderType.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/Signing.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/Signing.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SigningAlgorithm.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SigningAlgorithm.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SigningConfigAWS.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SigningConfigAWS.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SigningResult.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/SigningResult.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/StaticCredentialsProvider.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Auth/StaticCredentialsProvider.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/CRT.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/CRT.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/HTTP/Headers.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/HTTP/Headers.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/HTTP/Message.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/HTTP/Message.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/HTTP/Request.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/HTTP/Request.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/HTTP/Response.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/HTTP/Response.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/IO/EventLoopGroup.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/IO/EventLoopGroup.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/IO/InputStream.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/IO/InputStream.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Internal/Encoding.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Internal/Encoding.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Internal/Extension.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Internal/Extension.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Log.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Log.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/NativeResource.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/NativeResource.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Options.php
vendored
Normal file → Executable file
0
vendor/aws/aws-crt-php/src/AWS/CRT/Options.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/CODE_OF_CONDUCT.md
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/CODE_OF_CONDUCT.md
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/CRT_INSTRUCTIONS.md
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/CRT_INSTRUCTIONS.md
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/LICENSE
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/LICENSE
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/NOTICE
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/NOTICE
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/THIRD-PARTY-LICENSES
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/THIRD-PARTY-LICENSES
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/composer.json
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/composer.json
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ACMPCA/ACMPCAClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ACMPCA/ACMPCAClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ACMPCA/Exception/ACMPCAException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ACMPCA/Exception/ACMPCAException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AIOps/AIOpsClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AIOps/AIOpsClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AIOps/Exception/AIOpsException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AIOps/Exception/AIOpsException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ARCRegionSwitch/ARCRegionSwitchClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ARCRegionSwitch/ARCRegionSwitchClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ARCRegionSwitch/Exception/ARCRegionSwitchException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ARCRegionSwitch/Exception/ARCRegionSwitchException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ARCZonalShift/ARCZonalShiftClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ARCZonalShift/ARCZonalShiftClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ARCZonalShift/Exception/ARCZonalShiftException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/ARCZonalShift/Exception/ARCZonalShiftException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AbstractConfigurationProvider.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AbstractConfigurationProvider.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AccessAnalyzer/AccessAnalyzerClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AccessAnalyzer/AccessAnalyzerClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AccessAnalyzer/Exception/AccessAnalyzerException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AccessAnalyzer/Exception/AccessAnalyzerException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Account/AccountClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Account/AccountClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Account/Exception/AccountException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Account/Exception/AccountException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Acm/AcmClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Acm/AcmClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Acm/Exception/AcmException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Acm/Exception/AcmException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Amplify/AmplifyClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Amplify/AmplifyClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Amplify/Exception/AmplifyException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Amplify/Exception/AmplifyException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AmplifyBackend/AmplifyBackendClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AmplifyBackend/AmplifyBackendClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AmplifyBackend/Exception/AmplifyBackendException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AmplifyBackend/Exception/AmplifyBackendException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AmplifyUIBuilder/AmplifyUIBuilderClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AmplifyUIBuilder/AmplifyUIBuilderClient.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AmplifyUIBuilder/Exception/AmplifyUIBuilderException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/AmplifyUIBuilder/Exception/AmplifyUIBuilderException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/AbstractModel.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/AbstractModel.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ApiProvider.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ApiProvider.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/DateTimeResult.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/DateTimeResult.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/DocModel.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/DocModel.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/AbstractErrorParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/AbstractErrorParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/JsonParserTrait.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/JsonParserTrait.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/JsonRpcErrorParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/JsonRpcErrorParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/RestJsonErrorParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/RestJsonErrorParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/XmlErrorParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ErrorParser/XmlErrorParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ListShape.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/ListShape.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/MapShape.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/MapShape.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Operation.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Operation.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/AbstractParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/AbstractParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/AbstractRestParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/AbstractRestParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/Crc32ValidatingParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/Crc32ValidatingParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/DecodingEventStreamIterator.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/EventParsingIterator.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/EventParsingIterator.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/Exception/ParserException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/Exception/ParserException.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/JsonParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/JsonParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/JsonRpcParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/JsonRpcParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/MetadataParserTrait.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/MetadataParserTrait.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/NonSeekableStreamDecodingEventStreamIterator.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/NonSeekableStreamDecodingEventStreamIterator.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/PayloadParserTrait.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/PayloadParserTrait.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/QueryParser.php
vendored
Normal file → Executable file
0
vendor/aws/aws-sdk-php/src/Api/Parser/QueryParser.php
vendored
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user