0
*
* @var bool
*/
protected $debug;
/**
* List of countries.
*
* @var array
*/
protected $pt_countries = [];
/**
* List of ID types.
*
* @var array
*/
protected $pt_idtypes = [];
/**
* Class constructor should define the name of the report and
* other vars. Call the parent constructor to define the DB object.
*/
public function __construct()
{
$this->reportFile = basename(__FILE__, '.php');
$this->reportName = 'SEF - Servico de Estrangeiros e Fronteiras';
$this->reportFilters = [];
$this->cols = [];
$this->rows = [];
$this->footerRow = [];
$this->pt_countries = $this->loadCountries();
$this->pt_idtypes = $this->loadIdTypes();
$this->debug = (VikRequest::getInt('e4j_debug', 0, 'request') > 0);
$this->registerExportFileName();
parent::__construct();
}
/**
* Returns the name of this report.
*
* @return string
*/
public function getName()
{
return $this->reportName;
}
/**
* Returns the name of this file without .php.
*
* @return string
*/
public function getFileName()
{
return $this->reportFile;
}
/**
* Returns the filters of this report.
*
* @return array
*/
public function getFilters()
{
if (count($this->reportFilters)) {
//do not run this method twice, as it could load JS and CSS files.
return $this->reportFilters;
}
// JS lang defs
JText::script('VBOADMINLEGENDSETTINGS');
JText::script('VBANNULLA');
JText::script('VBAPPLY');
// get VBO Application Object
$vbo_app = VikBooking::getVboApplication();
// load the jQuery UI Datepicker
$this->loadDatePicker();
// custom export button
$this->customExport = 'Download Ficheiros';
// From Date Filter
$filter_opt = array(
'label' => '',
'html' => '',
'type' => 'calendar',
'name' => 'fromdate'
);
array_push($this->reportFilters, $filter_opt);
// build JS helpers (fillers)
$hidden_vals = '';
$hidden_vals .= '
';
// countries
$hidden_vals .= '
';
$hidden_vals .= ' ';
$hidden_vals .= '
';
// ID types
$hidden_vals .= '
';
$hidden_vals .= ' ';
$hidden_vals .= '
';
// ID number
$hidden_vals .= '
';
$hidden_vals .= '
';
$hidden_vals .= ' ';
$hidden_vals .= '
';
// date of birth
$hidden_vals .= '
';
$hidden_vals .= '
';
$hidden_vals .= ' ';
$hidden_vals .= '
';
$hidden_vals .= '
';
// build HTML helper
$html_helper =
<<
HTML;
// 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
$filter_opt = array(
'label' => '',
'html' => '' . $hidden_vals . $html_helper,
);
array_push($this->reportFilters, $filter_opt);
// append button to save the data when creating manual values
$filter_opt = array(
'label' => '',
'html' => '',
);
array_push($this->reportFilters, $filter_opt);
// get minimum check-in and maximum check-out for dates filters
$df = $this->getDateFormat();
$mincheckin = 0;
$maxcheckout = 0;
$q = "SELECT MIN(`checkin`) AS `mincheckin`, MAX(`checkout`) AS `maxcheckout` FROM `#__vikbooking_orders` WHERE `status`='confirmed' AND `closure`=0;";
$this->dbo->setQuery($q);
$data = $this->dbo->loadAssoc();
if ($data) {
if (!empty($data['mincheckin']) && !empty($data['maxcheckout'])) {
$mincheckin = $data['mincheckin'];
$maxcheckout = $data['maxcheckout'];
}
}
// jQuery code for the datepicker calendars and select2
$pfromdate = VikRequest::getString('fromdate', date($df), 'request');
$js = 'jQuery(function() {
jQuery(".vbo-report-datepicker:input").datepicker({
'.(!empty($mincheckin) ? 'minDate: "'.date($df, $mincheckin).'", ' : '').'
'.(!empty($maxcheckout) ? 'maxDate: "'.date($df, $maxcheckout).'", ' : '').'
dateFormat: "'.$this->getDateFormat('jui').'"
});
'.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').'
});';
$this->setScript($js);
// js for managing the property information and for using the fillers
$report_settings = VBOFactory::getConfig()->getArray("report_{$this->reportFile}_settings", []);
$report_settings_js = json_encode($report_settings);
$js_ajax_base = VikBooking::ajaxUrl('index.php?option=com_vikbooking&task=invoke_report&report=' . $this->reportFile);
$js_save_icn = VikBookingIcons::i('save');
$js_saving_icn = VikBookingIcons::i('circle-notch', 'fa-spin fa-fw');
$js_saved_icn = VikBookingIcons::i('check-circle');
$js_birth_min = (date('Y') - 100);
$js_birth_max = date('Y');
$this->setScript(
<<' + Joomla.JText._('VBANNULLA') + '',
footer_right: '',
dismiss_event: 'vbo-report-pt-sef-settings-dismiss',
onDismiss: () => {
jQuery('.vbo-report-pt-sef-settings-filler').appendTo('.vbo-report-pt-sef-settings-helper');
},
});
jQuery('.vbo-report-pt-sef-settings-filler').appendTo(modal_body);
// populate current settings
vbo_report_settings_def.forEach((sett_name) => {
if (vbo_report_settings.hasOwnProperty(sett_name)) {
jQuery('#' + sett_name).val(vbo_report_settings[sett_name]);
}
});
}
// save settings
function vboPtSefSaveSettings() {
let vbo_pt_sef_save_settings = {
nif: jQuery('#nif').val(),
estabelecimento: jQuery('#estabelecimento').val(),
nome: jQuery('#nome').val(),
morada: jQuery('#morada').val(),
localidade: jQuery('#localidade').val(),
postalcode: jQuery('#postalcode').val(),
zonapostal: jQuery('#zonapostal').val(),
telefone: jQuery('#telefone').val(),
fax: jQuery('#fax').val(),
contacto: jQuery('#contacto').val(),
email: jQuery('#email').val(),
};
VBOCore.doAjax(
vbo_report_js_ajax_base,
{
call: "savePtSefSettings",
params: vbo_pt_sef_save_settings,
tmpl: "component"
},
(success) => {
// overwrite current settings object
vbo_report_settings = Object.assign(vbo_report_settings, vbo_pt_sef_save_settings);
// dismiss modal
VBOCore.emitEvent('vbo-report-pt-sef-settings-dismiss');
},
(err) => {
alert(err.responseText);
},
);
}
// cancel settings
function vboPtSefCancelSettings() {
// dismiss modal
VBOCore.emitEvent('vbo-report-pt-sef-settings-dismiss');
}
// download function
function vboDownloadPtSefReport() {
if (!confirm("Certifique-se de ter preenchido todas as informações. Continuar com o download?")) {
return false;
}
document.adminForm.target = "_blank";
document.adminForm.action += "&tmpl=component";
vboSetFilters({exportreport: "1", filler: JSON.stringify(reportObj)}, true);
setTimeout(function() {
document.adminForm.target = "";
document.adminForm.action = document.adminForm.action.replace("&tmpl=component", "");
vboSetFilters({exportreport: "0", filler: ""}, false);
}, 1000);
}
// save data after manual fillers
function vboPtSefSaveData() {
jQuery("button.vbo-report-ptsef-manualsave").find("i").attr("class", vbo_ptsef_saving_icn);
VBOCore.doAjax(
vbo_report_js_ajax_base,
{
call: "updatePaxData",
params: reportObj,
tmpl: "component"
},
(response) => {
if (!response || !response[0]) {
alert("An error occurred.");
return false;
}
jQuery("button.vbo-report-ptsef-manualsave").addClass("btn-success").find("i").attr("class", vbo_ptsef_saved_icn);
},
(error) => {
alert(error.responseText);
jQuery("button.vbo-report-ptsef-manualsave").removeClass("btn-success").find("i").attr("class", vbo_ptsef_save_icn);
}
);
}
// DOM ready state
jQuery(function() {
// prepare filler helpers
jQuery("#vbo-report-ptsef-hidden").children().detach().appendTo(".vbo-info-overlay-report");
jQuery("#choose-country").select2({placeholder: "- País -", width: "200px"});
jQuery("#choose-doctype").select2({placeholder: "- Tipo Documento -", width: "200px"});
jQuery("#choose-dbirth").datepicker({
maxDate: 0,
dateFormat: "dd/mm/yy",
changeMonth: true,
changeYear: true,
yearRange: "$js_birth_min:$js_birth_max"
});
// click events
jQuery(".vbo-report-load-nazione, .vbo-report-load-nazione-stay, .vbo-report-load-cittadinanza").click(function() {
reportActiveCell = this;
jQuery(".vbo-report-ptsef-selcont").hide();
jQuery("#vbo-report-ptsef-nazione").show();
vboShowOverlay();
});
jQuery(".vbo-report-load-doctype").click(function() {
reportActiveCell = this;
jQuery(".vbo-report-ptsef-selcont").hide();
jQuery("#vbo-report-ptsef-doctype").show();
vboShowOverlay();
});
jQuery(".vbo-report-load-docplace").click(function() {
reportActiveCell = this;
jQuery(".vbo-report-ptsef-selcont").hide();
jQuery("#vbo-report-ptsef-nazione").show();
vboShowOverlay();
});
jQuery(".vbo-report-load-docnum").click(function() {
reportActiveCell = this;
jQuery(".vbo-report-ptsef-selcont").hide();
jQuery("#vbo-report-ptsef-docnum").show();
vboShowOverlay();
setTimeout(function(){jQuery("#choose-docnum").focus();}, 500);
});
jQuery(".vbo-report-load-dbirth").click(function() {
reportActiveCell = this;
jQuery(".vbo-report-ptsef-selcont").hide();
jQuery("#vbo-report-ptsef-dbirth").show();
vboShowOverlay();
});
});
function vboReportChosenCountry(country_el) {
var c_code = country_el.value;
var c_val = country_el.options[country_el.selectedIndex].text;
if (reportActiveCell !== null) {
var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
if (isNaN(nowindex) || parseInt(nowindex) < 0) {
alert("Error, cannot find element to update.");
} else {
var rep_act_cell = jQuery(reportActiveCell);
rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
if (!reportObj.hasOwnProperty(nowindex)) {
reportObj[nowindex] = {
bid: rep_guest_bid,
bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
};
}
if (jQuery(reportActiveCell).hasClass("vbo-report-load-nazione")) {
reportObj[nowindex]["country_b"] = c_code;
} else if (jQuery(reportActiveCell).hasClass("vbo-report-load-nazione-stay")) {
reportObj[nowindex]["country_s"] = c_code;
} else if (jQuery(reportActiveCell).hasClass("vbo-report-load-docplace")) {
reportObj[nowindex]["docplace"] = c_code;
} else {
reportObj[nowindex]["country_c"] = c_code;
}
}
}
reportActiveCell = null;
vboHideOverlay();
jQuery("#choose-nazione").val("").select2("data", null, false);
jQuery(".vbo-report-ptsef-manualsave").show();
}
function vboReportChosenDoctype(doctype) {
var c_code = doctype.value;
var c_val = doctype.options[doctype.selectedIndex].text;
if (reportActiveCell !== null) {
var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
if (isNaN(nowindex) || parseInt(nowindex) < 0) {
alert("Error, cannot find element to update.");
} else {
var rep_act_cell = jQuery(reportActiveCell);
rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
if (!reportObj.hasOwnProperty(nowindex)) {
reportObj[nowindex] = {
bid: rep_guest_bid,
bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
};
}
reportObj[nowindex]["doctype"] = c_code;
}
}
reportActiveCell = null;
vboHideOverlay();
jQuery("#choose-doctype").val("").select2("data", null, false);
jQuery(".vbo-report-ptsef-manualsave").show();
}
function vboReportChosenDocnum(val) {
var c_code = val, c_val = val;
if (reportActiveCell !== null) {
var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
if (isNaN(nowindex) || parseInt(nowindex) < 0) {
alert("Error, cannot find element to update.");
} else {
var rep_act_cell = jQuery(reportActiveCell);
rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
if (!reportObj.hasOwnProperty(nowindex)) {
reportObj[nowindex] = {
bid: rep_guest_bid,
bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
};
}
reportObj[nowindex]["docnum"] = c_code;
}
}
reportActiveCell = null;
vboHideOverlay();
jQuery("#choose-docnum").val("");
jQuery(".vbo-report-ptsef-manualsave").show();
}
function vboReportChosenDbirth(val) {
var c_code = val, c_val = val;
if (reportActiveCell !== null) {
var nowindex = jQuery(".vbo-reports-output table tbody tr").index(jQuery(reportActiveCell).closest("tr"));
if (isNaN(nowindex) || parseInt(nowindex) < 0) {
alert("Error, cannot find element to update.");
} else {
var rep_act_cell = jQuery(reportActiveCell);
rep_act_cell.addClass("vbo-report-load-elem-filled").find("span").text(c_val);
var rep_guest_bid = rep_act_cell.closest("tr").find("a[data-bid]").attr("data-bid");
if (!reportObj.hasOwnProperty(nowindex)) {
reportObj[nowindex] = {
bid: rep_guest_bid,
bid_index: jQuery(".vbo-reports-output table tbody tr").index(jQuery("a[data-bid=\"" + rep_guest_bid + "\"]").first().closest("tr"))
};
}
reportObj[nowindex]["date_birth"] = c_code;
}
}
reportActiveCell = null;
vboHideOverlay();
jQuery("#choose-dbirth").val("");
jQuery(".vbo-report-ptsef-manualsave").show();
}
JS
);
return $this->reportFilters;
}
/**
* Loads the report data from the DB.
* Returns true in case of success, false otherwise.
* Sets the columns and rows for the report to be displayed.
*
* @return boolean
*/
public function getReportData()
{
if (strlen($this->getError())) {
// Export functions may set errors rather than exiting the process, and the View may continue the execution to attempt to render the report.
return false;
}
// input fields and other vars
$pfromdate = VikRequest::getString('fromdate', '', 'request');
$pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request');
$pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort;
$pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request');
$pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder;
$pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC';
$currency_symb = VikBooking::getCurrencySymb();
$df = $this->getDateFormat();
$datesep = VikBooking::getDateSeparator();
// Get dates timestamps
$from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
$to_ts = VikBooking::getDateTimestamp($pfromdate, 23, 59, 59);
if (empty($pfromdate) || empty($from_ts)) {
$this->setError(JText::translate('VBOREPORTSERRNODATES'));
return false;
}
// Query to obtain the records (arrivals, departures and stayovers for the selected date)
$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`,".
"`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`,".
"`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` ".
"FROM `#__vikbooking_orders` AS `o` LEFT JOIN `#__vikbooking_ordersrooms` AS `or` ON `or`.`idorder`=`o`.`id` ".
"LEFT JOIN `#__vikbooking_customers_orders` AS `co` ON `co`.`idorder`=`o`.`id` LEFT JOIN `#__vikbooking_customers` AS `c` ON `c`.`id`=`co`.`idcustomer` ".
"WHERE `o`.`status`='confirmed' AND `o`.`closure`=0 AND (
(`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)
) ".
"ORDER BY `o`.`checkin` ASC, `o`.`id` ASC, `or`.`id` ASC;";
$this->dbo->setQuery($q);
$records = $this->dbo->loadAssocList();
if (!$records) {
$this->setError(JText::translate('VBOREPORTSERRNORESERV'));
return false;
}
// nest records with multiple rooms booked inside sub-array
$bookings = [];
foreach ($records as $v) {
if (!isset($bookings[$v['id']])) {
$bookings[$v['id']] = [];
}
array_push($bookings[$v['id']], $v);
}
// free some memory up
unset($records);
// define the columns of the report
$this->cols = array(
// type
array(
'key' => 'type',
'sortable' => 1,
'label' => JText::translate('VBPSHOWSEASONSTHREE')
),
// last name
array(
'key' => 'lastname',
'sortable' => 1,
'label' => JText::translate('ORDER_LNAME')
),
// name
array(
'key' => 'name',
'sortable' => 1,
'label' => JText::translate('ORDER_NAME')
),
// nationality
array(
'key' => 'nationality',
'attr' => array(
'class="center"'
),
'label' => JText::translate('VBCUSTOMERNATION')
),
// birthplace
array(
'key' => 'place_birth',
'attr' => array(
'class="center"'
),
'tip' => ucwords(JText::translate('VBOFILTEISROPTIONAL')),
'label' => JText::translate('VBOCUSTPLACEBIRTH')
),
// birth date
array(
'key' => 'date_birth',
'attr' => array(
'class="center"'
),
'label' => JText::translate('VBCUSTOMERBDATE')
),
// docnum
array(
'key' => 'docnum',
'attr' => array(
'class="center"'
),
'label' => JText::translate('VBOCUSTDOCNUM')
),
// doctype
array(
'key' => 'doctype',
'attr' => array(
'class="center"'
),
'label' => JText::translate('VBOCUSTDOCTYPE')
),
// docplace
array(
'key' => 'docplace',
'attr' => array(
'class="center"'
),
'label' => 'País Emissor'
),
// country_s
array(
'key' => 'country_s',
'attr' => array(
'class="center"'
),
'label' => 'País Residência'
),
// place_s
array(
'key' => 'place_s',
'tip' => ucwords(JText::translate('VBOFILTEISROPTIONAL')),
'label' => 'Local Residência'
),
// checkin
array(
'key' => 'checkin',
'sortable' => 1,
'label' => JText::translate('VBPICKUPAT')
),
// checkout
array(
'key' => 'checkout',
'sortable' => 1,
'label' => JText::translate('VBRELEASEAT')
),
// id booking
array(
'key' => 'idbooking',
'sortable' => 1,
'attr' => array(
'class="center"'
),
'label' => 'ID'
),
);
// loop over the bookings to build the rows of the report
$from_info = getdate($from_ts);
foreach ($bookings as $gbook) {
// count the total number of guests for all rooms of this booking
$tot_booking_guests = 0;
$room_guests = [];
foreach ($gbook as $rbook) {
$tot_booking_guests += ($rbook['adults'] + $rbook['children']);
$room_guests[] = ($rbook['adults'] + $rbook['children']);
}
// make sure to decode the current pax data
if (!empty($gbook[0]['pax_data'])) {
$gbook[0]['pax_data'] = json_decode($gbook[0]['pax_data'], true);
$gbook[0]['pax_data'] = !is_array($gbook[0]['pax_data']) ? [] : $gbook[0]['pax_data'];
}
// push a copy of the booking for each guest
$guests_rows = [];
for ($i = 1; $i <= $tot_booking_guests; $i++) {
array_push($guests_rows, $gbook[0]);
}
// create one row for each guest
$guest_ind = 1;
foreach ($guests_rows as $ind => $guests) {
// prepare row record for this room-guest
$insert_row = [];
// find the actual guest-room-index
$guest_room_ind = $this->calcGuestRoomIndex($room_guests, $guest_ind);
// stay type
if (date('Y-m-d', $guests['checkin']) == date('Y-m-d', $from_ts)) {
$stay_type = JText::translate('VBOTYPEARRIVAL');
} elseif (date('Y-m-d', $guests['checkout']) == date('Y-m-d', $from_ts)) {
$stay_type = JText::translate('VBOTYPEDEPARTURE');
} else {
$stay_type = JText::translate('VBOTYPESTAYOVER');
}
array_push($insert_row, array(
'key' => 'type',
'ignore_export' => 1,
'value' => $stay_type
));
// last name
$cognome = !empty($guests['t_last_name']) ? $guests['t_last_name'] : $guests['last_name'];
$pax_cognome = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'last_name');
$cognome = !empty($pax_cognome) ? $pax_cognome : $cognome;
array_push($insert_row, array(
'key' => 'lastname',
'value' => $cognome
));
// name
$nome = !empty($guests['t_first_name']) ? $guests['t_first_name'] : $guests['first_name'];
$pax_nome = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'first_name');
$nome = !empty($pax_nome) ? $pax_nome : $nome;
array_push($insert_row, array(
'key' => 'name',
'value' => $nome
));
/**
* Nationality.
* Check compatibility with pax_data field of driver for "Portugal".
*/
$citizen = !empty($guests['country']) && $guest_ind < 2 ? $guests['country'] : '';
$pax_country_c = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'country_c');
$citizen = !empty($pax_country_c) ? $pax_country_c : $citizen;
// check nationality field from pre-checkin
$pax_citizen = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'nationality');
$citizen = empty($citizen) && !empty($pax_citizen) ? $pax_citizen : $citizen;
array_push($insert_row, array(
'key' => 'nationality',
'attr' => array(
'class="center' . (empty($citizen) ? ' vbo-report-load-cittadinanza' : '') . '"'
),
'callback' => function ($val) {
return !empty($val) && isset($this->pt_countries[$val]) ? $this->pt_countries[$val]['country_name'] : '?';
},
'no_export_callback' => 1,
'value' => $citizen
));
// birth place
$pax_pbirth = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'place_birth');
array_push($insert_row, array(
'key' => 'place_birth',
'attr' => array(
'class="center"'
),
'value' => ($pax_pbirth ? $pax_pbirth : '')
));
// birth date
$dbirth = !empty($guests['bdate']) && $guest_ind < 2 ? VikBooking::getDateTimestamp($guests['bdate'], 0, 0) : '';
$pax_dbirth = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'date_birth');
$dbirth = !empty($pax_dbirth) ? $pax_dbirth : $dbirth;
$dbirth = (strpos($dbirth, '/') === false && strpos($dbirth, VikBooking::getDateSeparator()) === false) ? $dbirth : VikBooking::getDateTimestamp($dbirth, 0, 0);
array_push($insert_row, array(
'key' => 'date_birth',
'attr' => array(
'class="center' . (empty($dbirth) ? ' vbo-report-load-dbirth' : '') . '"'
),
'callback' => function ($val) {
if (!empty($val) && strpos($val, '/') === false && strpos($val, VikBooking::getDateSeparator()) === false) {
return date('d/m/Y', $val);
}
if (!empty($val) && strpos($val, '/') !== false) {
return $val;
}
return '?';
},
'export_callback' => function ($val) {
if (!empty($val) && strpos($val, '/') === false && strpos($val, VikBooking::getDateSeparator()) === false) {
return date('Ymd', $val);
}
if (!empty($val) && strpos($val, '/') !== false) {
return date('Ymd', VikBooking::getDateTimestamp($val, 0, 0));
}
return '?';
},
'value' => $dbirth
));
/**
* ID Number
* Check compatibility with pax_data field of driver for "Portugal".
*/
$docnum = !empty($guests['docnum']) && $guest_ind < 2 ? $guests['docnum'] : '';
$pax_docnum = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'docnum');
$docnum = !empty($pax_docnum) ? $pax_docnum : $docnum;
array_push($insert_row, array(
'key' => 'docnum',
'attr' => array(
'class="center' . (empty($docnum) ? ' vbo-report-load-docnum' : '') . '"'
),
'callback' => function ($val) {
return empty($val) ? '?' : $val;
},
'value' => $docnum
));
/**
* ID Type
* Check compatibility with pax_data field of driver for "Portugal".
*/
$pax_doctype = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'doctype');
$doctype = 'O';
$doctype_cur_val = '';
if (!empty($pax_doctype)) {
$doctype = $pax_doctype;
$doctype_cur_val = $pax_doctype;
}
array_push($insert_row, array(
'key' => 'doctype',
'attr' => array(
// we always allow to rectify this field, but if guessed, we style it with the class "vbo-report-load-elem-filled"
'class="center vbo-report-load-doctype' . (!empty($doctype_cur_val) ? ' vbo-report-load-elem-filled' : '') . '"'
),
'callback' => function ($val) use ($doctype_cur_val) {
return !empty($doctype_cur_val) ? $doctype_cur_val : $val;
},
'no_export_callback' => 1,
'value' => $doctype
));
/**
* ID Issuing Country
* Check compatibility with pax_data field of driver for "Portugal".
*/
$pax_docplace = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'docplace');
$docplace = $pax_docplace;
array_push($insert_row, array(
'key' => 'docplace',
'attr' => array(
'class="center' . (empty($docplace) ? ' vbo-report-load-docplace' : '') . '"'
),
'callback' => function ($val) {
return !empty($val) && isset($this->pt_countries[$val]) ? $this->pt_countries[$val]['country_name'] : '?';
},
'no_export_callback' => 1,
'value' => $docplace
));
/**
* Country of residence.
* Check compatibility with pax_data field of driver for "Portugal".
*/
$pax_countrystay = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'country_s');
array_push($insert_row, array(
'key' => 'country_s',
'attr' => array(
'class="center' . (empty($pax_countrystay) ? ' vbo-report-load-field vbo-report-load-nazione-stay' : '') . '"'
),
'callback' => function($val) {
if (!empty($val) && isset($this->pt_countries[$val])) {
return $this->pt_countries[$val]['country_name'];
}
// information is missing and should be provided
return '?';
},
'no_export_callback' => 1,
'value' => $pax_countrystay,
));
/**
* Place of residence.
* Check compatibility with pax_data field of driver for "Portugal".
*/
$pax_placestay = $this->getGuestPaxDataValue($guests['pax_data'], $room_guests, $guest_ind, 'place_s');
array_push($insert_row, array(
'key' => 'place_s',
'value' => $pax_placestay,
));
// checkin
array_push($insert_row, array(
'key' => 'checkin',
'callback' => function ($val) {
return date('d/m/Y', $val);
},
'export_callback' => function ($val) {
return date('Ymd', $val);
},
'value' => $guests['checkin']
));
// checkout
array_push($insert_row, array(
'key' => 'checkout',
'callback' => function ($val) {
return date('d/m/Y', $val);
},
'export_callback' => function ($val) {
return date('Ymd', $val);
},
'value' => $guests['checkout']
));
// id booking
array_push($insert_row, array(
'key' => 'idbooking',
'attr' => array(
'class="center"'
),
'callback' => function ($val) {
// make sure to keep the data-bid attribute as it's used by JS to identify the booking ID
return ' ' . $val . '';
},
'ignore_export' => 1,
'value' => $guests['id']
));
// push fields in the rows array as a new row
array_push($this->rows, $insert_row);
// increment guest index
$guest_ind++;
}
}
// sort the rows
$this->sortRows($pkrsort, $pkrorder);
// the footer row will just print the amount of records to export
array_push($this->footerRow, array(
array(
'attr' => array(
'class="vbo-report-total"'
),
'value' => '
');
}
return true;
}
/**
* Generates the text file for the Italian Police,
* then it sends it to output for download.
* In case of errors, the process is not terminated (exit)
* to let the View display the error message.
*
* @param string $export_type Differentiates the type of export requested.
*
* @return void|bool Void in case of script termination, boolean otherwise.
*/
public function customExport($export_type = 0)
{
if (!$this->getReportData()) {
return false;
}
// load settings from DB to populate the first line of the DAT file
$report_settings = VBOFactory::getConfig()->getArray("report_{$this->reportFile}_settings", []);
if (!$report_settings) {
$this->setError('Mandatory report settings are missing');
return false;
}
$pfromdate = VikRequest::getString('fromdate', '', 'request');
$pfiller = VikRequest::getString('filler', '', 'request', VIKREQUEST_ALLOWRAW);
$pfiller = !empty($pfiller) ? json_decode($pfiller, true) : [];
$pfiller = !is_array($pfiller) ? [] : $pfiller;
// map of the rows keys with their related length
$keys_length_map = [
'lastname' => 40,
'name' => 40,
'nationality' => 3,
'place_birth' => 40,
'date_birth' => 8,
'docnum' => 16,
'doctype' => 3,
'docplace' => 3,
'country_s' => 3,
'place_s' => 30,
'checkin' => 8,
'checkout' => 8,
];
// the below field keys are optional and can be empty
$optional_fields = ['place_birth', 'place_s'];
// pool of booking IDs to update their history
$booking_ids = [];
// array of lines (header, one line for each guest and closure)
$lines = [];
// build header line by reading the parameters
$header_line_parts = [
'0',
'BA03',
str_pad((!empty($report_settings['nif']) ? $report_settings['nif'] : ''), 9),
str_pad((!empty($report_settings['estabelecimento']) ? $report_settings['estabelecimento'] : ''), 4),
str_pad((!empty($report_settings['nome']) ? $report_settings['nome'] : ''), 40),
str_pad((!empty($report_settings['morada']) ? $report_settings['morada'] : ''), 40),
str_pad((!empty($report_settings['localidade']) ? $report_settings['localidade'] : ''), 30),
str_pad((!empty($report_settings['postalcode']) ? $report_settings['postalcode'] : ''), 4),
str_pad((!empty($report_settings['zonapostal']) ? $report_settings['zonapostal'] : ''), 3),
str_pad((!empty($report_settings['telefone']) ? $report_settings['telefone'] : ''), 10),
str_pad((!empty($report_settings['fax']) ? $report_settings['fax'] : ''), 10),
str_pad((!empty($report_settings['contacto']) ? $report_settings['contacto'] : ''), 40),
str_pad((!empty($report_settings['email']) ? $report_settings['email'] : ''), 140),
];
// push registration header line
$lines[] = implode('|', $header_line_parts) . '|';
// build registration lines
$registration_lines = [];
// push the lines of the DAT file
foreach ($this->rows as $ind => $row) {
// build registration line for this guest
$registration_line_parts = [
// fixed registration type for each guest is 1
'1'
];
// parse row for this guest
foreach ($row as $field) {
if (!isset($keys_length_map[$field['key']]) || isset($field['ignore_export'])) {
// we don't need this information
continue;
}
if ($field['key'] == 'idbooking' && !in_array($field['value'], $booking_ids)) {
// register booking ID for later history update
$booking_ids[] = $field['value'];
}
// report value
if (isset($pfiller[$ind]) && isset($pfiller[$ind][$field['key']])) {
if (strlen((string)$pfiller[$ind][$field['key']])) {
$field['value'] = $pfiller[$ind][$field['key']];
}
}
// always cast to string
$field['value'] = (string)$field['value'];
if (!$field['value'] && !in_array($field['key'], $optional_fields)) {
// raise error message without stopping
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.');
}
// get the final value to be included in the exported file
if (isset($field['callback_export'])) {
$field['callback'] = $field['callback_export'];
}
$value = !isset($field['no_export_callback']) && isset($field['callback']) && is_callable($field['callback']) ? $field['callback']($field['value']) : $field['value'];
// set guest registration value with proper length
$registration_line_parts[] = str_pad($value, $keys_length_map[$field['key']]);
}
// push guest registration line
$registration_lines[] = implode('|', $registration_line_parts) . '|';
}
// append all guest registration lines
$lines = array_merge($lines, $registration_lines);
// build file number (size 5) by using the last 2 digit of the current year, and the day of the year (0 through 365)
$now_info = getdate();
$file_number = substr((string)$now_info['year'], -2) . $now_info['yday'];
// build last registration line with summary
$last_line_parts = [
// fixed registration type for the last line is 9
'9',
// number of records (lines) in the file, including this one (header + guests + last line)
str_pad((string)(count($registration_lines) + 2), 5, '0', STR_PAD_LEFT),
// generation date
date('Ymd'),
// Hotel unit file serial number
str_pad($file_number, 5, '0', STR_PAD_LEFT),
];
// append last line
$lines[] = implode('|', $last_line_parts) . '|';
// update the history for all bookings affected
foreach ($booking_ids as $bid) {
VikBooking::getBookingHistoryInstance()->setBid($bid)->store('RP', $this->reportName);
}
/**
* Custom export method supports a custom export handler, if previously set.
*/
if ($this->hasExportHandler()) {
// write data onto the custom file handler
$fp = $this->getExportCSVHandler();
fwrite($fp, implode("\r\n", $lines));
fclose($fp);
return true;
}
// force text file download
header("Content-type: text/plain");
header("Cache-Control: no-store, no-cache");
header('Content-Disposition: attachment; filename="' . $this->getExportCSVFileName() . '"');
echo implode("\r\n", $lines);
exit;
}
/**
* AJAX endpoint to store the property settings.
*
* @param array $prop_settings the settings to store.
*
* @return void
*/
public function savePtSefSettings(array $prop_settings = [])
{
if (!$prop_settings) {
VBOHttpDocument::getInstance()->close(500, 'Missing property settings');
}
VBOFactory::getConfig()->set("report_{$this->reportFile}_settings", $prop_settings);
VBOHttpDocument::getInstance()->json(['success' => 1]);
}
/**
* Helper method invoked via AJAX by the controller.
* Needed to save the manual entries for the pax data.
*
* @param array $manual_data the object representation of the manual entries.
*
* @return array one boolean value array with the operation result.
*/
public function updatePaxData($manual_data = [])
{
if (!is_array($manual_data) || !$manual_data) {
VBOHttpDocument::getInstance()->close(400, 'Nothing to save!');
}
// re-build manual entries object representation
$bids_guests = [];
foreach ($manual_data as $guest_ind => $guest_data) {
if (!is_numeric($guest_ind) || !is_array($guest_data) || empty($guest_data['bid']) || !isset($guest_data['bid_index']) || count($guest_data) < 2) {
// empty or invalid manual entries array
continue;
}
// the guest index in the reportObj starts from 0
$use_guest_ind = ($guest_ind + 1 - (int)$guest_data['bid_index']);
if (!isset($bids_guests[$guest_data['bid']])) {
$bids_guests[$guest_data['bid']] = [];
}
// set manual entries for this guest number
$bids_guests[$guest_data['bid']][$use_guest_ind] = $guest_data;
// remove the "bid" and "bid_index" keys
unset($bids_guests[$guest_data['bid']][$use_guest_ind]['bid'], $bids_guests[$guest_data['bid']][$use_guest_ind]['bid_index']);
}
if (!$bids_guests) {
VBOHttpDocument::getInstance()->close(400, 'No manual entries to save found');
}
// loop through all bookings to update the data for the various rooms and guests
$bids_updated = 0;
foreach ($bids_guests as $bid => $entries) {
$b_rooms = VikBooking::loadOrdersRoomsData($bid);
if (empty($b_rooms)) {
continue;
}
// count guests per room
$room_guests = [];
foreach ($b_rooms as $b_room) {
$room_guests[] = $b_room['adults'] + $b_room['children'];
}
// get current booking pax data
$pax_data = VBOCheckinPax::getBookingPaxData($bid);
$pax_data = empty($pax_data) ? [] : $pax_data;
foreach ($entries as $guest_ind => $guest_data) {
// find room index for this guest
$room_num = 0;
$use_guest_ind = $guest_ind;
foreach ($room_guests as $room_index => $tot_guests) {
// find the proper guest index for the room to which this belongs
if ($use_guest_ind <= $tot_guests) {
// proper room index found for this guest
$room_num = $room_index;
break;
} else {
// it's probably in a next room
$use_guest_ind -= $tot_guests;
}
}
// push new pax data for this room and guest
if (!isset($pax_data[$room_num])) {
$pax_data[$room_num] = [];
}
if (!isset($pax_data[$room_num][$use_guest_ind])) {
$pax_data[$room_num][$use_guest_ind] = $guest_data;
} else {
$pax_data[$room_num][$use_guest_ind] = array_merge($pax_data[$room_num][$use_guest_ind], $guest_data);
}
}
// update booking pax data
if (VBOCheckinPax::setBookingPaxData($bid, $pax_data)) {
$bids_updated++;
}
}
return $bids_updated ? [true] : [false];
}
/**
* Registers the name to give to the file being exported.
*
* @return void
*/
protected function registerExportFileName()
{
// load settings from DB to populate the first line of the DAT file
$report_settings = VBOFactory::getConfig()->getArray("report_{$this->reportFile}_settings", []);
// build file number (size 5) by using the last 2 digit of the current year, and the day of the year (0 through 365)
$now_info = getdate();
$file_number = substr((string)$now_info['year'], -2) . $now_info['yday'];
// build file name (Nomenclatura – .DAT)
$dat_fname = 'SEF - ';
$dat_fname .= (!empty($report_settings['nif']) ? $report_settings['nif'] : '') . (!empty($report_settings['estabelecimento']) ? $report_settings['estabelecimento'] : '');
$dat_fname .= $file_number . '.DAT';
$this->setExportCSVFileName($dat_fname);
}
/**
* Loads the country names from DB.
*
* @return array
*/
protected function loadCountries()
{
return VikBooking::getCountriesArray();
}
/**
* Returns the associative list of ID types for Portugal.
*
* @return array
*/
protected function loadIdTypes()
{
return [
"B" => "Identity card (Bilhete de Identidade)",
"P" => "Passport (Passaporte)",
"O" => "Other (Outro documento de identificação)",
];
}
/**
* Helper method to quickly get a pax_data property for the guest.
*
* @param array $pax_data the current pax_data stored.
* @param array $guests list of total guests per room.
* @param int $guest_ind the guest index.
* @param string $key the pax_data key to look for.
*
* @return mixed null on failure or value fetched.
*/
protected function getGuestPaxDataValue($pax_data, $guests, $guest_ind, $key)
{
if (!is_array($pax_data) || !count($pax_data) || empty($key)) {
return null;
}
// find room index for this guest number
$room_num = 0;
$use_guest_ind = $guest_ind;
foreach ($guests as $room_index => $room_tot_guests) {
// find the proper guest index for the room to which this belongs
if ($use_guest_ind <= $room_tot_guests) {
// proper room index found for this guest
$room_num = $room_index;
break;
} else {
// it's probably in a next room
$use_guest_ind -= $room_tot_guests;
}
}
// check if a value exists for the requested key in the found room and guest indexes
if (isset($pax_data[$room_num]) && isset($pax_data[$room_num][$use_guest_ind])) {
if (isset($pax_data[$room_num][$use_guest_ind][$key])) {
// we've got a value previously stored
return $pax_data[$room_num][$use_guest_ind][$key];
}
}
// nothing was found
return null;
}
/**
* Helper method to determine the exact number for this guest in the room booked.
*
* @param array $guests list of total guests per room.
* @param int $guest_ind the guest index.
*
* @return int the actual guest room index starting from 1.
*/
protected function calcGuestRoomIndex($guests, $guest_ind)
{
// find room index for this guest number
$room_num = 0;
$use_guest_ind = $guest_ind;
foreach ($guests as $room_index => $room_tot_guests) {
// find the proper guest index for the room to which this belongs
if ($use_guest_ind <= $room_tot_guests) {
// proper room index found for this guest
$room_num = $room_index;
break;
} else {
// it's probably in a next room
$use_guest_ind -= $room_tot_guests;
}
}
return $use_guest_ind;
}
}