PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.13
VikBooking Hotel Booking Engine & PMS v1.8.13
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / helpers / report / es_hospedajes.php

es_hospedajes.php in VikBooking Hotel Booking Engine & PMS 1.8.13, at admin/helpers/report/es_hospedajes.php

3,685 lines 125.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - E4J srl
6 * @copyright Copyright (C) 2024 E4J srl. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * VikBookingReport implementation for (Spain) Hospedajes - Ministerio del Interior.
15 *
16 * This report supports two different types of registration, one based on the reservation
17 * date (Reservas de hospedaje) and another based on the check-in date (Partes de viajeros).
18 * This report also provides some custom scoped actions for the electronic operations.
19 *
20 * @see Instrucciones-hospedajes.pdf
21 * @see MIR-HOSPE-DSI-WS-Servicio-de-Hospedajes---Comunicaciones-v3.1.2.pdf
22 *
23 * @since 1.17.2 (J) - 1.7.2 (WP)
24 */
25 class VikBookingReportEsHospedajes extends VikBookingReport
26 {
27 /**
28 * Property 'defaultKeySort' is used by the View that renders the report.
29 *
30 * @var string
31 */
32 public $defaultKeySort = 'idbooking';
33
34 /**
35 * Property 'defaultKeyOrder' is used by the View that renders the report.
36 *
37 * @var string
38 */
39 public $defaultKeyOrder = 'ASC';
40
41 /**
42 * Property 'customExport' is used by the View to display custom export buttons.
43 *
44 * @var string
45 */
46 public $customExport = '';
47
48 /**
49 * List of booking IDs affected by the export.
50 *
51 * @var array
52 */
53 protected $export_booking_ids = [];
54
55 /**
56 * List of exported check-in dates (range).
57 *
58 * @var array
59 */
60 protected $exported_checkin_dates = [];
61
62 /**
63 * The development (test, pruebas) endpoint.
64 *
65 * @var string
66 */
67 protected $endpoint_test = 'https://hospedajes.pre-ses.mir.es/hospedajes-web/ws/v1/comunicacion';
68
69 /**
70 * The production (live, producción) endpoint.
71 *
72 * @var string
73 */
74 protected $endpoint_production = 'https://hospedajes.ses.mir.es/hospedajes-web/ws/v1/comunicacion';
75
76 /**
77 * The path to the temporary directory used by this report.
78 *
79 * @var string
80 */
81 protected $report_tmp_path = '';
82
83 /**
84 * The software application name.
85 *
86 * @var string
87 */
88 protected $software_application_name = 'VikBooking - E4jConnect';
89
90 /**
91 * Associative list of RELACIÓN DE PARENTESCO
92 *
93 * @var array
94 */
95 protected $relacion_de_parentesco = [
96 'AB' => 'Abuelo/a',
97 'BA' => 'Bisabuelo/a',
98 'BN' => 'Bisnieto/a',
99 'CD' => 'Cuñado/a',
100 'CY' => 'Cónyuge',
101 'HJ' => 'Hijo/a',
102 'HR' => 'Hermano/a',
103 'NI' => 'Nieto/a',
104 'PM' => 'Padre o madre',
105 'SB' => 'Sobrino/a',
106 'SG' => 'Suegro/a',
107 'TI' => 'Tío/a',
108 'YN' => 'Yerno o nuera',
109 'TU' => 'Tutor/a',
110 'OT' => 'Otro',
111 ];
112
113 /**
114 * Associative list of SEXO
115 *
116 * @var array
117 */
118 protected $sexo = [
119 'H' => 'Hombre',
120 'M' => 'Mujer',
121 'O' => 'Otro',
122 ];
123
124 /**
125 * Associative list of TIPOS DE DOCUMENTO
126 *
127 * @var array
128 */
129 protected $tipos_de_documento = [
130 'NIF' => 'NIF - Número de Identificación Fiscal',
131 'NIE' => 'NIE - Número de Identidad de Extranjero',
132 'PAS' => 'Número de pasaporte',
133 'OTRO' => 'Otro',
134 ];
135
136 /**
137 * Associative list of TIPOS DE ESTABLECIMIENTO
138 *
139 * @var array
140 */
141 protected $tipos_de_establecimiento = [
142 'ALBERGUE' => 'Albergue',
143 'APART' => 'Apartamento',
144 'APARTHOTEL' => 'Apartahotel',
145 'AP_RURAL' => 'Apartamento rural',
146 'BALNEARIO' => 'Balneario',
147 'CAMPING' => 'Camping',
148 'CASA_HUESP' => 'Casa de huéspedes',
149 'CASA_RURAL' => 'Casa rural',
150 'HOSTAL' => 'Hostal',
151 'HOTEL' => 'Hotel',
152 'H_RURAL' => 'Hotel rural',
153 'MOTEL' => 'Motel',
154 'OFIC_VEHIC' => 'Oficina de alquiler de vehículos',
155 'OTROS' => 'Otro',
156 'PARADOR' => 'Parador de turismo',
157 'PENSION' => 'Pensión',
158 'RESIDENCIA' => 'Residencia',
159 ];
160
161 /**
162 * Associative list of TIPOS DE PAGO
163 *
164 * @var array
165 */
166 protected $tipos_de_pago = [
167 'EFECT' => 'Efectivo',
168 'TARJT' => 'Tarjeta de crédito',
169 'PLATF' => 'Plataforma de pago',
170 'TRANS' => 'Transferencia',
171 'MOVIL' => 'Pago por móvil',
172 'TREG' => 'Tarjeta regalo',
173 'DESTI' => 'Pago en destino',
174 'OTRO' => 'Otros medios de pago',
175 ];
176
177 /**
178 * Associative list of guest types.
179 *
180 * @var array
181 */
182 protected $guest_types = [
183 'TI' => 'Titular',
184 'VI' => 'Viajero',
185 ];
186
187 /**
188 * Class constructor should define the name of the report and
189 * other vars. Call the parent constructor to define the DB object.
190 */
191 public function __construct()
192 {
193 $this->reportFile = basename(__FILE__, '.php');
194 $this->reportName = 'SES Hospedajes - Ministerio del Interior';
195 $this->reportFilters = [];
196
197 $this->cols = [];
198 $this->rows = [];
199 $this->footerRow = [];
200
201 $this->registerExportFileName();
202
203 // set the temporary report directory path
204 $this->report_tmp_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'hospedajes_tmp';
205
206 parent::__construct();
207 }
208
209 /**
210 * Returns the name of this report.
211 *
212 * @return string
213 */
214 public function getName()
215 {
216 return $this->reportName;
217 }
218
219 /**
220 * Returns the name of this file without .php.
221 *
222 * @return string
223 */
224 public function getFileName()
225 {
226 return $this->reportFile;
227 }
228
229 /**
230 * @inheritDoc
231 *
232 * @since 1.18.0 (J) - 1.8.0 (WP)
233 */
234 public function allowsProfileSettings()
235 {
236 // allow multiple report profile settings
237 return true;
238 }
239
240 /**
241 * @inheritDoc
242 */
243 public function getSettingFields()
244 {
245 return [
246 'codigo' => [
247 'type' => 'text',
248 'label' => 'Código del establecimiento',
249 'help' => 'Se deberá informar, si se conoce, el código del establecimiento asignado por el Sistema de Hospedajes en el momento del registro.',
250 ],
251 'tipo_establecimiento' => [
252 'type' => 'select',
253 'label' => 'Tipo de establecimiento',
254 'help' => 'Datos del establecimento en caso de no disponer de un código de establecimiento en el Sistema de Hospedajes.',
255 'options' => $this->tipos_de_establecimiento,
256 'default' => 'OTROS',
257 ],
258 'nombre_establecimiento' => [
259 'type' => 'text',
260 'label' => 'Nombre del establecimiento',
261 'help' => 'Datos del establecimento en caso de no disponer de un código de establecimiento en el Sistema de Hospedajes.',
262 'attributes' => [
263 'maxlength' => 50,
264 ],
265 'default' => strip_tags((string) VikBooking::getFrontTitle()),
266 ],
267 'direccion' => [
268 'type' => 'text',
269 'label' => 'Dirección del establecimiento',
270 'help' => 'Datos del establecimento en caso de no disponer de un código de establecimiento en el Sistema de Hospedajes.',
271 ],
272 'codigo_postal' => [
273 'type' => 'text',
274 'label' => 'Código postal',
275 'help' => 'Datos del establecimento en caso de no disponer de un código de establecimiento en el Sistema de Hospedajes.',
276 'attributes' => [
277 'maxlength' => 20,
278 ],
279 ],
280 'pais' => [
281 'type' => 'text',
282 'label' => 'Código del país',
283 'help' => 'Este campo va codificado según la norma ISO 3166-1 Alfa-3.',
284 'default' => 'ESP',
285 'attributes' => [
286 'maxlength' => 3,
287 ],
288 ],
289 'codigo_municipio' => [
290 'type' => 'text',
291 'label' => 'Código municipio',
292 'help' => 'Este campo irá codificado con los códigos de municipios del INE (5 dígitos). Obligatorio cuando el país es España.',
293 'attributes' => [
294 'maxlength' => 5,
295 ],
296 ],
297 'nombre_municipio' => [
298 'type' => 'text',
299 'label' => 'Nombre municipio',
300 'help' => 'Nombre del municipio, ciudad, estado, etc.. Obligatorio cuando el país no es España.',
301 'attributes' => [
302 'maxlength' => 100,
303 ],
304 ],
305 'service_test_mode' => [
306 'type' => 'checkbox',
307 'label' => 'Endpoint pruebas',
308 'help' => 'Datos de acceso al Servicio de Comunicación. Pruebas o Producción.',
309 'default' => 0,
310 ],
311 'service_arrendador' => [
312 'type' => 'text',
313 'label' => 'Arrendador',
314 'help' => 'Código del arrendador asignado por el Sistema de Hospedajes en el momento del registro.',
315 ],
316 'service_username' => [
317 'type' => 'text',
318 'label' => 'Usuario',
319 'help' => 'Datos de acceso al Servicio de Comunicación. Usuario.',
320 ],
321 'service_password' => [
322 'type' => 'password',
323 'label' => 'Contraseña',
324 'help' => 'Datos de acceso al Servicio de Comunicación. Contraseña.',
325 ],
326 ];
327 }
328
329 /**
330 * @inheritDoc
331 */
332 public function getScopedActions($scope = null, $visible = true)
333 {
334 // list of custom actions for this report
335 $actions = [
336 [
337 'id' => 'registerTravelerParts',
338 'name' => 'Alta de partes de viajeros',
339 'icon' => VikBookingIcons::i('cloud-upload-alt'),
340 // flag to indicate that it requires the report data (lines)
341 'export_data' => true,
342 'scopes' => [
343 'web',
344 'cron',
345 ],
346 ],
347 [
348 'id' => 'registerAccommodationReservations',
349 'name' => 'Alta de reservas de hospedaje',
350 'icon' => VikBookingIcons::i('cloud-upload-alt'),
351 // flag to indicate that it requires the report data (lines)
352 'export_data' => true,
353 'scopes' => [
354 'web',
355 'cron',
356 ],
357 ],
358 [
359 'id' => 'batchArchive',
360 'name' => 'Archivo lotes',
361 'help' => 'Archivo de lotes obtenidos como resultado de transmisiones de datos de huéspedes.',
362 'icon' => VikBookingIcons::i('folder-open'),
363 'scopes' => [
364 'web',
365 ],
366 'params' => [
367 'op_month' => [
368 'type' => 'calendar',
369 'label' => 'Mes de la comunicación',
370 'help' => 'Seleccione el mes en el que se produjo la comunicación.',
371 'default' => date('Y-m-01'),
372 ],
373 'rp_type' => [
374 'type' => 'select',
375 'label' => 'Tipo de comunicación',
376 'help' => 'Filtro opcional por tipo de comunicación.',
377 'options' => [
378 '0' => '',
379 'PV' => 'Alta de partes de viajeros',
380 'HR' => 'Alta de reservas de hospedaje',
381 ],
382 'default' => '0',
383 ],
384 ],
385 'params_submit_lbl' => 'Búsqueda lotes',
386 ],
387 [
388 'id' => 'checkBatchStatus',
389 'name' => 'Consulta de lotes',
390 'help' => 'Consultar el estado de los lotes obtenidos de transmisiones de datos anteriores.',
391 'icon' => VikBookingIcons::i('user-check'),
392 'scopes' => [
393 'web',
394 'cron',
395 ],
396 'params' => [
397 'batch_sequence' => [
398 'type' => 'text',
399 'label' => 'Lote',
400 'help' => 'Ingrese el número de lote a buscar.',
401 ],
402 'op_date' => [
403 'type' => 'calendar',
404 'label' => 'Fecha de la comunicación',
405 'help' => 'Seleccione la fecha en el que se produjo la comunicación.',
406 'default' => date('Y-m-d'),
407 ],
408 ],
409 'params_submit_lbl' => 'Consulta lotes',
410 ],
411 [
412 'id' => 'prepareCommunicationMessage',
413 'name' => 'Prepare communication mesage',
414 // flag to indicate that it's callable internally, but not graphically
415 'hidden' => true,
416 'scopes' => [
417 'web',
418 ],
419 ],
420 ];
421
422 // filter actions by scope
423 if ($scope && (!strcasecmp($scope, 'cron') || !strcasecmp($scope, 'web'))) {
424 $actions = array_filter($actions, function($action) use ($scope) {
425 if (!($action['scopes'] ?? [])) {
426 return true;
427 }
428
429 return in_array(strtolower($scope), $action['scopes']);
430 });
431 }
432
433 // filter by visibility
434 if ($visible) {
435 $actions = array_filter($actions, function($action) {
436 return !($action['hidden'] ?? false);
437 });
438 }
439
440 return array_values($actions);
441 }
442
443 /**
444 * Returns the filters of this report.
445 *
446 * @return array
447 */
448 public function getFilters()
449 {
450 if ($this->reportFilters) {
451 // do not run this method twice, as it could load JS and CSS files.
452 return $this->reportFilters;
453 }
454
455 $app = JFactory::getApplication();
456
457 // get VBO Application Object
458 $vbo_app = VikBooking::getVboApplication();
459
460 // get the possibly injected report options
461 $options = $this->getReportOptions();
462
463 // load the jQuery UI Datepicker
464 $this->loadDatePicker();
465
466 // custom export button
467 $this->customExport = '<a href="JavaScript: void(0);" onclick="vboDownloadHospedajes();" class="vbcsvexport"><i class="'.VikBookingIcons::i('download').'"></i> <span>Download XML</span></a>';
468
469 // build the hidden values for the selection of various fields.
470 $hidden_vals = '<div id="vbo-report-hospedajes-hidden" style="display: none;">';
471
472 // build params container HTML structure
473 $hidden_vals .= '<div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">';
474 $hidden_vals .= ' <div class="vbo-params-wrap">';
475 $hidden_vals .= ' <div class="vbo-params-container">';
476 $hidden_vals .= ' <div class="vbo-params-block vbo-params-block-noborder">';
477
478 // tipos de pago
479 $hidden_vals .= ' <div id="vbo-report-hospedajes-paytype" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
480 $hidden_vals .= ' <div class="vbo-param-label">Tipo de pago</div>';
481 $hidden_vals .= ' <div class="vbo-param-setting">';
482 $hidden_vals .= ' <select id="choose-paytype" onchange="vboReportChosenTipodepago(this);"><option value=""></option>';
483 foreach ($this->tipos_de_pago as $code => $pago) {
484 $hidden_vals .= ' <option value="' . $code . '">' . $pago . '</option>' . "\n";
485 }
486 $hidden_vals .= ' </select>';
487 $hidden_vals .= ' </div>';
488 $hidden_vals .= ' </div>';
489
490 // Nacionalidad
491 $hidden_vals .= ' <div id="vbo-report-hospedajes-nazione" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
492 $hidden_vals .= ' <div class="vbo-param-label">Pais</div>';
493 $hidden_vals .= ' <div class="vbo-param-setting">';
494 $hidden_vals .= ' <select id="choose-nazione" onchange="vboReportChosenNacionalidad(this);"><option value=""></option>';
495 foreach (VikBooking::getCountriesArray() as $code => $country) {
496 $hidden_vals .= ' <option value="' . $code . '">' . $country['country_name'] . '</option>'."\n";
497 }
498 $hidden_vals .= ' </select>';
499 $hidden_vals .= ' </div>';
500 $hidden_vals .= ' </div>';
501
502 // tipos de documento
503 $hidden_vals .= ' <div id="vbo-report-hospedajes-doctype" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
504 $hidden_vals .= ' <div class="vbo-param-label">Tipos de documento</div>';
505 $hidden_vals .= ' <div class="vbo-param-setting">';
506 $hidden_vals .= ' <select id="choose-documento" onchange="vboReportChosenDocumento(this);"><option value=""></option>';
507 foreach ($this->tipos_de_documento as $code => $documento) {
508 $hidden_vals .= ' <option value="' . $code . '">' . $documento . '</option>' . "\n";
509 }
510 $hidden_vals .= ' </select>';
511 $hidden_vals .= ' </div>';
512 $hidden_vals .= ' </div>';
513
514 // relacion de parentesco
515 $hidden_vals .= ' <div id="vbo-report-hospedajes-parentesco" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
516 $hidden_vals .= ' <div class="vbo-param-label">Relacion de parentesco</div>';
517 $hidden_vals .= ' <div class="vbo-param-setting">';
518 $hidden_vals .= ' <select id="choose-parentesco" onchange="vboReportChosenParentesco(this);"><option value=""></option>';
519 foreach ($this->relacion_de_parentesco as $code => $val) {
520 $hidden_vals .= ' <option value="' . $code . '">' . $val . '</option>' . "\n";
521 }
522 $hidden_vals .= ' </select>';
523 $hidden_vals .= ' </div>';
524 $hidden_vals .= ' </div>';
525
526 // sexo
527 $hidden_vals .= ' <div id="vbo-report-hospedajes-sesso" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
528 $hidden_vals .= ' <div class="vbo-param-label">Sexo</div>';
529 $hidden_vals .= ' <div class="vbo-param-setting">';
530 $hidden_vals .= ' <select id="choose-sesso" onchange="vboReportChosenSesso(this);"><option value=""></option>';
531 foreach ($this->sexo as $code => $ses) {
532 $hidden_vals .= ' <option value="' . $code . '">' . $ses . '</option>' . "\n";
533 }
534 $hidden_vals .= ' </select>';
535 $hidden_vals .= ' </div>';
536 $hidden_vals .= ' </div>';
537
538 // id number
539 $hidden_vals .= ' <div id="vbo-report-hospedajes-docnum" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
540 $hidden_vals .= ' <div class="vbo-param-label">Número de documento</div>';
541 $hidden_vals .= ' <div class="vbo-param-setting">';
542 $hidden_vals .= ' <input type="text" size="40" id="choose-docnum" value="" />';
543 $hidden_vals .= ' </div>';
544 $hidden_vals .= ' </div>';
545
546 // número de soporte del documento
547 $hidden_vals .= ' <div id="vbo-report-hospedajes-docsoporte" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
548 $hidden_vals .= ' <div class="vbo-param-label">Número de soporte del documento</div>';
549 $hidden_vals .= ' <div class="vbo-param-setting">';
550 $hidden_vals .= ' <input type="text" size="40" id="choose-docsoporte" value="" />';
551 $hidden_vals .= ' <span class="vbo-param-setting-comment">Obligatorio si el tipo de documento es NIF, NIE.</span>';
552 $hidden_vals .= ' </div>';
553 $hidden_vals .= ' </div>';
554
555 // address
556 $hidden_vals .= ' <div id="vbo-report-hospedajes-address" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
557 $hidden_vals .= ' <div class="vbo-param-label">Direccion</div>';
558 $hidden_vals .= ' <div class="vbo-param-setting">';
559 $hidden_vals .= ' <input type="text" size="40" id="choose-address" value="" />';
560 $hidden_vals .= ' <span class="vbo-param-setting-comment">Calle, número, escalera, piso, puerta, y demás campos que indiquen la dirección.</span>';
561 $hidden_vals .= ' </div>';
562 $hidden_vals .= ' </div>';
563
564 // codigo municipio
565 $hidden_vals .= ' <div id="vbo-report-hospedajes-municipio" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
566 $hidden_vals .= ' <div class="vbo-param-label">Código de municipio</div>';
567 $hidden_vals .= ' <div class="vbo-param-setting">';
568 $hidden_vals .= ' <select id="choose-municipio" onchange="vboReportChosenMunicipio(this);"><option value=""></option>';
569 foreach (VBOCheckinPaxfieldTypeSpainMunicipio::loadMunicipioCodes() as $code => $val) {
570 $hidden_vals .= ' <option value="' . $code . '">' . $val . '</option>' . "\n";
571 }
572 $hidden_vals .= ' </select>';
573 $hidden_vals .= ' <span class="vbo-param-setting-comment">Este campo irá codificado con los códigos de municipios del INE (5 dígitos). Obligatorio cuando el país es España.</span>';
574 $hidden_vals .= ' </div>';
575 $hidden_vals .= ' </div>';
576
577 // city
578 $hidden_vals .= ' <div id="vbo-report-hospedajes-city" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
579 $hidden_vals .= ' <div class="vbo-param-label">Nombre del municipio</div>';
580 $hidden_vals .= ' <div class="vbo-param-setting">';
581 $hidden_vals .= ' <input type="text" size="40" id="choose-city" value="" />';
582 $hidden_vals .= ' <span class="vbo-param-setting-comment">Nombre del municipio, ciudad, estado, etc.. Obligatorio cuando el país no es España.</span>';
583 $hidden_vals .= ' </div>';
584 $hidden_vals .= ' </div>';
585
586 // postal code
587 $hidden_vals .= ' <div id="vbo-report-hospedajes-postalcode" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
588 $hidden_vals .= ' <div class="vbo-param-label">Código postal</div>';
589 $hidden_vals .= ' <div class="vbo-param-setting">';
590 $hidden_vals .= ' <input type="text" size="40" id="choose-postalcode" value="" />';
591 $hidden_vals .= ' </div>';
592 $hidden_vals .= ' </div>';
593
594 // phone
595 $hidden_vals .= ' <div id="vbo-report-hospedajes-phone" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
596 $hidden_vals .= ' <div class="vbo-param-label">Teléfono</div>';
597 $hidden_vals .= ' <div class="vbo-param-setting">';
598 $hidden_vals .= ' <input type="tel" size="40" id="choose-phone" value="" />';
599 $hidden_vals .= ' </div>';
600 $hidden_vals .= ' </div>';
601
602 // email
603 $hidden_vals .= ' <div id="vbo-report-hospedajes-email" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
604 $hidden_vals .= ' <div class="vbo-param-label">Dirección de correo electrónico</div>';
605 $hidden_vals .= ' <div class="vbo-param-setting">';
606 $hidden_vals .= ' <input type="email" size="40" id="choose-email" value="" />';
607 $hidden_vals .= ' </div>';
608 $hidden_vals .= ' </div>';
609
610 // birth date
611 $hidden_vals .= ' <div id="vbo-report-hospedajes-dbirth" class="vbo-report-hospedajes-selcont vbo-param-container" style="display: none;">';
612 $hidden_vals .= ' <div class="vbo-param-label">Data di nascita</div>';
613 $hidden_vals .= ' <div class="vbo-param-setting">';
614 $hidden_vals .= ' <input type="text" size="40" id="choose-dbirth" value="" />';
615 $hidden_vals .= ' </div>';
616 $hidden_vals .= ' </div>';
617
618 // close params container HTML structure
619 $hidden_vals .= ' </div>';
620 $hidden_vals .= ' </div>';
621 $hidden_vals .= ' </div>';
622 $hidden_vals .= '</div>';
623
624 // close hidden values container
625 $hidden_vals .= '</div>';
626
627 // From Date Filter (with hidden values for the dropdown menus of Comuni, Province, Stati etc..)
628 $filter_opt = array(
629 'label' => '<label for="fromdate">' . JText::translate('VBOREPORTSDATEFROM') . '</label>',
630 'html' => '<input type="text" id="fromdate" name="fromdate" value="" class="vbo-report-datepicker vbo-report-datepicker-from" />' . $hidden_vals,
631 'type' => 'calendar',
632 'name' => 'fromdate'
633 );
634 array_push($this->reportFilters, $filter_opt);
635
636 // To Date Filter
637 $filter_opt = array(
638 'label' => '<label for="todate">' . JText::translate('VBOREPORTSDATETO') . '</label>',
639 'html' => '<input type="text" id="todate" name="todate" value="" class="vbo-report-datepicker vbo-report-datepicker-to" />',
640 'type' => 'calendar',
641 'name' => 'todate'
642 );
643 array_push($this->reportFilters, $filter_opt);
644
645 // Listings Filter
646 $filter_opt = array(
647 'label' => '<label for="listingsfilt">' . JText::translate('VBO_LISTINGS') . '</label>',
648 'html' => '<span class="vbo-toolbar-multiselect-wrap">' . $vbo_app->renderElementsDropDown([
649 'id' => 'listingsfilt',
650 'elements' => 'listings',
651 'placeholder' => JText::translate('VBO_LISTINGS'),
652 'allow_clear' => 1,
653 'attributes' => [
654 'name' => 'listings[]',
655 'multiple' => 'multiple',
656 ],
657 'selected_values' => (array) ($app->input->get('listings', [], 'array') ?: $options->get('listings', [])),
658 ]) . '</span>',
659 'type' => 'select',
660 'multiple' => true,
661 'name' => 'listings',
662 );
663 array_push($this->reportFilters, $filter_opt);
664
665 // export type filter
666 $types = [
667 'checkin' => sprintf('%s (%s)', JText::translate('VBPICKUPAT'), 'Partes de viajeros'),
668 'reservation' => sprintf('%s (%s)', JText::translate('VBPCHOOSEBUSYORDATE'), 'Reservas de hospedaje'),
669 ];
670 $ptype = VikRequest::getString('type', 'checkin', 'request');
671 $types_sel_html = $vbo_app->getNiceSelect($types, $ptype, 'type', '', '', '', '', 'type');
672 $filter_opt = array(
673 'label' => '<label for="type">' . JText::translate('VBPSHOWSEASONSTHREE') . '</label>',
674 'html' => $types_sel_html,
675 'type' => 'select',
676 'name' => 'type'
677 );
678 array_push($this->reportFilters, $filter_opt);
679
680 // append button to save the data when creating manual values
681 $filter_opt = array(
682 'label' => '<label class="vbo-report-hospedajes-manualsave" style="display: none;">Guests data</label>',
683 'html' => '<button type="button" class="btn vbo-config-btn vbo-report-hospedajes-manualsave" style="display: none;" onclick="vboHospedajesSaveData();"><i class="' . VikBookingIcons::i('save') . '"></i> ' . JText::translate('VBSAVE') . '</button>',
684 );
685 array_push($this->reportFilters, $filter_opt);
686
687 // datepicker calendars, select2 and triggers for the dropdown menus
688 $pfromdate = VikRequest::getString('fromdate', '', 'request');
689 $ptodate = VikRequest::getString('todate', '', 'request');
690
691 $js = 'var reportActiveCell = null, reportObj = {};
692 var vbo_hospedajes_ajax_uri = "' . VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=invoke_report&report=' . $this->reportFile) . '";
693 var vbo_hospedajes_save_icn = "' . VikBookingIcons::i('save') . '";
694 var vbo_hospedajes_saving_icn = "' . VikBookingIcons::i('circle-notch', 'fa-spin fa-fw') . '";
695 var vbo_hospedajes_saved_icn = "' . VikBookingIcons::i('check-circle') . '";
696 jQuery(function() {
697 //prepare main filters
698 jQuery(".vbo-report-datepicker:input").datepicker({
699 maxDate: "+7 days",
700 dateFormat: "'.$this->getDateFormat('jui').'",
701 onSelect: vboReportCheckDates
702 });
703 '.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').'
704 '.(!empty($ptodate) ? 'jQuery(".vbo-report-datepicker-to").datepicker("setDate", "'.$ptodate.'");' : '').'
705 //prepare filler helpers
706 jQuery("#vbo-report-hospedajes-hidden").children().detach().appendTo(".vbo-info-overlay-report");
707 jQuery("#choose-paytype").select2({placeholder: "- Tipo de pago -", width: "200px"});
708 jQuery("#choose-nazione").select2({placeholder: "- Seleccione un país -", width: "200px"});
709 jQuery("#choose-documento").select2({placeholder: "- Seleccione un tipo de documento -", width: "200px"});
710 jQuery("#choose-parentesco").select2({placeholder: "----", width: "200px"});
711 jQuery("#choose-municipio").select2({placeholder: "----", width: "200px"});
712 jQuery("#choose-sesso").select2({placeholder: "- Seleccione sexo -", width: "200px"});
713 jQuery("#choose-dbirth").datepicker({
714 maxDate: 0,
715 dateFormat: "dd/mm/yy",
716 changeMonth: true,
717 changeYear: true,
718 yearRange: "'.(date('Y') - 100).':'.date('Y').'"
719 });
720 //click events
721 jQuery(".vbo-report-load-nazione, .vbo-report-load-nazione-stay, .vbo-report-load-cittadinanza , .vbo-report-load-countrystay").click(function() {
722 reportActiveCell = this;
723 jQuery(".vbo-report-hospedajes-selcont").hide();
724 jQuery("#vbo-report-hospedajes-nazione").show();
725 vboShowOverlay({
726 title: "Completa la información",
727 extra_class: "vbo-modal-rounded vbo-modal-dialog vbo-modal-nofooter",
728 });
729 });
730 jQuery(".vbo-report-load-doctype").click(function() {
731 reportActiveCell = this;
732 jQuery(".vbo-report-hospedajes-selcont").hide();
733 jQuery("#vbo-report-hospedajes-doctype").show();
734 vboShowOverlay({
735 title: "Completa la información",
736 extra_class: "vbo-modal-rounded vbo-modal-dialog vbo-modal-nofooter",
737 });
738 });
739 jQuery(".vbo-report-load-paytype").click(function() {
740 reportActiveCell = this;
741 jQuery(".vbo-report-hospedajes-selcont").hide();
742 jQuery("#vbo-report-hospedajes-paytype").show();
743 vboShowOverlay({
744 title: "Completa la información",
745 extra_class: "vbo-modal-rounded vbo-modal-dialog vbo-modal-nofooter",
746 });
747 });
748 jQuery(".vbo-report-load-sesso").click(function() {
749 reportActiveCell = this;
750 jQuery(".vbo-report-hospedajes-selcont").hide();
751 jQuery("#vbo-report-hospedajes-sesso").show();
752 vboShowOverlay({
753 title: "Completa la información",
754 extra_class: "vbo-modal-rounded vbo-modal-dialog vbo-modal-nofooter",
755 });
756 });
757 jQuery(".vbo-report-load-docnum").click(function() {
758 reportActiveCell = this;
759 jQuery(".vbo-report-hospedajes-selcont").hide();
760 jQuery("#vbo-report-hospedajes-docnum").show();
761 vboShowOverlay({
762 title: "Completa la información",
763 extra_class: "vbo-modal-rounded vbo-modal-dialog",
764 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenDocnum(document.getElementById(\'choose-docnum\').value);\">Guardar</button>",
765 });
766 setTimeout(function(){jQuery("#choose-docnum").focus();}, 500);
767 });
768 jQuery(".vbo-report-load-docsoporte").click(function() {
769 reportActiveCell = this;
770 jQuery(".vbo-report-hospedajes-selcont").hide();
771 jQuery("#vbo-report-hospedajes-docsoporte").show();
772 vboShowOverlay({
773 title: "Completa la información",
774 extra_class: "vbo-modal-rounded vbo-modal-dialog",
775 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenDocsoporte(document.getElementById(\'choose-docsoporte\').value);\">Guardar</button>",
776 });
777 setTimeout(function(){jQuery("#choose-docsoporte").focus();}, 500);
778 });
779 jQuery(".vbo-report-load-address").click(function() {
780 reportActiveCell = this;
781 jQuery(".vbo-report-hospedajes-selcont").hide();
782 jQuery("#vbo-report-hospedajes-address").show();
783 vboShowOverlay({
784 title: "Completa la información",
785 extra_class: "vbo-modal-rounded vbo-modal-dialog",
786 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenAddress(document.getElementById(\'choose-address\').value);\">Guardar</button>",
787 });
788 setTimeout(function(){jQuery("#choose-address").focus();}, 500);
789 });
790 jQuery(".vbo-report-load-parentesco").click(function() {
791 reportActiveCell = this;
792 jQuery(".vbo-report-hospedajes-selcont").hide();
793 jQuery("#vbo-report-hospedajes-parentesco").show();
794 vboShowOverlay({
795 title: "Completa la información",
796 extra_class: "vbo-modal-rounded vbo-modal-dialog",
797 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenParentesco(document.getElementById(\'choose-parentesco\').value);\">Guardar</button>",
798 });
799 setTimeout(function(){jQuery("#choose-parentesco").focus();}, 500);
800 });
801 jQuery(".vbo-report-load-municipio").click(function() {
802 reportActiveCell = this;
803 jQuery(".vbo-report-hospedajes-selcont").hide();
804 jQuery("#vbo-report-hospedajes-municipio").show();
805 vboShowOverlay({
806 title: "Completa la información",
807 extra_class: "vbo-modal-rounded vbo-modal-dialog",
808 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenMunicipio(document.getElementById(\'choose-municipio\').value);\">Guardar</button>",
809 });
810 setTimeout(function(){jQuery("#choose-municipio").focus();}, 500);
811 });
812 jQuery(".vbo-report-load-city").click(function() {
813 reportActiveCell = this;
814 jQuery(".vbo-report-hospedajes-selcont").hide();
815 jQuery("#vbo-report-hospedajes-city").show();
816 vboShowOverlay({
817 title: "Completa la información",
818 extra_class: "vbo-modal-rounded vbo-modal-dialog",
819 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenCity(document.getElementById(\'choose-city\').value);\">Guardar</button>",
820 });
821 setTimeout(function(){jQuery("#choose-city").focus();}, 500);
822 });
823 jQuery(".vbo-report-load-postalcode").click(function() {
824 reportActiveCell = this;
825 jQuery(".vbo-report-hospedajes-selcont").hide();
826 jQuery("#vbo-report-hospedajes-postalcode").show();
827 vboShowOverlay({
828 title: "Completa la información",
829 extra_class: "vbo-modal-rounded vbo-modal-dialog",
830 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenPostalcode(document.getElementById(\'choose-postalcode\').value);\">Guardar</button>",
831 });
832 setTimeout(function(){jQuery("#choose-postalcode").focus();}, 500);
833 });
834 jQuery(".vbo-report-load-phone").click(function() {
835 reportActiveCell = this;
836 jQuery(".vbo-report-hospedajes-selcont").hide();
837 jQuery("#vbo-report-hospedajes-phone").show();
838 vboShowOverlay({
839 title: "Completa la información",
840 extra_class: "vbo-modal-rounded vbo-modal-dialog",
841 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenPhone(document.getElementById(\'choose-phone\').value);\">Guardar</button>",
842 });
843 setTimeout(function(){jQuery("#choose-phone").focus();}, 500);
844 });
845 jQuery(".vbo-report-load-email").click(function() {
846 reportActiveCell = this;
847 jQuery(".vbo-report-hospedajes-selcont").hide();
848 jQuery("#vbo-report-hospedajes-email").show();
849 vboShowOverlay({
850 title: "Completa la información",
851 extra_class: "vbo-modal-rounded vbo-modal-dialog",
852 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenEmail(document.getElementById(\'choose-email\').value);\">Guardar</button>",
853 });
854 setTimeout(function(){jQuery("#choose-email").focus();}, 500);
855 });
856 jQuery(".vbo-report-load-dbirth").click(function() {
857 reportActiveCell = this;
858 jQuery(".vbo-report-hospedajes-selcont").hide();
859 jQuery("#vbo-report-hospedajes-dbirth").show();
860 vboShowOverlay({
861 title: "Completa la información",
862 extra_class: "vbo-modal-rounded vbo-modal-dialog",
863 footer_right: "<button type=\"button\" class=\"btn btn-success\" onclick=\"vboReportChosenDbirth(document.getElementById(\'choose-dbirth\').value);\">Guardar</button>",
864 });
865 });
866 });
867 function vboReportCheckDates(selectedDate, inst) {
868 if (selectedDate === null || inst === null) {
869 return;
870 }
871 var cur_from_date = jQuery(this).val();
872 if (jQuery(this).hasClass("vbo-report-datepicker-from") && cur_from_date.length) {
873 var nowstart = jQuery(this).datepicker("getDate");
874 var nowstartdate = new Date(nowstart.getTime());
875 jQuery(".vbo-report-datepicker-to").datepicker("option", {minDate: nowstartdate});
876 }
877 }
878 function vboReportChosenTipodepago(pago) {
879 var c_code = pago.value;
880 var c_val = pago.options[pago.selectedIndex].text;
881
882 // apply the selected value to all guests of this booking
883 if (reportActiveCell !== null) {
884 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
885 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
886 alert("Error, cannot find element to update.");
887 return false;
888 }
889 var rep_act_cell = jQuery(reportActiveCell);
890 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
891 jQuery(\'[data-field="paytype"][data-fieldbid="\' + rep_guest_bid + \'"]\').each(function(k, v) {
892 let nowcell = jQuery(v);
893 nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(nowcell).closest("tr"));
894 nowcell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
895 if (!reportObj.hasOwnProperty(nowindex)) {
896 reportObj[nowindex] = {
897 bid: rep_guest_bid,
898 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
899 };
900 }
901 reportObj[nowindex]["paytype"] = c_code;
902 });
903 }
904
905 reportActiveCell = null;
906 vboHideOverlay();
907 jQuery("#choose-paytype").val("").select2("data", null, false);
908 jQuery(".vbo-report-hospedajes-manualsave").show();
909 }
910 function vboReportChosenNacionalidad(naz) {
911 var c_code = naz.value;
912 var c_val = naz.options[naz.selectedIndex].text;
913 if (reportActiveCell !== null) {
914 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
915 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
916 alert("Error, cannot find element to update.");
917 } else {
918 var rep_act_cell = jQuery(reportActiveCell);
919 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
920 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
921 if (!reportObj.hasOwnProperty(nowindex)) {
922 reportObj[nowindex] = {
923 bid: rep_guest_bid,
924 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
925 };
926 }
927 if (jQuery(reportActiveCell).hasClass("vbo-report-load-nazione")) {
928 reportObj[nowindex]["country_b"] = c_code;
929 } else if (jQuery(reportActiveCell).hasClass("vbo-report-load-nazione-stay")) {
930 reportObj[nowindex]["country_s"] = c_code;
931 } else if (jQuery(reportActiveCell).hasClass("vbo-report-load-docplace")) {
932 reportObj[nowindex]["docplace"] = c_code;
933 } else if (jQuery(reportActiveCell).hasClass("vbo-report-load-countrystay")) {
934 reportObj[nowindex]["country_s"] = c_code;
935 } else {
936 reportObj[nowindex]["country_c"] = c_code;
937 }
938 }
939 }
940 reportActiveCell = null;
941 vboHideOverlay();
942 jQuery("#choose-nazione").val("").select2("data", null, false);
943 jQuery(".vbo-report-hospedajes-manualsave").show();
944 }
945 function vboReportChosenDocumento(doctype) {
946 var c_code = doctype.value;
947 var c_val = doctype.options[doctype.selectedIndex].text;
948 if (reportActiveCell !== null) {
949 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
950 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
951 alert("Error, cannot find element to update.");
952 } else {
953 var rep_act_cell = jQuery(reportActiveCell);
954 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
955 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
956 if (!reportObj.hasOwnProperty(nowindex)) {
957 reportObj[nowindex] = {
958 bid: rep_guest_bid,
959 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
960 };
961 }
962 reportObj[nowindex]["doctype"] = c_code;
963 }
964 }
965 reportActiveCell = null;
966 vboHideOverlay();
967 jQuery("#choose-documento").val("").select2("data", null, false);
968 jQuery(".vbo-report-hospedajes-manualsave").show();
969 }
970 function vboReportChosenParentesco(parentesco) {
971 var c_code = parentesco.value;
972 var c_val = parentesco.options[parentesco.selectedIndex].text;
973 if (reportActiveCell !== null) {
974 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
975 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
976 alert("Error, cannot find element to update.");
977 } else {
978 var rep_act_cell = jQuery(reportActiveCell);
979 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
980 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
981 if (!reportObj.hasOwnProperty(nowindex)) {
982 reportObj[nowindex] = {
983 bid: rep_guest_bid,
984 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
985 };
986 }
987 reportObj[nowindex]["parentesco"] = c_code;
988 }
989 }
990 reportActiveCell = null;
991 vboHideOverlay();
992 jQuery("#choose-parentesco").val("").select2("data", null, false);
993 jQuery(".vbo-report-hospedajes-manualsave").show();
994 }
995 function vboReportChosenSesso(sesso) {
996 var c_code = sesso.value;
997 var c_val = sesso.options[sesso.selectedIndex].text;
998 if (reportActiveCell !== null) {
999 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1000 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1001 alert("Error, cannot find element to update.");
1002 } else {
1003 var rep_act_cell = jQuery(reportActiveCell);
1004 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1005 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1006 if (!reportObj.hasOwnProperty(nowindex)) {
1007 reportObj[nowindex] = {
1008 bid: rep_guest_bid,
1009 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1010 };
1011 }
1012 reportObj[nowindex]["gender"] = c_code;
1013 }
1014 }
1015 reportActiveCell = null;
1016 vboHideOverlay();
1017 jQuery("#choose-sesso").val("").select2("data", null, false);
1018 jQuery(".vbo-report-hospedajes-manualsave").show();
1019 }
1020 function vboReportChosenDocnum(val) {
1021 var c_code = val, c_val = val;
1022 if (reportActiveCell !== null) {
1023 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1024 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1025 alert("Error, cannot find element to update.");
1026 } else {
1027 var rep_act_cell = jQuery(reportActiveCell);
1028 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1029 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1030 if (!reportObj.hasOwnProperty(nowindex)) {
1031 reportObj[nowindex] = {
1032 bid: rep_guest_bid,
1033 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1034 };
1035 }
1036 reportObj[nowindex]["docnum"] = c_code;
1037 }
1038 }
1039 reportActiveCell = null;
1040 vboHideOverlay();
1041 jQuery("#choose-docnum").val("");
1042 jQuery(".vbo-report-hospedajes-manualsave").show();
1043 }
1044 function vboReportChosenDocsoporte(val) {
1045 var c_code = val, c_val = val;
1046 if (reportActiveCell !== null) {
1047 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1048 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1049 alert("Error, cannot find element to update.");
1050 } else {
1051 var rep_act_cell = jQuery(reportActiveCell);
1052 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1053 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1054 if (!reportObj.hasOwnProperty(nowindex)) {
1055 reportObj[nowindex] = {
1056 bid: rep_guest_bid,
1057 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1058 };
1059 }
1060 reportObj[nowindex]["docsoporte"] = c_code;
1061 }
1062 }
1063 reportActiveCell = null;
1064 vboHideOverlay();
1065 jQuery("#choose-docsoporte").val("");
1066 jQuery(".vbo-report-hospedajes-manualsave").show();
1067 }
1068 function vboReportChosenAddress(val) {
1069 var c_code = val, c_val = val;
1070 if (reportActiveCell !== null) {
1071 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1072 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1073 alert("Error, cannot find element to update.");
1074 } else {
1075 var rep_act_cell = jQuery(reportActiveCell);
1076 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1077 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1078 if (!reportObj.hasOwnProperty(nowindex)) {
1079 reportObj[nowindex] = {
1080 bid: rep_guest_bid,
1081 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1082 };
1083 }
1084 reportObj[nowindex]["address"] = c_code;
1085 }
1086 }
1087 reportActiveCell = null;
1088 vboHideOverlay();
1089 jQuery("#choose-address").val("");
1090 jQuery(".vbo-report-hospedajes-manualsave").show();
1091 }
1092 function vboReportChosenMunicipio(municipio) {
1093 var c_code = municipio.value;
1094 var c_val = municipio.options[municipio.selectedIndex].text;
1095 if (reportActiveCell !== null) {
1096 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1097 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1098 alert("Error, cannot find element to update.");
1099 } else {
1100 var rep_act_cell = jQuery(reportActiveCell);
1101 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1102 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1103 if (!reportObj.hasOwnProperty(nowindex)) {
1104 reportObj[nowindex] = {
1105 bid: rep_guest_bid,
1106 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1107 };
1108 }
1109 reportObj[nowindex]["municipio"] = c_code;
1110 }
1111 }
1112 reportActiveCell = null;
1113 vboHideOverlay();
1114 jQuery("#choose-municipio").val("").select2("data", null, false);
1115 jQuery(".vbo-report-hospedajes-manualsave").show();
1116 }
1117 function vboReportChosenCity(val) {
1118 var c_code = val, c_val = val;
1119 if (reportActiveCell !== null) {
1120 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1121 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1122 alert("Error, cannot find element to update.");
1123 } else {
1124 var rep_act_cell = jQuery(reportActiveCell);
1125 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1126 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1127 if (!reportObj.hasOwnProperty(nowindex)) {
1128 reportObj[nowindex] = {
1129 bid: rep_guest_bid,
1130 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1131 };
1132 }
1133 reportObj[nowindex]["city"] = c_code;
1134 }
1135 }
1136 reportActiveCell = null;
1137 vboHideOverlay();
1138 jQuery("#choose-city").val("");
1139 jQuery(".vbo-report-hospedajes-manualsave").show();
1140 }
1141 function vboReportChosenPostalcode(val) {
1142 var c_code = val, c_val = val;
1143 if (reportActiveCell !== null) {
1144 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1145 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1146 alert("Error, cannot find element to update.");
1147 } else {
1148 var rep_act_cell = jQuery(reportActiveCell);
1149 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1150 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1151 if (!reportObj.hasOwnProperty(nowindex)) {
1152 reportObj[nowindex] = {
1153 bid: rep_guest_bid,
1154 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1155 };
1156 }
1157 reportObj[nowindex]["postalcode"] = c_code;
1158 }
1159 }
1160 reportActiveCell = null;
1161 vboHideOverlay();
1162 jQuery("#choose-postalcode").val("");
1163 jQuery(".vbo-report-hospedajes-manualsave").show();
1164 }
1165 function vboReportChosenPhone(val) {
1166 var c_code = val, c_val = val;
1167 if (reportActiveCell !== null) {
1168 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1169 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1170 alert("Error, cannot find element to update.");
1171 } else {
1172 var rep_act_cell = jQuery(reportActiveCell);
1173 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1174 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1175 if (!reportObj.hasOwnProperty(nowindex)) {
1176 reportObj[nowindex] = {
1177 bid: rep_guest_bid,
1178 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1179 };
1180 }
1181 reportObj[nowindex]["phone"] = c_code;
1182 }
1183 }
1184 reportActiveCell = null;
1185 vboHideOverlay();
1186 jQuery("#choose-phone").val("");
1187 jQuery(".vbo-report-hospedajes-manualsave").show();
1188 }
1189 function vboReportChosenEmail(val) {
1190 var c_code = val, c_val = val;
1191 if (reportActiveCell !== null) {
1192 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1193 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1194 alert("Error, cannot find element to update.");
1195 } else {
1196 var rep_act_cell = jQuery(reportActiveCell);
1197 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1198 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1199 if (!reportObj.hasOwnProperty(nowindex)) {
1200 reportObj[nowindex] = {
1201 bid: rep_guest_bid,
1202 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1203 };
1204 }
1205 reportObj[nowindex]["email"] = c_code;
1206 }
1207 }
1208 reportActiveCell = null;
1209 vboHideOverlay();
1210 jQuery("#choose-email").val("");
1211 jQuery(".vbo-report-hospedajes-manualsave").show();
1212 }
1213 function vboReportChosenDbirth(val) {
1214 var c_code = val, c_val = val;
1215 if (reportActiveCell !== null) {
1216 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
1217 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
1218 alert("Error, cannot find element to update.");
1219 } else {
1220 var rep_act_cell = jQuery(reportActiveCell);
1221 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
1222 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
1223 if (!reportObj.hasOwnProperty(nowindex)) {
1224 reportObj[nowindex] = {
1225 bid: rep_guest_bid,
1226 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
1227 };
1228 }
1229 reportObj[nowindex]["date_birth"] = c_code;
1230 }
1231 }
1232 reportActiveCell = null;
1233 vboHideOverlay();
1234 jQuery("#choose-dbirth").val("");
1235 jQuery(".vbo-report-hospedajes-manualsave").show();
1236 }
1237 //download function
1238 function vboDownloadHospedajes(type, report_type) {
1239 if (!confirm("¿Estás seguro de que quieres continuar?")) {
1240 return false;
1241 }
1242
1243 let use_blank = true;
1244 if (typeof type === "undefined") {
1245 type = 1;
1246 } else {
1247 use_blank = false;
1248 }
1249
1250 if (typeof report_type !== "undefined") {
1251 jQuery(\'#adminForm\').find(\'select[name="type"]\').val(report_type).trigger(\'change\');
1252 }
1253
1254 if (use_blank) {
1255 document.adminForm.target = "_blank";
1256 document.adminForm.action += "&tmpl=component";
1257 }
1258
1259 vboSetFilters({exportreport: type, filler: JSON.stringify(reportObj)}, true);
1260
1261 setTimeout(function() {
1262 document.adminForm.target = "";
1263 document.adminForm.action = document.adminForm.action.replace("&tmpl=component", "");
1264 vboSetFilters({exportreport: "0", filler: ""}, false);
1265 }, 1000);
1266 }
1267 // save data function
1268 function vboHospedajesSaveData() {
1269 jQuery("button.vbo-report-hospedajes-manualsave").find("i").attr("class", vbo_hospedajes_saving_icn);
1270 VBOCore.doAjax(
1271 vbo_hospedajes_ajax_uri,
1272 {
1273 call: "updatePaxData",
1274 params: reportObj,
1275 tmpl: "component"
1276 },
1277 function(response) {
1278 if (!response || !response[0]) {
1279 alert("An error occurred.");
1280 return false;
1281 }
1282 jQuery("button.vbo-report-hospedajes-manualsave").addClass("btn-success").find("i").attr("class", vbo_hospedajes_saved_icn);
1283 },
1284 function(error) {
1285 alert(error.responseText);
1286 jQuery("button.vbo-report-hospedajes-manualsave").removeClass("btn-success").find("i").attr("class", vbo_hospedajes_save_icn);
1287 }
1288 );
1289 }
1290 ';
1291 $this->setScript($js);
1292
1293 return $this->reportFilters;
1294 }
1295
1296 /**
1297 * Loads the report data from the DB.
1298 * Returns true in case of success, false otherwise.
1299 * Sets the columns and rows for the report to be displayed.
1300 *
1301 * @return boolean
1302 */
1303 public function getReportData()
1304 {
1305 if ($this->getError()) {
1306 // export functions may set errors rather than exiting the process, and
1307 // the View may continue the execution to attempt to render the report.
1308 return false;
1309 }
1310
1311 if ($this->rows) {
1312 // method must have run already
1313 return true;
1314 }
1315
1316 // get the possibly injected report options
1317 $options = $this->getReportOptions();
1318
1319 // injected options will replace request variables, if any
1320 $opt_fromdate = $options->get('fromdate', '');
1321 $opt_todate = $options->get('todate', '');
1322 $opt_type = $options->get('type', '');
1323
1324 // input fields and other vars
1325 $pfromdate = $opt_fromdate ?: VikRequest::getString('fromdate', '', 'request');
1326 $ptodate = $opt_todate ?: VikRequest::getString('todate', '', 'request');
1327 $ptype = $opt_type ?: VikRequest::getString('type', 'checkin', 'request');
1328
1329 $pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request');
1330 $pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort;
1331 $pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request');
1332 $pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder;
1333 $pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC';
1334 $plistings = ((array) VikRequest::getVar('listings', array())) ?: ((array) $options->get('listings', []));
1335 $plistings = array_filter(array_map('intval', $plistings));
1336
1337 $currency_symb = VikBooking::getCurrencySymb();
1338 $df = $this->getDateFormat();
1339 $datesep = VikBooking::getDateSeparator();
1340
1341 if (empty($ptodate)) {
1342 $ptodate = $pfromdate;
1343 }
1344
1345 // get date timestamps
1346 $from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
1347 $to_ts = VikBooking::getDateTimestamp($ptodate, 23, 59, 59);
1348 if (empty($pfromdate) || empty($from_ts) || empty($to_ts)) {
1349 $this->setError(JText::translate('VBOREPORTSERRNODATES'));
1350 return false;
1351 }
1352
1353 // set the dates being exported
1354 $this->exported_checkin_dates = [
1355 date('Y-m-d', $from_ts),
1356 date('Y-m-d', $to_ts),
1357 ];
1358
1359 // query to obtain the records (all check-ins or reservations created within the dates filter)
1360 $q = $this->dbo->getQuery(true)
1361 ->select([
1362 $this->dbo->qn('o.id'),
1363 $this->dbo->qn('o.custdata'),
1364 $this->dbo->qn('o.ts'),
1365 $this->dbo->qn('o.days'),
1366 $this->dbo->qn('o.checkin'),
1367 $this->dbo->qn('o.checkout'),
1368 $this->dbo->qn('o.totpaid'),
1369 $this->dbo->qn('o.roomsnum'),
1370 $this->dbo->qn('o.total'),
1371 $this->dbo->qn('o.idorderota'),
1372 $this->dbo->qn('o.channel'),
1373 $this->dbo->qn('o.country'),
1374 $this->dbo->qn('o.custmail'),
1375 $this->dbo->qn('o.phone'),
1376 $this->dbo->qn('or.idorder'),
1377 $this->dbo->qn('or.idroom'),
1378 $this->dbo->qn('or.adults'),
1379 $this->dbo->qn('or.children'),
1380 $this->dbo->qn('or.t_first_name'),
1381 $this->dbo->qn('or.t_last_name'),
1382 $this->dbo->qn('or.cust_cost'),
1383 $this->dbo->qn('or.cust_idiva'),
1384 $this->dbo->qn('or.extracosts'),
1385 $this->dbo->qn('or.room_cost'),
1386 $this->dbo->qn('co.idcustomer'),
1387 $this->dbo->qn('co.pax_data'),
1388 $this->dbo->qn('c.first_name'),
1389 $this->dbo->qn('c.last_name'),
1390 $this->dbo->qn('c.country', 'customer_country'),
1391 $this->dbo->qn('c.address'),
1392 $this->dbo->qn('c.doctype'),
1393 $this->dbo->qn('c.docnum'),
1394 $this->dbo->qn('c.gender'),
1395 $this->dbo->qn('c.bdate'),
1396 $this->dbo->qn('c.pbirth'),
1397 ])
1398 ->from($this->dbo->qn('#__vikbooking_orders', 'o'))
1399 ->leftJoin($this->dbo->qn('#__vikbooking_ordersrooms', 'or') . ' ON ' . $this->dbo->qn('or.idorder') . ' = ' . $this->dbo->qn('o.id'))
1400 ->leftJoin($this->dbo->qn('#__vikbooking_customers_orders', 'co') . ' ON ' . $this->dbo->qn('co.idorder') . ' = ' . $this->dbo->qn('o.id'))
1401 ->leftJoin($this->dbo->qn('#__vikbooking_customers', 'c') . ' ON ' . $this->dbo->qn('c.id') . ' = ' . $this->dbo->qn('co.idcustomer'))
1402 ->where($this->dbo->qn('o.status') . ' = ' . $this->dbo->q('confirmed'))
1403 ->where($this->dbo->qn('o.closure') . ' = 0')
1404 ->order($this->dbo->qn('o.checkin') . ' ASC')
1405 ->order($this->dbo->qn('o.id') . ' ASC')
1406 ->order($this->dbo->qn('or.id') . ' ASC');
1407
1408 if ($plistings) {
1409 $q->where($this->dbo->qn('or.idroom') . ' IN (' . implode(', ', $plistings) . ')');
1410 }
1411
1412 if ($ptype === 'reservation') {
1413 $q->where($this->dbo->qn('o.ts') . ' >= ' . $from_ts);
1414 $q->where($this->dbo->qn('o.ts') . ' <= ' . $to_ts);
1415 } else {
1416 $q->where($this->dbo->qn('o.checkin') . ' >= ' . $from_ts);
1417 $q->where($this->dbo->qn('o.checkin') . ' <= ' . $to_ts);
1418 }
1419
1420 $this->dbo->setQuery($q);
1421 $records = $this->dbo->loadAssocList();
1422
1423 if (!$records) {
1424 $this->setError(JText::translate('VBOREPORTSERRNORESERV'));
1425 $this->setError('No hay reservas con los filtros seleccionados.');
1426 return false;
1427 }
1428
1429 // nest records with multiple rooms booked inside sub-array
1430 $bookings = [];
1431 foreach ($records as $v) {
1432 if (!isset($bookings[$v['id']])) {
1433 $bookings[$v['id']] = [];
1434 }
1435 array_push($bookings[$v['id']], $v);
1436 }
1437
1438 // free some memory up
1439 unset($records);
1440
1441 // define the columns of the report
1442 $this->cols = array(
1443 // guest type (rol)
1444 array(
1445 'key' => 'guest_type',
1446 'label' => 'Rol',
1447 ),
1448 // first name
1449 array(
1450 'key' => 'first_name',
1451 'label' => JText::translate('VBTRAVELERNAME'),
1452 ),
1453 // last name
1454 array(
1455 'key' => 'last_name',
1456 'label' => JText::translate('VBTRAVELERLNAME'),
1457 ),
1458 // reservation date
1459 array(
1460 'key' => 'resdate',
1461 'attr' => array(
1462 'class="center"'
1463 ),
1464 'label' => JText::translate('VBPCHOOSEBUSYORDATE'),
1465 'ignore_view' => 1,
1466 ),
1467 // number of guests
1468 array(
1469 'key' => 'numguests',
1470 'attr' => array(
1471 'class="center"'
1472 ),
1473 'label' => JText::translate('VBPVIEWORDERSPEOPLE'),
1474 'ignore_view' => 1,
1475 ),
1476 // sexo
1477 array(
1478 'key' => 'gender',
1479 'attr' => array(
1480 'class="center"'
1481 ),
1482 'label' => JText::translate('VBCUSTOMERGENDER'),
1483 ),
1484 // id type
1485 array(
1486 'key' => 'doctype',
1487 'attr' => array(
1488 'class="center"'
1489 ),
1490 'label' => JText::translate('VBCUSTOMERDOCTYPE'),
1491 ),
1492 // id number
1493 array(
1494 'key' => 'docnum',
1495 'attr' => array(
1496 'class="center"'
1497 ),
1498 'label' => JText::translate('VBCUSTOMERDOCNUM'),
1499 ),
1500 // número de soporte del documento
1501 array(
1502 'key' => 'docsoporte',
1503 'attr' => array(
1504 'class="center"'
1505 ),
1506 'label' => 'Número de soporte',
1507 'tip' => 'Número de soporte del documento. Obligatorio si el tipo de documento es NIF, NIE.',
1508 ),
1509 // birth date
1510 array(
1511 'key' => 'date_birth',
1512 'attr' => array(
1513 'class="center"'
1514 ),
1515 'label' => JText::translate('VBCUSTOMERBDATE'),
1516 ),
1517 // parentesco
1518 array(
1519 'key' => 'parentesco',
1520 'label' => 'Parentesco',
1521 'attr' => array(
1522 'class="center"'
1523 ),
1524 'tip' => 'Si alguna de las personas es menor de edad, al menos una de las personas mayores de edad ha de tener informada su relación de parentesco con esta persona menor de edad.',
1525 ),
1526 // address
1527 array(
1528 'key' => 'address',
1529 'attr' => array(
1530 'class="center"'
1531 ),
1532 'label' => JText::translate('ORDER_ADDRESS'),
1533 ),
1534 // codigo municipio
1535 array(
1536 'key' => 'municipio',
1537 'label' => 'Municipio',
1538 'attr' => array(
1539 'class="center"'
1540 ),
1541 'tip' => 'Código de municipio del INE (5 dígitos). Obligatorio cuando el país es España.',
1542 ),
1543 // city
1544 array(
1545 'key' => 'city',
1546 'label' => JText::translate('ORDER_CITY'),
1547 ),
1548 // postal code
1549 array(
1550 'key' => 'postalcode',
1551 'attr' => array(
1552 'class="center"'
1553 ),
1554 'label' => JText::translate('ORDER_ZIP'),
1555 ),
1556 // country of stay
1557 array(
1558 'key' => 'country_s',
1559 'attr' => array(
1560 'class="center"'
1561 ),
1562 'label' => JText::translate('ORDER_STATE'),
1563 ),
1564 // citizenship
1565 array(
1566 'key' => 'country_c',
1567 'attr' => array(
1568 'class="center"'
1569 ),
1570 'label' => JText::translate('VBCUSTOMERNATION'),
1571 ),
1572 // phone
1573 array(
1574 'key' => 'phone',
1575 'attr' => array(
1576 'class="center"'
1577 ),
1578 'label' => JText::translate('ORDER_PHONE'),
1579 ),
1580 // email
1581 array(
1582 'key' => 'email',
1583 'attr' => array(
1584 'class="center"'
1585 ),
1586 'label' => JText::translate('ORDER_EMAIL'),
1587 ),
1588 // payment type
1589 array(
1590 'key' => 'paytype',
1591 'attr' => array(
1592 'class="center"'
1593 ),
1594 'label' => JText::translate('VBLIBPAYNAME'),
1595 ),
1596 // check-in
1597 array(
1598 'key' => 'checkin',
1599 'attr' => array(
1600 'class="center"'
1601 ),
1602 'label' => JText::translate('VBPICKUPAT'),
1603 ),
1604 // check-out
1605 array(
1606 'key' => 'checkout',
1607 'attr' => array(
1608 'class="center"'
1609 ),
1610 'label' => JText::translate('VBRELEASEAT'),
1611 ),
1612 // id booking
1613 array(
1614 'key' => 'idbooking',
1615 'attr' => array(
1616 'class="center"'
1617 ),
1618 'label' => 'ID',
1619 ),
1620 );
1621
1622 // codigo municipio list
1623 $municipio_codes_list = VBOCheckinPaxfieldTypeSpainMunicipio::loadMunicipioCodes();
1624
1625 // loop over the bookings to build the rows of the report
1626 $from_info = getdate($from_ts);
1627 foreach ($bookings as $gbook) {
1628 // count the total number of guests for all rooms of this booking
1629 $tot_booking_guests = 0;
1630 $tot_booking_adults = 0;
1631 $tot_booking_children = 0;
1632 $room_guests = [];
1633 foreach ($gbook as $rbook) {
1634 $tot_booking_guests += ($rbook['adults'] + $rbook['children']);
1635 $tot_booking_adults += $rbook['adults'];
1636 $tot_booking_children += $rbook['children'];
1637 $room_guests[] = ($rbook['adults'] + $rbook['children']);
1638 }
1639
1640 // make sure to decode the current pax data
1641 if (!empty($gbook[0]['pax_data'])) {
1642 $gbook[0]['pax_data'] = (array) json_decode($gbook[0]['pax_data'], true);
1643 }
1644
1645 // push a copy of the booking for each guest (adults and children)
1646 $guests_rows = [];
1647 for ($i = 1; $i <= $tot_booking_guests; $i++) {
1648 array_push($guests_rows, $gbook[0]);
1649 }
1650
1651 // create one row for each guest
1652 $guest_ind = 1;
1653 foreach ($guests_rows as $ind => $guests) {
1654 // prepare row record for this room-guest
1655 $insert_row = [];
1656
1657 // find the actual guest-room-index
1658 $guest_room_ind = $this->calcGuestRoomIndex($room_guests, $guest_ind);
1659
1660 // guest type (rol)
1661 $pax_guesttype = $ptype === 'reservation' && $guest_room_ind < 2 ? 'TI' : 'VI';
1662
1663 array_push($insert_row, array(
1664 'key' => 'guest_type',
1665 'callback' => function($val) {
1666 return $this->guest_types[$val] ?? $val;
1667 },
1668 'no_export_callback' => 1,
1669 'value' => $pax_guesttype,
1670 ));
1671
1672 // name
1673 $nome = !empty($guests['t_first_name']) ? $guests['t_first_name'] : $guests['first_name'];
1674 $pax_nome = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'first_name');
1675 $nome = !empty($pax_nome) ? $pax_nome : $nome;
1676 array_push($insert_row, array(
1677 'key' => 'first_name',
1678 'value' => $nome,
1679 ));
1680
1681 // last name
1682 $cognome = !empty($guests['t_last_name']) ? $guests['t_last_name'] : $guests['last_name'];
1683 $pax_cognome = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'last_name');
1684 $cognome = !empty($pax_cognome) ? $pax_cognome : $cognome;
1685 array_push($insert_row, array(
1686 'key' => 'last_name',
1687 'value' => $cognome,
1688 ));
1689
1690 // reservation date
1691 array_push($insert_row, array(
1692 'key' => 'resdate',
1693 'attr' => array(
1694 'class="center"'
1695 ),
1696 'callback' => function($val) {
1697 return date('Y-m-d', $val);
1698 },
1699 'value' => $guests['ts'],
1700 'ignore_view' => 1,
1701 ));
1702
1703 // number of guests
1704 array_push($insert_row, array(
1705 'key' => 'numguests',
1706 'attr' => array(
1707 'class="center"'
1708 ),
1709 'value' => $tot_booking_guests,
1710 'ignore_view' => 1,
1711 ));
1712
1713 // Sexo
1714 $gender = !empty($guests['gender']) && $guest_ind < 2 ? strtoupper($guests['gender']) : '';
1715 $pax_gender = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'gender');
1716 $gender = !empty($pax_gender) ? $pax_gender : $gender;
1717
1718 /**
1719 * We make sure the gender will be compatible with both back-end and front-end
1720 * check-in/registration data collection driver and processes.
1721 */
1722 if (is_numeric($gender)) {
1723 $gender = (int) $gender;
1724 } elseif (!strcasecmp($gender, 'H')) {
1725 $gender = 1;
1726 } elseif (!strcasecmp($gender, 'F') || !strcasecmp($gender, 'M')) {
1727 $gender = 2;
1728 } elseif (!strcasecmp($gender, 'O')) {
1729 $gender = -1;
1730 }
1731
1732 // normalize value to hospedajes codes
1733 switch ($gender) {
1734 case 2:
1735 // mujer
1736 $spain_gender = 'M';
1737 break;
1738
1739 case 1:
1740 // hombre
1741 $spain_gender = 'H';
1742 break;
1743
1744 default:
1745 // otro
1746 $spain_gender = 'O';
1747 break;
1748 }
1749
1750 $sexo_elem_class = '';
1751 if (empty($gender)) {
1752 // optional selection style
1753 $sexo_elem_class = ' vbo-report-load-sesso vbo-report-load-field-optional';
1754 } else {
1755 // rectify selection style
1756 $sexo_elem_class = ' vbo-report-load-sesso vbo-report-load-elem-filled';
1757 }
1758
1759 array_push($insert_row, array(
1760 'key' => 'gender',
1761 'attr' => array(
1762 'class="center' . $sexo_elem_class . '"'
1763 ),
1764 'callback' => function($val) {
1765 return $this->sexo[$val] ?? 'O';
1766 },
1767 'no_export_callback' => 1,
1768 'value' => $spain_gender,
1769 ));
1770
1771 // document type
1772 $pax_doctype = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'doctype');
1773
1774 $doctype = $pax_doctype ?: '';
1775
1776 if (strlen((string) $doctype) === 1) {
1777 // try to normalize the doc-type value from pre-checkin
1778 switch ($doctype) {
1779 case 'D':
1780 case 'C':
1781 case 'I':
1782 $doctype = 'NIF';
1783 break;
1784
1785 case 'N':
1786 case 'X':
1787 $doctype = 'NIE';
1788 break;
1789
1790 case 'P':
1791 $doctype = 'PAS';
1792 break;
1793
1794 default:
1795 $doctype = $doctype;
1796 break;
1797 }
1798 }
1799
1800 $doctype_elem_class = '';
1801 if (empty($doctype)) {
1802 // optional selection style
1803 $doctype_elem_class = ' vbo-report-load-doctype vbo-report-load-field-optional';
1804 } else {
1805 // rectify selection style
1806 $doctype_elem_class = ' vbo-report-load-doctype vbo-report-load-elem-filled';
1807 }
1808
1809 array_push($insert_row, array(
1810 'key' => 'doctype',
1811 'attr' => array(
1812 'class="center' . $doctype_elem_class . '"'
1813 ),
1814 'callback' => function($val) {
1815 return empty($val) ? '?' : $val;
1816 },
1817 'no_export_callback' => 1,
1818 'value' => $doctype,
1819 ));
1820
1821 // document number
1822 $pax_docnum = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'docnum');
1823
1824 $docnum = $pax_docnum ?: '';
1825
1826 $docnum_elem_class = '';
1827 if (empty($docnum)) {
1828 // optional selection style
1829 $docnum_elem_class = ' vbo-report-load-docnum vbo-report-load-field-optional';
1830 } else {
1831 // rectify selection style
1832 $docnum_elem_class = ' vbo-report-load-docnum vbo-report-load-elem-filled';
1833 }
1834
1835 array_push($insert_row, array(
1836 'key' => 'docnum',
1837 'attr' => array(
1838 'class="center' . $docnum_elem_class . '"'
1839 ),
1840 'callback' => function($val) {
1841 return empty($val) ? '?' : $val;
1842 },
1843 'no_export_callback' => 1,
1844 'value' => $docnum,
1845 ));
1846
1847 // número de soporte del documento
1848 $pax_docsoporte = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'docsoporte');
1849
1850 $docsoporte = $pax_docsoporte ?: '';
1851
1852 $docsoporte_elem_class = '';
1853 if (empty($docsoporte)) {
1854 // optional selection style
1855 $docsoporte_elem_class = ' vbo-report-load-docsoporte vbo-report-load-field-optional';
1856 } else {
1857 // rectify selection style
1858 $docsoporte_elem_class = ' vbo-report-load-docsoporte vbo-report-load-elem-filled';
1859 }
1860
1861 array_push($insert_row, array(
1862 'key' => 'docsoporte',
1863 'attr' => array(
1864 'class="center' . $docsoporte_elem_class . '"'
1865 ),
1866 'callback' => function($val) {
1867 return empty($val) ? '?' : $val;
1868 },
1869 'no_export_callback' => 1,
1870 'value' => $docsoporte,
1871 ));
1872
1873 // birth date (attempt to convert it into a timestamp to support any date format)
1874 $dbirth = !empty($guests['bdate']) && $guest_ind < 2 ? VikBooking::getDateTimestamp($guests['bdate'], 0, 0) : '';
1875 $pax_dbirth = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'date_birth');
1876 $dbirth = $pax_dbirth ?: $dbirth;
1877 $dbirth = !is_numeric($dbirth) ? VikBooking::getDateTimestamp($dbirth) : $dbirth;
1878
1879 $dbirth_elem_class = '';
1880 if (empty($dbirth)) {
1881 // optional selection style
1882 $dbirth_elem_class = ' vbo-report-load-dbirth vbo-report-load-field-optional';
1883 } else {
1884 // rectify selection style
1885 $dbirth_elem_class = ' vbo-report-load-dbirth vbo-report-load-elem-filled';
1886 }
1887
1888 array_push($insert_row, array(
1889 'key' => 'date_birth',
1890 'attr' => array(
1891 'class="center' . $dbirth_elem_class . '"'
1892 ),
1893 'callback' => function($val) {
1894 if (!empty($val) && is_numeric($val)) {
1895 return date('Y-m-d', $val);
1896 }
1897 return $val ?: '?';
1898 },
1899 'value' => $dbirth,
1900 ));
1901
1902 // parentesco
1903 $pax_parentesco = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'parentesco');
1904
1905 $parentesco = $pax_parentesco ?: '';
1906
1907 $parentesco_elem_class = '';
1908 if (empty($parentesco)) {
1909 // optional selection style
1910 $parentesco_elem_class = ' vbo-report-load-parentesco vbo-report-load-field vbo-report-load-field-optional';
1911 } else {
1912 // rectify selection style
1913 $parentesco_elem_class = ' vbo-report-load-parentesco vbo-report-load-field vbo-report-load-elem-filled';
1914 }
1915
1916 array_push($insert_row, array(
1917 'key' => 'parentesco',
1918 'attr' => array(
1919 'class="center' . $parentesco_elem_class . '"'
1920 ),
1921 'callback' => function($val) use ($tot_booking_children) {
1922 if (!$tot_booking_children) {
1923 return '';
1924 }
1925 return empty($val) ? '?' : ($this->relacion_de_parentesco[$val] ?? $val);
1926 },
1927 'no_export_callback' => 1,
1928 'value' => $parentesco,
1929 ));
1930
1931 // address
1932 $pax_address = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'address');
1933
1934 $address = $pax_address ?: '';
1935
1936 $address_elem_class = '';
1937 if (empty($address)) {
1938 // optional selection style
1939 $address_elem_class = ' vbo-report-load-address vbo-report-load-field vbo-report-load-field-optional';
1940 } else {
1941 // rectify selection style
1942 $address_elem_class = ' vbo-report-load-address vbo-report-load-field vbo-report-load-elem-filled';
1943 }
1944
1945 array_push($insert_row, array(
1946 'key' => 'address',
1947 'attr' => array(
1948 'class="center' . $address_elem_class . '"'
1949 ),
1950 'callback' => function($val) {
1951 return empty($val) ? '?' : $val;
1952 },
1953 'no_export_callback' => 1,
1954 'value' => $address,
1955 ));
1956
1957 // codigo municipio
1958 $pax_municipio = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'municipio');
1959
1960 $municipio = $pax_municipio ?: '';
1961
1962 $municipio_elem_class = '';
1963 if (empty($municipio)) {
1964 // optional selection style
1965 $municipio_elem_class = ' vbo-report-load-municipio vbo-report-load-field vbo-report-load-field-optional';
1966 } else {
1967 // rectify selection style
1968 $municipio_elem_class = ' vbo-report-load-municipio vbo-report-load-field vbo-report-load-elem-filled';
1969 }
1970
1971 array_push($insert_row, array(
1972 'key' => 'municipio',
1973 'attr' => array(
1974 'class="center' . $municipio_elem_class . '"'
1975 ),
1976 'callback' => function($val) use ($municipio_codes_list) {
1977 return empty($val) ? '?' : ($municipio_codes_list[$val] ?? $val);
1978 },
1979 'no_export_callback' => 1,
1980 'value' => $municipio,
1981 ));
1982
1983 // city
1984 $pax_city = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'city');
1985
1986 $city = $pax_city ?: '';
1987
1988 $city_elem_class = '';
1989 if (empty($city)) {
1990 // optional selection style
1991 $city_elem_class = ' vbo-report-load-city vbo-report-load-field vbo-report-load-field-optional';
1992 } else {
1993 // rectify selection style
1994 $city_elem_class = ' vbo-report-load-city vbo-report-load-field vbo-report-load-elem-filled';
1995 }
1996
1997 array_push($insert_row, array(
1998 'key' => 'city',
1999 'attr' => array(
2000 'class="center' . $city_elem_class . '"'
2001 ),
2002 'callback' => function($val) {
2003 return empty($val) ? '?' : $val;
2004 },
2005 'no_export_callback' => 1,
2006 'value' => $city,
2007 ));
2008
2009 // postal code
2010 $pax_postalcode = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'postalcode');
2011
2012 $postalcode = $pax_postalcode ?: '';
2013
2014 $postalcode_elem_class = '';
2015 if (empty($postalcode)) {
2016 // optional selection style
2017 $postalcode_elem_class = ' vbo-report-load-postalcode vbo-report-load-field vbo-report-load-field-optional';
2018 } else {
2019 // rectify selection style
2020 $postalcode_elem_class = ' vbo-report-load-postalcode vbo-report-load-field vbo-report-load-elem-filled';
2021 }
2022
2023 array_push($insert_row, array(
2024 'key' => 'postalcode',
2025 'attr' => array(
2026 'class="center' . $postalcode_elem_class . '"'
2027 ),
2028 'callback' => function($val) {
2029 return empty($val) ? '?' : $val;
2030 },
2031 'no_export_callback' => 1,
2032 'value' => $postalcode,
2033 ));
2034
2035 // country of stay
2036 $pax_country_s = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'country_s');
2037
2038 // check country field from pre-checkin
2039 $pax_citizen = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'country');
2040
2041 $countrystay = !empty($guests['country']) ? $guests['country'] : '';
2042 $countrystay = $pax_citizen ?: $countrystay;
2043 $countrystay = $pax_country_s ?: $countrystay;
2044
2045 $country_elem_class = '';
2046 if (empty($countrystay)) {
2047 // optional selection style
2048 $country_elem_class = ' vbo-report-load-countrystay vbo-report-load-field-optional';
2049 } else {
2050 // rectify selection style
2051 $country_elem_class = ' vbo-report-load-countrystay vbo-report-load-elem-filled';
2052 }
2053
2054 array_push($insert_row, array(
2055 'key' => 'country_s',
2056 'attr' => array(
2057 'class="center' . $country_elem_class . '"'
2058 ),
2059 'callback' => function($val) {
2060 return $val ?: '---';
2061 },
2062 'no_export_callback' => 1,
2063 'value' => $countrystay,
2064 ));
2065
2066 // citizenship
2067 $pax_country_c = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'country_c');
2068
2069 // check country field from pre-checkin
2070 $pax_citizen = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'country');
2071
2072 $citizen = !empty($guests['country']) ? $guests['country'] : '';
2073 $citizen = $pax_citizen ?: $citizen;
2074 $citizen = $pax_country_c ?: $citizen;
2075
2076 $country_elem_class = '';
2077 if (empty($citizen)) {
2078 // optional selection style
2079 $country_elem_class = ' vbo-report-load-cittadinanza vbo-report-load-field-optional';
2080 } else {
2081 // rectify selection style
2082 $country_elem_class = ' vbo-report-load-cittadinanza vbo-report-load-elem-filled';
2083 }
2084
2085 array_push($insert_row, array(
2086 'key' => 'country_c',
2087 'attr' => array(
2088 'class="center' . $country_elem_class . '"'
2089 ),
2090 'callback' => function($val) {
2091 return $val ?: '---';
2092 },
2093 'no_export_callback' => 1,
2094 'value' => $citizen,
2095 ));
2096
2097 // direccion (phone number or email address from pre-checkin fields)
2098 $pax_direccion = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'direccion');
2099
2100 $direccion_phone = preg_match("/^[0-9\+\s]+$/", (string) $pax_direccion) ? $pax_direccion : '';
2101
2102 // phone number
2103 $pax_phone = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'phone');
2104
2105 $phone = $direccion_phone;
2106 $phone = empty($phone) && !empty($guests['phone']) ? $guests['phone'] : $phone;
2107 $phone = $pax_phone ?: $phone;
2108
2109 $phone_elem_class = '';
2110 if (empty($phone)) {
2111 // optional selection style
2112 $phone_elem_class = ' vbo-report-load-phone vbo-report-load-field vbo-report-load-field-optional';
2113 } else {
2114 // rectify selection style
2115 $phone_elem_class = ' vbo-report-load-phone vbo-report-load-field vbo-report-load-elem-filled';
2116 }
2117
2118 array_push($insert_row, array(
2119 'key' => 'phone',
2120 'attr' => array(
2121 'class="center' . $phone_elem_class . '"'
2122 ),
2123 'callback' => function($val) {
2124 return $val ?: '---';
2125 },
2126 'no_export_callback' => 1,
2127 'value' => $phone,
2128 ));
2129
2130 // direccion (phone number or email address from pre-checkin fields)
2131 $pax_direccion = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'direccion');
2132
2133 $direccion_email = preg_match("/^.+@.+\..+$/", (string) $pax_direccion) ? $pax_direccion : '';
2134
2135 // email
2136 $pax_email = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'email');
2137
2138 $email = $direccion_email;
2139 $email = empty($email) && !empty($guests['custmail']) ? $guests['custmail'] : $email;
2140 $email = $pax_email ?: $email;
2141
2142 $email_elem_class = '';
2143 if (empty($email)) {
2144 // optional selection style
2145 $email_elem_class = ' vbo-report-load-email vbo-report-load-field vbo-report-load-field-optional';
2146 } else {
2147 // rectify selection style
2148 $email_elem_class = ' vbo-report-load-email vbo-report-load-field vbo-report-load-elem-filled';
2149 }
2150
2151 array_push($insert_row, array(
2152 'key' => 'email',
2153 'attr' => array(
2154 'class="center' . $email_elem_class . '"'
2155 ),
2156 'callback' => function($val) {
2157 return $val ?: '---';
2158 },
2159 'no_export_callback' => 1,
2160 'value' => $email,
2161 ));
2162
2163 // payment type
2164 $pax_paytype = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'paytype') ?: 'TARJT';
2165
2166 $paytype_elem_class = '';
2167 if ($guest_room_ind < 2 && empty($pax_paytype)) {
2168 // mandatory selection style
2169 $paytype_elem_class = ' vbo-report-load-paytype vbo-report-load-field';
2170 } elseif (empty($pax_paytype)) {
2171 // optional selection style
2172 $paytype_elem_class = ' vbo-report-load-paytype vbo-report-load-field vbo-report-load-field-optional';
2173 } elseif (!empty($pax_paytype)) {
2174 // rectify selection style
2175 $paytype_elem_class = ' vbo-report-load-paytype vbo-report-load-field vbo-report-load-elem-filled';
2176 }
2177
2178 array_push($insert_row, array(
2179 'key' => 'paytype',
2180 'attr' => array(
2181 'class="center' . $paytype_elem_class . '"',
2182 'data-field="paytype"',
2183 'data-fieldbid="' . $guests['id'] . '"',
2184 ),
2185 'callback' => function($val) {
2186 return $this->tipos_de_pago[$val] ?? $val;
2187 },
2188 'no_export_callback' => 1,
2189 'value' => $pax_paytype,
2190 ));
2191
2192 // check-in
2193 array_push($insert_row, array(
2194 'key' => 'checkin',
2195 'attr' => array(
2196 'class="center"'
2197 ),
2198 'callback' => function($val) {
2199 return date('Y-m-d\TH:i:s', $val);
2200 },
2201 'value' => $guests['checkin'],
2202 ));
2203
2204 // check-out
2205 array_push($insert_row, array(
2206 'key' => 'checkout',
2207 'attr' => array(
2208 'class="center"'
2209 ),
2210 'callback' => function($val) {
2211 return date('Y-m-d\TH:i:s', $val);
2212 },
2213 'value' => $guests['checkout'],
2214 ));
2215
2216 // id booking
2217 array_push($insert_row, array(
2218 'key' => 'idbooking',
2219 'attr' => array(
2220 'class="center"'
2221 ),
2222 'callback' => function($val) {
2223 // make sure to keep the data-bid attribute as it's used by JS to identify the booking ID
2224 return '<a data-bid="' . $val . '" href="index.php?option=com_vikbooking&task=editorder&cid[]=' . $val . '" target="_blank"><i class="' . VikBookingIcons::i('external-link') . '"></i> ' . $val . '</a>';
2225 },
2226 'no_export_callback' => 1,
2227 'value' => $guests['id'],
2228 ));
2229
2230 // push fields in the rows array as a new row
2231 array_push($this->rows, $insert_row);
2232
2233 // increment guest index
2234 $guest_ind++;
2235 }
2236 }
2237
2238 // do not sort the rows for this report because the lines of the guests of the same booking must be consecutive
2239 // $this->sortRows($pkrsort, $pkrorder);
2240
2241 // the footer row will just print the amount of records to export
2242 array_push($this->footerRow, array(
2243 array(
2244 'attr' => array(
2245 'class="vbo-report-total"',
2246 ),
2247 'value' => '<h3>' . JText::translate('VBOREPORTSTOTALROW') . '</h3>',
2248 ),
2249 array(
2250 'attr' => array(
2251 'colspan="' . (count($this->cols) - 1) . '"',
2252 ),
2253 'value' => count($this->rows),
2254 )
2255 ));
2256
2257 return true;
2258 }
2259
2260 /**
2261 * Generates the authority file, then sends it to output for download.
2262 * In case of errors, the process is not terminated (exit) to let the View display the
2263 * error message(s). The export type argument can eventually determine an action to run.
2264 *
2265 * @param string $export_type Differentiates the type of export requested.
2266 *
2267 * @return void|bool Void in case of script termination, boolean otherwise.
2268 */
2269 public function customExport($export_type = null)
2270 {
2271 // build the XML file
2272 $xml = $this->buildXMLFile();
2273
2274 // build report action data, if needed
2275 $action_data = array_merge($this->getActionData($registry = false), ['xml' => $xml]);
2276
2277 /**
2278 * Custom export method can run a custom action.
2279 */
2280 if ($export_type && !is_numeric($export_type)) {
2281 try {
2282 // ensure the type of export is a callable scoped action, hidden or visible
2283 $actions = $this->getScopedActions($this->getScope(), $visible = false);
2284 $action_ids = array_column($actions, 'id');
2285 $action_names = array_column($actions, 'name');
2286 if (!in_array($export_type, $action_ids)) {
2287 throw new Exception(sprintf('Cannot invoke the requested type of export [%s].', $export_type), 403);
2288 }
2289
2290 // get the requested action readable name
2291 $action_name = $action_names[array_search($export_type, $action_ids)];
2292
2293 if ($this->getScope() === 'web') {
2294 // run the action and output the HTML response string
2295 $html_result = $this->_callActionReturn($export_type, 'html', $this->getScope(), $action_data);
2296
2297 // build the action result data object
2298 $js_result = json_encode([
2299 'actionName' => $action_name,
2300 'actionHtml' => $html_result,
2301 ]);
2302
2303 // render modal script with the action result
2304 JFactory::getDocument()->addScriptDeclaration(
2305 <<<JS
2306 ;(function($) {
2307 $(function() {
2308 let result = $js_result;
2309 VBOCore.displayModal({
2310 suffix: 'report-custom-scopedaction-result',
2311 extra_class: 'vbo-modal-rounded vbo-modal-tall vbo-modal-nofooter',
2312 title: result.actionName,
2313 body: result.actionHtml,
2314 });
2315 });
2316 })(jQuery);
2317 JS
2318 );
2319
2320 // abort and let the View render the result within a modal
2321 return;
2322 }
2323
2324 // let the report custom action run and return a boolean value if invoked by a cron
2325 return (bool) $this->_callActionReturn($export_type, 'success', $this->getScope(), $action_data);
2326
2327 } catch (Exception $e) {
2328 // silently catch the error and set it
2329 $this->setError(sprintf('(%s) %s', $e->getCode(), $e->getMessage()));
2330
2331 // abort
2332 return false;
2333 }
2334 }
2335
2336 // proceed with the regular export function (write on file through cron or download file through web)
2337
2338 if (!$xml) {
2339 // abort
2340 return false;
2341 }
2342
2343 // update history for all bookings affected before exporting
2344 foreach ($this->export_booking_ids as $bid) {
2345 VikBooking::getBookingHistoryInstance($bid)->store('RP', $this->reportName . ' - Export');
2346 }
2347
2348 // custom export method supports a custom export handler, if previously set.
2349 if ($this->hasExportHandler()) {
2350 // write data onto the custom file handler
2351 $fp = $this->getExportCSVHandler();
2352 fwrite($fp, $xml);
2353 fclose($fp);
2354
2355 // return true as data was written
2356 return true;
2357 }
2358
2359 // force text file download in case of regular export
2360 header("Content-type: text/xml");
2361 header("Cache-Control: no-store, no-cache");
2362 header('Content-Disposition: attachment; filename="' . $this->getExportCSVFileName() . '"');
2363 echo $xml;
2364
2365 exit;
2366 }
2367
2368 /**
2369 * Helper method invoked via AJAX by the controller.
2370 * Needed to save the manual entries for the pax data.
2371 *
2372 * @param array $manual_data the object representation of the manual entries.
2373 *
2374 * @return array one boolean value array with the operation result.
2375 */
2376 public function updatePaxData($manual_data = [])
2377 {
2378 if (!is_array($manual_data) || !$manual_data) {
2379 VBOHttpDocument::getInstance()->close(400, 'Nothing to save!');
2380 }
2381
2382 // re-build manual entries object representation
2383 $bids_guests = [];
2384 foreach ($manual_data as $guest_ind => $guest_data) {
2385 if (!is_numeric($guest_ind) || !is_array($guest_data) || empty($guest_data['bid']) || !isset($guest_data['bid_index']) || count($guest_data) < 2) {
2386 // empty or invalid manual entries array
2387 continue;
2388 }
2389 // the guest index in the reportObj starts from 0
2390 $use_guest_ind = ($guest_ind + 1 - (int)$guest_data['bid_index']);
2391 if (!isset($bids_guests[$guest_data['bid']])) {
2392 $bids_guests[$guest_data['bid']] = [];
2393 }
2394 // set manual entries for this guest number
2395 $bids_guests[$guest_data['bid']][$use_guest_ind] = $guest_data;
2396 // remove the "bid" and "bid_index" keys
2397 unset($bids_guests[$guest_data['bid']][$use_guest_ind]['bid'], $bids_guests[$guest_data['bid']][$use_guest_ind]['bid_index']);
2398 }
2399
2400 if (!$bids_guests) {
2401 VBOHttpDocument::getInstance()->close(400, 'No manual entries to save found');
2402 }
2403
2404 // loop through all bookings to update the data for the various rooms and guests
2405 $bids_updated = 0;
2406 foreach ($bids_guests as $bid => $entries) {
2407 $b_rooms = VikBooking::loadOrdersRoomsData($bid);
2408 if (empty($b_rooms)) {
2409 continue;
2410 }
2411 // count guests per room (adults + children)
2412 $room_guests = [];
2413 foreach ($b_rooms as $b_room) {
2414 $room_guests[] = $b_room['adults'] + $b_room['children'];
2415 }
2416 // get current booking pax data
2417 $pax_data = VBOCheckinPax::getBookingPaxData($bid);
2418 $pax_data = empty($pax_data) ? [] : $pax_data;
2419 foreach ($entries as $guest_ind => $guest_data) {
2420 // find room index for this guest
2421 $room_num = 0;
2422 $use_guest_ind = $guest_ind;
2423 foreach ($room_guests as $room_index => $tot_guests) {
2424 // find the proper guest index for the room to which this belongs
2425 if ($use_guest_ind <= $tot_guests) {
2426 // proper room index found for this guest
2427 $room_num = $room_index;
2428 break;
2429 } else {
2430 // it's probably in a next room
2431 $use_guest_ind -= $tot_guests;
2432 }
2433 }
2434 // push new pax data for this room and guest
2435 if (!isset($pax_data[$room_num])) {
2436 $pax_data[$room_num] = [];
2437 }
2438 if (!isset($pax_data[$room_num][$use_guest_ind])) {
2439 $pax_data[$room_num][$use_guest_ind] = $guest_data;
2440 } else {
2441 $pax_data[$room_num][$use_guest_ind] = array_merge($pax_data[$room_num][$use_guest_ind], $guest_data);
2442 }
2443 }
2444 // update booking pax data
2445 if (VBOCheckinPax::setBookingPaxData($bid, $pax_data)) {
2446 $bids_updated++;
2447 }
2448 }
2449
2450 return $bids_updated ? [true] : [false];
2451 }
2452
2453 /**
2454 * Builds the XML file for export or transmission.
2455 *
2456 * @param string $type The XML export type (checkin or reservation).
2457 *
2458 * @return string Empty string in case of errors, XML otherwise.
2459 */
2460 protected function buildXMLFile($type = '')
2461 {
2462 if (!$this->getReportData()) {
2463 return '';
2464 }
2465
2466 // load report settings
2467 $settings = $this->loadSettings();
2468
2469 // get the possibly injected report options
2470 $options = $this->getReportOptions();
2471
2472 // injected options will substitute request variables, if any
2473 $export_type = $type ?: VikRequest::getString('type', $options->get('type', 'checkin'), 'request');
2474
2475 // access manually filled values, if any
2476 $pfiller = VikRequest::getString('filler', '', 'request', VIKREQUEST_ALLOWRAW);
2477 $pfiller = !empty($pfiller) ? (array) json_decode($pfiller, true) : [];
2478
2479 // determine the XML namespace to use
2480 if ($export_type === 'checkin') {
2481 $xmlns = 'http://www.neg.hospedajes.mir.es/altaParteHospedaje';
2482 } else {
2483 $xmlns = 'http://www.neg.hospedajes.mir.es/altaReservaHospedaje';
2484 }
2485
2486 // start building the XML
2487 $xml = $this->loadXmlSoap('<alt:peticion xmlns:alt="' . $xmlns . '"></alt:peticion>');
2488
2489 // add main child common node (no value, empty namespace)
2490 $solicitud = $xml->addChild('solicitud', null, '');
2491
2492 if ($export_type === 'checkin') {
2493 // partes de viajeros (check-in report)
2494 $solicitud->addChild('codigoEstablecimiento', htmlspecialchars($settings['codigo'] ?? ''));
2495 }
2496
2497 // group all rows by booking id
2498 $booking_rows = [];
2499 foreach ($this->rows as $ind => $row) {
2500 $bid = 0;
2501 foreach ($row as $row_ind => $field) {
2502 if ($field['key'] == 'idbooking') {
2503 // set booking ID
2504 $bid = (int) $field['value'];
2505
2506 if (!isset($booking_rows[$bid])) {
2507 // start booking rows container
2508 $booking_rows[$bid] = [];
2509 }
2510
2511 // check for manual report value
2512 if (strlen((string) ($pfiller[$ind][$field['key']] ?? ''))) {
2513 $row[$row_ind]['value'] = $pfiller[$ind][$field['key']];
2514 }
2515
2516 // break this one loop
2517 break;
2518 }
2519 }
2520 // push booking row
2521 $booking_rows[$bid][] = $row;
2522 }
2523
2524 // scan all booking rows
2525 foreach ($booking_rows as $bid => $rows) {
2526 // add <comunicacion> node for this reservation
2527 $comunicacion = $solicitud->addChild('comunicacion');
2528
2529 // check if <establecimiento> node is needed
2530 if ($export_type !== 'checkin') {
2531 // reservas de hospedaje (reservation report)
2532 $establecimiento = $comunicacion->addChild('establecimiento');
2533 if (!empty($settings['codigo'])) {
2534 // property code, if known
2535 $establecimiento->addChild('codigo', htmlspecialchars($settings['codigo']));
2536 } else {
2537 // property details otherwise
2538 $datos = $establecimiento->addChild('datosEstablecimiento');
2539 $datos->addChild('tipo', htmlspecialchars($settings['tipo_establecimiento'] ?? ''));
2540 $datos->addChild('nombre', htmlspecialchars($settings['nombre_establecimiento'] ?? ''));
2541 $direccion = $datos->addChild('direccion');
2542 $direccion->addChild('direccion', htmlspecialchars($settings['direccion'] ?? ''));
2543 $direccion->addChild('codigoMunicipio', htmlspecialchars($settings['codigo_municipio'] ?? ''));
2544 $direccion->addChild('nombreMunicipio', htmlspecialchars($settings['nombre_municipio'] ?? ''));
2545 $direccion->addChild('codigoPostal', htmlspecialchars($settings['codigo_postal'] ?? ''));
2546 $direccion->addChild('pais', htmlspecialchars($settings['pais'] ?? ''));
2547 }
2548 }
2549
2550 // build an associative list of data for the first guest row of this booking
2551 $main_res_data = $this->getAssocRowData($rows[0]);
2552
2553 // add <contrato> node
2554 $contrato = $comunicacion->addChild('contrato');
2555 $contrato->addChild('referencia', $main_res_data['idbooking']);
2556 $contrato->addChild('fechaContrato', htmlspecialchars($main_res_data['resdate']));
2557 $contrato->addChild('fechaEntrada', htmlspecialchars($main_res_data['checkin']));
2558 $contrato->addChild('fechaSalida', htmlspecialchars($main_res_data['checkout']));
2559 $contrato->addChild('numPersonas', htmlspecialchars($main_res_data['numguests']));
2560 $pago = $contrato->addChild('pago');
2561 $pago->addChild('tipoPago', htmlspecialchars($main_res_data['paytype']));
2562
2563 // scan all guest rows for this reservation to build the various <persona> nodes
2564 foreach ($rows as $row) {
2565 // get the associative list of guest data from the current row
2566 $row_data = $this->getAssocRowData($row);
2567
2568 // add <persona> node for this guest
2569 $persona = $comunicacion->addChild('persona');
2570
2571 // last name parts
2572 $last_name_parts = explode(' ', (string) $row_data['last_name']);
2573 if (count($last_name_parts) === 1) {
2574 $last_name_one = $last_name_parts[0];
2575 $last_name_two = $last_name_parts[0];
2576 } else {
2577 $last_name_two = $last_name_parts[count($last_name_parts) - 1];
2578 unset($last_name_parts[count($last_name_parts) - 1]);
2579 $last_name_one = implode(' ', $last_name_parts);
2580 }
2581
2582 // set persona details
2583 $persona->addChild('rol', htmlspecialchars($row_data['guest_type']));
2584 $persona->addChild('nombre', htmlspecialchars($row_data['first_name']));
2585 $persona->addChild('apellido1', htmlspecialchars($last_name_one));
2586 if ($last_name_one != $last_name_two || (!empty($row_data['doctype']) && !strcasecmp($row_data['doctype'], 'NIF'))) {
2587 // apellido2 is mandatory in this case
2588 $persona->addChild('apellido2', htmlspecialchars($last_name_two));
2589 }
2590 if (!empty($row_data['doctype'])) {
2591 $persona->addChild('tipoDocumento', htmlspecialchars($row_data['doctype']));
2592 }
2593 if (!empty($row_data['docnum'])) {
2594 $persona->addChild('numeroDocumento', htmlspecialchars($row_data['docnum']));
2595 }
2596 if (!empty($row_data['docsoporte']) && $export_type == 'checkin') {
2597 $persona->addChild('soporteDocumento', htmlspecialchars($row_data['docsoporte']));
2598 }
2599 $persona->addChild('fechaNacimiento', htmlspecialchars($row_data['date_birth']));
2600 $persona->addChild('nacionalidad', htmlspecialchars(($row_data['country_c'] ?: $row_data['country_s'])));
2601 $persona->addChild('sexo', htmlspecialchars($row_data['gender']));
2602
2603 // direccion
2604 $direccion = $persona->addChild('direccion');
2605 if ($row_data['address']) {
2606 $direccion->addChild('direccion', htmlspecialchars($row_data['address']));
2607 }
2608 if ($row_data['municipio']) {
2609 $direccion->addChild('codigoMunicipio', htmlspecialchars($row_data['municipio']));
2610 }
2611 if ($row_data['city']) {
2612 $direccion->addChild('nombreMunicipio', htmlspecialchars($row_data['city']));
2613 }
2614 if ($row_data['postalcode']) {
2615 $direccion->addChild('codigoPostal', htmlspecialchars($row_data['postalcode']));
2616 }
2617 if ($row_data['country_s'] || $row_data['country_c']) {
2618 $direccion->addChild('pais', htmlspecialchars(($row_data['country_s'] ?: $row_data['country_c'])));
2619 }
2620
2621 if (!empty($row_data['phone'])) {
2622 $persona->addChild('telefono', htmlspecialchars($row_data['phone']));
2623 }
2624 if (!empty($row_data['email'])) {
2625 $persona->addChild('correo', htmlspecialchars($row_data['email']));
2626 }
2627 if ($export_type === 'checkin' && !empty($row_data['parentesco'])) {
2628 $persona->addChild('parentesco', htmlspecialchars($row_data['parentesco']));
2629 }
2630 }
2631 }
2632
2633 // set pool of booking IDs to update their history
2634 $this->export_booking_ids = array_keys($booking_rows);
2635
2636 // get the formatted XML file string
2637 $formatted_xml = $xml->formatXml();
2638
2639 // make sure to get rid of empty namespace attributes
2640 $formatted_xml = str_replace(' xmlns=""', '', $formatted_xml);
2641
2642 // return the final XML file string
2643 return $formatted_xml;
2644 }
2645
2646 /**
2647 * Custom scoped action to transmit the registration of the traveler parts (Alta de partes de viajeros).
2648 * Accepted scopes are "web" and "cron", so the "success" property must be returned.
2649 *
2650 * @param string $scope Optional scope identifier (cron, web, etc..).
2651 * @param array $data Optional associative list of data to process.
2652 *
2653 * @return array The execution result properties.
2654 *
2655 * @throws Exception
2656 */
2657 protected function registerTravelerParts($scope = null, array $data = [])
2658 {
2659 if (!($data['xml'] ?? '') && $scope === 'web') {
2660 // start the process through the interface by submitting the current data
2661 return [
2662 'html' => '<script type="text/javascript">vboDownloadHospedajes(\'registerTravelerParts\', \'checkin\');</script>',
2663 ];
2664 }
2665
2666 if (!($data['xml'] ?? '')) {
2667 // attempt to build the XML file if not set
2668 $data['xml'] = $this->buildXMLFile();
2669 }
2670
2671 if (!$data['xml']) {
2672 throw new Exception('Missing XML request message.', 500);
2673 }
2674
2675 // load report settings
2676 $settings = $this->loadSettings();
2677
2678 if (!$settings || empty($settings['service_username']) || empty($settings['service_password']) || empty($settings['service_arrendador'])) {
2679 throw new Exception(sprintf('[%s] error: missing service settings.', __METHOD__), 500);
2680 }
2681
2682 // set the type of transmission
2683 $tn_type = 'PV';
2684
2685 // prepare the communication message data
2686 $message_data = array_merge($data, [
2687 'operation_type' => 'A',
2688 'report_type' => $tn_type,
2689 ]);
2690
2691 // get the communication message
2692 $message = $this->_callActionReturn('prepareCommunicationMessage', 'message', $scope, $message_data);
2693
2694 try {
2695 // post the message to the electronic service and get the Soap XML response message
2696 $response = $this->postMessage($message, $settings);
2697
2698 // parse the response message and get the main node across multiple namespaces
2699 $xmlBody = $this->loadXmlSoap($response)->getSoapRecursiveNsElement([
2700 [
2701 'SOAP-ENV',
2702 true,
2703 'Body',
2704 ],
2705 [
2706 'http://www.soap.servicios.hospedajes.mir.es/comunicacion',
2707 false,
2708 'respuesta',
2709 ],
2710 ]);
2711
2712 // access the desired XML element
2713 $xmlBody = $xmlBody->respuesta ?? $xmlBody;
2714
2715 // access the result code
2716 $result_code = $xmlBody->codigoRetorno ?? $xmlBody->codigo ?? null;
2717
2718 if (is_null($result_code)) {
2719 // unexpected response
2720 throw new UnexpectedValueException(sprintf('Unexpected response [%s]', htmlspecialchars($xmlBody->formatXml() ?: $response)), 500);
2721 }
2722
2723 // validate the response
2724 $is_error = (int) $result_code !== 0;
2725
2726 if ($is_error || !($xmlBody->lote ?? '')) {
2727 $error_code = (string) ($result_code ?? '500');
2728 $error_dets = (string) ($xmlBody->descripcion ?? 'Unknown');
2729 // terminate the execution in case of errors or empty result
2730 throw new Exception(sprintf("[%s] Response error:\n%s", $error_code, $error_dets), 500);
2731 }
2732
2733 // the batch value obtained with the transmission
2734 $batch_value = (string) $xmlBody->lote;
2735
2736 // store the batch value on the DB for later fetching
2737 $this->storeBatchData([
2738 'batch' => $batch_value,
2739 'op_date' => date('Y-m-d H:i:s'),
2740 'from_dt' => $this->exported_checkin_dates[0] ?? date('Y-m-d'),
2741 'to_dt' => $this->exported_checkin_dates[1] ?? date('Y-m-d'),
2742 'op_type' => 'A',
2743 'rp_type' => $tn_type,
2744 ], 'hospedajes_batches_' . date('Yn'));
2745
2746 // build HTML response string
2747 $html = '';
2748 $html .= '<p class="successmade">Lote: ' . $batch_value . '</p>';
2749
2750 } catch (Exception $e) {
2751 // propagate the error caught
2752 throw new Exception(sprintf('[%s] error: %s', __METHOD__, $e->getMessage()), $e->getCode() ?: 500);
2753 }
2754
2755 // when executed through a cron, store an event in the Notifications Center
2756 if ($scope === 'cron') {
2757 // build the notification record
2758 $notification = [
2759 'sender' => 'reports',
2760 'type' => 'pmsreport.travelparts.ok',
2761 'title' => 'Hospedajes - Alta de partes de viajeros',
2762 'summary' => sprintf(
2763 'Las partes de viajeros se han registrado correctamente para las fechas de check-in del %s al %s. Número de lote obtenido: %s.',
2764 $this->exported_checkin_dates[0] ?? '',
2765 $this->exported_checkin_dates[1] ?? '',
2766 $batch_value
2767 ),
2768 ];
2769
2770 try {
2771 // store the notification record
2772 VBOFactory::getNotificationCenter()->store([$notification]);
2773 } catch (Exception $e) {
2774 // silently catch the error without doing anything
2775 }
2776 }
2777
2778 return [
2779 'html' => $html,
2780 'success' => !empty($batch_value),
2781 'batch' => $batch_value,
2782 ];
2783 }
2784
2785 /**
2786 * Custom scoped action to transmit the registration of the accommodation reservation (Alta de reservas de hospedaje).
2787 * Accepted scopes are "web" and "cron", so the "success" property must be returned.
2788 *
2789 * @param string $scope Optional scope identifier (cron, web, etc..).
2790 * @param array $data Optional associative list of data to process.
2791 *
2792 * @return array The execution result properties.
2793 *
2794 * @throws Exception
2795 */
2796 protected function registerAccommodationReservations($scope = null, array $data = [])
2797 {
2798 if (!($data['xml'] ?? '') && $scope === 'web') {
2799 // start the process through the interface by submitting the current data
2800 return [
2801 'html' => '<script type="text/javascript">vboDownloadHospedajes(\'registerAccommodationReservations\', \'reservation\');</script>',
2802 ];
2803 }
2804
2805 if (!($data['xml'] ?? '')) {
2806 // attempt to build the XML file if not set
2807 $data['xml'] = $this->buildXMLFile();
2808 }
2809
2810 if (!$data['xml']) {
2811 throw new Exception('Missing XML request message.', 500);
2812 }
2813
2814 // load report settings
2815 $settings = $this->loadSettings();
2816
2817 if (!$settings || empty($settings['service_username']) || empty($settings['service_password']) || empty($settings['service_arrendador'])) {
2818 throw new Exception(sprintf('[%s] error: missing service settings.', __METHOD__), 500);
2819 }
2820
2821 // set the type of transmission
2822 $tn_type = 'RH';
2823
2824 // prepare the communication message data
2825 $message_data = array_merge($data, [
2826 'operation_type' => 'A',
2827 'report_type' => $tn_type,
2828 ]);
2829
2830 // get the communication message
2831 $message = $this->_callActionReturn('prepareCommunicationMessage', 'message', $scope, $message_data);
2832
2833 try {
2834 // post the message to the electronic service and get the Soap XML response message
2835 $response = $this->postMessage($message, $settings);
2836
2837 // parse the response message and get the main node across multiple namespaces
2838 $xmlBody = $this->loadXmlSoap($response)->getSoapRecursiveNsElement([
2839 [
2840 'SOAP-ENV',
2841 true,
2842 'Body',
2843 ],
2844 [
2845 'http://www.soap.servicios.hospedajes.mir.es/comunicacion',
2846 false,
2847 'respuesta',
2848 ],
2849 ]);
2850
2851 // access the desired XML element
2852 $xmlBody = $xmlBody->respuesta ?? $xmlBody;
2853
2854 // access the result code (should be "codigo")
2855 $result_code = $xmlBody->codigo ?? $xmlBody->codigoRetorno ?? null;
2856
2857 if (is_null($result_code)) {
2858 // unexpected response
2859 throw new UnexpectedValueException(sprintf('Unexpected response [%s]', htmlspecialchars($xmlBody->formatXml() ?: $response)), 500);
2860 }
2861
2862 // validate the response
2863 $is_error = (int) $result_code !== 0;
2864
2865 if ($is_error || !($xmlBody->lote ?? '')) {
2866 $error_code = (string) ($result_code ?? '500');
2867 $error_dets = (string) ($xmlBody->descripcion ?? 'Unknown');
2868 // terminate the execution in case of errors or empty result
2869 throw new Exception(sprintf("[%s] Response error:\n%s", $error_code, $error_dets), 500);
2870 }
2871
2872 // the batch value obtained with the transmission
2873 $batch_value = (string) $xmlBody->lote;
2874
2875 // store the batch value on the DB for later fetching
2876 $this->storeBatchData([
2877 'batch' => $batch_value,
2878 'op_date' => date('Y-m-d H:i:s'),
2879 'from_dt' => $this->exported_checkin_dates[0] ?? date('Y-m-d'),
2880 'to_dt' => $this->exported_checkin_dates[1] ?? date('Y-m-d'),
2881 'op_type' => 'A',
2882 'rp_type' => $tn_type,
2883 ], 'hospedajes_batches_' . date('Yn'));
2884
2885 // build HTML response string
2886 $html = '';
2887 $html .= '<p class="successmade">Lote: ' . $batch_value . '</p>';
2888
2889 } catch (Exception $e) {
2890 // propagate the error caught
2891 throw new Exception(sprintf('[%s] error: %s', __METHOD__, $e->getMessage()), $e->getCode() ?: 500);
2892 }
2893
2894 // when executed through a cron, store an event in the Notifications Center
2895 if ($scope === 'cron') {
2896 // build the notification record
2897 $notification = [
2898 'sender' => 'reports',
2899 'type' => 'pmsreport.reshospedaje.ok',
2900 'title' => 'Hospedajes - Alta de reservas de hospedaje',
2901 'summary' => sprintf(
2902 'Las reservas de hospedaje se han registrado correctamente para las fechas del %s al %s. Número de lote obtenido: %s.',
2903 $this->exported_checkin_dates[0] ?? '',
2904 $this->exported_checkin_dates[1] ?? '',
2905 $batch_value
2906 ),
2907 ];
2908
2909 try {
2910 // store the notification record
2911 VBOFactory::getNotificationCenter()->store([$notification]);
2912 } catch (Exception $e) {
2913 // silently catch the error without doing anything
2914 }
2915 }
2916
2917 return [
2918 'html' => $html,
2919 'success' => !empty($batch_value),
2920 'batch' => $batch_value,
2921 ];
2922 }
2923
2924 /**
2925 * Custom scoped action to list all the previously obtained batches.
2926 *
2927 * @param string $scope Optional scope identifier (cron, web, etc..).
2928 * @param array $data Optional associative list of data to process.
2929 *
2930 * @return array The execution result properties.
2931 *
2932 * @throws Exception
2933 */
2934 protected function batchArchive($scope = null, array $data = [])
2935 {
2936 // response properties
2937 $html = '';
2938
2939 // optional filter by exact date
2940 $date_filter = '';
2941
2942 // optional filter by report type
2943 $rptype_filter = $data['rp_type'] ?? '';
2944
2945 // read exact or current period (4-digit year + month without leading zero)
2946 $read_period = $data['yearmon'] ?? date('Yn');
2947
2948 if (!empty($data['op_month'])) {
2949 // date in Y-m-d format to be converted into year+mon for reading a specific month
2950 $read_period = date('Yn', strtotime($data['op_month']));
2951 }
2952
2953 if (!empty($data['op_date'])) {
2954 // full date in Y-m-d format for reading the operations of a specific date
2955 $date_filter = $data['op_date'];
2956 // set the month to read from the database
2957 $read_period = date('Yn', strtotime($data['op_date']));
2958 }
2959
2960 // read batches for the requested period
2961 $batches = $this->readBatchData('hospedajes_batches_' . $read_period);
2962
2963 if ($date_filter) {
2964 // filter batches by exact date
2965 $batches = $this->filterBatchData($batches, $date_filter);
2966 }
2967
2968 if (strlen((string) $rptype_filter) === 2) {
2969 // filter batches by report type
2970 $batches = $this->filterBatchData($batches, '', $rptype_filter);
2971 }
2972
2973 if (!$batches) {
2974 $html .= '<p class="warn">No se encontraron lotes para el período solicitado.</p>';
2975 } else {
2976 $html .= '<div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">';
2977 $html .= ' <div class="vbo-params-wrap">';
2978 $html .= ' <div class="vbo-params-container">';
2979 $html .= ' <div class="vbo-params-block">';
2980
2981 foreach ($batches as $batch) {
2982 $report_type = $batch['rp_type'] === 'PV' ? 'Partes de viajeros' : 'Reservas de hospedaje';
2983
2984 $html .= ' <div class="vbo-param-container">';
2985 $html .= ' <div class="vbo-param-label">' . $report_type . '<br/>' . $batch['op_date'] . '</div>';
2986 $html .= ' <div class="vbo-param-setting">';
2987 $html .= ' <span>' . $batch['batch'] . '</span>';
2988 $html .= ' <span class="vbo-param-setting-comment">' . sprintf('(%s) %s - %s', $batch['op_type'], $batch['from_dt'], $batch['to_dt']) . '</span>';
2989 $html .= ' </div>';
2990 $html .= ' </div>';
2991 }
2992
2993 $html .= ' </div>';
2994 $html .= ' </div>';
2995 $html .= ' </div>';
2996 $html .= '</div>';
2997 }
2998
2999 return [
3000 'html' => $html,
3001 'total' => count($batches),
3002 'batches' => $batches,
3003 'period' => $read_period,
3004 'date' => $date_filter,
3005 ];
3006 }
3007
3008 /**
3009 * Custom scoped action to check the status of batches obtained from previous transmissions.
3010 * Accepted scopes are "web" and "cron", so the "success" property must be returned.
3011 *
3012 * @param string $scope Optional scope identifier (cron, web, etc..).
3013 * @param array $data Optional associative list of data to process.
3014 *
3015 * @return array The execution result properties.
3016 *
3017 * @throws Exception
3018 */
3019 protected function checkBatchStatus($scope = null, array $data = [])
3020 {
3021 // response properties
3022 $html = '';
3023 $communication_ok = 0;
3024 $communication_err = 0;
3025
3026 // list of batch strings (sequences) to consult
3027 $batches = [];
3028
3029 if ($data['batch_sequence'] ?? '') {
3030 // push a single batch to check
3031 $batches[] = $data['batch_sequence'];
3032 } elseif ($data['op_date'] ?? '') {
3033 // get the batches from a specific date
3034 $list = $this->_callActionReturn('batchArchive', 'batches', $scope, ['op_date' => $data['op_date']]);
3035 $batches = array_column($list, 'batch');
3036 }
3037
3038 if (!$batches) {
3039 throw new Exception(sprintf('[%s] error: %s', __METHOD__, 'No batch values to check according to filters.'), 404);
3040 }
3041
3042 // build the communication XML string
3043 $batch_nodes = implode("\n", array_map(function($batch) {
3044 return "\t" . '<con:lote>' . $batch . '</con:lote>';
3045 }, $batches));
3046
3047 $xml_comunication = <<<XML
3048 <con:lotes xmlns:con="http://www.neg.hospedajes.mir.es/consultarComunicacion">
3049 $batch_nodes
3050 </con:lotes>
3051 XML;
3052
3053 // prepare the communication message data
3054 $message_data = [
3055 'xml' => $xml_comunication,
3056 'operation_type' => 'C',
3057 ];
3058
3059 // get the communication message
3060 $message = $this->_callActionReturn('prepareCommunicationMessage', 'message', $scope, $message_data);
3061
3062 try {
3063 // post the message to the electronic service and get the Soap XML response message
3064 $response = $this->postMessage($message, $settings);
3065
3066 // parse the response message and get the main node across multiple namespaces
3067 $xmlResponseNode = $this->loadXmlSoap($response)->getSoapRecursiveNsElement([
3068 [
3069 'SOAP-ENV',
3070 true,
3071 'Body',
3072 ],
3073 [
3074 'http://www.soap.servicios.hospedajes.mir.es/comunicacion',
3075 false,
3076 'respuesta',
3077 ],
3078 ]);
3079
3080 // access the "response" node for validation
3081 $xmlResponseNode = $xmlResponseNode->respuesta ?? $xmlResponseNode;
3082
3083 if (!isset($xmlResponseNode->codigo)) {
3084 // unexpected response
3085 throw new UnexpectedValueException(sprintf('Unexpected response [%s]', $xmlResponseNode->formatXml() ?: $response), 500);
3086 }
3087
3088 // validate the response
3089 $is_error = (int) $xmlResponseNode->codigo !== 0;
3090
3091 if ($is_error) {
3092 $error_code = (string) ($xmlBody->codigo ?? '500');
3093 $error_dets = (string) ($xmlBody->descripcion ?? 'Unknown');
3094 // terminate the execution in case of errors or empty result
3095 throw new Exception(sprintf("[%s] Response error:\n%s", $error_code, $error_dets), 500);
3096 }
3097
3098 // now access the "result" node across multiple namespaces
3099 $xmlResultNode = $this->loadXmlSoap($response)->getSoapRecursiveNsElement([
3100 [
3101 'SOAP-ENV',
3102 true,
3103 'Body',
3104 ],
3105 [
3106 'http://www.soap.servicios.hospedajes.mir.es/comunicacion',
3107 false,
3108 'resultado',
3109 ],
3110 ]);
3111
3112 // access the "result" node for validation
3113 $xmlResultNode = $xmlResultNode->resultado ?? $xmlResultNode;
3114
3115 if (!isset($xmlResultNode->resultadoComunicaciones->resultadoComunicacion)) {
3116 // unexpected response
3117 throw new UnexpectedValueException(sprintf('Unexpected response with no results [%s]', $xmlResultNode->formatXml() ?: $response), 500);
3118 }
3119
3120 // build HTML response string
3121 $html .= '<div class="vbo-admin-container vbo-admin-container-full vbo-admin-container-compact">';
3122 $html .= ' <div class="vbo-params-wrap">';
3123 $html .= ' <div class="vbo-params-container">';
3124 $html .= ' <div class="vbo-params-block">';
3125
3126 $html .= ' <div class="vbo-param-container">';
3127 $html .= ' <div class="vbo-param-label">Lote</div>';
3128 $html .= ' <div class="vbo-param-setting">';
3129 $html .= ' <span>' . (string) $xmlResultNode->lote . '</span>';
3130 $html .= ' </div>';
3131 $html .= ' </div>';
3132
3133 $html .= ' <div class="vbo-param-container">';
3134 $html .= ' <div class="vbo-param-label">Tipo de comunicaciones enviadas</div>';
3135 $html .= ' <div class="vbo-param-setting">';
3136 $html .= ' <span>' . (string) $xmlResultNode->tipoComunicacion . ' - ' . (string) ($xmlResultNode->tipoOperacion ?? '') . '</span>';
3137 $html .= ' </div>';
3138 $html .= ' </div>';
3139
3140 $html .= ' <div class="vbo-param-container">';
3141 $html .= ' <div class="vbo-param-label">Fecha Peticion</div>';
3142 $html .= ' <div class="vbo-param-setting">';
3143 $html .= ' <span>' . (string) $xmlResultNode->fechaPeticion . '</span>';
3144 $html .= ' <span class="vbo-param-setting-comment">Fecha Procesamiento ' . (string) ($xmlResultNode->fechaProcesamiento ?? '') . '</span>';
3145 $html .= ' </div>';
3146 $html .= ' </div>';
3147
3148 $html .= ' <div class="vbo-param-container">';
3149 $html .= ' <div class="vbo-param-label">Estado</div>';
3150 $html .= ' <div class="vbo-param-setting">';
3151 $html .= ' <span>(' . (string) $xmlResultNode->codigoEstado . ') ' . (string) $xmlResultNode->descEstado . '</span>';
3152 $html .= ' </div>';
3153 $html .= ' </div>';
3154
3155 // scan all results and increase success/error counters
3156 foreach ($xmlResultNode->resultadoComunicaciones->resultadoComunicacion as $rescom) {
3157 $html .= ' <div class="vbo-param-container">';
3158 $html .= ' <div class="vbo-param-label">Resultado #' . (string) $rescom->orden . '</div>';
3159 $html .= ' <div class="vbo-param-setting">';
3160 if ((string) ($rescom->codigoComunicacion ?? '')) {
3161 // expected communication code (success)
3162 $html .= ' <span>' . (string) $rescom->codigoComunicacion . '</span>';
3163 $html .= ' <span class="vbo-param-setting-comment">Codigo comunicacion.</span>';
3164 // increase counter
3165 $communication_ok++;
3166 } else {
3167 // expected error details
3168 $html .= ' <span>' . (string) ($rescom->tipoError ?? '') . '</span>';
3169 $html .= ' <span class="vbo-param-setting-comment">' . (string) ($rescom->error ?? '') . '</span>';
3170 // increase counter
3171 $communication_err++;
3172 }
3173 $html .= ' </div>';
3174 $html .= ' </div>';
3175 }
3176
3177 $html .= ' </div>';
3178 $html .= ' </div>';
3179 $html .= ' </div>';
3180 $html .= '</div>';
3181 } catch (Exception $e) {
3182 // propagate the error caught
3183 throw new Exception(sprintf('[%s] error: %s', __METHOD__, $e->getMessage()), $e->getCode() ?: 500);
3184 }
3185
3186 // when executed through a cron, store an event in the Notifications Center
3187 if ($scope === 'cron') {
3188 // build the notification record
3189 $notification = [
3190 'sender' => 'reports',
3191 'type' => 'pmsreport.checkbatchstatus.' . ($communication_err ? 'error' : 'ok'),
3192 'title' => 'Hospedajes - Consulta de lotes',
3193 'summary' => sprintf(
3194 'Consulta de lotes finalizados. Resultados totales: %d. Éxito: %d. Error: %d.',
3195 ($communication_ok + $communication_err),
3196 $communication_ok,
3197 $communication_err
3198 ),
3199 ];
3200
3201 try {
3202 // store the notification record
3203 VBOFactory::getNotificationCenter()->store([$notification]);
3204 } catch (Exception $e) {
3205 // silently catch the error without doing anything
3206 }
3207 }
3208
3209 return [
3210 'html' => $html,
3211 'success' => true,
3212 'count_ok' => $communication_ok,
3213 'count_err' => $communication_err,
3214 ];
3215 }
3216
3217 /**
3218 * Custom scoped action to prepare a communication request towards the Electronic Service.
3219 *
3220 * @param string $scope Optional scope identifier (cron, web, etc..).
3221 * @param array $data Optional associative list of data to process.
3222 *
3223 * @return array The execution result properties.
3224 *
3225 * @throws Exception
3226 */
3227 protected function prepareCommunicationMessage($scope = null, array $data = [])
3228 {
3229 // load report settings
3230 $settings = $this->loadSettings();
3231
3232 if (empty($data['operation_type'])) {
3233 throw new Exception('Missing mandatory Operation Type.', 500);
3234 }
3235
3236 // ensure the operation type is upper-case (A, C or B)
3237 $data['operation_type'] = strtoupper($data['operation_type']);
3238
3239 if ($data['operation_type'] === 'A' && empty($data['report_type'])) {
3240 // either "PV" (checkin) or "HR" (reservation) is mandatory
3241 throw new Exception('Missing mandatory Report Type.', 500);
3242 }
3243
3244 // ensure the report type is upper-case ("PV" or "HR")
3245 $data['report_type'] = strtoupper($data['report_type'] ?? '');
3246
3247 if (!($data['xml'] ?? '')) {
3248 // attempt to build the XML file if not set
3249 $data['xml'] = $this->buildXMLFile();
3250 }
3251
3252 if (!$data['xml']) {
3253 throw new Exception('Missing XML request (solicitud).', 500);
3254 }
3255
3256 // first off, clean up the report temporary directory to ensure we've got no other files
3257 JFolder::delete($this->report_tmp_path);
3258
3259 // build the temporary XML file name
3260 $xml_fname = uniqid('hospedajes_') . '.xml';
3261
3262 // store the XML file internally
3263 $xml_stored = JFile::write($this->report_tmp_path . DIRECTORY_SEPARATOR . $xml_fname, $data['xml']);
3264
3265 if (!$xml_stored) {
3266 throw new Exception('Could not store the XML file internally for the data transmission.', 500);
3267 }
3268
3269 // build the zip file destination path
3270 $zip_destination = $this->report_tmp_path . DIRECTORY_SEPARATOR . preg_replace("/\.xml$/", '.zip', $xml_fname);
3271
3272 // compress the folder (should contain a single XML file) into an archive
3273 $xml_compressed = VBOArchiveFactory::compress($this->report_tmp_path, $zip_destination);
3274
3275 if (!$xml_compressed) {
3276 throw new Exception('Could not compress the XML file in zip format for the data transmission.', 500);
3277 }
3278
3279 // read the archive into a base64-encoded string
3280 $base64_request = base64_encode(file_get_contents($zip_destination));
3281
3282 if (!$base64_request) {
3283 throw new Exception('Could not read the zip archive containing the XML file into a base64-encoded string for the data transmission.', 500);
3284 }
3285
3286 // build SOAP message values
3287 $codigo_arrendador = $settings['service_arrendador'] ?? '';
3288 $application_name = $this->software_application_name;
3289 $operation_type_code = $data['operation_type'];
3290 $communication_type_node = '';
3291 if ($data['operation_type'] === 'A') {
3292 // communication type node is mandatory
3293 $communication_type_node = '<tipoComunicacion>' . $data['report_type'] . '</tipoComunicacion>';
3294 }
3295
3296 // build SOAP XML message
3297 $soap_xml = <<<XML
3298 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://www.soap.servicios.hospedajes.mir.es/comunicacion">
3299 <soapenv:Header/>
3300 <soapenv:Body>
3301 <com:comunicacionRequest>
3302 <peticion>
3303 <cabecera>
3304 <codigoArrendador>$codigo_arrendador</codigoArrendador>
3305 <aplicacion>$application_name</aplicacion>
3306 <tipoOperacion>$operation_type_code</tipoOperacion>
3307 $communication_type_node
3308 </cabecera>
3309 <solicitud>$base64_request</solicitud>
3310 </peticion>
3311 </com:comunicacionRequest>
3312 </soapenv:Body>
3313 </soapenv:Envelope>
3314 XML;
3315
3316 return [
3317 'message' => $soap_xml,
3318 'solicitud' => $data['xml'],
3319 ];
3320 }
3321
3322 /**
3323 * Parses a report row into an associative list of key-value pairs.
3324 *
3325 * @param array $row The report row to parse.
3326 *
3327 * @return array
3328 */
3329 protected function getAssocRowData(array $row)
3330 {
3331 $row_data = [];
3332
3333 foreach ($row as $field) {
3334 $field_val = $field['value'];
3335 if (!($field['no_export_callback'] ?? 0) && is_callable($field['callback'] ?? null)) {
3336 $field_val = $field['callback']($field_val);
3337 }
3338 $row_data[$field['key']] = $field_val;
3339 }
3340
3341 return $row_data;
3342 }
3343
3344 /**
3345 * Makes a POST request towards the electronic service by posting a SOAP XML message.
3346 *
3347 * @param string $message The SOAP XML message to post.
3348 * @param array $settings The report settings.
3349 *
3350 * @return string The response obtained.
3351 *
3352 * @throws Exception
3353 */
3354 protected function postMessage($message, array $settings = [])
3355 {
3356 if (!$settings) {
3357 // load report settings
3358 $settings = $this->loadSettings();
3359 }
3360
3361 /**
3362 * @todo start debug responses
3363 */
3364 /*
3365 $ok = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
3366 <SOAP-ENV:Header/>
3367 <SOAP-ENV:Body>
3368 <ns3:comunicacionResponse xmlns:ns3="http://www.soap.servicios.hospedajes.mir.es/comunicacion">
3369 <respuesta>
3370 <codigoRetorno>0</codigoRetorno>
3371 <descripcion>Ok</descripcion>
3372 <lote>00000000-1234-5678-0000-00000000' . rand(1000, 9999) . '</lote>
3373 </respuesta>
3374 </ns3:comunicacionResponse>
3375 </SOAP-ENV:Body>
3376 </SOAP-ENV:Envelope>';
3377
3378 $nok = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
3379 <SOAP-ENV:Header/>
3380 <SOAP-ENV:Body>
3381 <ns3:comunicacionResponse xmlns:ns3="http://www.soap.servicios.hospedajes.mir.es/comunicacion">
3382 <respuesta>
3383 <codigoRetorno>109</codigoRetorno>
3384 <descripcion>Formato de solicitud incorrecto. Ha de ir comprimido (zip) y codificado en Base64.</descripcion>
3385 </respuesta>
3386 </ns3:comunicacionResponse>
3387 </SOAP-ENV:Body>
3388 </SOAP-ENV:Envelope>';
3389
3390 $okc = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
3391 <SOAP-ENV:Header/>
3392 <SOAP-ENV:Body>
3393 <ns3:comunicacionResponse xmlns:ns3="http://www.soap.servicios.hospedajes.mir.es/comunicacion">
3394 <respuesta>
3395 <codigo>0</codigo>
3396 <descripcion>Ok</descripcion>
3397 </respuesta>
3398 <resultado>
3399 <lote>00000000-0000-0000-0000-000000000000</lote>
3400 <tipoComunicacion>PV</tipoComunicacion>
3401 <tipoOperacion>A</tipoOperacion>
3402 <fechaPeticion>2023-02-23T11:37:13.000+01:00</fechaPeticion>
3403 <fechaProcesamiento>2023-02-23T11:37:33.000+01:00</fechaProcesamiento>
3404 <codigoEstado>6</codigoEstado>
3405 <descEstado>Se ha ejecutado pero hay errores en algunas comunicaciones</descEstado>
3406 <identificadorUsuario>B00000000</identificadorUsuario>
3407 <nombreUsuario>Prueba</nombreUsuario>
3408 <codigoArrendador>0000000001</codigoArrendador>
3409 <aplicacion>Sistema de pruebas</aplicacion>
3410 <resultadoComunicaciones>
3411 <resultadoComunicacion>
3412 <orden>1</orden>
3413 <codigoComunicacion>12345678-4321-3210-6789-210987654321</codigoComunicacion>
3414 </resultadoComunicacion>
3415 </resultadoComunicaciones>
3416 </resultado>
3417 </ns3:comunicacionResponse>
3418 </SOAP-ENV:Body>
3419 </SOAP-ENV:Envelope>';
3420
3421 $nokc = '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
3422 <SOAP-ENV:Header/>
3423 <SOAP-ENV:Body>
3424 <ns3:comunicacionResponse xmlns:ns3="http://www.soap.servicios.hospedajes.mir.es/comunicacion">
3425 <respuesta>
3426 <codigo>0</codigo>
3427 <descripcion>Ok</descripcion>
3428 </respuesta>
3429 <resultado>
3430 <lote>00000000-0000-0000-0000-000000000000</lote>
3431 <tipoComunicacion>PV</tipoComunicacion>
3432 <tipoOperacion>A</tipoOperacion>
3433 <fechaPeticion>2023-02-23T11:37:13.000+01:00</fechaPeticion>
3434 <fechaProcesamiento>2023-02-23T11:37:33.000+01:00</fechaProcesamiento>
3435 <codigoEstado>6</codigoEstado>
3436 <descEstado>Se ha ejecutado pero hay errores en algunas comunicaciones</descEstado>
3437 <identificadorUsuario>B00000000</identificadorUsuario>
3438 <nombreUsuario>Prueba</nombreUsuario>
3439 <codigoArrendador>0000000001</codigoArrendador>
3440 <aplicacion>Sistema de pruebas</aplicacion>
3441 <resultadoComunicaciones>
3442 <resultadoComunicacion>
3443 <orden>1</orden>
3444 <tipoError>Error validación de datos</tipoError>
3445 <error>Error de validación en la comunicación [1]: [No existe un establecimiento para ese código. codigoEstablecimiento: [1234567890]]</error>
3446 </resultadoComunicacion>
3447 </resultadoComunicaciones>
3448 </resultado>
3449 </ns3:comunicacionResponse>
3450 </SOAP-ENV:Body>
3451 </SOAP-ENV:Envelope>';
3452
3453 if (strpos($message, '<tipoOperacion>C</tipoOperacion>')) {
3454 // consulta de lotes response simulation
3455 return rand(0, 1) ? $okc : $nokc;
3456 }
3457
3458 return rand(0, 1) ? $ok : $nok;
3459 */
3460 // end debug responses
3461
3462
3463 // build endpoint URL
3464 if ($settings['service_test_mode'] ?? 1) {
3465 $endpoint = $this->endpoint_test;
3466 } else {
3467 $endpoint = $this->endpoint_production;
3468 }
3469
3470 // build basic-auth value
3471 $basic_auth = base64_encode(($settings['service_username'] ?? '') . ':' . ($settings['service_password'] ?? ''));
3472
3473 // start HTTP transporter
3474 $http = new JHttp();
3475
3476 // build connection request headers
3477 $headers = [
3478 'Authorization' => 'Basic ' . $basic_auth,
3479 'Content-Type' => 'text/xml',
3480 ];
3481
3482 // check SSL request header settings
3483 if (is_file(dirname(__FILE__) . DIRECTORY_SEPARATOR . '_.ses.mir.es_1.crt')) {
3484 // register custom SSL certificate path
3485 $headers['sslcertificates'] = dirname(__FILE__) . DIRECTORY_SEPARATOR . '_.ses.mir.es_1.crt';
3486 } elseif (is_file(dirname(__FILE__) . DIRECTORY_SEPARATOR . '_.ses.mir.es.crt')) {
3487 // register custom SSL certificate path
3488 $headers['sslcertificates'] = dirname(__FILE__) . DIRECTORY_SEPARATOR . '_.ses.mir.es.crt';
3489 } else {
3490 // disable SSL peer validation for using an unsecure connection
3491 $headers['sslverify'] = false;
3492 }
3493
3494 // make POST request to the specified URL
3495 $response = $http->post($endpoint, $message, $headers);
3496
3497 if ($response->code != 200) {
3498 // invalid response, throw an exception
3499 throw new Exception(strip_tags((string) $response->body), $response->code);
3500 }
3501
3502 return $response->body;
3503 }
3504
3505 /**
3506 * Stores batch data under the given batch field name.
3507 *
3508 * @param array $data The batch data to store.
3509 * @param string $field The batch field name.
3510 *
3511 * @return void
3512 */
3513 protected function storeBatchData(array $data, $field = '')
3514 {
3515 // the configuration field name
3516 $field = $field ?: 'hospedajes_batches_' . date('Yn');
3517
3518 // access batches from the current field
3519 $current_batches = VBOFactory::getConfig()->getArray($field, []);
3520
3521 // prepend new batch data
3522 array_unshift($current_batches, $data);
3523
3524 // save new batches
3525 VBOFactory::getConfig()->set($field, $current_batches);
3526 }
3527
3528 /**
3529 * Reads batch data under the given batch field name.
3530 *
3531 * @param string $field The batch field name.
3532 *
3533 * @return array
3534 */
3535 protected function readBatchData($field = '')
3536 {
3537 // the configuration field name
3538 $field = $field ?: 'hospedajes_batches_' . date('Yn');
3539
3540 // return batches from the current field
3541 return VBOFactory::getConfig()->getArray($field, []);
3542 }
3543
3544 /**
3545 * Filters the given list of batch data by date and/or type.
3546 *
3547 * @param string $date The exact date to filter for.
3548 * @param string $type Filter by report type ("PV", "HR").
3549 *
3550 * @return array
3551 */
3552 protected function filterBatchData(array $data, $date = '', $type = '')
3553 {
3554 // return the filtered batches, if any
3555 return array_filter($data, function($batch) use ($date, $type) {
3556 $date_valid = true;
3557 $type_valid = true;
3558
3559 if ($date) {
3560 $date_valid = (bool) preg_match("/^" . preg_quote($date) . "/", $batch['op_date'] ?? '');
3561 }
3562
3563 if ($type) {
3564 $type_valid = !strcasecmp($type, $batch['rp_type'] ?? '');
3565 }
3566
3567 return $date_valid && $type_valid;
3568 });
3569 }
3570
3571 /**
3572 * Parses a SOAP XML message into a VBOXmlSoap object.
3573 *
3574 * @param string $xmlMessage The SOAP XML message to load.
3575 *
3576 * @return VBOXmlSoap
3577 *
3578 * @throws Exception
3579 */
3580 protected function loadXmlSoap($xmlMessage)
3581 {
3582 if (empty($xmlMessage)) {
3583 throw new Exception('Empty Soap XML message.', 500);
3584 }
3585
3586 // suppress warning messages
3587 libxml_use_internal_errors(true);
3588
3589 // parse the Soap XML message
3590 return simplexml_load_string($xmlMessage, VBOXmlSoap::class);
3591 }
3592
3593 /**
3594 * Registers the name to give to the file being exported.
3595 *
3596 * @return void
3597 */
3598 protected function registerExportFileName()
3599 {
3600 $pfromdate = VikRequest::getString('fromdate', '', 'request');
3601 $ptodate = VikRequest::getString('todate', '', 'request');
3602 $ptype = VikRequest::getString('type', '', 'request');
3603
3604 // build report type name
3605 if ($ptype === 'checkin') {
3606 $export_name = 'Partes_de_viajeros';
3607 } else {
3608 $export_name = 'Reservas_de_hospedaje';
3609 }
3610
3611 $this->setExportCSVFileName($export_name . '-' . $this->reportName . '-' . str_replace('/', '_', $pfromdate) . '-' . str_replace('/', '_', $ptodate) . '.xml');
3612 }
3613
3614 /**
3615 * Helper method to quickly get a pax_data property for the guest.
3616 *
3617 * @param array $pax_data the current pax_data stored.
3618 * @param array $guests list of total guests per room.
3619 * @param int $guest_ind the guest index.
3620 * @param string $key the pax_data key to look for.
3621 *
3622 * @return mixed null on failure or value fetched.
3623 */
3624 protected function getGuestPaxDataValue($pax_data, $guests, $guest_ind, $key)
3625 {
3626 if (!is_array($pax_data) || !$pax_data || empty($key)) {
3627 return null;
3628 }
3629
3630 // find room index for this guest number
3631 $room_num = 0;
3632 $use_guest_ind = $guest_ind;
3633 foreach ($guests as $room_index => $room_tot_guests) {
3634 // find the proper guest index for the room to which this belongs
3635 if ($use_guest_ind <= $room_tot_guests) {
3636 // proper room index found for this guest
3637 $room_num = $room_index;
3638 break;
3639 } else {
3640 // it's probably in a next room
3641 $use_guest_ind -= $room_tot_guests;
3642 }
3643 }
3644
3645 // check if a value exists for the requested key in the found room and guest indexes
3646 if (isset($pax_data[$room_num]) && isset($pax_data[$room_num][$use_guest_ind])) {
3647 if (isset($pax_data[$room_num][$use_guest_ind][$key])) {
3648 // we've got a value previously stored
3649 return $pax_data[$room_num][$use_guest_ind][$key];
3650 }
3651 }
3652
3653 // nothing was found
3654 return null;
3655 }
3656
3657 /**
3658 * Helper method to determine the exact number for this guest in the room booked.
3659 *
3660 * @param array $guests list of total guests per room.
3661 * @param int $guest_ind the guest index.
3662 *
3663 * @return int the actual guest room index starting from 1.
3664 */
3665 protected function calcGuestRoomIndex($guests, $guest_ind)
3666 {
3667 // find room index for this guest number
3668 $room_num = 0;
3669 $use_guest_ind = $guest_ind;
3670 foreach ($guests as $room_index => $room_tot_guests) {
3671 // find the proper guest index for the room to which this belongs
3672 if ($use_guest_ind <= $room_tot_guests) {
3673 // proper room index found for this guest
3674 $room_num = $room_index;
3675 break;
3676 } else {
3677 // it's probably in a next room
3678 $use_guest_ind -= $room_tot_guests;
3679 }
3680 }
3681
3682 return $use_guest_ind;
3683 }
3684 }
3685