/** * Método que genera la previsualización del PDF del DTE * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]delaf.cl) * @version 2015-09-24 */ public function pdf($receptor, $dte, $codigo) { $Emisor = $this->getContribuyente(); // obtener datos JSON del DTE $DteTmp = new Model_DteTmp($Emisor->rut, $receptor, $dte, $codigo); if (!$DteTmp->exists()) { \sowerphp\core\Model_Datasource_Session::message('No existe el DTE temporal solicitado', 'error'); $this->redirect('/dte/dte_tmps'); } // armar xml a partir de datos del dte temporal $xml = $DteTmp->getEnvioDte()->generar(); if (!$xml) { \sowerphp\core\Model_Datasource_Session::message('No fue posible crear el PDF para previsualización:<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); $this->redirect('/dte/dte_tmps'); } // armar datos con archivo XML y flag para indicar si es cedible o no $data = ['xml' => base64_encode($xml), 'cedible' => false, 'compress' => false]; // si hay un logo para la empresa se usa $logo = \sowerphp\core\Configure::read('dte.logos.dir') . '/' . $Emisor->rut . '.png'; if (is_readable($logo)) { $data['logo'] = base64_encode(file_get_contents($logo)); } // realizar consulta a la API $rest = new \sowerphp\core\Network_Http_Rest(); $rest->setAuth($this->Auth->User ? $this->Auth->User->hash : $this->token); $response = $rest->post($this->request->url . '/api/dte/documentos/generar_pdf', $data); if ($response['status']['code'] != 200) { \sowerphp\core\Model_Datasource_Session::message($response['body'], 'error'); return; } // si dió código 200 se entrega la respuesta del servicio web foreach (['Content-Disposition', 'Content-Length', 'Content-Type'] as $header) { if (isset($response['header'][$header])) { header($header . ': ' . $response['header'][$header]); } } echo $response['body']; exit; }
*/ /** * @file 005-estado_envio_dte.php * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-09-16 */ // respuesta en texto plano header('Content-type: text/plain'); // incluir archivos php de la biblioteca y configuraciones include 'inc.php'; // solicitar token $token = \sasco\LibreDTE\Sii\Autenticacion::getToken($config['firma']); if (!$token) { foreach (\sasco\LibreDTE\Log::readAll() as $error) { echo $error, "\n"; } exit; } // consultar estado enviado $rut = ''; $dv = ''; $trackID = ''; $estado = \sasco\LibreDTE\Sii::request('QueryEstUp', 'getEstUp', [$rut, $dv, $trackID, $token]); // si el estado se pudo recuperar se muestra estado y glosa if ($estado !== false) { print_r(['codigo' => (string) $estado->xpath('/SII:RESPUESTA/SII:RESP_HDR/ESTADO')[0], 'glosa' => (string) $estado->xpath('/SII:RESPUESTA/SII:RESP_HDR/GLOSA')[0]]); } // mostrar error si hubo foreach (\sasco\LibreDTE\Log::readAll() as $error) { echo $error, "\n"; }
/** * Método que agrega o modifica un DTE recibido * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-09-28 */ private function save() { $Emisor = $this->getContribuyente(); // revisar datos minimos foreach (['emisor', 'dte', 'folio', 'fecha', 'tasa'] as $attr) { if (!isset($_POST[$attr][0])) { \sowerphp\core\Model_Datasource_Session::message('Debe indicar ' . $attr, 'error'); return; } } // crear dte recibido list($emisor, $dv) = explode('-', str_replace('.', '', $_POST['emisor'])); $DteRecibido = new Model_DteRecibido($emisor, $_POST['dte'], (int) $_POST['folio'], (int) $Emisor->certificacion); $DteRecibido->receptor = $Emisor->rut; $DteRecibido->tasa = (int) $_POST['tasa']; $DteRecibido->fecha = $_POST['fecha']; $DteRecibido->exento = !empty($_POST['exento']) ? $_POST['exento'] : null; $DteRecibido->neto = !empty($_POST['neto']) ? $_POST['neto'] : null; $DteRecibido->iva = round((int) $DteRecibido->neto * ($DteRecibido->tasa / 100)); $DteRecibido->total = (int) $DteRecibido->exento + (int) $DteRecibido->neto + $DteRecibido->iva; $DteRecibido->usuario = $this->Auth->User->id; // iva uso común, no recuperable e impuesto adicional $DteRecibido->iva_uso_comun = !empty($_POST['iva_uso_comun']) ? $_POST['iva_uso_comun'] : null; $DteRecibido->iva_no_recuperable = !empty($_POST['iva_no_recuperable']) ? $_POST['iva_no_recuperable'] : null; if (!empty($_POST['impuesto_adicional']) and !empty($_POST['impuesto_adicional_tasa'])) { $DteRecibido->impuesto_adicional = $_POST['impuesto_adicional']; $DteRecibido->impuesto_adicional_tasa = $_POST['impuesto_adicional_tasa']; } else { $DteRecibido->impuesto_adicional = null; $DteRecibido->impuesto_adicional_tasa = null; } $DteRecibido->impuesto_tipo = $_POST['impuesto_tipo']; $DteRecibido->anulado = isset($_POST['anulado']) ? 'A' : null; $DteRecibido->impuesto_sin_credito = !empty($_POST['impuesto_sin_credito']) ? $_POST['impuesto_sin_credito'] : null; $DteRecibido->monto_activo_fijo = !empty($_POST['monto_activo_fijo']) ? $_POST['monto_activo_fijo'] : null; $DteRecibido->monto_iva_activo_fijo = !empty($_POST['monto_iva_activo_fijo']) ? $_POST['monto_iva_activo_fijo'] : null; $DteRecibido->iva_no_retenido = !empty($_POST['iva_no_retenido']) ? $_POST['iva_no_retenido'] : null; // si el DTE es de producción y es electrónico entonces se consultará su // estado antes de poder guardar, esto evitará agregar documentos que no // han sido recibidos en el SII o sus datos son incorrectos if (!$Emisor->certificacion and $DteRecibido->getTipo()->electronico) { // obtener firma $Firma = $Emisor->getFirma($this->Auth->User->id); if (!$Firma) { \sowerphp\core\Model_Datasource_Session::message('No hay firma electrónica asociada a la empresa (o bien no se pudo cargar), debe agregar su firma antes de generar DTE', 'error'); $this->redirect('/dte/admin/firma_electronicas'); } // consultar estado dte $estado = $DteRecibido->getEstado($Firma); if ($estado === false) { \sowerphp\core\Model_Datasource_Session::message('No se pudo obtener el estado del DTE.<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); return; } else { if (is_string($estado)) { \sowerphp\core\Model_Datasource_Session::message('Estado DTE: ' . $estado, 'error'); return; } } } // todo ok con el dte así que se agrega a los dte recibidos try { $DteRecibido->save(); \sowerphp\core\Model_Datasource_Session::message('DTE recibido guardado', 'ok'); $this->redirect('/dte/dte_recibidos/listar'); } catch (\sowerphp\core\Exception_Model_Datasource_Database $e) { \sowerphp\core\Model_Datasource_Session::message('No fue posible guardar el DTE: ' . $e->getMessage(), 'error'); } }
// EnvioDTE::enviar() que envía el XML que se genera, ver ejemplo 010-set_pruebas.php $EnvioDTE = new \sasco\LibreDTE\Sii\EnvioDTE(); $EnvioDTE->agregar($DTE); $EnvioDTE->setCaratula($caratula); $EnvioDTE->setFirma($Firma); $xml = $EnvioDTE->generar(); // solicitar token $token = \sasco\LibreDTE\Sii\Autenticacion::getToken($Firma); if (!$token) { foreach (\sasco\LibreDTE\Log::readAll() as $log) { echo $log, "\n"; } exit; } // enviar DTE $result = \sasco\LibreDTE\Sii::enviar($caratula['RutEnvia'], $factura['Encabezado']['Emisor']['RUTEmisor'], $xml, $token); // si hubo algún error al enviar al servidor mostrar if ($result === false) { foreach (\sasco\LibreDTE\Log::readAll() as $log) { echo $log, "\n"; } exit; } // Mostrar resultado del envío if ($result->STATUS != '0') { foreach (\sasco\LibreDTE\Log::readAll() as $log) { echo $log, "\n"; } exit; } echo 'DTE enviado. Track ID ' . $result->TRACKID, "\n";
/** * Método que permite generar un libro de guías de despacho a partir de un * archivo CSV con el detalle del mismo * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-12-24 */ public function generar_libro_guia() { // si no se viene por post terminar if (!isset($_POST['submit'])) { return; } // verificar campos no estén vacíos $campos = ['RutEmisorLibro', 'PeriodoTributario', 'FchResol', 'NroResol', 'TipoLibro', 'TipoEnvio', 'FolioNotificacion', 'contrasenia']; foreach ($campos as $campo) { if (!isset($_POST[$campo][0])) { \sowerphp\core\Model_Datasource_Session::message($campo . ' no puede estar en blanco', 'error'); return; } } // si no se pasó el archivo error if (!isset($_FILES['archivo']) or $_FILES['archivo']['error']) { \sowerphp\core\Model_Datasource_Session::message('Debes enviar el archivo CSV con el detalle de las guías a la que deseas generar su XML', 'error'); return; } // si no se pasó la firma error if (!isset($_FILES['firma']) or $_FILES['firma']['error']) { \sowerphp\core\Model_Datasource_Session::message('Debes enviar el archivo con la firma digital', 'error'); return; } // Objeto de la Firma try { $Firma = new \sasco\LibreDTE\FirmaElectronica(['data' => file_get_contents($_FILES['firma']['tmp_name']), 'pass' => $_POST['contrasenia']]); } catch (\Exception $e) { \sowerphp\core\Model_Datasource_Session::message('No fue posible abrir la firma digital, quizás contraseña incorrecta', 'error'); return; } // generar caratula del libro $caratula = ['RutEmisorLibro' => str_replace('.', '', $_POST['RutEmisorLibro']), 'PeriodoTributario' => $_POST['PeriodoTributario'], 'FchResol' => $_POST['FchResol'], 'NroResol' => $_POST['NroResol'], 'TipoLibro' => $_POST['TipoLibro'], 'TipoEnvio' => $_POST['TipoEnvio'], 'FolioNotificacion' => $_POST['FolioNotificacion']]; // generar libro de guías $LibroGuia = new \sasco\LibreDTE\Sii\LibroGuia(); $LibroGuia->agregarCSV($_FILES['archivo']['tmp_name']); $LibroGuia->setFirma($Firma); $LibroGuia->setCaratula($caratula); $xml = $LibroGuia->generar(); if (!$LibroGuia->schemaValidate()) { \sowerphp\core\Model_Datasource_Session::message(implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); return; } // descargar XML $file = TMP . '/' . $LibroGuia->getID() . '.xml'; file_put_contents($file, $xml); \sasco\LibreDTE\File::compress($file, ['format' => 'zip', 'delete' => true]); exit; }
/** * Método que permite generar un libro de Compras o Ventas a partir de un * archivo CSV con el detalle del mismo * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-10-05 */ public function generar_libro() { // si no se viene por post terminar if (!isset($_POST['submit'])) { return; } // verificar campos no estén vacíos $campos = ['TipoOperacion', 'RutEmisorLibro', 'PeriodoTributario', 'FchResol', 'NroResol', 'TipoLibro', 'TipoEnvio', 'FolioNotificacion', 'contrasenia']; foreach ($campos as $campo) { if (empty($_POST[$campo])) { \sowerphp\core\Model_Datasource_Session::message($campo . ' no puede estar en blanco', 'error'); return; } } // si no se pasó el archivo error if (!isset($_FILES['archivo']) or $_FILES['archivo']['error']) { \sowerphp\core\Model_Datasource_Session::message('Debes enviar el archivo CSV con el detalle de las compras o ventas al que deseas generar su XML', 'error'); return; } // si no se pasó la firma error if (!isset($_FILES['firma']) or $_FILES['firma']['error']) { \sowerphp\core\Model_Datasource_Session::message('Debes enviar el archivo con la firma digital', 'error'); return; } // Objeto de la Firma try { $Firma = new \sasco\LibreDTE\FirmaElectronica(['data' => file_get_contents($_FILES['firma']['tmp_name']), 'pass' => $_POST['contrasenia']]); } catch (\Exception $e) { \sowerphp\core\Model_Datasource_Session::message('No fue posible abrir la firma digital, quizás contraseña incorrecta', 'error'); return; } // generar caratula del libro $caratula = ['RutEmisorLibro' => str_replace('.', '', $_POST['RutEmisorLibro']), 'RutEnvia' => $Firma->getID(), 'PeriodoTributario' => $_POST['PeriodoTributario'], 'FchResol' => $_POST['FchResol'], 'NroResol' => $_POST['NroResol'], 'TipoOperacion' => $_POST['TipoOperacion'], 'TipoLibro' => $_POST['TipoLibro'], 'TipoEnvio' => $_POST['TipoEnvio'], 'FolioNotificacion' => $_POST['FolioNotificacion']]; // definir si es certificacion $caratula_certificacion = ['COMPRA' => ['PeriodoTributario' => 2000, 'FchResol' => '2006-01-20', 'NroResol' => 102006, 'TipoLibro' => 'ESPECIAL', 'TipoEnvio' => 'TOTAL', 'FolioNotificacion' => 102006], 'VENTA' => ['PeriodoTributario' => 1980, 'FchResol' => '2006-01-20', 'NroResol' => 102006, 'TipoLibro' => 'ESPECIAL', 'TipoEnvio' => 'TOTAL', 'FolioNotificacion' => 102006]]; $certificacion = true; foreach ($caratula_certificacion[$caratula['TipoOperacion']] as $attr => $val) { if ($caratula[$attr] != $val or $attr == 'PeriodoTributario' and substr($caratula[$attr], 0, 4) != $val) { $certificacion = false; break; } } // generar libro de compras o venta $LibroCompraVenta = new \sasco\LibreDTE\Sii\LibroCompraVenta(); if ($caratula['TipoOperacion'] === 'COMPRA') { $LibroCompraVenta->agregarComprasCSV($_FILES['archivo']['tmp_name']); } else { $LibroCompraVenta->agregarVentasCSV($_FILES['archivo']['tmp_name']); } $LibroCompraVenta->setCaratula($caratula); if (!$certificacion) { $LibroCompraVenta->setFirma($Firma); } $xml = $LibroCompraVenta->generar($caratula['TipoOperacion'] == 'COMPRA'); if (!$certificacion and !$LibroCompraVenta->schemaValidate()) { \sowerphp\core\Model_Datasource_Session::message(implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); return; } // descargar XML $file = TMP . '/' . $LibroCompraVenta->getID() . '.xml'; file_put_contents($file, $xml); \sasco\LibreDTE\File::compress($file, ['format' => 'zip', 'delete' => true]); exit; }
/** * Acción que permite subir un caf para un tipo de folio * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-09-22 */ public function subir_caf() { $Emisor = $this->getContribuyente(); $this->set(['Emisor' => $Emisor]); // procesar solo si se envió el formulario if (isset($_POST['submit'])) { // verificar que se haya podido subir CAF if (!isset($_FILES['caf']) or $_FILES['caf']['error']) { \sowerphp\core\Model_Datasource_Session::message('Ocurrió un error al subir el CAF', 'error'); return; } if (\sowerphp\general\Utility_File::mimetype($_FILES['caf']['tmp_name']) != 'application/xml') { \sowerphp\core\Model_Datasource_Session::message('Formato del archivo ' . $_FILES['caf']['name'] . ' es incorrecto', 'error'); return; } // cargar caf $caf = file_get_contents($_FILES['caf']['tmp_name']); $Folios = new \sasco\LibreDTE\Sii\Folios($caf); // si no se pudo validar el caf error if (!$Folios->getTipo()) { \sowerphp\core\Model_Datasource_Session::message('No fue posible cargar el CAF ' . $_FILES['caf']['name'] . ':<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); return; } // verificar que el caf tenga previamente cargado un mantenedor de folio $DteFolio = new Model_DteFolio($Emisor->rut, $Folios->getTipo(), (int) $Folios->getCertificacion()); if (!$DteFolio->exists()) { \sowerphp\core\Model_Datasource_Session::message('Primero debe crear el mantenedor de los folios de tipo ' . $Folios->getTipo(), 'error'); return; } // verificar que el caf sea del emisor if ($Folios->getEmisor() != $Emisor->rut . '-' . $Emisor->dv) { \sowerphp\core\Model_Datasource_Session::message('RUT del CAF ' . $Folios->getEmisor() . ' no corresponde con el RUT de la empresa ' . $Emisor->razon_social . ' ' . $Emisor->rut . '-' . $Emisor->dv, 'error'); return; } // verificar que el folio que se está subiendo sea para el ambiente actual de la empresa $ambiente_empresa = $Emisor->certificacion ? 'certificación' : 'producción'; $ambiente_caf = $Folios->getCertificacion() ? 'certificación' : 'producción'; if ($ambiente_empresa != $ambiente_caf) { \sowerphp\core\Model_Datasource_Session::message('Empresa está en ambiente de ' . $ambiente_empresa . ' pero folios son de ' . $ambiente_caf, 'error'); return; } // crear caf para el folio $DteCaf = new Model_DteCaf($DteFolio->emisor, $DteFolio->dte, (int) $Folios->getCertificacion(), $Folios->getDesde()); if ($DteCaf->exists()) { \sowerphp\core\Model_Datasource_Session::message('El CAF para el documento de tipo ' . $DteCaf->dte . ' que inicia en ' . $Folios->getDesde() . ' en ambiente de ' . $ambiente_caf . ' ya estaba cargado', 'warning'); return; } $DteCaf->hasta = $Folios->getHasta(); $DteCaf->xml = \website\Dte\Utility_Data::encrypt($caf); try { $DteCaf->save(); } catch (\sowerphp\core\Exception_Model_Datasource_Database $e) { \sowerphp\core\Model_Datasource_Session::message('No fue posible guardar el CAF: ' . $e->getMessage(), 'error'); return; } // actualizar mantenedor de folios if (!$DteFolio->disponibles) { $DteFolio->siguiente = $Folios->getDesde(); $DteFolio->disponibles = $Folios->getHasta() - $Folios->getDesde() + 1; } else { $DteFolio->disponibles += $Folios->getHasta() - $Folios->getDesde() + 1; } $DteFolio->alertado = 'f'; try { $DteFolio->save(); \sowerphp\core\Model_Datasource_Session::message('El CAF para el documento de tipo ' . $DteCaf->dte . ' que inicia en ' . $Folios->getDesde() . ' en ambiente de ' . $ambiente_caf . ' fue cargado, el siguiente folio disponible es ' . $DteFolio->siguiente, 'ok'); $this->redirect('/dte/admin/dte_folios'); } catch (\sowerphp\core\Exception_Model_Datasource_Database $e) { \sowerphp\core\Model_Datasource_Session::message('El CAF se guardó, pero no fue posible actualizar el mantenedor de folios, deberá actualizar manualmente. ' . $e->getMessage(), 'error'); return; } } }
/** * Acción que solicita se envíe una nueva revisión del DTE al email * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-09-25 */ public function solicitar_revision($dte, $folio) { $Emisor = $this->getContribuyente(); // obtener DTE emitido $DteEmitido = new Model_DteEmitido($Emisor->rut, $dte, $folio, (int) $Emisor->certificacion); if (!$DteEmitido->exists()) { \sowerphp\core\Model_Datasource_Session::message('No existe el DTE solicitado', 'error'); $this->redirect('/dte/dte_emitidos/listar'); } // si no tiene track id error if (!$DteEmitido->track_id) { \sowerphp\core\Model_Datasource_Session::message('DTE no tiene Track ID, primero debe enviarlo al SII', 'error'); $this->redirect(str_replace('solicitar_revision', 'ver', $this->request->request)); } // obtener firma $Firma = $Emisor->getFirma($this->Auth->User->id); if (!$Firma) { \sowerphp\core\Model_Datasource_Session::message('No hay firma electrónica asociada a la empresa (o bien no se pudo cargar), debe agregar su firma antes de generar DTE', 'error'); $this->redirect('/dte/admin/firma_electronicas'); } // obtener token $token = \sasco\LibreDTE\Sii\Autenticacion::getToken($Firma); if (!$token) { \sowerphp\core\Model_Datasource_Session::message('No fue posible obtener el token para el SII<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); $this->redirect(str_replace('solicitar_revision', 'ver', $this->request->request)); } // solicitar envío de nueva revisión $estado = \sasco\LibreDTE\Sii::request('wsDTECorreo', 'reenvioCorreo', [$token, $Emisor->rut, $Emisor->dv, $DteEmitido->track_id]); if ($estado === false) { \sowerphp\core\Model_Datasource_Session::message('No fue posible solicitar una nueva revisión del DTE.<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); } else { if ((int) $estado->xpath('/SII:RESPUESTA/SII:RESP_HDR/SII:ESTADO')[0]) { \sowerphp\core\Model_Datasource_Session::message('No fue posible solicitar una nueva revisión del DTE: ' . $estado->xpath('/SII:RESPUESTA/SII:RESP_HDR/SII:GLOSA')[0], 'error'); } else { \sowerphp\core\Model_Datasource_Session::message('Se solicitó nueva revisión del DTE, verificar estado en unos segundos', 'ok'); } } // redireccionar $this->redirect(str_replace('solicitar_revision', 'ver', $this->request->request)); }
/** * Acción que envía el archivo XML del libro de ventas al SII * Si no hay documentos en el período se enviará sin movimientos * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-12-27 */ public function enviar_sii($periodo) { $Emisor = $this->getContribuyente(); // si el periodo es mayor o igual al actual no se puede enviar if ($periodo >= date('Ym')) { \sowerphp\core\Model_Datasource_Session::message('No puede enviar el libro de ventas del período ' . $periodo . ', debe esperar al mes siguiente del período', 'error'); $this->redirect(str_replace('enviar_sii', 'ver', $this->request->request)); } // obtener ventas $ventas = $Emisor->getVentas($periodo); // crear libro $Libro = new \sasco\LibreDTE\Sii\LibroCompraVenta(); // obtener firma $Firma = $Emisor->getFirma($this->Auth->User->id); if (!$Firma) { \sowerphp\core\Model_Datasource_Session::message('No hay firma electrónica asociada a la empresa (o bien no se pudo cargar), debe agregar su firma antes de generar DTE', 'error'); $this->redirect('/dte/admin/firma_electronicas'); } // agregar detalle $documentos = 0; foreach ($ventas as $venta) { $documentos++; // armar detalle para agregar al libro $d = []; foreach ($venta as $k => $v) { if (strpos($k, 'impuesto_') !== 0) { if ($v !== null) { $d[$this->libro_cols[$k]] = $v; } } } // agregar otros impuestos if (!empty($venta['impuesto_codigo'])) { $d['OtrosImp'] = ['CodImp' => $venta['impuesto_codigo'], 'TasaImp' => $venta['impuesto_tasa'], 'MntImp' => $venta['impuesto_monto']]; } // agregar detalle al libro $Libro->agregar($d); } // agregar carátula al libro $Libro->setCaratula(['RutEmisorLibro' => $Emisor->rut . '-' . $Emisor->dv, 'RutEnvia' => $Firma->getID(), 'PeriodoTributario' => substr($periodo, 0, 4) . '-' . substr($periodo, 4), 'FchResol' => $Emisor->certificacion ? $Emisor->certificacion_resolucion : $Emisor->resolucion_fecha, 'NroResol' => $Emisor->certificacion ? 0 : $Emisor->resolucion_numero, 'TipoOperacion' => 'VENTA', 'TipoLibro' => 'MENSUAL', 'TipoEnvio' => 'TOTAL']); // obtener XML $Libro->setFirma($Firma); $xml = $Libro->generar(); if (!$xml) { \sowerphp\core\Model_Datasource_Session::message('No fue posible generar el libro de ventas<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); $this->redirect(str_replace('enviar_sii', 'ver', $this->request->request)); } // enviar al SII $track_id = $Libro->enviar(); if (!$track_id) { \sowerphp\core\Model_Datasource_Session::message('No fue posible enviar el libro de ventas al SII<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); $this->redirect(str_replace('enviar_sii', 'ver', $this->request->request)); } // guardar libro de ventas $DteVenta = new Model_DteVenta($Emisor->rut, $periodo, (int) $Emisor->certificacion); $DteVenta->documentos = $documentos; $DteVenta->xml = base64_encode($xml); $DteVenta->track_id = $track_id; $DteVenta->save(); \sowerphp\core\Model_Datasource_Session::message('Libro de ventas período ' . $periodo . ' envíado', 'ok'); $this->redirect(str_replace('enviar_sii', 'ver', $this->request->request)); }
/** * Recurso de la API que permite validar el TED (timbre electrónico) * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-09-19 */ public function _api_verificar_ted_POST() { // verificar si se pasaron credenciales de un usuario $User = $this->Api->getAuthUser(); if (is_string($User)) { $this->Api->send($User, 401); } // obtener TED $TED = base64_decode($this->Api->data); if (strpos($TED, '<?xml') !== 0) { $TED = '<?xml version="1.0" encoding="ISO-8859-1"?>' . "\n" . $TED; } // crear xml con el ted y obtener datos en arreglo $xml = new \sasco\LibreDTE\XML(); $xml->loadXML($TED); $datos = $xml->toArray(); // verificar firma del ted $DD = $xml->getFlattened('/TED/DD'); $FRMT = $datos['TED']['FRMT']; $pub_key = \sasco\LibreDTE\FirmaElectronica::getFromModulusExponent($datos['TED']['DD']['CAF']['DA']['RSAPK']['M'], $datos['TED']['DD']['CAF']['DA']['RSAPK']['E']); if (openssl_verify($DD, base64_decode($FRMT), $pub_key, OPENSSL_ALGO_SHA1) !== 1) { $this->Api->send('Firma del timbre incorrecta', 500); } // verificar que datos del timbre correspondan con datos del CAF if ($datos['TED']['DD']['RE'] != $datos['TED']['DD']['CAF']['DA']['RE']) { $this->Api->send('RUT del timbre no corresponde con RUT del CAF', 500); } if ($datos['TED']['DD']['TD'] != $datos['TED']['DD']['CAF']['DA']['TD']) { $this->Api->send('Tipo de DTE del timbre no corresponde con tipo de DTE del CAF', 500); } if ($datos['TED']['DD']['F'] < $datos['TED']['DD']['CAF']['DA']['RNG']['D'] or $datos['TED']['DD']['F'] > $datos['TED']['DD']['CAF']['DA']['RNG']['H']) { $this->Api->send('Folio del DTE del timbre fuera del rango del CAF', 500); } // definir si se consultará en certificación o producción define('_LibreDTE_CERTIFICACION_', $datos['TED']['DD']['CAF']['DA']['IDK'] == 100); // crear objeto firma $Firma = new \sasco\LibreDTE\FirmaElectronica(); // obtener token $token = \sasco\LibreDTE\Sii\Autenticacion::getToken($Firma); if (!$token) { return $this->Api->send(\sasco\LibreDTE\Log::readAll(), 500); } // verificar estado del DTE con el SII list($RutConsultante, $DvConsultante) = explode('-', $Firma->getID()); list($RutCompania, $DvCompania) = explode('-', $datos['TED']['DD']['RE']); list($RutReceptor, $DvReceptor) = explode('-', $datos['TED']['DD']['RR']); list($a, $m, $d) = explode('-', $datos['TED']['DD']['FE']); $xml = \sasco\LibreDTE\Sii::request('QueryEstDte', 'getEstDte', ['RutConsultante' => $RutConsultante, 'DvConsultante' => $DvConsultante, 'RutCompania' => $RutCompania, 'DvCompania' => $DvCompania, 'RutReceptor' => $RutReceptor, 'DvReceptor' => $DvReceptor, 'TipoDte' => $datos['TED']['DD']['TD'], 'FolioDte' => $datos['TED']['DD']['F'], 'FechaEmisionDte' => $d . $m . $a, 'MontoDte' => $datos['TED']['DD']['MNT'], 'token' => $token]); if ($xml === false) { return $this->Api->send(\sasco\LibreDTE\Log::readAll(), 500); } return (array) $xml->xpath('/SII:RESPUESTA/SII:RESP_HDR')[0]; }
/** * Acción que procesa y responde al intercambio recibido * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) * @version 2015-10-08 */ public function responder($codigo) { $Emisor = $this->getContribuyente(); // si no se viene por post error if (!isset($_POST['submit'])) { \sowerphp\core\Model_Datasource_Session::message('No puede acceder de forma directa a ' . $this->request->request, 'error'); $this->redirect(str_replace('responder', 'ver', $this->request->request)); } // obtener DTE emitido $DteIntercambio = new Model_DteIntercambio($Emisor->rut, $codigo, (int) $Emisor->certificacion); if (!$DteIntercambio->exists()) { \sowerphp\core\Model_Datasource_Session::message('No existe el intercambio solicitado', 'error'); $this->redirect('/dte/dte_intercambios'); } // obtener firma $Firma = $Emisor->getFirma($this->Auth->User->id); if (!$Firma) { \sowerphp\core\Model_Datasource_Session::message('No hay firma electrónica asociada a la empresa (o bien no se pudo cargar), debe agregar su firma antes de generar DTE', 'error'); $this->redirect('/dte/admin/firma_electronicas'); } // // construir RecepcionDTE // $RecepcionDTE = []; $n_dtes = count($_POST['TipoDTE']); for ($i = 0; $i < $n_dtes; $i++) { $RecepcionDTE[] = ['TipoDTE' => $_POST['TipoDTE'][$i], 'Folio' => $_POST['Folio'][$i], 'FchEmis' => $_POST['FchEmis'][$i], 'RUTEmisor' => $_POST['RUTEmisor'][$i], 'RUTRecep' => $_POST['RUTRecep'][$i], 'MntTotal' => $_POST['MntTotal'][$i], 'EstadoRecepDTE' => $_POST['EstadoRecepDTE'][$i], 'RecepDTEGlosa' => $_POST['RecepDTEGlosa'][$i]]; } // armar respuesta de envío $EnvioDte = new \sasco\LibreDTE\Sii\EnvioDte(); $EnvioDte->loadXML(base64_decode($DteIntercambio->archivo_xml)); $Caratula = $EnvioDte->getCaratula(); $RespuestaEnvio = new \sasco\LibreDTE\Sii\RespuestaEnvio(); $RespuestaEnvio->agregarRespuestaEnvio(['NmbEnvio' => $DteIntercambio->archivo, 'CodEnvio' => $DteIntercambio->codigo, 'EnvioDTEID' => $EnvioDte->getID(), 'Digest' => $EnvioDte->getDigest(), 'RutEmisor' => $EnvioDte->getEmisor(), 'RutReceptor' => $EnvioDte->getReceptor(), 'EstadoRecepEnv' => $_POST['EstadoRecepEnv'], 'RecepEnvGlosa' => $_POST['RecepEnvGlosa'], 'NroDTE' => count($RecepcionDTE), 'RecepcionDTE' => $RecepcionDTE]); // asignar carátula y Firma $RespuestaEnvio->setCaratula(['RutResponde' => $Emisor->rut . '-' . $Emisor->dv, 'RutRecibe' => $Caratula['RutEmisor'], 'IdRespuesta' => $DteIntercambio->codigo, 'NmbContacto' => $_POST['NmbContacto'], 'MailContacto' => $_POST['MailContacto']]); $RespuestaEnvio->setFirma($Firma); // generar y validar XML $RecepcionDTE_xml = $RespuestaEnvio->generar(); if (!$RespuestaEnvio->schemaValidate()) { \sowerphp\core\Model_Datasource_Session::message('No fue posible generar RecepcionDTE.<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); $this->redirect(str_replace('responder', 'ver', $this->request->request)); } // // generar EnvioRecibos // $EnvioRecibos = new \sasco\LibreDTE\Sii\EnvioRecibos(); $EnvioRecibos->setCaratula(['RutResponde' => $Emisor->rut . '-' . $Emisor->dv, 'RutRecibe' => $Caratula['RutEmisor'], 'NmbContacto' => $_POST['NmbContacto'], 'MailContacto' => $_POST['MailContacto']]); $EnvioRecibos->setFirma($Firma); // procesar cada DTE $EnvioRecibos_r = []; for ($i = 0; $i < $n_dtes; $i++) { if ($_POST['acuse'][$i]) { $EnvioRecibos->agregar(['TipoDoc' => $_POST['TipoDTE'][$i], 'Folio' => $_POST['Folio'][$i], 'FchEmis' => $_POST['FchEmis'][$i], 'RUTEmisor' => $_POST['RUTEmisor'][$i], 'RUTRecep' => $_POST['RUTRecep'][$i], 'MntTotal' => $_POST['MntTotal'][$i], 'Recinto' => $_POST['Recinto'], 'RutFirma' => $Firma->getID()]); $EnvioRecibos_r[] = 'T' . $_POST['TipoDTE'][$i] . 'F' . $_POST['Folio'][$i]; } } // generar y validar XML if ($EnvioRecibos_r) { $EnvioRecibos_xml = $EnvioRecibos->generar(); if (!$EnvioRecibos->schemaValidate()) { \sowerphp\core\Model_Datasource_Session::message('No fue posible generar EnvioRecibos.<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); $this->redirect(str_replace('responder', 'ver', $this->request->request)); } } // // generar ResultadoDTE // // objeto para la respuesta $RespuestaEnvio = new \sasco\LibreDTE\Sii\RespuestaEnvio(); // procesar cada DTE for ($i = 0; $i < $n_dtes; $i++) { $estado = !$_POST['EstadoRecepDTE'][$i] ? 0 : 2; $RespuestaEnvio->agregarRespuestaDocumento(['TipoDTE' => $_POST['TipoDTE'][$i], 'Folio' => $_POST['Folio'][$i], 'FchEmis' => $_POST['FchEmis'][$i], 'RUTEmisor' => $_POST['RUTEmisor'][$i], 'RUTRecep' => $_POST['RUTRecep'][$i], 'MntTotal' => $_POST['MntTotal'][$i], 'CodEnvio' => $i + 1, 'EstadoDTE' => $estado, 'EstadoDTEGlosa' => \sasco\LibreDTE\Sii\RespuestaEnvio::$estados['respuesta_documento'][$estado]]); } // asignar carátula y Firma $RespuestaEnvio->setCaratula(['RutResponde' => $Emisor->rut . '-' . $Emisor->dv, 'RutRecibe' => $Caratula['RutEmisor'], 'IdRespuesta' => $DteIntercambio->codigo, 'NmbContacto' => $_POST['NmbContacto'], 'MailContacto' => $_POST['MailContacto']]); $RespuestaEnvio->setFirma($Firma); // generar y validar XML $ResultadoDTE_xml = $RespuestaEnvio->generar(); if (!$RespuestaEnvio->schemaValidate()) { \sowerphp\core\Model_Datasource_Session::message('No fue posible generar ResultadoDTE.<br/>' . implode('<br/>', \sasco\LibreDTE\Log::readAll()), 'error'); $this->redirect(str_replace('responder', 'ver', $this->request->request)); } // // guardar estado del intercambio y usuario que lo procesó // $DteIntercambio->estado = (int) $_POST['EstadoRecepEnv']; $DteIntercambio->recepcion_xml = base64_encode($RecepcionDTE_xml); if (isset($EnvioRecibos_xml)) { $DteIntercambio->recibos_xml = base64_encode($EnvioRecibos_xml); } $DteIntercambio->resultado_xml = base64_encode($ResultadoDTE_xml); $DteIntercambio->fecha_hora_respuesta = date('Y-m-d H:i:s'); $DteIntercambio->usuario = $this->Auth->User->id; $DteIntercambio->save(); // // guardar documentos que han sido aceptados con acuse de recibo // if (isset($EnvioRecibos_xml)) { // actualizar datos del emisor si no tine usuario asociado $EmisorIntercambio = $DteIntercambio->getEmisor(); if (!$EmisorIntercambio->usuario) { $emisor = $DteIntercambio->getDocumentos()[0]->getDatos()['Encabezado']['Emisor']; $EmisorIntercambio->razon_social = $emisor['RznSoc']; if (!empty($emisor['GiroEmis'])) { $EmisorIntercambio->giro = $emisor['GiroEmis']; } if (!empty($emisor['CorreoEmisor'])) { $EmisorIntercambio->email = $emisor['CorreoEmisor']; } if (!empty($emisor['Acteco'])) { $actividad_economica = $EmisorIntercambio->actividad_economica; $EmisorIntercambio->actividad_economica = $emisor['Acteco']; if (!$EmisorIntercambio->getActividadEconomica()->exists()) { $EmisorIntercambio->actividad_economica = $actividad_economica; } } $comuna = (new \sowerphp\app\Sistema\General\DivisionGeopolitica\Model_Comunas())->getComunaByName($emisor['CmnaOrigen']); if ($comuna) { $EmisorIntercambio->direccion = $emisor['DirOrigen']; $EmisorIntercambio->comuna = $comuna; } if (!empty($emisor['CdgSIISucur'])) { $EmisorIntercambio->sucursal_sii = (int) $emisor['CdgSIISucur']; } $EmisorIntercambio->modificado = date('Y-m-d H:i:s'); $EmisorIntercambio->save(); } // guardar documentos que tienen acuse de recibo como dte recibidos $Documentos = $DteIntercambio->getDocumentos(); foreach ($Documentos as $Dte) { if (in_array($Dte->getID(), $EnvioRecibos_r)) { $resumen = $Dte->getResumen(); $DteRecibido = new Model_DteRecibido(); $DteRecibido->emisor = $DteIntercambio->getEmisor()->rut; $DteRecibido->dte = $resumen['TpoDoc']; $DteRecibido->folio = $resumen['NroDoc']; $DteRecibido->certificacion = (int) $DteIntercambio->certificacion; if (!$DteRecibido->exists()) { $DteRecibido->receptor = $Emisor->rut; $DteRecibido->tasa = (int) $resumen['TasaImp']; $DteRecibido->fecha = $resumen['FchDoc']; $DteRecibido->sucursal_sii = (int) $resumen['CdgSIISucur']; if ($resumen['MntExe']) { $DteRecibido->exento = $resumen['MntExe']; } if ($resumen['MntNeto']) { $DteRecibido->neto = $resumen['MntNeto']; } $DteRecibido->iva = (int) $resumen['MntIVA']; $DteRecibido->total = (int) $resumen['MntTotal']; $DteRecibido->usuario = $this->Auth->User->id; $DteRecibido->intercambio = $DteIntercambio->codigo; $DteRecibido->save(); } } } } // // enviar los 3 XML de respuesta por email // $email = $Emisor->getEmailSmtp(); $email->to($_POST['responder_a']); $email->subject($Emisor->rut . '-' . $Emisor->dv . ' - Respuesta intercambio N° ' . $DteIntercambio->codigo); foreach (['RecepcionDTE', 'EnvioRecibos', 'ResultadoDTE'] as $xml) { if (isset(${$xml . '_xml'})) { $email->attach(['data' => ${$xml . '_xml'}, 'name' => $xml . '_' . $Emisor->rut . '-' . $Emisor->dv . '_' . $DteIntercambio->codigo . '.xml', 'type' => 'application/xml']); } } // enviar email $status = $email->send('Se adjuntan XMLs de respuesta a intercambio de DTE.'); if ($status === true) { \sowerphp\core\Model_Datasource_Session::message('Se procesaron DTEs de intercambio y se envió la respuesta a: ' . $_POST['responder_a'], 'ok'); } else { \sowerphp\core\Model_Datasource_Session::message('Se procesaron DTEs de intercambio, pero no fue posible enviar el email, por favor intente nuevamente.<br /><em>' . $status['message'] . '</em>', 'error'); } $this->redirect(str_replace('responder', 'ver', $this->request->request)); }