Solucion a todos los problemas actuales y envio de correo a travez de Dolibarr
Con este código se solucionan varios problemas, el original, el de la pantalla en blanco que está generando la versión final del módulo que tienes publicado y el envío del correo, este correo se envía desde el motor de Dolibarr, así que eso debe estar bien configurado en Dolibarr. Todo esta con comentarios, quedo atento a cualquier sugerencia. invoice_electronic.php
<?php
// Load Dolibarr environment
$res = 0;
// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
if (! $res && ! empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res=@include($_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php");
// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
$tmp=empty($_SERVER['SCRIPT_FILENAME'])?'':$_SERVER['SCRIPT_FILENAME'];$tmp2=realpath(__FILE__); $i=strlen($tmp)-1; $j=strlen($tmp2)-1;
while($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i]==$tmp2[$j]) { $i--; $j--; }
if (! $res && $i > 0 && file_exists(substr($tmp, 0, ($i+1))."/main.inc.php")) $res=@include(substr($tmp, 0, ($i+1))."/main.inc.php");
if (! $res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i+1)))."/main.inc.php")) $res=@include(dirname(substr($tmp, 0, ($i+1)))."/main.inc.php");
// Try main.inc.php using relative path
if (! $res && file_exists("../main.inc.php")) $res=@include("../main.inc.php");
if (! $res && file_exists("../../main.inc.php")) $res=@include("../../main.inc.php");
if (! $res && file_exists("../../../main.inc.php")) $res=@include("../../../main.inc.php");
if (! $res) die("Include of main fails");
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/invoice.lib.php';
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
require_once '../class/apilog.class.php';
global $user;
$langs->load('electronicinvoice@electronicinvoice');
// Obtener el ID de la factura
$invoice_id = GETPOST('id', 'int');
if ($invoice_id <= 0) {
print "Invalid invoice ID";
exit;
}
$invoice = new Facture($db);
if ($invoice->fetch($invoice_id) <= 0) {
print "Failed to load invoice";
exit;
}
$client = new Societe($db);
$client->fetch($invoice->socid);
$customer = [
"type_document_identification_id" => (integer) $client->array_options['options_ei_type_document_identification_id'],
"identification_number" => (integer) $client->idprof1,
"type_organization_id" => (integer) $client->array_options['options_ei_type_organization_id'],
"name" => $client->name,
"phone" => $client->phone,
"address" => $client->address,
"email" => $client->email,
"municipality_id" => (integer) $client->zip,
"merchant_registration" => (string) $client->array_options['options_ei_merchant_registration'],
"type_liability_id" => (integer) $client->array_options['options_ei_type_liability_id'],
"dv" => (integer) $client->array_options['options_ei_verification_digit'],
"type_regime_id" => (integer) $client->array_options['options_ei_type_regime_id'],
];
$date = dol_print_date($invoice->date, '%Y-%m-%d');
$time = dol_print_date($invoice->date_creation, '%H:%M:%S');
$prefix = dolibarr_get_const($db, 'ELECTRONICINVOICE_INVOICE_PREFIX', $conf->entity);
$resolution = dolibarr_get_const($db, 'ELECTRONICINVOICE_INVOICE_RESOLUTION_NUMBER', $conf->entity);
// Configuración de un payment_form fijo para evitar el error
$payment_form = [
"payment_form_id" => 1,
"payment_method_id" => 10, // Código 10 = efectivo
"payment_due_date" => dol_print_date($invoice->date_lim_reglement ?: dol_now(), '%Y-%m-%d'),
"duration_measure" => 0
];
$json_generate = [
"number" => "",
"type_document_id" => 1,
"date" => $date,
"time" => $time,
"customer" => $customer,
"payment_form" => $payment_form, // Cambiado a singular en lugar de plural
"resolution_number" => $resolution,
"prefix" => $prefix,
"sendmail" => true,
"sendmailtome" => true,
"legal_monetary_totals" => [
"line_extension_amount" => (float) $invoice->total_ht,
"tax_exclusive_amount" => (float) $invoice->total_ht,
"tax_inclusive_amount" => (float) $invoice->total_ttc,
"payable_amount" => (float) $invoice->total_ttc
],
"invoice_lines" => [],
"tax_totals" => [
[
"tax_id" => 1,
"tax_amount" => (float) $invoice->total_tva,
"taxable_amount" => (float) $invoice->total_ht,
"percent" => $invoice->total_ht > 0 ? ($invoice->total_tva / $invoice->total_ht) * 100 : 0
]
],
"notes" => $invoice->note
];
foreach ($invoice->lines as $line) {
$json_generate['invoice_lines'][] = [
"free_of_charge_indicator" => false,
"price_amount" => (float) $line->subprice,
"base_quantity" => $line->qty,
"code" => $line->ref,
"description" => $line->desc,
"unit_measure_id" => 70,
"invoiced_quantity" => $line->qty,
"line_extension_amount" => (float) $line->subprice * $line->qty,
"type_item_identification_id" => 4,
"tax_totals" => [
[
"tax_id" => 1,
"tax_amount" => (float) $line->total_tva,
"taxable_amount" => (float) $line->total_ht,
"percent" => (float) $line->tva_tx
]
],
];
}
/**
* Función para realizar peticiones HTTP POST
*
* @param string $url URL de destino
* @param string $data Datos a enviar en formato JSON
* @param array $headers Cabeceras HTTP
* @return object Respuesta decodificada
*/
function http_post($url, $data, $headers = array()) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response);
}
/**
* Función para descargar archivos desde URL
*
* @param string $url URL del archivo a descargar
* @param string $savePath Ruta donde guardar el archivo
* @return boolean True si se descargó correctamente, False en caso contrario
*/
function downloadFile($url, $savePath) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$data = curl_exec($ch);
$error = curl_error($ch);
curl_close($ch);
if (empty($data) || !empty($error)) {
return false;
}
$file = fopen($savePath, "w+");
if ($file == false) {
return false;
}
fwrite($file, $data);
fclose($file);
return true;
}
/**
* Función para enviar correo con archivos adjuntos
*
* @param string $to Destinatario
* @param string $subject Asunto
* @param string $message Mensaje
* @param array $attachments Array de rutas de archivos adjuntos
* @return boolean True si se envió correctamente, False en caso contrario
*/
function sendEmailWithAttachments($to, $subject, $message, $attachments = array()) {
global $conf, $langs, $user;
$from = $conf->global->MAIN_MAIL_EMAIL_FROM;
// Verificar si el destinatario es válido
if (empty($to) || !filter_var($to, FILTER_VALIDATE_EMAIL)) {
return false;
}
$filename_list = array();
$mimetype_list = array();
$mimefilename_list = array();
// Preparar archivos adjuntos
foreach ($attachments as $attachment) {
if (file_exists($attachment['path'])) {
$filename_list[] = $attachment['path'];
$mimetype_list[] = $attachment['mime'] ?: 'application/octet-stream';
$mimefilename_list[] = $attachment['name'] ?: basename($attachment['path']);
}
}
// Crear y enviar el email
$mailfile = new CMailFile(
$subject,
$to,
$from,
$message,
$filename_list,
$mimetype_list,
$mimefilename_list,
'', '', 0, 1
);
if ($mailfile->error) {
return false;
}
$result = $mailfile->sendfile();
return $result;
}
if (isset($_POST['action']) && $_POST['action'] == 'consume_api') {
$url = dolibarr_get_const($db, 'ELECTRONICINVOICE_API_URL', $conf->entity);
$token = dolibarr_get_const($db, 'ELECTRONICINVOICE_API_TOKEN', $conf->entity);
// Primera Solicitud: Obtener el número consecutivo
$urlNextConsecutive = $url.'/api/ubl2.1/next-consecutive';
$dataNextConsecutive = [
'type_document_id' => 1,
'prefix' => $prefix
];
$datajsonNC = json_encode($dataNextConsecutive);
$headers = array(
'Content-Type: application/json',
'Accept: application/json',
'Authorization: Bearer ' . $token
);
$responseNextConsecutive = http_post($urlNextConsecutive, $datajsonNC, $headers);
if (isset($responseNextConsecutive->success) && $responseNextConsecutive->success) {
$json_generate['number'] = $responseNextConsecutive->number;
// Segunda Solicitud: Enviar la factura
$urlInvoice = $url.'/api/ubl2.1/invoice';
$dataInvoice = json_encode($json_generate);
$responseInvoice = http_post($urlInvoice, $dataInvoice, $headers);
$url_pdf = property_exists($responseInvoice, 'urlinvoicepdf') ? $responseInvoice->urlinvoicepdf : '-';
$url_xml = property_exists($responseInvoice, 'urlinvoicexml') ? $responseInvoice->urlinvoicexml : '-';
$status = ($responseInvoice->ResponseDian->Envelope->Body->SendBillSyncResponse->SendBillSyncResult->IsValid ?? 'false') === "true" ? 1 : 0;
$company_id_number = $conf->global->MAIN_INFO_SIREN;
$newLog = new ApiLog($db);
$newLog->invoice_id = $invoice_id;
$newLog->json_request = json_encode($json_generate);
$newLog->json_response = json_encode($responseInvoice);
$newLog->status_response = $status;
$newLog->url_pdf = $url.'/api/invoice/'.$company_id_number.'/'.$url_pdf;
$newLog->date_sent = dol_now();
$result = $newLog->create($user);
if ($result < 0) {
echo "Error al crear el registro: " . $result. 'error:' .$newLog->error;
}
// Si la respuesta es positiva, enviar correo con PDF y XML
if ($status == 1) {
// Crear directorio temporal para descargar archivos
$tempDir = DOL_DATA_ROOT . '/temp/electronicinvoice/' . $invoice_id;
if (!file_exists($tempDir)) {
dol_mkdir($tempDir);
}
// Rutas para guardar los archivos
$pdfPath = $tempDir . '/factura_' . $invoice->ref . '.pdf';
$xmlPath = $tempDir . '/factura_' . $invoice->ref . '.xml';
// Descargar PDF y XML
$pdfUrl = $url.'/api/invoice/'.$company_id_number.'/'.$url_pdf;
$xmlUrl = $url.'/api/invoice/'.$company_id_number.'/'.$url_xml;
$pdfDownloaded = downloadFile($pdfUrl, $pdfPath);
$xmlDownloaded = downloadFile($xmlUrl, $xmlPath);
// Si se descargaron correctamente, enviar correo
if ($pdfDownloaded && $xmlDownloaded && !empty($client->email)) {
$subject = $langs->trans('ElectronicInvoiceMailSubject', $invoice->ref);
$message = $langs->trans('ElectronicInvoiceMailBody', $client->name, $invoice->ref);
$attachments = array(
array('path' => $pdfPath, 'name' => 'factura_' . $invoice->ref . '.pdf', 'mime' => 'application/pdf'),
array('path' => $xmlPath, 'name' => 'factura_' . $invoice->ref . '.xml', 'mime' => 'application/xml')
);
$mailSent = sendEmailWithAttachments($client->email, $subject, $message, $attachments);
if ($mailSent) {
setEventMessages($langs->trans('ElectronicInvoiceMailSent', $client->email), null, 'mesgs');
} else {
setEventMessages($langs->trans('ElectronicInvoiceMailError'), null, 'errors');
}
// Limpieza: eliminar archivos temporales
unlink($pdfPath);
unlink($xmlPath);
rmdir($tempDir);
} else {
if (!$pdfDownloaded || !$xmlDownloaded) {
setEventMessages($langs->trans('ElectronicInvoiceFileDownloadError'), null, 'errors');
}
if (empty($client->email)) {
setEventMessages($langs->trans('ElectronicInvoiceNoClientEmail'), null, 'warnings');
}
}
}
} else {
print "Error al obtener el número consecutivo para la factura electrónica: " . json_encode($responseNextConsecutive);
}
}
llxHeader();
$head = facture_prepare_head($invoice);
$titre = $langs->trans("Invoice");
$picto = 'bill';
dol_fiche_head($head, 'electronicinvoice', $titre, 0, $picto);
$token_form = newToken();
$actionUrl = htmlspecialchars($_SERVER['PHP_SELF']) . '?id=' . urlencode($invoice_id);
print '<form method="post" action="' . $actionUrl . '">';
print '<input type="hidden" name="token" value="'.$token_form.'">';
print '<input type="hidden" name="action" value="consume_api">';
print '<input type="hidden" name="id" value="'.$invoice_id.'">';
print '<input type="submit" class="button" value="Enviar a API">';
print '</form>';
// Obtener los logs relacionados a la factura
$queryLogs = new ApiLog($db);
$logs = $queryLogs->fetchAll('DESC', 'rowid', 0, 0, '(invoice_id:=:'. $invoice_id .')');
if (is_array($logs)) {
print '<div class="underbanner clearboth"></div>';
print '<div class="div-table-responsive">';
print '<table class="tagtable nobottomiftotal liste">';
print '<tr class="liste_titre" style="background: var(--colorbacktitle1) !important;">';
print '
<th class="liste_titre">Fecha de Envío</th>
<th class="liste_titre">Estado DIAN</th>
<th class="wrapcolumntitle liste_titre">PDF</th>
<td class="wrapcolumntitle liste_titre" width="40%">Solicitud JSON</td>
<th class="wrapcolumntitle liste_titre" width="40%">Respuesta JSON</th>
';
print '</tr>';
foreach ($logs as $log) {
print '<tr class="oddeven">';
print '<td style="vertical-align: top">' . dol_print_date($log->date_sent, 'dayhour') . '</td>';
print '<td class="nowrap center" style="vertical-align: top"><span class="badge badge-status'.($log->status_response ? '4' : '8').' badge-status" title="'.($log->status_response ? 'Si' : 'No').'">'.($log->status_response ? 'Si' : 'No').'</span></td>';
print $log->url_pdf != '-' ? '<td style="vertical-align: top"><a href="' . htmlspecialchars($log->url_pdf) . '" target="_blank"><span class="fas fa-download valignmiddle"></span></a></td>' : '';
print '<td><textarea readonly style="width: 100%; min-height: 80px;">' . htmlspecialchars(json_encode(json_decode($log->json_request, true), JSON_PRETTY_PRINT)) . '</textarea></td>';
print '<td><textarea readonly style="width: 100%; min-height: 80px;">' . htmlspecialchars(json_encode(json_decode($log->json_response, true), JSON_PRETTY_PRINT)) . '</textarea></td>';
print '</tr>';
}
print '</table>';
print '</div>';
}
dol_fiche_end();
llxFooter();
$db->close();