PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.7.2
VikBooking Hotel Booking Engine & PMS v1.7.2
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.7.2, at admin/helpers/report/es_hospedajes.php

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