Skip to content

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();
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information