PluginProbe
VikBooking Hotel Booking Engine & PMS / trunk
VikBooking Hotel Booking Engine & PMS vtrunk
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 1.7.3 All 36 releases
vikbooking / admin / helpers / report / pt_sef.php

pt_sef.php in VikBooking Hotel Booking Engine & PMS trunk, at admin/helpers/report/pt_sef.php

1,459 lines 48.0 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 - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2023 e4j - Extensionsforjoomla.com. 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 * PtSef child Class of VikBookingReport.
15 * Portugal: Servico de Estrangeiros e Fronteiras
16 *
17 * @link https://siba.sef.pt/ajuda/modos-de-envio/#upload
18 *
19 * @since 1.16.2 (J) - 1.6.2 (WP)
20 */
21 class VikBookingReportPtSef extends VikBookingReport
22 {
23 /**
24 * Property 'defaultKeySort' is used by the View that renders the report.
25 *
26 * @var string
27 */
28 public $defaultKeySort = 'idbooking';
29
30 /**
31 * Property 'defaultKeyOrder' is used by the View that renders the report.
32 *
33 * @var string
34 */
35 public $defaultKeyOrder = 'ASC';
36
37 /**
38 * Property 'customExport' is used by the View to display custom export buttons.
39 *
40 * @var string
41 */
42 public $customExport = '';
43
44 /**
45 * Debug mode is activated by passing the value 'e4j_debug' > 0
46 *
47 * @var bool
48 */
49 protected $debug;
50
51 /**
52 * List of countries.
53 *
54 * @var array
55 */
56 protected $pt_countries = [];
57
58 /**
59 * List of ID types.
60 *
61 * @var array
62 */
63 protected $pt_idtypes = [];
64
65 /**
66 * Class constructor should define the name of the report and
67 * other vars. Call the parent constructor to define the DB object.
68 */
69 public function __construct()
70 {
71 $this->reportFile = basename(__FILE__, '.php');
72 $this->reportName = 'SEF - Servico de Estrangeiros e Fronteiras';
73 $this->reportFilters = [];
74
75 $this->cols = [];
76 $this->rows = [];
77 $this->footerRow = [];
78
79 $this->pt_countries = $this->loadCountries();
80 $this->pt_idtypes = $this->loadIdTypes();
81
82 $this->debug = (VikRequest::getInt('e4j_debug', 0, 'request') > 0);
83
84 $this->registerExportFileName();
85
86 parent::__construct();
87 }
88
89 /**
90 * Returns the name of this report.
91 *
92 * @return string
93 */
94 public function getName()
95 {
96 return $this->reportName;
97 }
98
99 /**
100 * Returns the name of this file without .php.
101 *
102 * @return string
103 */
104 public function getFileName()
105 {
106 return $this->reportFile;
107 }
108
109 /**
110 * Returns the filters of this report.
111 *
112 * @return array
113 */
114 public function getFilters()
115 {
116 if (count($this->reportFilters)) {
117 //do not run this method twice, as it could load JS and CSS files.
118 return $this->reportFilters;
119 }
120
121 // JS lang defs
122 JText::script('VBOADMINLEGENDSETTINGS');
123 JText::script('VBANNULLA');
124 JText::script('VBAPPLY');
125
126 // get VBO Application Object
127 $vbo_app = VikBooking::getVboApplication();
128
129 // load the jQuery UI Datepicker
130 $this->loadDatePicker();
131
132 // custom export button
133 $this->customExport = '<a href="JavaScript: void(0);" onclick="vboDownloadPtSefReport();" class="vbcsvexport"><i class="'.VikBookingIcons::i('download').'"></i> <span>Download Ficheiros</span></a>';
134
135 // From Date Filter
136 $filter_opt = array(
137 'label' => '<label for="fromdate">'.JText::translate('VBOREPORTREVENUEDAY').'</label>',
138 'html' => '<input type="text" id="fromdate" name="fromdate" value="" class="vbo-report-datepicker vbo-report-datepicker-from" />',
139 'type' => 'calendar',
140 'name' => 'fromdate'
141 );
142 array_push($this->reportFilters, $filter_opt);
143
144 // build JS helpers (fillers)
145 $hidden_vals = '';
146 $hidden_vals .= '<div id="vbo-report-ptsef-hidden" style="display: none;">';
147
148 // countries
149 $hidden_vals .= ' <div id="vbo-report-ptsef-nazione" class="vbo-report-ptsef-selcont" style="display: none;">';
150 $hidden_vals .= ' <select id="choose-country" onchange="vboReportChosenCountry(this);"><option value=""></option>';
151 foreach ($this->pt_countries as $code => $nat) {
152 $hidden_vals .= ' <option value="' . $code . '">' . $nat['country_name'] . '</option>' . "\n";
153 }
154 $hidden_vals .= ' </select>';
155 $hidden_vals .= ' </div>';
156
157 // ID types
158 $hidden_vals .= ' <div id="vbo-report-ptsef-doctype" class="vbo-report-ptsef-selcont" style="display: none;">';
159 $hidden_vals .= ' <select id="choose-doctype" onchange="vboReportChosenDoctype(this);"><option value=""></option>';
160 foreach ($this->pt_idtypes as $code => $documento) {
161 $hidden_vals .= ' <option value="' . $code . '">' . $documento . '</option>'."\n";
162 }
163 $hidden_vals .= ' </select>';
164 $hidden_vals .= ' </div>';
165
166 // ID number
167 $hidden_vals .= ' <div id="vbo-report-ptsef-docnum" class="vbo-report-ptsef-selcont" style="display: none;">';
168 $hidden_vals .= ' <input type="text" size="40" id="choose-docnum" placeholder="NĆŗmero Documento..." value="" /><br /><br />';
169 $hidden_vals .= ' <button type="button" class="btn vbo-config-btn" onclick="vboReportChosenDocnum(document.getElementById(\'choose-docnum\').value);">' . JText::translate('VBAPPLY') . '</button>';
170 $hidden_vals .= ' </div>';
171
172 // date of birth
173 $hidden_vals .= ' <div id="vbo-report-ptsef-dbirth" class="vbo-report-ptsef-selcont" style="display: none;">';
174 $hidden_vals .= ' <input type="text" size="40" id="choose-dbirth" placeholder="Data Nascimento" value="" /><br /><br />';
175 $hidden_vals .= ' <button type="button" class="btn vbo-config-btn" onclick="vboReportChosenDbirth(document.getElementById(\'choose-dbirth\').value);">' . JText::translate('VBAPPLY') . '</button>';
176 $hidden_vals .= ' </div>';
177
178 $hidden_vals .= '</div>';
179
180
181 // build HTML helper
182 $html_helper =
183 <<<HTML
184 <div class="vbo-report-pt-sef-settings-helper" style="display: none;">
185 <div class="vbo-report-pt-sef-settings-filler">
186 <div class="vbo-calendar-cfields-filler">
187 <div class="vbo-calendar-cfields-inner">
188 <div class="vbo-calendar-cfield-entry">
189 <label for="nif">NIF</label>
190 <span>
191 <input type="text" id="nif" value="" placeholder="Identificação fiscal" />
192 </span>
193 </div>
194 <div class="vbo-calendar-cfield-entry">
195 <label for="estabelecimento">Estabelecimento</label>
196 <span>
197 <input type="number" id="estabelecimento" min="0" max="9999" value="" placeholder="NĆŗmero do Estabelecimento" />
198 </span>
199 </div>
200 <div class="vbo-calendar-cfield-entry">
201 <label for="nome">Nome</label>
202 <span>
203 <input type="text" id="nome" value="" placeholder="Nome" />
204 </span>
205 </div>
206 <div class="vbo-calendar-cfield-entry">
207 <label for="morada">Morada</label>
208 <span>
209 <input type="text" id="morada" value="" placeholder="Morada" />
210 </span>
211 </div>
212 <div class="vbo-calendar-cfield-entry">
213 <label for="localidade">Localidade</label>
214 <span>
215 <input type="text" id="localidade" value="" placeholder="Localidade" />
216 </span>
217 </div>
218 <div class="vbo-calendar-cfield-entry">
219 <label for="postalcode">Codigo Postal</label>
220 <span>
221 <input type="text" id="postalcode" value="" />
222 </span>
223 </div>
224 <div class="vbo-calendar-cfield-entry">
225 <label for="zonapostal">Zona Postal</label>
226 <span>
227 <input type="text" id="zonapostal" value="" />
228 </span>
229 </div>
230 <div class="vbo-calendar-cfield-entry">
231 <label for="telefone">Telefone</label>
232 <span>
233 <input type="text" id="telefone" value="" />
234 </span>
235 </div>
236 <div class="vbo-calendar-cfield-entry">
237 <label for="fax">Fax</label>
238 <span>
239 <input type="text" id="fax" value="" />
240 </span>
241 </div>
242 <div class="vbo-calendar-cfield-entry">
243 <label for="contacto">Nome Contacto</label>
244 <span>
245 <input type="text" id="contacto" value="" />
246 </span>
247 </div>
248 <div class="vbo-calendar-cfield-entry">
249 <label for="email">Email Contacto</label>
250 <span>
251 <input type="email" id="email" value="" />
252 </span>
253 </div>
254 </div>
255 </div>
256 </div>
257 </div>
258 HTML;
259
260 // append button to manage the property information (Registo de CabeƧalho, which is the "header", so the first line of the DAT file) and HTML helper
261 $filter_opt = array(
262 'label' => '<label>Registo de CabeƧalho</label>',
263 'html' => '<button type="button" class="btn vbo-config-btn vbo-report-ptsef-mngsettings" onclick="vboPtSefManageSettings();"><i class="' . VikBookingIcons::i('cogs') . '"></i> ' . JText::translate('VBOADMINLEGENDSETTINGS') . '</button>' . $hidden_vals . $html_helper,
264 );
265 array_push($this->reportFilters, $filter_opt);
266
267 // append button to save the data when creating manual values
268 $filter_opt = array(
269 'label' => '<label class="vbo-report-ptsef-manualsave" style="display: none;">' . JText::translate('VBOGUESTSDETAILS') . '</label>',
270 'html' => '<button type="button" class="btn vbo-config-btn vbo-report-ptsef-manualsave" style="display: none;" onclick="vboPtSefSaveData();"><i class="' . VikBookingIcons::i('save') . '"></i> ' . JText::translate('VBSAVE') . '</button>',
271 );
272 array_push($this->reportFilters, $filter_opt);
273
274 // get minimum check-in and maximum check-out for dates filters
275 $df = $this->getDateFormat();
276 $mincheckin = 0;
277 $maxcheckout = 0;
278 $q = "SELECT MIN(`checkin`) AS `mincheckin`, MAX(`checkout`) AS `maxcheckout` FROM `#__vikbooking_orders` WHERE `status`='confirmed' AND `closure`=0;";
279 $this->dbo->setQuery($q);
280 $data = $this->dbo->loadAssoc();
281 if ($data) {
282 if (!empty($data['mincheckin']) && !empty($data['maxcheckout'])) {
283 $mincheckin = $data['mincheckin'];
284 $maxcheckout = $data['maxcheckout'];
285 }
286 }
287
288 // jQuery code for the datepicker calendars and select2
289 $pfromdate = VikRequest::getString('fromdate', date($df), 'request');
290 $js = 'jQuery(function() {
291 jQuery(".vbo-report-datepicker:input").datepicker({
292 '.(!empty($mincheckin) ? 'minDate: "'.date($df, $mincheckin).'", ' : '').'
293 '.(!empty($maxcheckout) ? 'maxDate: "'.date($df, $maxcheckout).'", ' : '').'
294 dateFormat: "'.$this->getDateFormat('jui').'"
295 });
296 '.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').'
297 });';
298 $this->setScript($js);
299
300 // js for managing the property information and for using the fillers
301 $report_settings = VBOFactory::getConfig()->getArray("report_{$this->reportFile}_settings", []);
302 $report_settings_js = json_encode($report_settings);
303 $js_ajax_base = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=invoke_report&report=' . $this->reportFile);
304 $js_save_icn = VikBookingIcons::i('save');
305 $js_saving_icn = VikBookingIcons::i('circle-notch', 'fa-spin fa-fw');
306 $js_saved_icn = VikBookingIcons::i('check-circle');
307 $js_birth_min = (date('Y') - 100);
308 $js_birth_max = date('Y');
309
310 $this->setScript(
311 <<<JS
312 var reportActiveCell = null, reportObj = {};
313 var vbo_report_js_ajax_base = "$js_ajax_base";
314 var vbo_report_settings = $report_settings_js;
315 var vbo_report_settings_def = [
316 'nif',
317 'estabelecimento',
318 'nome',
319 'morada',
320 'localidade',
321 'postalcode',
322 'zonapostal',
323 'telefone',
324 'fax',
325 'contacto',
326 'email',
327 ];
328 var vbo_ptsef_save_icn = "$js_save_icn";
329 var vbo_ptsef_saving_icn = "$js_saving_icn";
330 var vbo_ptsef_saved_icn = "$js_saved_icn";
331
332 // manage property settings/information
333 function vboPtSefManageSettings() {
334 let modal_body = VBOCore.displayModal({
335 suffix: 'report-pt-sef',
336 extra_class: 'vbo-modal-tall',
337 title: Joomla.JText._('VBOADMINLEGENDSETTINGS'),
338 body_prepend: true,
339 footer_left: '<button type="button" class="btn" onclick="vboPtSefCancelSettings();">' + Joomla.JText._('VBANNULLA') + '</button>',
340 footer_right: '<button type="button" class="btn btn-success" onclick="vboPtSefSaveSettings();"><i class="icon-edit"></i> ' + Joomla.JText._('VBAPPLY') + '</button>',
341 dismiss_event: 'vbo-report-pt-sef-settings-dismiss',
342 onDismiss: () => {
343 jQuery('.vbo-report-pt-sef-settings-filler').appendTo('.vbo-report-pt-sef-settings-helper');
344 },
345 });
346
347 jQuery('.vbo-report-pt-sef-settings-filler').appendTo(modal_body);
348
349 // populate current settings
350 vbo_report_settings_def.forEach((sett_name) => {
351 if (vbo_report_settings.hasOwnProperty(sett_name)) {
352 jQuery('#' + sett_name).val(vbo_report_settings[sett_name]);
353 }
354 });
355 }
356
357 // save settings
358 function vboPtSefSaveSettings() {
359 let vbo_pt_sef_save_settings = {
360 nif: jQuery('#nif').val(),
361 estabelecimento: jQuery('#estabelecimento').val(),
362 nome: jQuery('#nome').val(),
363 morada: jQuery('#morada').val(),
364 localidade: jQuery('#localidade').val(),
365 postalcode: jQuery('#postalcode').val(),
366 zonapostal: jQuery('#zonapostal').val(),
367 telefone: jQuery('#telefone').val(),
368 fax: jQuery('#fax').val(),
369 contacto: jQuery('#contacto').val(),
370 email: jQuery('#email').val(),
371 };
372
373 VBOCore.doAjax(
374 vbo_report_js_ajax_base,
375 {
376 call: "savePtSefSettings",
377 params: vbo_pt_sef_save_settings,
378 tmpl: "component"
379 },
380 (success) => {
381 // overwrite current settings object
382 vbo_report_settings = Object.assign(vbo_report_settings, vbo_pt_sef_save_settings);
383 // dismiss modal
384 VBOCore.emitEvent('vbo-report-pt-sef-settings-dismiss');
385 },
386 (err) => {
387 alert(err.responseText);
388 },
389 );
390 }
391
392 // cancel settings
393 function vboPtSefCancelSettings() {
394 // dismiss modal
395 VBOCore.emitEvent('vbo-report-pt-sef-settings-dismiss');
396 }
397
398 // download function
399 function vboDownloadPtSefReport() {
400 if (!confirm("Certifique-se de ter preenchido todas as informaƧƵes. Continuar com o download?")) {
401 return false;
402 }
403 document.adminForm.target = "_blank";
404 document.adminForm.action += "&tmpl=component";
405 vboSetFilters({exportreport: "1", filler: JSON.stringify(reportObj)}, true);
406 setTimeout(function() {
407 document.adminForm.target = "";
408 document.adminForm.action = document.adminForm.action.replace("&tmpl=component", "");
409 vboSetFilters({exportreport: "0", filler: ""}, false);
410 }, 1000);
411 }
412
413 // save data after manual fillers
414 function vboPtSefSaveData() {
415 jQuery("button.vbo-report-ptsef-manualsave").find("i").attr("class", vbo_ptsef_saving_icn);
416 VBOCore.doAjax(
417 vbo_report_js_ajax_base,
418 {
419 call: "updatePaxData",
420 params: reportObj,
421 tmpl: "component"
422 },
423 (response) => {
424 if (!response || !response[0]) {
425 alert("An error occurred.");
426 return false;
427 }
428 jQuery("button.vbo-report-ptsef-manualsave").addClass("btn-success").find("i").attr("class", vbo_ptsef_saved_icn);
429 },
430 (error) => {
431 alert(error.responseText);
432 jQuery("button.vbo-report-ptsef-manualsave").removeClass("btn-success").find("i").attr("class", vbo_ptsef_save_icn);
433 }
434 );
435 }
436
437 // DOM ready state
438 jQuery(function() {
439 // prepare filler helpers
440 jQuery("#vbo-report-ptsef-hidden").children().detach().appendTo(".vbo-info-overlay-report");
441 jQuery("#choose-country").select2({placeholder: "- PaĆ­s -", width: "200px"});
442 jQuery("#choose-doctype").select2({placeholder: "- Tipo Documento -", width: "200px"});
443 jQuery("#choose-dbirth").datepicker({
444 maxDate: 0,
445 dateFormat: "dd/mm/yy",
446 changeMonth: true,
447 changeYear: true,
448 yearRange: "$js_birth_min:$js_birth_max"
449 });
450
451 // click events
452 jQuery(".vbo-report-load-nazione, .vbo-report-load-nazione-stay, .vbo-report-load-cittadinanza").click(function() {
453 reportActiveCell = this;
454 jQuery(".vbo-report-ptsef-selcont").hide();
455 jQuery("#vbo-report-ptsef-nazione").show();
456 vboShowOverlay();
457 });
458 jQuery(".vbo-report-load-doctype").click(function() {
459 reportActiveCell = this;
460 jQuery(".vbo-report-ptsef-selcont").hide();
461 jQuery("#vbo-report-ptsef-doctype").show();
462 vboShowOverlay();
463 });
464 jQuery(".vbo-report-load-docplace").click(function() {
465 reportActiveCell = this;
466 jQuery(".vbo-report-ptsef-selcont").hide();
467 jQuery("#vbo-report-ptsef-nazione").show();
468 vboShowOverlay();
469 });
470 jQuery(".vbo-report-load-docnum").click(function() {
471 reportActiveCell = this;
472 jQuery(".vbo-report-ptsef-selcont").hide();
473 jQuery("#vbo-report-ptsef-docnum").show();
474 vboShowOverlay();
475 setTimeout(function(){jQuery("#choose-docnum").focus();}, 500);
476 });
477 jQuery(".vbo-report-load-dbirth").click(function() {
478 reportActiveCell = this;
479 jQuery(".vbo-report-ptsef-selcont").hide();
480 jQuery("#vbo-report-ptsef-dbirth").show();
481 vboShowOverlay();
482 });
483 });
484
485 function vboReportChosenCountry(country_el) {
486 var c_code = country_el.value;
487 var c_val = country_el.options[country_el.selectedIndex].text;
488 if (reportActiveCell !== null) {
489 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
490 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
491 alert("Error, cannot find element to update.");
492 } else {
493 var rep_act_cell = jQuery(reportActiveCell);
494 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
495 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
496 if (!reportObj.hasOwnProperty(nowindex)) {
497 reportObj[nowindex] = {
498 bid: rep_guest_bid,
499 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
500 };
501 }
502 if (jQuery(reportActiveCell).hasClass("vbo-report-load-nazione")) {
503 reportObj[nowindex]["country_b"] = c_code;
504 } else if (jQuery(reportActiveCell).hasClass("vbo-report-load-nazione-stay")) {
505 reportObj[nowindex]["country_s"] = c_code;
506 } else if (jQuery(reportActiveCell).hasClass("vbo-report-load-docplace")) {
507 reportObj[nowindex]["docplace"] = c_code;
508 } else {
509 reportObj[nowindex]["country_c"] = c_code;
510 }
511 }
512 }
513 reportActiveCell = null;
514 vboHideOverlay();
515 jQuery("#choose-nazione").val("").select2("data", null, false);
516 jQuery(".vbo-report-ptsef-manualsave").show();
517 }
518
519 function vboReportChosenDoctype(doctype) {
520 var c_code = doctype.value;
521 var c_val = doctype.options[doctype.selectedIndex].text;
522 if (reportActiveCell !== null) {
523 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
524 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
525 alert("Error, cannot find element to update.");
526 } else {
527 var rep_act_cell = jQuery(reportActiveCell);
528 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
529 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
530 if (!reportObj.hasOwnProperty(nowindex)) {
531 reportObj[nowindex] = {
532 bid: rep_guest_bid,
533 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
534 };
535 }
536 reportObj[nowindex]["doctype"] = c_code;
537 }
538 }
539 reportActiveCell = null;
540 vboHideOverlay();
541 jQuery("#choose-doctype").val("").select2("data", null, false);
542 jQuery(".vbo-report-ptsef-manualsave").show();
543 }
544
545 function vboReportChosenDocnum(val) {
546 var c_code = val, c_val = val;
547 if (reportActiveCell !== null) {
548 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
549 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
550 alert("Error, cannot find element to update.");
551 } else {
552 var rep_act_cell = jQuery(reportActiveCell);
553 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
554 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
555 if (!reportObj.hasOwnProperty(nowindex)) {
556 reportObj[nowindex] = {
557 bid: rep_guest_bid,
558 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
559 };
560 }
561 reportObj[nowindex]["docnum"] = c_code;
562 }
563 }
564 reportActiveCell = null;
565 vboHideOverlay();
566 jQuery("#choose-docnum").val("");
567 jQuery(".vbo-report-ptsef-manualsave").show();
568 }
569
570 function vboReportChosenDbirth(val) {
571 var c_code = val, c_val = val;
572 if (reportActiveCell !== null) {
573 var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
574 if (isNaN(nowindex) || parseInt(nowindex) < 0) {
575 alert("Error, cannot find element to update.");
576 } else {
577 var rep_act_cell = jQuery(reportActiveCell);
578 rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
579 var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
580 if (!reportObj.hasOwnProperty(nowindex)) {
581 reportObj[nowindex] = {
582 bid: rep_guest_bid,
583 bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
584 };
585 }
586 reportObj[nowindex]["date_birth"] = c_code;
587 }
588 }
589 reportActiveCell = null;
590 vboHideOverlay();
591 jQuery("#choose-dbirth").val("");
592 jQuery(".vbo-report-ptsef-manualsave").show();
593 }
594 JS
595 );
596
597 return $this->reportFilters;
598 }
599
600 /**
601 * Loads the report data from the DB.
602 * Returns true in case of success, false otherwise.
603 * Sets the columns and rows for the report to be displayed.
604 *
605 * @return boolean
606 */
607 public function getReportData()
608 {
609 if (strlen($this->getError())) {
610 // Export functions may set errors rather than exiting the process, and the View may continue the execution to attempt to render the report.
611 return false;
612 }
613
614 // input fields and other vars
615 $pfromdate = VikRequest::getString('fromdate', '', 'request');
616 $pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request');
617 $pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort;
618 $pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request');
619 $pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder;
620 $pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC';
621 $currency_symb = VikBooking::getCurrencySymb();
622 $df = $this->getDateFormat();
623 $datesep = VikBooking::getDateSeparator();
624
625 // Get dates timestamps
626 $from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
627 $to_ts = VikBooking::getDateTimestamp($pfromdate, 23, 59, 59);
628 if (empty($pfromdate) || empty($from_ts)) {
629 $this->setError(JText::translate('VBOREPORTSERRNODATES'));
630 return false;
631 }
632
633 // Query to obtain the records (arrivals, departures and stayovers for the selected date)
634 $q = "SELECT `o`.`id`,`o`.`custdata`,`o`.`ts`,`o`.`days`,`o`.`checkin`,`o`.`checkout`,`o`.`totpaid`,`o`.`roomsnum`,`o`.`total`,`o`.`idorderota`,`o`.`channel`,`o`.`country`,".
635 "`or`.`idorder`,`or`.`idroom`,`or`.`adults`,`or`.`children`,`or`.`t_first_name`,`or`.`t_last_name`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`room_cost`,".
636 "`co`.`idcustomer`,`co`.`pax_data`,`c`.`first_name`,`c`.`last_name`,`c`.`country` AS `customer_country`,`c`.`address`,`c`.`doctype`,`c`.`docnum`,`c`.`gender`,`c`.`bdate`,`c`.`pbirth` ".
637 "FROM `#__vikbooking_orders` AS `o` LEFT JOIN `#__vikbooking_ordersrooms` AS `or` ON `or`.`idorder`=`o`.`id` ".
638 "LEFT JOIN `#__vikbooking_customers_orders` AS `co` ON `co`.`idorder`=`o`.`id` LEFT JOIN `#__vikbooking_customers` AS `c` ON `c`.`id`=`co`.`idcustomer` ".
639 "WHERE `o`.`status`='confirmed' AND `o`.`closure`=0 AND (
640 (`o`.`checkin` >= $from_ts AND `o`.`checkin` <= $to_ts) OR (`o`.`checkout` >= $from_ts AND `o`.`checkout` <= $to_ts) OR (`o`.`checkin` < $from_ts AND `o`.`checkout` > $to_ts)
641 ) ".
642 "ORDER BY `o`.`checkin` ASC, `o`.`id` ASC, `or`.`id` ASC;";
643 $this->dbo->setQuery($q);
644 $records = $this->dbo->loadAssocList();
645
646 if (!$records) {
647 $this->setError(JText::translate('VBOREPORTSERRNORESERV'));
648 return false;
649 }
650
651 // nest records with multiple rooms booked inside sub-array
652 $bookings = [];
653 foreach ($records as $v) {
654 if (!isset($bookings[$v['id']])) {
655 $bookings[$v['id']] = [];
656 }
657 array_push($bookings[$v['id']], $v);
658 }
659
660 // free some memory up
661 unset($records);
662
663 // define the columns of the report
664 $this->cols = array(
665 // type
666 array(
667 'key' => 'type',
668 'sortable' => 1,
669 'label' => JText::translate('VBPSHOWSEASONSTHREE')
670 ),
671 // last name
672 array(
673 'key' => 'lastname',
674 'sortable' => 1,
675 'label' => JText::translate('ORDER_LNAME')
676 ),
677 // name
678 array(
679 'key' => 'name',
680 'sortable' => 1,
681 'label' => JText::translate('ORDER_NAME')
682 ),
683 // nationality
684 array(
685 'key' => 'nationality',
686 'attr' => array(
687 'class="center"'
688 ),
689 'label' => JText::translate('VBCUSTOMERNATION')
690 ),
691 // birthplace
692 array(
693 'key' => 'place_birth',
694 'attr' => array(
695 'class="center"'
696 ),
697 'tip' => ucwords(JText::translate('VBOFILTEISROPTIONAL')),
698 'label' => JText::translate('VBOCUSTPLACEBIRTH')
699 ),
700 // birth date
701 array(
702 'key' => 'date_birth',
703 'attr' => array(
704 'class="center"'
705 ),
706 'label' => JText::translate('VBCUSTOMERBDATE')
707 ),
708 // docnum
709 array(
710 'key' => 'docnum',
711 'attr' => array(
712 'class="center"'
713 ),
714 'label' => JText::translate('VBOCUSTDOCNUM')
715 ),
716 // doctype
717 array(
718 'key' => 'doctype',
719 'attr' => array(
720 'class="center"'
721 ),
722 'label' => JText::translate('VBOCUSTDOCTYPE')
723 ),
724 // docplace
725 array(
726 'key' => 'docplace',
727 'attr' => array(
728 'class="center"'
729 ),
730 'label' => 'PaĆ­s Emissor'
731 ),
732 // country_s
733 array(
734 'key' => 'country_s',
735 'attr' => array(
736 'class="center"'
737 ),
738 'label' => 'PaĆ­s ResidĆŖncia'
739 ),
740 // place_s
741 array(
742 'key' => 'place_s',
743 'tip' => ucwords(JText::translate('VBOFILTEISROPTIONAL')),
744 'label' => 'Local ResidĆŖncia'
745 ),
746 // checkin
747 array(
748 'key' => 'checkin',
749 'sortable' => 1,
750 'label' => JText::translate('VBPICKUPAT')
751 ),
752 // checkout
753 array(
754 'key' => 'checkout',
755 'sortable' => 1,
756 'label' => JText::translate('VBRELEASEAT')
757 ),
758 // id booking
759 array(
760 'key' => 'idbooking',
761 'sortable' => 1,
762 'attr' => array(
763 'class="center"'
764 ),
765 'label' => 'ID'
766 ),
767 );
768
769 // loop over the bookings to build the rows of the report
770 $from_info = getdate($from_ts);
771 foreach ($bookings as $gbook) {
772 // count the total number of guests for all rooms of this booking
773 $tot_booking_guests = 0;
774 $room_guests = [];
775 foreach ($gbook as $rbook) {
776 $tot_booking_guests += ($rbook['adults'] + $rbook['children']);
777 $room_guests[] = ($rbook['adults'] + $rbook['children']);
778 }
779
780 // make sure to decode the current pax data
781 if (!empty($gbook[0]['pax_data'])) {
782 $gbook[0]['pax_data'] = json_decode($gbook[0]['pax_data'], true);
783 $gbook[0]['pax_data'] = !is_array($gbook[0]['pax_data']) ? [] : $gbook[0]['pax_data'];
784 }
785
786 // push a copy of the booking for each guest
787 $guests_rows = [];
788 for ($i = 1; $i <= $tot_booking_guests; $i++) {
789 array_push($guests_rows, $gbook[0]);
790 }
791
792 // create one row for each guest
793 $guest_ind = 1;
794 foreach ($guests_rows as $ind => $guests) {
795 // prepare row record for this room-guest
796 $insert_row = [];
797
798 // find the actual guest-room-index
799 $guest_room_ind = $this->calcGuestRoomIndex($room_guests, $guest_ind);
800
801 // stay type
802 if (date('Y-m-d', $guests['checkin']) == date('Y-m-d', $from_ts)) {
803 $stay_type = JText::translate('VBOTYPEARRIVAL');
804 } elseif (date('Y-m-d', $guests['checkout']) == date('Y-m-d', $from_ts)) {
805 $stay_type = JText::translate('VBOTYPEDEPARTURE');
806 } else {
807 $stay_type = JText::translate('VBOTYPESTAYOVER');
808 }
809 array_push($insert_row, array(
810 'key' => 'type',
811 'ignore_export' => 1,
812 'value' => $stay_type
813 ));
814
815 // last name
816 $cognome = !empty($guests['t_last_name']) ? $guests['t_last_name'] : $guests['last_name'];
817 $pax_cognome = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'last_name');
818 $cognome = !empty($pax_cognome) ? $pax_cognome : $cognome;
819 array_push($insert_row, array(
820 'key' => 'lastname',
821 'value' => $cognome
822 ));
823
824 // name
825 $nome = !empty($guests['t_first_name']) ? $guests['t_first_name'] : $guests['first_name'];
826 $pax_nome = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'first_name');
827 $nome = !empty($pax_nome) ? $pax_nome : $nome;
828 array_push($insert_row, array(
829 'key' => 'name',
830 'value' => $nome
831 ));
832
833 /**
834 * Nationality.
835 * Check compatibility with pax_data field of driver for "Portugal".
836 */
837 $citizen = !empty($guests['country']) && $guest_ind < 2 ? $guests['country'] : '';
838 $pax_country_c = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'country_c');
839 $citizen = !empty($pax_country_c) ? $pax_country_c : $citizen;
840
841 // check nationality field from pre-checkin
842 $pax_citizen = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'nationality');
843 $citizen = empty($citizen) && !empty($pax_citizen) ? $pax_citizen : $citizen;
844
845 array_push($insert_row, array(
846 'key' => 'nationality',
847 'attr' => array(
848 'class="center' . (empty($citizen) ? ' vbo-report-load-cittadinanza' : '') . '"'
849 ),
850 'callback' => function ($val) {
851 return !empty($val) && isset($this->pt_countries[$val]) ? $this->pt_countries[$val]['country_name'] : '?';
852 },
853 'no_export_callback' => 1,
854 'value' => $citizen
855 ));
856
857 // birth place
858 $pax_pbirth = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'place_birth');
859 array_push($insert_row, array(
860 'key' => 'place_birth',
861 'attr' => array(
862 'class="center"'
863 ),
864 'value' => ($pax_pbirth ? $pax_pbirth : '')
865 ));
866
867 // birth date
868 $dbirth = !empty($guests['bdate']) && $guest_ind < 2 ? VikBooking::getDateTimestamp($guests['bdate'], 0, 0) : '';
869 $pax_dbirth = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'date_birth');
870 $dbirth = !empty($pax_dbirth) ? $pax_dbirth : $dbirth;
871 $dbirth = (strpos($dbirth, '/') === false && strpos($dbirth, VikBooking::getDateSeparator()) === false) ? $dbirth : VikBooking::getDateTimestamp($dbirth, 0, 0);
872 array_push($insert_row, array(
873 'key' => 'date_birth',
874 'attr' => array(
875 'class="center' . (empty($dbirth) ? ' vbo-report-load-dbirth' : '') . '"'
876 ),
877 'callback' => function ($val) {
878 if (!empty($val) && strpos($val, '/') === false && strpos($val, VikBooking::getDateSeparator()) === false) {
879 return date('d/m/Y', $val);
880 }
881 if (!empty($val) && strpos($val, '/') !== false) {
882 return $val;
883 }
884 return '?';
885 },
886 'export_callback' => function ($val) {
887 if (!empty($val) && strpos($val, '/') === false && strpos($val, VikBooking::getDateSeparator()) === false) {
888 return date('Ymd', $val);
889 }
890 if (!empty($val) && strpos($val, '/') !== false) {
891 return date('Ymd', VikBooking::getDateTimestamp($val, 0, 0));
892 }
893 return '?';
894 },
895 'value' => $dbirth
896 ));
897
898 /**
899 * ID Number
900 * Check compatibility with pax_data field of driver for "Portugal".
901 */
902 $docnum = !empty($guests['docnum']) && $guest_ind < 2 ? $guests['docnum'] : '';
903 $pax_docnum = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'docnum');
904 $docnum = !empty($pax_docnum) ? $pax_docnum : $docnum;
905
906 array_push($insert_row, array(
907 'key' => 'docnum',
908 'attr' => array(
909 'class="center' . (empty($docnum) ? ' vbo-report-load-docnum' : '') . '"'
910 ),
911 'callback' => function ($val) {
912 return empty($val) ? '?' : $val;
913 },
914 'value' => $docnum
915 ));
916
917 /**
918 * ID Type
919 * Check compatibility with pax_data field of driver for "Portugal".
920 */
921 $pax_doctype = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'doctype');
922 $doctype = 'O';
923 $doctype_cur_val = '';
924 if (!empty($pax_doctype)) {
925 $doctype = $pax_doctype;
926 $doctype_cur_val = $pax_doctype;
927 }
928
929 array_push($insert_row, array(
930 'key' => 'doctype',
931 'attr' => array(
932 // we always allow to rectify this field, but if guessed, we style it with the class "vbo-report-load-elem-filled"
933 'class="center vbo-report-load-doctype' . (!empty($doctype_cur_val) ? ' vbo-report-load-elem-filled' : '') . '"'
934 ),
935 'callback' => function ($val) use ($doctype_cur_val) {
936 return !empty($doctype_cur_val) ? $doctype_cur_val : $val;
937 },
938 'no_export_callback' => 1,
939 'value' => $doctype
940 ));
941
942 /**
943 * ID Issuing Country
944 * Check compatibility with pax_data field of driver for "Portugal".
945 */
946 $pax_docplace = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'docplace');
947 $docplace = $pax_docplace;
948
949 array_push($insert_row, array(
950 'key' => 'docplace',
951 'attr' => array(
952 'class="center' . (empty($docplace) ? ' vbo-report-load-docplace' : '') . '"'
953 ),
954 'callback' => function ($val) {
955 return !empty($val) && isset($this->pt_countries[$val]) ? $this->pt_countries[$val]['country_name'] : '?';
956 },
957 'no_export_callback' => 1,
958 'value' => $docplace
959 ));
960
961 /**
962 * Country of residence.
963 * Check compatibility with pax_data field of driver for "Portugal".
964 */
965 $pax_countrystay = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'country_s');
966 array_push($insert_row, array(
967 'key' => 'country_s',
968 'attr' => array(
969 'class="center' . (empty($pax_countrystay) ? ' vbo-report-load-field vbo-report-load-nazione-stay' : '') . '"'
970 ),
971 'callback' => function($val) {
972 if (!empty($val) && isset($this->pt_countries[$val])) {
973 return $this->pt_countries[$val]['country_name'];
974 }
975 // information is missing and should be provided
976 return '?';
977 },
978 'no_export_callback' => 1,
979 'value' => $pax_countrystay,
980 ));
981
982 /**
983 * Place of residence.
984 * Check compatibility with pax_data field of driver for "Portugal".
985 */
986 $pax_placestay = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'place_s');
987 array_push($insert_row, array(
988 'key' => 'place_s',
989 'value' => $pax_placestay,
990 ));
991
992 // checkin
993 array_push($insert_row, array(
994 'key' => 'checkin',
995 'callback' => function ($val) {
996 return date('d/m/Y', $val);
997 },
998 'export_callback' => function ($val) {
999 return date('Ymd', $val);
1000 },
1001 'value' => $guests['checkin']
1002 ));
1003
1004 // checkout
1005 array_push($insert_row, array(
1006 'key' => 'checkout',
1007 'callback' => function ($val) {
1008 return date('d/m/Y', $val);
1009 },
1010 'export_callback' => function ($val) {
1011 return date('Ymd', $val);
1012 },
1013 'value' => $guests['checkout']
1014 ));
1015
1016 // id booking
1017 array_push($insert_row, array(
1018 'key' => 'idbooking',
1019 'attr' => array(
1020 'class="center"'
1021 ),
1022 'callback' => function ($val) {
1023 // make sure to keep the data-bid attribute as it's used by JS to identify the booking ID
1024 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>';
1025 },
1026 'ignore_export' => 1,
1027 'value' => $guests['id']
1028 ));
1029
1030 // push fields in the rows array as a new row
1031 array_push($this->rows, $insert_row);
1032
1033 // increment guest index
1034 $guest_ind++;
1035 }
1036 }
1037
1038 // sort the rows
1039 $this->sortRows($pkrsort, $pkrorder);
1040
1041 // the footer row will just print the amount of records to export
1042 array_push($this->footerRow, array(
1043 array(
1044 'attr' => array(
1045 'class="vbo-report-total"'
1046 ),
1047 'value' => '<h3>' . JText::translate('VBOREPORTSTOTALROW') . '</h3>'
1048 ),
1049 array(
1050 'attr' => array(
1051 'colspan="' . (count($this->cols) - 1) . '"'
1052 ),
1053 'value' => count($this->rows)
1054 )
1055 ));
1056
1057 // Debug
1058 if ($this->debug) {
1059 $this->setWarning('path to report file = '.urlencode(dirname(__FILE__)).'<br/>');
1060 $this->setWarning('$total_rooms_units = '.$total_rooms_units.'<br/>');
1061 $this->setWarning('$bookings:<pre>'.print_r($bookings, true).'</pre><br/>');
1062 }
1063
1064 return true;
1065 }
1066
1067 /**
1068 * Generates the text file for the Italian Police,
1069 * then it sends it to output for download.
1070 * In case of errors, the process is not terminated (exit)
1071 * to let the View display the error message.
1072 *
1073 * @param string $export_type Differentiates the type of export requested.
1074 *
1075 * @return void|bool Void in case of script termination, boolean otherwise.
1076 */
1077 public function customExport($export_type = 0)
1078 {
1079 if (!$this->getReportData()) {
1080 return false;
1081 }
1082
1083 // load settings from DB to populate the first line of the DAT file
1084 $report_settings = VBOFactory::getConfig()->getArray("report_{$this->reportFile}_settings", []);
1085 if (!$report_settings) {
1086 $this->setError('Mandatory report settings are missing');
1087 return false;
1088 }
1089
1090 $pfromdate = VikRequest::getString('fromdate', '', 'request');
1091 $pfiller = VikRequest::getString('filler', '', 'request', VIKREQUEST_ALLOWRAW);
1092 $pfiller = !empty($pfiller) ? json_decode($pfiller, true) : [];
1093 $pfiller = !is_array($pfiller) ? [] : $pfiller;
1094
1095 // map of the rows keys with their related length
1096 $keys_length_map = [
1097 'lastname' => 40,
1098 'name' => 40,
1099 'nationality' => 3,
1100 'place_birth' => 40,
1101 'date_birth' => 8,
1102 'docnum' => 16,
1103 'doctype' => 3,
1104 'docplace' => 3,
1105 'country_s' => 3,
1106 'place_s' => 30,
1107 'checkin' => 8,
1108 'checkout' => 8,
1109 ];
1110
1111 // the below field keys are optional and can be empty
1112 $optional_fields = ['place_birth', 'place_s'];
1113
1114 // pool of booking IDs to update their history
1115 $booking_ids = [];
1116
1117 // array of lines (header, one line for each guest and closure)
1118 $lines = [];
1119
1120 // build header line by reading the parameters
1121 $header_line_parts = [
1122 '0',
1123 'BA03',
1124 str_pad((!empty($report_settings['nif']) ? $report_settings['nif'] : ''), 9),
1125 str_pad((!empty($report_settings['estabelecimento']) ? $report_settings['estabelecimento'] : ''), 4),
1126 str_pad((!empty($report_settings['nome']) ? $report_settings['nome'] : ''), 40),
1127 str_pad((!empty($report_settings['morada']) ? $report_settings['morada'] : ''), 40),
1128 str_pad((!empty($report_settings['localidade']) ? $report_settings['localidade'] : ''), 30),
1129 str_pad((!empty($report_settings['postalcode']) ? $report_settings['postalcode'] : ''), 4),
1130 str_pad((!empty($report_settings['zonapostal']) ? $report_settings['zonapostal'] : ''), 3),
1131 str_pad((!empty($report_settings['telefone']) ? $report_settings['telefone'] : ''), 10),
1132 str_pad((!empty($report_settings['fax']) ? $report_settings['fax'] : ''), 10),
1133 str_pad((!empty($report_settings['contacto']) ? $report_settings['contacto'] : ''), 40),
1134 str_pad((!empty($report_settings['email']) ? $report_settings['email'] : ''), 140),
1135 ];
1136
1137 // push registration header line
1138 $lines[] = implode('|', $header_line_parts) . '|';
1139
1140 // build registration lines
1141 $registration_lines = [];
1142
1143 // push the lines of the DAT file
1144 foreach ($this->rows as $ind => $row) {
1145 // build registration line for this guest
1146 $registration_line_parts = [
1147 // fixed registration type for each guest is 1
1148 '1'
1149 ];
1150
1151 // parse row for this guest
1152 foreach ($row as $field) {
1153 if (!isset($keys_length_map[$field['key']]) || isset($field['ignore_export'])) {
1154 // we don't need this information
1155 continue;
1156 }
1157
1158 if ($field['key'] == 'idbooking' && !in_array($field['value'], $booking_ids)) {
1159 // register booking ID for later history update
1160 $booking_ids[] = $field['value'];
1161 }
1162
1163 // report value
1164 if (isset($pfiller[$ind]) && isset($pfiller[$ind][$field['key']])) {
1165 if (strlen((string)$pfiller[$ind][$field['key']])) {
1166 $field['value'] = $pfiller[$ind][$field['key']];
1167 }
1168 }
1169
1170 // always cast to string
1171 $field['value'] = (string)$field['value'];
1172 if (!$field['value'] && !in_array($field['key'], $optional_fields)) {
1173 // raise error message without stopping
1174 VikError::raiseWarning('', 'Row #' . ($ind + 1) . ' has got an empty value that should have been manually filled (' . $field['key'] . '). The file may be broken or incomplete.');
1175 }
1176
1177 // get the final value to be included in the exported file
1178 if (isset($field['callback_export'])) {
1179 $field['callback'] = $field['callback_export'];
1180 }
1181 $value = !isset($field['no_export_callback']) && isset($field['callback']) && is_callable($field['callback']) ? $field['callback']($field['value']) : $field['value'];
1182
1183 // set guest registration value with proper length
1184 $registration_line_parts[] = str_pad($value, $keys_length_map[$field['key']]);
1185 }
1186
1187 // push guest registration line
1188 $registration_lines[] = implode('|', $registration_line_parts) . '|';
1189 }
1190
1191 // append all guest registration lines
1192 $lines = array_merge($lines, $registration_lines);
1193
1194 // build file number (size 5) by using the last 2 digit of the current year, and the day of the year (0 through 365)
1195 $now_info = getdate();
1196 $file_number = substr((string)$now_info['year'], -2) . $now_info['yday'];
1197
1198 // build last registration line with summary
1199 $last_line_parts = [
1200 // fixed registration type for the last line is 9
1201 '9',
1202 // number of records (lines) in the file, including this one (header + guests + last line)
1203 str_pad((string)(count($registration_lines) + 2), 5, '0', STR_PAD_LEFT),
1204 // generation date
1205 date('Ymd'),
1206 // Hotel unit file serial number
1207 str_pad($file_number, 5, '0', STR_PAD_LEFT),
1208 ];
1209
1210 // append last line
1211 $lines[] = implode('|', $last_line_parts) . '|';
1212
1213 // update the history for all bookings affected
1214 foreach ($booking_ids as $bid) {
1215 VikBooking::getBookingHistoryInstance()->setBid($bid)->store('RP', $this->reportName);
1216 }
1217
1218 /**
1219 * Custom export method supports a custom export handler, if previously set.
1220 */
1221 if ($this->hasExportHandler()) {
1222 // write data onto the custom file handler
1223 $fp = $this->getExportCSVHandler();
1224 fwrite($fp, implode("\r\n", $lines));
1225 fclose($fp);
1226
1227 return true;
1228 }
1229
1230 // force text file download
1231 header("Content-type: text/plain");
1232 header("Cache-Control: no-store, no-cache");
1233 header('Content-Disposition: attachment; filename="' . $this->getExportCSVFileName() . '"');
1234 echo implode("\r\n", $lines);
1235
1236 exit;
1237 }
1238
1239 /**
1240 * AJAX endpoint to store the property settings.
1241 *
1242 * @param array $prop_settings the settings to store.
1243 *
1244 * @return void
1245 */
1246 public function savePtSefSettings(array $prop_settings = [])
1247 {
1248 if (!$prop_settings) {
1249 VBOHttpDocument::getInstance()->close(500, 'Missing property settings');
1250 }
1251
1252 VBOFactory::getConfig()->set("report_{$this->reportFile}_settings", $prop_settings);
1253
1254 VBOHttpDocument::getInstance()->json(['success' => 1]);
1255 }
1256
1257 /**
1258 * Helper method invoked via AJAX by the controller.
1259 * Needed to save the manual entries for the pax data.
1260 *
1261 * @param array $manual_data the object representation of the manual entries.
1262 *
1263 * @return array one boolean value array with the operation result.
1264 */
1265 public function updatePaxData($manual_data = [])
1266 {
1267 if (!is_array($manual_data) || !$manual_data) {
1268 VBOHttpDocument::getInstance()->close(400, 'Nothing to save!');
1269 }
1270
1271 // re-build manual entries object representation
1272 $bids_guests = [];
1273 foreach ($manual_data as $guest_ind => $guest_data) {
1274 if (!is_numeric($guest_ind) || !is_array($guest_data) || empty($guest_data['bid']) || !isset($guest_data['bid_index']) || count($guest_data) < 2) {
1275 // empty or invalid manual entries array
1276 continue;
1277 }
1278 // the guest index in the reportObj starts from 0
1279 $use_guest_ind = ($guest_ind + 1 - (int)$guest_data['bid_index']);
1280 if (!isset($bids_guests[$guest_data['bid']])) {
1281 $bids_guests[$guest_data['bid']] = [];
1282 }
1283 // set manual entries for this guest number
1284 $bids_guests[$guest_data['bid']][$use_guest_ind] = $guest_data;
1285 // remove the "bid" and "bid_index" keys
1286 unset($bids_guests[$guest_data['bid']][$use_guest_ind]['bid'], $bids_guests[$guest_data['bid']][$use_guest_ind]['bid_index']);
1287 }
1288
1289 if (!$bids_guests) {
1290 VBOHttpDocument::getInstance()->close(400, 'No manual entries to save found');
1291 }
1292
1293 // loop through all bookings to update the data for the various rooms and guests
1294 $bids_updated = 0;
1295 foreach ($bids_guests as $bid => $entries) {
1296 $b_rooms = VikBooking::loadOrdersRoomsData($bid);
1297 if (empty($b_rooms)) {
1298 continue;
1299 }
1300 // count guests per room
1301 $room_guests = [];
1302 foreach ($b_rooms as $b_room) {
1303 $room_guests[] = $b_room['adults'] + $b_room['children'];
1304 }
1305 // get current booking pax data
1306 $pax_data = VBOCheckinPax::getBookingPaxData($bid);
1307 $pax_data = empty($pax_data) ? [] : $pax_data;
1308 foreach ($entries as $guest_ind => $guest_data) {
1309 // find room index for this guest
1310 $room_num = 0;
1311 $use_guest_ind = $guest_ind;
1312 foreach ($room_guests as $room_index => $tot_guests) {
1313 // find the proper guest index for the room to which this belongs
1314 if ($use_guest_ind <= $tot_guests) {
1315 // proper room index found for this guest
1316 $room_num = $room_index;
1317 break;
1318 } else {
1319 // it's probably in a next room
1320 $use_guest_ind -= $tot_guests;
1321 }
1322 }
1323 // push new pax data for this room and guest
1324 if (!isset($pax_data[$room_num])) {
1325 $pax_data[$room_num] = [];
1326 }
1327 if (!isset($pax_data[$room_num][$use_guest_ind])) {
1328 $pax_data[$room_num][$use_guest_ind] = $guest_data;
1329 } else {
1330 $pax_data[$room_num][$use_guest_ind] = array_merge($pax_data[$room_num][$use_guest_ind], $guest_data);
1331 }
1332 }
1333 // update booking pax data
1334 if (VBOCheckinPax::setBookingPaxData($bid, $pax_data)) {
1335 $bids_updated++;
1336 }
1337 }
1338
1339 return $bids_updated ? [true] : [false];
1340 }
1341
1342 /**
1343 * Registers the name to give to the file being exported.
1344 *
1345 * @return void
1346 */
1347 protected function registerExportFileName()
1348 {
1349 // load settings from DB to populate the first line of the DAT file
1350 $report_settings = VBOFactory::getConfig()->getArray("report_{$this->reportFile}_settings", []);
1351
1352 // build file number (size 5) by using the last 2 digit of the current year, and the day of the year (0 through 365)
1353 $now_info = getdate();
1354 $file_number = substr((string)$now_info['year'], -2) . $now_info['yday'];
1355
1356 // build file name (Nomenclatura – <NIF><Estabelecimento><Numero de Ficheiro>.DAT)
1357 $dat_fname = 'SEF - ';
1358 $dat_fname .= (!empty($report_settings['nif']) ? $report_settings['nif'] : '') . (!empty($report_settings['estabelecimento']) ? $report_settings['estabelecimento'] : '');
1359 $dat_fname .= $file_number . '.DAT';
1360
1361 $this->setExportCSVFileName($dat_fname);
1362 }
1363
1364 /**
1365 * Loads the country names from DB.
1366 *
1367 * @return array
1368 */
1369 protected function loadCountries()
1370 {
1371 return VikBooking::getCountriesArray();
1372 }
1373
1374 /**
1375 * Returns the associative list of ID types for Portugal.
1376 *
1377 * @return array
1378 */
1379 protected function loadIdTypes()
1380 {
1381 return [
1382 "B" => "Identity card (Bilhete de Identidade)",
1383 "P" => "Passport (Passaporte)",
1384 "O" => "Other (Outro documento de identificação)",
1385 ];
1386 }
1387
1388 /**
1389 * Helper method to quickly get a pax_data property for the guest.
1390 *
1391 * @param array $pax_data the current pax_data stored.
1392 * @param array $guests list of total guests per room.
1393 * @param int $guest_ind the guest index.
1394 * @param string $key the pax_data key to look for.
1395 *
1396 * @return mixed null on failure or value fetched.
1397 */
1398 protected function getGuestPaxDataValue($pax_data, $guests, $guest_ind, $key)
1399 {
1400 if (!is_array($pax_data) || !count($pax_data) || empty($key)) {
1401 return null;
1402 }
1403
1404 // find room index for this guest number
1405 $room_num = 0;
1406 $use_guest_ind = $guest_ind;
1407 foreach ($guests as $room_index => $room_tot_guests) {
1408 // find the proper guest index for the room to which this belongs
1409 if ($use_guest_ind <= $room_tot_guests) {
1410 // proper room index found for this guest
1411 $room_num = $room_index;
1412 break;
1413 } else {
1414 // it's probably in a next room
1415 $use_guest_ind -= $room_tot_guests;
1416 }
1417 }
1418
1419 // check if a value exists for the requested key in the found room and guest indexes
1420 if (isset($pax_data[$room_num]) && isset($pax_data[$room_num][$use_guest_ind])) {
1421 if (isset($pax_data[$room_num][$use_guest_ind][$key])) {
1422 // we've got a value previously stored
1423 return $pax_data[$room_num][$use_guest_ind][$key];
1424 }
1425 }
1426
1427 // nothing was found
1428 return null;
1429 }
1430
1431 /**
1432 * Helper method to determine the exact number for this guest in the room booked.
1433 *
1434 * @param array $guests list of total guests per room.
1435 * @param int $guest_ind the guest index.
1436 *
1437 * @return int the actual guest room index starting from 1.
1438 */
1439 protected function calcGuestRoomIndex($guests, $guest_ind)
1440 {
1441 // find room index for this guest number
1442 $room_num = 0;
1443 $use_guest_ind = $guest_ind;
1444 foreach ($guests as $room_index => $room_tot_guests) {
1445 // find the proper guest index for the room to which this belongs
1446 if ($use_guest_ind <= $room_tot_guests) {
1447 // proper room index found for this guest
1448 $room_num = $room_index;
1449 break;
1450 } else {
1451 // it's probably in a next room
1452 $use_guest_ind -= $room_tot_guests;
1453 }
1454 }
1455
1456 return $use_guest_ind;
1457 }
1458 }
1459