Hot fix para poder enviar notoficaciones a mas de un contacto de tipo whatsapp en un mismo cliente, verificacion para la sincroniazacion de clientes hacia callbell cuando se agrega una factura o se edita ademas s ajustó para que solo se pueda editar el email en stripe cuando se haga una edición en el CRM

This commit is contained in:
server 2025-02-18 10:42:18 +00:00
parent e1e3f409c9
commit c3824ba827
17 changed files with 5004 additions and 362 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

File diff suppressed because one or more lines are too long

View 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",
"description": "Este plugin sincroniza los clientes del sitema 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/",
"version": "2.6.4",
"version": "2.6.5",
"unmsVersionCompliancy": {
"min": "2.1.0",
"max": null

View File

@ -69,28 +69,56 @@ abstract class AbstractMessageNotifierFacade
//$this->logger->debug(print_r(json_encode($notificationData),true).PHP_EOL);
// Obtener los números de teléfono de los contactos
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
// Imprimir el array de teléfonos
$this->logger->info(json_encode($arrayPhones));
// Procesar el array de teléfonos y ejecutar la función correspondiente
foreach ($arrayPhones as $type => $phones) {
foreach ($arrayPhones as $type => $phone) {
// Registrar el tipo de contacto antes de la normalización para verificar qué valor se recibe
$this->logger->info("Procesando tipo de contacto original: '$type'");
switch ($type) {
case 'WhatsApp':
$this->notifyAndUpdate($notificationData, $phone); // Ejecuta función de Notificar y Actualizar
break;
case 'WhatsNotifica':
$this->notify($notificationData, $phone); // Ejecuta función de Notificar
break;
case 'WhatsActualiza':
$this->onlyUpdate($notificationData, $phone); // Ejecuta función de Actualizar
break;
default:
$this->logger->info("Tipo de contacto no reconocido: $type" . PHP_EOL);
break;
// Normalizar el tipo para manejar posibles diferencias en las mayúsculas o espacios en blanco
$type = trim(strtolower($type));
// Registrar el tipo de contacto después de la normalización
$this->logger->info("Tipo de contacto normalizado: '$type'");
// Asegurarse de que $phones es un array y recorrerlo
if (is_array($phones)) {
foreach ($phones as $phone) {
switch ($type) {
case 'whatsapp':
// Ejecutar función de notificación y actualización
$this->notifyAndUpdate($notificationData, $phone);
break;
case 'whatsnotifica':
// Ejecutar función de notificación
$this->notify($notificationData, $phone);
break;
case 'whatsactualiza':
// Ejecutar función de actualización
$this->onlyUpdate($notificationData, $phone, false);
break;
default:
// Registrar cuando el tipo no es reconocido
$this->logger->info("Tipo de contacto no reconocido: '$type'" . PHP_EOL);
break;
}
}
} else {
$this->logger->info("Formato no esperado para los números de teléfono en el tipo: $type" . PHP_EOL);
}
}
}
/*
@ -103,20 +131,42 @@ abstract class AbstractMessageNotifierFacade
$this->logger->debug(print_r(json_encode($notificationData), true) . PHP_EOL);
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
// Procesar el array de teléfonos y ejecutar la función correspondiente
foreach ($arrayPhones as $type => $phones) {
foreach ($arrayPhones as $type => $phone) {
// Registrar el tipo de contacto antes de la normalización para verificar qué valor se recibe
$this->logger->info("Procesando tipo de contacto original: '$type'");
switch ($type) {
case 'WhatsApp':
$this->onlyUpdate($notificationData, $phone); // Ejecuta función de Notificar y Actualizar
break;
case 'WhatsActualiza':
$this->onlyUpdate($notificationData, $phone); // Ejecuta función de Actualizar
break;
default:
$this->logger->info("Tipo de contacto no reconocido: $type" . PHP_EOL);
break;
// Normalizar el tipo para manejar posibles diferencias en las mayúsculas o espacios en blanco
$type = trim(strtolower($type));
// Registrar el tipo de contacto después de la normalización
$this->logger->info("Tipo de contacto normalizado: '$type'");
// Asegurarse de que $phones es un array y recorrerlo
if (is_array($phones)) {
foreach ($phones as $phone) {
switch ($type) {
case 'whatsapp':
// Ejecutar función de notificación y actualización
$this->onlyUpdate($notificationData, $phone);
break;
case 'whatsactualiza':
// Ejecutar función de actualización
$this->onlyUpdate($notificationData, $phone);
break;
default:
// Registrar cuando el tipo no es reconocido
$this->logger->info("Tipo de contacto no reconocido: '$type'" . PHP_EOL);
break;
}
}
} else {
$this->logger->info("Formato no esperado para los números de teléfono en el tipo: $type" . PHP_EOL);
}
}
@ -131,20 +181,42 @@ abstract class AbstractMessageNotifierFacade
//$this->logger->debug(print_r(json_encode($notificationData),true).PHP_EOL);
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
// Procesar el array de teléfonos y ejecutar la función correspondiente
foreach ($arrayPhones as $type => $phones) {
foreach ($arrayPhones as $type => $phone) {
// Registrar el tipo de contacto antes de la normalización para verificar qué valor se recibe
$this->logger->info("Procesando tipo de contacto original: '$type'");
switch ($type) {
case 'WhatsApp':
$this->onlyUpdateService($notificationData, $phone); // Ejecuta función de Notificar y Actualizar
break;
case 'WhatsActualiza':
$this->onlyUpdateService($notificationData, $phone); // Ejecuta función de Actualizar
break;
default:
$this->logger->info("Tipo de contacto no reconocido: $type" . PHP_EOL);
break;
// Normalizar el tipo para manejar posibles diferencias en las mayúsculas o espacios en blanco
$type = trim(strtolower($type));
// Registrar el tipo de contacto después de la normalización
$this->logger->info("Tipo de contacto normalizado: '$type'");
// Asegurarse de que $phones es un array y recorrerlo
if (is_array($phones)) {
foreach ($phones as $phone) {
switch ($type) {
case 'whatsapp':
// Ejecutar función de notificación y actualización
$this->onlyUpdateService($notificationData, $phone);
break;
case 'whatsactualiza':
// Ejecutar función de actualización
$this->onlyUpdateService($notificationData, $phone);
break;
default:
// Registrar cuando el tipo no es reconocido
$this->logger->info("Tipo de contacto no reconocido: '$type'" . PHP_EOL);
break;
}
}
} else {
$this->logger->info("Formato no esperado para los números de teléfono en el tipo: $type" . PHP_EOL);
}
}
@ -336,37 +408,48 @@ abstract class AbstractMessageNotifierFacade
// Procesar el array de teléfonos y ejecutar la función correspondiente
foreach ($arrayPhones as $type => $phone) {
foreach ($arrayPhones as $type => $phones) {
// Normalizar el tipo para manejar posibles diferencias en las mayúsculas o espacios en blanco
$type = trim(strtolower($type));
switch ($type) {
case 'WhatsApp':
case 'WhatsNotifica':
case 'whatsapp':
case 'whatsnotifica':
$this->logger->debug("Se encontró un tipo de contacto $type" . PHP_EOL);
$attempts = 0;
$maxAttempts = 3;
$result = false;
// Verificar si el valor es un array de teléfonos
if (is_array($phones)) {
foreach ($phones as $phone) {
$attempts = 0;
$maxAttempts = 3;
$result = false;
// Reintentar hasta 3 veces si la función retorna false
while ($attempts < $maxAttempts && $result === false) {
$attempts++;
$result = $client_callbell_api->sendJobNotificationWhatsAppToClient(
$this->validarNumeroTelefono($phone),
$jsonClientJobNotificationData,
$reprogramming,
$changeInstaller
);
// Reintentar hasta 3 veces si la función retorna false
while ($attempts < $maxAttempts && $result === false) {
$attempts++;
$result = $client_callbell_api->sendJobNotificationWhatsAppToClient(
$this->validarNumeroTelefono($phone), // Validar número de teléfono
$jsonClientJobNotificationData, // Datos de notificación
$reprogramming, // Reprogramación
$changeInstaller // Cambio de instalador
);
if ($result === false) {
sleep(1);
$this->logger->warning("Intento $attempts fallido para enviar notificación a $phone. Reintentando..." . PHP_EOL);
if ($result === false) {
sleep(1); // Esperar 1 segundo antes de reintentar
$this->logger->warning("Intento $attempts fallido para enviar notificación a $phone. Reintentando..." . PHP_EOL);
}
}
// Verificar el resultado final
if ($result === true) {
$this->logger->info("Notificación enviada correctamente al contacto $phone después de $attempts intento(s)." . PHP_EOL);
} else {
$this->logger->error("No se pudo enviar la notificación al contacto $phone después de $maxAttempts intentos." . PHP_EOL);
}
}
}
// Verificar el resultado final
if ($result === true) {
$this->logger->info("Notificación enviada correctamente al contacto $phone después de $attempts intento(s)." . PHP_EOL);
} else {
$this->logger->error("No se pudo enviar la notificación al contacto $phone después de $maxAttempts intentos." . PHP_EOL);
$this->logger->warning("No se encontraron números de teléfono para el tipo de contacto $type." . PHP_EOL);
}
break;
@ -383,6 +466,7 @@ abstract class AbstractMessageNotifierFacade
//$installerWhatsApp = $this->validarNumeroTelefono((string)$installerWhatsApp); //Obtiene el número de celular del cliente que sea del tipo de contacto "WhatsApp"
$this->logger->debug('Valor de $installerWhatsApp en verifyJobActionToDo: ' . $installerWhatsApp . PHP_EOL);
@ -529,27 +613,49 @@ abstract class AbstractMessageNotifierFacade
public function verifyInvoiceActionToDo(NotificationData $notificationData): void
{
$accountBalance = $notificationData->clientData['accountBalance'];
$this->logger->debug(print_r(json_encode($notificationData), true) . PHP_EOL);
$arrayPhones = $this->clientPhoneNumber->getUcrmClientNumbers($notificationData, null);
// Procesar el array de teléfonos y ejecutar la función correspondiente
foreach ($arrayPhones as $type => $phones) {
foreach ($arrayPhones as $type => $phone) {
// Registrar el tipo de contacto antes de la normalización para verificar qué valor se recibe
$this->logger->info("Procesando tipo de contacto original: '$type'");
switch ($type) {
case 'WhatsApp':
$this->onlyUpdate($notificationData, $phone); // Ejecuta función de Notificar y Actualizar
break;
case 'WhatsActualiza':
$this->onlyUpdate($notificationData, $phone); // Ejecuta función de Actualizar
break;
default:
$this->logger->info("Tipo de contacto no reconocido: $type" . PHP_EOL);
break;
// Normalizar el tipo para manejar posibles diferencias en las mayúsculas o espacios en blanco
$type = trim(strtolower($type));
// Registrar el tipo de contacto después de la normalización
$this->logger->info("Tipo de contacto normalizado: '$type'");
// Asegurarse de que $phones es un array y recorrerlo
if (is_array($phones)) {
foreach ($phones as $phone) {
switch ($type) {
case 'whatsapp':
// Ejec vb bbnbbnbvnutar función de notificación y actualización
$this->onlyUpdate($notificationData, $phone, false);
break;
case 'whatsactualiza':
// Ejecutar función de actualización
$this->onlyUpdate($notificationData, $phone, false);
break;
default:
// Registrar cuando el tipo no es reconocido
$this->logger->info("Tipo de contacto no reconocido: '$type'" . PHP_EOL);
break;
}
}
} else {
$this->logger->info("Formato no esperado para los números de teléfono en el tipo: $type" . PHP_EOL);
}
}
}
/*
@ -789,7 +895,7 @@ abstract class AbstractMessageNotifierFacade
/*
* Update the client's data at CallBell
*/
public function onlyUpdate(NotificationData $notificationData, $phoneToUpdate = null): void
public function onlyUpdate(NotificationData $notificationData, $phoneToUpdate = null, $updateEmail=false): void
{
//$this->logger->info("Se enviará una actualización a Callbell " . PHP_EOL);
@ -825,29 +931,32 @@ abstract class AbstractMessageNotifierFacade
}
$contacts = $notificationData->clientData['contacts'] ?? []; //Obtener los contactos que tiene el cliente
if ($updateEmail) {
$contacts = $notificationData->clientData['contacts'] ?? []; //Obtener los contactos que tiene el cliente para buscar el email
foreach ($contacts as $contact) {
foreach ($contacts as $contact) {
$types = $contact['types'] ?? []; //Obtener los tipos de contactos del cliente
foreach ($types as $type) {
$types = $contact['types'] ?? []; //Obtener los tipos de contactos del cliente
foreach ($types as $type) {
if ($type['name'] == 'WhatsApp') { //Si el tipo de contacto es Whatsapp
if ($type['name'] == 'WhatsApp') { //Si el tipo de contacto es Whatsapp
$client_email = $contact['email']; //Asignar el correo del cliente a la variable
break;
$client_email = $contact['email']; //Asignar el correo del cliente a la variable
break;
}
}
}
$this->logger->info("Se procesaron los contactos para revisar el o los email" . PHP_EOL);
$stripe = new \Stripe\StripeClient($StripeToken); //Instancia de la clase manejadora de clientes para la API de Stripe
if ($client_email !== null && $customerStripeID !== null) {
$this->logger->info("Se actualizara el correo del cliente en Stripe: " . $client_email . PHP_EOL);
$stripe->customers->update($customerStripeID, ['email' => $client_email]); //Actualiza el correo electrónico del cliente en la plataforma de Stripe en su correspondiente "customer Stripe ID"
}
}
$this->logger->info("Se proceso los contactos " . PHP_EOL);
$stripe = new \Stripe\StripeClient($StripeToken); //Instancia de la clase manejadora de clientes para la API de Stripe
if ($client_email !== null && $customerStripeID !== null) {
$this->logger->info("Se actualizara el correo del cliente en Stripe: " . $client_email . PHP_EOL);
$stripe->customers->update($customerStripeID, ['email' => $client_email]); //Actualiza el correo electrónico del cliente en la plataforma de Stripe en su correspondiente "customer Stripe ID"
}

View File

@ -127,7 +127,7 @@ abstract class AbstractStripeOperationsFacade
/*
* Creates the Stripe Customer
*/
public function createStripeClient(NotificationData $notificationData): void
public function createStripeClient(NotificationData $notificationData, $tagStripe = false): void
{
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
@ -135,11 +135,20 @@ abstract class AbstractStripeOperationsFacade
$IPServer = $config['ipserver'];
$UCRMAPIToken = $config['apitoken'];
$StripeToken = $config['tokenstripe'];
$baseUri = 'https://' . $IPServer . '/crm/api/v1.0/'; //endpoint de la API REST del CRM
$stripe = new \Stripe\StripeClient($StripeToken); //Token de clave privada en modo prueba para la API de Stripe
//$stripe = new \Stripe\StripeClient('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'); //Token de clave privada en modo prodcucción para la API de Stripe
//$this->logger->info("Ya dentro del metodo Create Stripe Client " . PHP_EOL);
//$this->sendWhatsApp('Hola Dany');
$this->ucrmApi = UcrmApi::create();
if ($tagStripe) {
$tagsIds = $this->ucrmApi->get('client-tags', ['name' => 'STRIPE']);
$this->createCustomerStripe($notificationData, $stripe, $baseUri, $UCRMAPIToken);
}
$customAttributes = $this->ucrmApi->get('custom-attributes/', ['attributeType' => 'client']);//Obtener los atributos del sistema que estén vinculados a la entidad "cliente"
//$this->logger->info("result del custom Attributes: " . json_encode($customAttributes) . PHP_EOL);
@ -164,10 +173,6 @@ abstract class AbstractStripeOperationsFacade
$baseUri = 'https://' . $IPServer . '/crm/api/v1.0/'; //endpoint de la API REST del CRM
$stripe = new \Stripe\StripeClient($StripeToken); //Token de clave privada en modo prueba para la API de Stripe
//$stripe = new \Stripe\StripeClient('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'); //Token de clave privada en modo prodcucción para la API de Stripe
// Verificar si la solicitud fue exitosa (código de estado 200)
@ -212,7 +217,7 @@ abstract class AbstractStripeOperationsFacade
): void;
function createCustomerStripe($notificationData, $stripe, $baseUri, $token)
function createCustomerStripe($notificationData, $stripe, $baseUri, $token): bool
{
$this->logger->info("Creando el Customer Stripe" . PHP_EOL);
@ -263,9 +268,19 @@ abstract class AbstractStripeOperationsFacade
'preferred_locales' => ['es-419']
]); //aquí se contruye la petición de creación de un customer en Stripe y se consume la API
$this->logger->info(json_encode($result) . PHP_EOL); //imprimir respuesta de creación del cliente
$stripe_customer_id = $result['id']; //obtenemos el customer id de Stripe recibido por medio del consumo del API y lo asignamos a una variable $stripe_customer_id
$this->logger->info(json_encode($result) . PHP_EOL); //imprimir respuesta de creación del cliente
//ejemplo de respuesta de stripe cuando se crea el cliente: {"id":"cus_Rkh9CoRTpjlZUu","object":"customer","address":null,"balance":0,"created":1739250682,"currency":null,"default_source":null,"delinquent":false,"description":"Cliente SIIP CRM con client_id: 167","discount":null,"email":null,"invoice_prefix":"5017E9D3","invoice_settings":{"custom_fields":null,"default_payment_method":null,"footer":null,"rendering_options":null},"livemode":false,"metadata":[],"name":"Javier Alatorre","next_invoice_sequence":1,"phone":"4181878106","preferred_locales":["es-419"],"shipping":null,"tax_exempt":"none","test_clock":null}
//validar si se creo el cliente con la API de Stripe $stripe->customers->create
if (isset($result->id)){
}
$stripe_customer_id = $result['id']; //obtenemos el customer id de Stripe recibido por medio del consumo del API y lo asignamos a una variable $stripe_customer_id
sleep(2);
$result2 = $stripe->customers->update(
$stripe_customer_id,

View File

@ -221,44 +221,60 @@ class Plugin
$configManager = \Ubnt\UcrmPluginSdk\Service\PluginConfigManager::create();
$config = $configManager->loadConfig();
$installersData = $config['installersDataWhatsApp'];
$this->logger->debug('Valor de $installersData: ' . $installersData . PHP_EOL);
// Decodificar el JSON y verificar errores
$jsonInstallersData = json_decode($installersData, true);
if (json_last_error() !== JSON_ERROR_NONE) {
$this->logger->error('Error al decodificar el JSON de instaladores: ' . json_last_error_msg());
return;
}
//ejemplo de json con etoquetas o tags: {"uuid":"52d0f856-fa68-42c3-b706-d94a82adcb51","changeType":"edit","entity":"client","entityId":"144","eventName":"client.edit","extraData":{"entity":{"id":144,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"56 Avenida Norte","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Avenida Norte 56, Dolores, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2024-07-29T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"El More","lastName":"L\u00f3pez","username":null,"contacts":[{"id":148,"clientId":144,"email":"elmorelopez2014@gmail.com","phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[{"id":3,"name":"STRIPE","colorBackground":"#e30000","colorText":"#fff"},{"id":2,"name":"NS EXENTO","colorBackground":"#42a3df","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#607d8b","addressGpsLat":21.1652947,"addressGpsLon":-100.940078,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":144,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"56 Avenida Norte","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Avenida Norte 56, Dolores, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2024-07-29T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"El More","lastName":"L\u00f3pez","username":null,"contacts":[{"id":148,"clientId":144,"email":"elmorelopez2014@gmail.com","phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[{"id":3,"name":"STRIPE","colorBackground":"#e30000","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#607d8b","addressGpsLat":21.1652947,"addressGpsLon":-100.940078,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}}
$jsonTest = '{"uuid":"79009823-1415-47b2-b170-304db4010453","changeType":"edit","entity":"client","entityId":"167","eventName":"client.edit","extraData":{"entity":{"id":167,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"34 Avenida Sur","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Avenida Sur 34, Centro, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-02-10T00:00:00-0600","leadConvertedAt":"2025-02-10T23:11:22-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Javier","lastName":"Alatorre","username":null,"contacts":[{"id":173,"clientId":167,"email":null,"phone":"4181878106","name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[{"id":174,"clientId":167,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_Rkh9CoRTpjlZUu","clientZoneVisible":true},{"id":175,"clientId":167,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180228993431717","clientZoneVisible":true}],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[{"id":3,"name":"STRIPE","colorBackground":"#e30000","colorText":"#fff"}],"invitationEmailSentDate":null,"avatarColor":"#6a1b9a","addressGpsLat":21.1519382,"addressGpsLon":-100.9371879,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":167,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"34 Avenida Sur","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37800","fullAddress":"Avenida Sur 34, Centro, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-02-10T00:00:00-0600","leadConvertedAt":"2025-02-10T23:11:22-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Javier","lastName":"Alatorre","username":null,"contacts":[{"id":173,"clientId":167,"email":null,"phone":"4181878106","name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[{"id":174,"clientId":167,"customAttributeId":10,"name":"Stripe Customer ID","key":"stripeCustomerId","value":"cus_Rkh9CoRTpjlZUu","clientZoneVisible":true},{"id":175,"clientId":167,"customAttributeId":11,"name":"Clabe Interbancaria","key":"clabeInterbancaria","value":"124180228993431717","clientZoneVisible":true}],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#6a1b9a","addressGpsLat":21.1519382,"addressGpsLon":-100.9371879,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}}';
// Verificar si el nodo "instaladores" existe
if (!isset($jsonInstallersData['instaladores'])) {
$this->logger->error('El nodo "instaladores" no existe en el JSON');
return;
}
$jsonData = json_decode($jsonTest, true);
// Buscar el número de WhatsApp en el JSON
$installerWhatsApp = '';
foreach ($jsonInstallersData['instaladores'] as $installer) {
if ($installer['id'] === 1019) {
$installerWhatsApp = $installer['whatsapp'];
break;
// Validar que 'entity' y 'entityBeforeEdit' existen y contienen las clave 'tags' y buscar si existe la etiqueta 'STRIPE' en entity pero no en entityBeforeEdit
if (
isset($jsonData['extraData']['entity']['tags']) &&
isset($jsonData['extraData']['entityBeforeEdit']['tags'])
) {
$tags = $jsonData['extraData']['entity']['tags'];
$tagsBefore = $jsonData['extraData']['entityBeforeEdit']['tags'];
$this->logger->debug('Validando claves dentro de entity y entityBeforeEdit');
// Validar que 'tags' existe en ambas entidades
if (array_key_exists('tags', $jsonData['extraData']['entity']) && array_key_exists('tags', $jsonData['extraData']['entityBeforeEdit'])) {
$this->logger->debug('Los datos entity y entityBeforeEdit contienen el campo tags');
$tags = $jsonData['extraData']['entity']['tags'];
$tagsBefore = $jsonData['extraData']['entityBeforeEdit']['tags'];
$this->logger->debug('Validando si la etiqueta STRIPE existe en entity pero no en entityBeforeEdit');
// Comprobar si la etiqueta 'STRIPE' existe en 'tags' pero no en 'tagsBefore'
$stripeTagExists = false;
$stripeTagExistsBefore = false;
foreach ($tags as $tag) {
if ($tag['name'] === 'STRIPE') {
$stripeTagExists = true;
break;
}
}
foreach ($tagsBefore as $tag) {
if ($tag['name'] === 'STRIPE') {
$stripeTagExistsBefore = true;
break;
}
}
// Comprobar si la etiqueta 'STRIPE' existe en 'tags' pero no en 'tagsBefore'
if ($stripeTagExists && !$stripeTagExistsBefore) {
$this->logger->debug('La etiqueta STRIPE se agregó al cliente');
$this->pluginNotifierFacade->createStripeClient($jsonData);
} else {
$this->logger->debug('La etiqueta STRIPE no se agregó al cliente');
}
} else {
$this->logger->warning('El campo tags no existe en entity o entityBeforeEdit');
}
}
// Validar si se encontró el WhatsApp
if (empty($installerWhatsApp)) {
$this->logger->warning('No se encontró un número de WhatsApp para el instalador con ID 1019');
} else {
$this->logger->debug('Número de WhatsApp del Instalador: ' . $installerWhatsApp . PHP_EOL);
$this->logger->warning('Los datos entity o entityBeforeEdit no están presentes en extraData');
}
$this->ucrmApi = UcrmApi::create();
$usersInstallers = $this->ucrmApi->get('users/admins/1019', []);
$this->logger->debug('Valor de $usersInstallers ' . json_encode($usersInstallers) . PHP_EOL);
$firstName = $usersInstallers['firstName'] ?? '';
$lastName = $usersInstallers['lastName'] ?? '';
$installerFullName = trim("$firstName $lastName");
$this->logger->debug('Valor de $installerFullName: ' . $installerFullName . PHP_EOL);
$this->logger->info('Webhook test successful.');
@ -279,7 +295,7 @@ class Plugin
if ($notification->eventName === 'payment.add') {
$result = json_encode($notification);
$this->logger->debug('datos del notification para el invoice add:' . $result . PHP_EOL);
$this->logger->debug('Notification encodificado en JSON:' . $result . PHP_EOL);
$datos_payment = $notification->paymentData;
$this->logger->debug('valor del payment data: ' . json_encode($datos_payment) . PHP_EOL);
@ -370,6 +386,8 @@ class Plugin
} else if ($notification->eventName === 'client.edit') {
$this->logger->debug('Se actualiza a un cliente');
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
//ejemplo de json_data: {"uuid":"17e043a7-03b5-4312-ab81-a7818124a77e","changeType":"edit","entity":"client","entityId":"158","eventName":"client.edit","extraData":{"entity":{"id":158,"userIdent":null,"previousIsp":null,"isLead":false,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"23 San Luis","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37804","fullAddress":"San Luis 23, Guadalupe, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-01-06T00:00:00-0600","leadConvertedAt":"2025-02-09T03:15:49-0600","companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Luis","lastName":"Guti\u00e9rrez","username":null,"contacts":[{"id":162,"clientId":158,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#2196f3","addressGpsLat":21.153272,"addressGpsLon":-100.9134508,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false},"entityBeforeEdit":{"id":158,"userIdent":null,"previousIsp":null,"isLead":true,"clientType":1,"companyName":null,"companyRegistrationNumber":null,"companyTaxId":null,"companyWebsite":null,"street1":"23 San Luis","street2":null,"city":"Dolores Hidalgo Cuna de la Independencia Nacional","countryId":173,"stateId":null,"zipCode":"37804","fullAddress":"San Luis 23, Guadalupe, Dolores Hidalgo Cuna de la Independencia Nacional, Gto., M\u00e9xico","invoiceStreet1":null,"invoiceStreet2":null,"invoiceCity":null,"invoiceStateId":null,"invoiceCountryId":null,"invoiceZipCode":null,"invoiceAddressSameAsContact":true,"note":null,"sendInvoiceByPost":null,"invoiceMaturityDays":null,"stopServiceDue":null,"stopServiceDueDays":null,"organizationId":1,"tax1Id":null,"tax2Id":null,"tax3Id":null,"registrationDate":"2025-01-06T00:00:00-0600","leadConvertedAt":null,"companyContactFirstName":null,"companyContactLastName":null,"isActive":false,"firstName":"Luis","lastName":"Guti\u00e9rrez","username":null,"contacts":[{"id":162,"clientId":158,"email":null,"phone":null,"name":null,"isBilling":true,"isContact":true,"types":[{"id":1,"name":"Billing"},{"id":2,"name":"General"}]}],"attributes":[],"accountBalance":0,"accountCredit":0,"accountOutstanding":0,"currencyCode":"MXN","organizationName":"SIIP Pruebas","bankAccounts":[],"tags":[],"invitationEmailSentDate":null,"avatarColor":"#2196f3","addressGpsLat":21.153272,"addressGpsLon":-100.9134508,"isArchived":false,"generateProformaInvoices":null,"usesProforma":false,"hasOverdueInvoice":false,"hasOutage":false,"hasSuspendedService":false,"hasServiceWithoutDevices":false,"referral":null,"hasPaymentSubscription":false,"hasAutopayCreditCard":false}}}
// Validar que 'extraData' existe y contiene las claves necesarias
if (
@ -403,28 +421,77 @@ class Plugin
} else {
$this->logger->warning('El campo isLead no existe en entityBeforeEdit o entity');
}
// buscar si existe la etiqueta 'STRIPE' en entity pero no en entityBeforeEdit
$tags = $jsonData['extraData']['entity']['tags'];
$tagsBefore = $jsonData['extraData']['entityBeforeEdit']['tags'];
$this->logger->debug('Validando claves dentro de entity y entityBeforeEdit');
// Validar que 'tags' existe en ambas entidades
if (array_key_exists('tags', $jsonData['extraData']['entity']) && array_key_exists('tags', $jsonData['extraData']['entityBeforeEdit'])) {
$this->logger->debug('Los datos entity y entityBeforeEdit contienen el campo tags');
$tags = $jsonData['extraData']['entity']['tags'];
$tagsBefore = $jsonData['extraData']['entityBeforeEdit']['tags'];
$this->logger->debug('Validando si la etiqueta STRIPE existe en entity pero no en entityBeforeEdit');
// Comprobar si la etiqueta 'STRIPE' existe en 'tags' pero no en 'tagsBefore'
$stripeTagExists = false;
$stripeTagExistsBefore = false;
foreach ($tags as $tag) {
if ($tag['name'] === 'STRIPE') {
$stripeTagExists = true;
break;
}
}
foreach ($tagsBefore as $tag) {
if ($tag['name'] === 'STRIPE') {
$stripeTagExistsBefore = true;
break;
}
}
// Comprobar si la etiqueta 'STRIPE' existe en 'tags' pero no en 'tagsBefore'
if ($stripeTagExists && !$stripeTagExistsBefore) {
$this->logger->debug('La etiqueta STRIPE se agregó al cliente');
$this->pluginNotifierFacade->createStripeClient($notification, true);
} else {
$this->logger->debug('La etiqueta STRIPE no se agregó al cliente');
}
} else {
$this->logger->warning('El campo tags no existe en entity o entityBeforeEdit');
}
} else {
$this->logger->warning('Los datos entityBeforeEdit o entity no están presentes en extraData');
}
$this->notifierFacade->verifyClientActionToDo($notification);
} else if ($notification->eventName === 'client.add') {
$this->logger->debug('Se agregó un nuevo cliente' . PHP_EOL);
// Verificar que existen tanto 'entityBeforeEdit' como 'entity'
if (isset($event_json['extraData']['entity']['isLead'])) {
$this->logger->debug('El campo isLead existe en los datos del evento');
$isLead = $jsonData['extraData']['entity']['isLead'];
$this->logger->debug('Valor de json_data: ' . json_encode($jsonData));
// Comprobar si 'isLead' es true
if ($isLead === true) {
$this->logger->debug('El campo isLead es true');
$this->pluginNotifierFacade->createStripeClient($notification);
} else {
$this->logger->debug('El campo isLead es false');
}
} else {
$this->logger->warning('El campo isLead no existe en los datos del evento');
}
// Verificar que existen tanto 'entityBeforeEdit' como 'entity'
// if (isset($jsonData['extraData']['entity']['isLead'])) {
// $this->logger->debug('El campo isLead existe en los datos del evento');
// $isLead = $jsonData['extraData']['entity']['isLead'];
// // Comprobar si 'isLead' es true
// if ($isLead === true) {
// $this->logger->debug('El cliente es potencial');
// $this->pluginNotifierFacade->createStripeClient($notification);
// } else {
// $this->logger->debug('El cliente no es potencial');
// $this->pluginNotifierFacade->createStripeClient($notification);
// }
// } else {
// $this->logger->warning('El campo isLead no existe en los datos del evento');
// }
} else if ($notification->eventName === 'service.suspend') {
$this->logger->debug('Se suspendió el servicio a un cliente' . PHP_EOL);
@ -449,9 +516,9 @@ class Plugin
$this->logger->debug('datos del notification para el invoice add:' . $result . PHP_EOL);
$accountBalance = $notification->clientData['accountBalance'];
$invoiceAmountPaid = $notification->invoiceData['amountPaid'];
//$invoiceAmountPaid = $notification->invoiceData['amountPaid'];
$this->logger->debug("Account Balance: " . $accountBalance . PHP_EOL);
$this->logger->debug("Pago hecho con la factura: " . $invoiceAmountPaid . PHP_EOL);
// $this->logger->debug("Pago hecho con la factura: " . $invoiceAmountPaid . PHP_EOL);
$this->notifierFacade->verifyInvoiceActionToDo($notification);
} else if ($notification->eventName === 'invoice.edit') {
$this->logger->debug('Edición de Factura' . PHP_EOL);
@ -461,7 +528,7 @@ class Plugin
// Verificar que existen tanto 'assignedUserId' como 'entity'
if (isset($event_json['extraData']['entity']['assignedUserId'])) {
if (isset($jsonData['extraData']['entity']['assignedUserId'])) {
$this->logger->debug('El campo assignedUserId existe en los datos del evento');
$assignedUserId = $jsonData['extraData']['entity']['assignedUserId'];
@ -506,19 +573,19 @@ class Plugin
if ($assignedUserIdBefore === null && $assignedUserIdAfter != null) { //Si el campo "assignedUserId" cambió de null a un valor
$this->logger->debug('El instalador cambió de null a un valor');
$this->notifierFacade->verifyJobActionToDo($jsonData); // Se envía notificación de trabajo asignado
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore)&&($dateBefore === $dateAfter)) {//Si el campo "assignedUserId" cambió de un valor a otro y la fecha no cambió
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore) && ($dateBefore === $dateAfter)) {//Si el campo "assignedUserId" cambió de un valor a otro y la fecha no cambió
$this->logger->debug('El instalador cambió y la fecha no cambió');
$this->notifierFacade->verifyJobActionToDo($jsonData, false , true); // Se envía notificación de trabajo reasignado
}else if (($assignedUserIdBefore != null && $assignedUserIdBefore === $assignedUserIdAfter) && ($dateBefore != $dateAfter)) {//Si el campo "assignedUserId" no cambió y la fecha cambió
$this->notifierFacade->verifyJobActionToDo($jsonData, false, true); // Se envía notificación de trabajo reasignado
} else if (($assignedUserIdBefore != null && $assignedUserIdBefore === $assignedUserIdAfter) && ($dateBefore != $dateAfter)) {//Si el campo "assignedUserId" no cambió y la fecha cambió
$this->logger->debug('El instalador no cambió y la fecha sí cambió');
$this->notifierFacade->verifyJobActionToDo($jsonData, true , false); // Se envía notificación de reprogramación de trabajo
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore)&&($dateBefore != $dateAfter)){
$this->notifierFacade->verifyJobActionToDo($jsonData, true, false); // Se envía notificación de reprogramación de trabajo
} else if (($assignedUserIdBefore != null && $assignedUserIdAfter != $assignedUserIdBefore) && ($dateBefore != $dateAfter)) {
$this->logger->debug('El instalador cambió y la fecha sí cambió');
$this->notifierFacade->verifyJobActionToDo($jsonData, true , true); // Se envía notificación de trabajo reasignado
$this->notifierFacade->verifyJobActionToDo($jsonData, true, true); // Se envía notificación de trabajo reasignado
} else if ($assignedUserIdBefore != null && $assignedUserIdAfter === null) { //Si el campo "assignedUserId" cambió de un valor a null
$this->logger->debug('El instalador cambió de un valor a null');
$this->notifierFacade->verifyJobActionToDo($jsonData, null,true); // Se envía notificación de trabajo desasignado
}else {
$this->notifierFacade->verifyJobActionToDo($jsonData, null, true); // Se envía notificación de trabajo desasignado
} else {
$this->logger->debug('No hubo cambio en el instalador ni en la fecha');
}

View File

@ -34,48 +34,43 @@ class SmsNumberProvider
* go through client's contacts and find an applicable one, if any
*/
public function getUcrmClientNumbers(NotificationData $notificationData = null, $arrayClientCRM = null)
{
$log = PluginLogManager::create(); //Initialize Logger
//$log->appendLog("Ejecutando metodo getUcrmClientNumbers: " . json_encode($arrayClientCRM) . PHP_EOL);
// Array asociativo donde almacenarás los números de teléfono con sus tipos correspondientes
$arrayPhones = [];
{
$log = PluginLogManager::create(); //Initialize Logger
$arrayPhones = [];
if ($arrayClientCRM != null) {
//$log->appendLog("Entrando al if del método getUcrmClientNumbers: " . $arrayClientCRM . PHP_EOL);
//$jsonNotificationData = json_decode($jsonNotificationData, true);
// Recorrer los contactos del cliente para encontrar los teléfonos con sus tipos
foreach ($arrayClientCRM['contacts'] as $contact) {
if (!empty($contact['phone'])) { // Verificar que el teléfono no esté vacío
if (isset($contact['types']) && is_array($contact['types'])) { // Verificar que 'types' exista y sea un array
foreach ($contact['types'] as $type) {
if (in_array($type['name'], ['WhatsApp', 'WhatsNotifica'])) {
// Si el tipo de contacto es uno de los deseados, agregamos al array asociativo
$arrayPhones[$type['name']] = $contact['phone'];
}
}
}
}
}
} else {
// Recorrer los contactos del cliente para encontrar los teléfonos con sus tipos
foreach ($notificationData->clientData['contacts'] as $contact) {
if (!empty($contact['phone'])) { // Verificar que el teléfono no esté vacío
if (isset($contact['types']) && is_array($contact['types'])) { // Verificar que 'types' exista y sea un array
foreach ($contact['types'] as $type) {
if (in_array($type['name'], ['WhatsApp', 'WhatsNotifica', 'WhatsActualiza'])) {
// Si el tipo de contacto es uno de los deseados, agregamos al array asociativo
$arrayPhones[$type['name']] = $contact['phone'];
}
if ($arrayClientCRM != null) {
foreach ($arrayClientCRM['contacts'] as $contact) {
if (!empty($contact['phone'])) {
if (isset($contact['types']) && is_array($contact['types'])) {
foreach ($contact['types'] as $type) {
if (in_array($type['name'], ['WhatsApp', 'WhatsNotifica'])) {
// Almacena varios números bajo el mismo tipo
$arrayPhones[$type['name']][] = $contact['phone'];
}
}
}
}
}
return $arrayPhones; // Devolver el arreglo de teléfonos por tipo
} else {
foreach ($notificationData->clientData['contacts'] as $contact) {
if (!empty($contact['phone'])) {
if (isset($contact['types']) && is_array($contact['types'])) {
foreach ($contact['types'] as $type) {
if (in_array($type['name'], ['WhatsApp', 'WhatsNotifica', 'WhatsActualiza'])) {
// Almacena varios números bajo el mismo tipo
$arrayPhones[$type['name']][] = $contact['phone'];
}
}
}
}
}
}
return $arrayPhones; // Devolver el arreglo de teléfonos por tipo
}
/*
* go through client's contacts and find an applicable one, if any
*/

View File

@ -5,7 +5,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'eafc8677421ac4e40512369b77241db03a88a091',
'reference' => 'e1e3f409c90cfd5edbae6d9c1ad7065106555849',
'name' => 'ucrm-plugins/sms-twilio',
'dev' => false,
),
@ -307,7 +307,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => 'eafc8677421ac4e40512369b77241db03a88a091',
'reference' => 'e1e3f409c90cfd5edbae6d9c1ad7065106555849',
'dev_requirement' => false,
),
),