Merge PR #287 into 14.0

Signed-off-by DarioLodeiros
This commit is contained in:
OCA-git-bot
2024-09-14 18:12:10 +00:00
5 changed files with 272 additions and 98 deletions

View File

@@ -19,24 +19,24 @@
<field name="code">model.send_file_institution_async()</field> <field name="code">model.send_file_institution_async()</field>
</record> </record>
<record model="ir.cron" id="autocreate_traveller_report_com"> <record model="ir.cron" id="autosend_incomplete_traveller_report_com">
<field name="name"> <field name="name">
SES Automatic Creation Traveller Communications SES Automatic Sending Incomplete Traveller Reports
</field> </field>
<field name="active" eval="False" /> <field name="active" eval="False" />
<field name="interval_number">1</field> <field name="interval_number">1</field>
<field name="user_id" ref="base.user_root" /> <field name="user_id" ref="base.user_root" />
<field name="interval_type">days</field> <field name="interval_type">hours</field>
<field name="numbercall">-1</field> <field name="numbercall">-1</field>
<field name="doall" eval="False" /> <field name="doall" eval="False" />
<field name="state">code</field> <field name="state">code</field>
<field name="model_id" ref="model_traveller_report_wizard" /> <field name="model_id" ref="model_traveller_report_wizard" />
<field <field
name="nextcall" name="nextcall"
eval="datetime.now(pytz.timezone('UTC')).strftime('%Y-%m-%d 23:30:00')" eval="datetime.now(pytz.timezone('UTC')).strftime('%Y-%m-%d %H:%M:%S')"
/> />
<field name="code"> <field name="code">
model.create_pending_notifications_traveller_report() model.ses_send_incomplete_traveller_reports(20)
</field> </field>
</record> </record>

View File

@@ -2,6 +2,8 @@ import logging
from odoo import api, fields, models from odoo import api, fields, models
from ..wizards.traveller_report import CREATE_OPERATION_CODE
CODE_SPAIN = "ES" CODE_SPAIN = "ES"
CODE_NIF = "D" CODE_NIF = "D"
CODE_NIE = "N" CODE_NIE = "N"
@@ -80,3 +82,47 @@ class PmsCheckinPartner(models.Model):
manual_fields = super(PmsCheckinPartner, self)._checkin_manual_fields() manual_fields = super(PmsCheckinPartner, self)._checkin_manual_fields()
manual_fields.extend(["support_number"]) manual_fields.extend(["support_number"])
return manual_fields return manual_fields
def write(self, vals):
result = super(PmsCheckinPartner, self).write(vals)
for record in self:
if (
"state" in vals
and record.reservation_id.pms_property_id.institution == "ses"
and record.state == "onboard"
):
previous_incomplete_traveller_communication = self.env[
"pms.ses.communication"
].search(
[
("reservation_id", "=", record.reservation_id.id),
("entity", "=", "PV"),
("operation", "=", CREATE_OPERATION_CODE),
("state", "=", "incomplete"),
]
)
if not previous_incomplete_traveller_communication:
previous_incomplete_traveller_communication = self.env[
"pms.ses.communication"
].create(
{
"reservation_id": record.reservation_id.id,
"operation": CREATE_OPERATION_CODE,
"entity": "PV",
"state": "incomplete",
}
)
# check if all checkin partners in the reservation are onboard
if (
all(
[
checkin.state == "onboard"
for checkin in record.reservation_id.checkin_partner_ids
]
)
and len(record.reservation_id.checkin_partner_ids)
== record.reservation_id.adults
):
previous_incomplete_traveller_communication.state = "to_send"
return result

View File

@@ -81,9 +81,9 @@ class PmsReservation(models.Model):
self.create_communication( self.create_communication(
reservation.id, DELETE_OPERATION_CODE, "RH" reservation.id, DELETE_OPERATION_CODE, "RH"
) )
elif ( elif vals["state"] != "cancel" and (
vals["state"] != "cancel" last_communication.operation == DELETE_OPERATION_CODE
and last_communication.operation == DELETE_OPERATION_CODE or not last_communication
): ):
self.create_communication( self.create_communication(
reservation.id, CREATE_OPERATION_CODE, "RH" reservation.id, CREATE_OPERATION_CODE, "RH"

View File

@@ -44,6 +44,7 @@ class PmsSesCommunication(models.Model):
default="to_send", default="to_send",
required=True, required=True,
selection=[ selection=[
("incomplete", "Incomplete checkin data"),
("to_send", "Pending Notification"), ("to_send", "Pending Notification"),
("to_process", "Pending Processing"), ("to_process", "Pending Processing"),
("error_sending", "Error Sending"), ("error_sending", "Error Sending"),
@@ -51,6 +52,7 @@ class PmsSesCommunication(models.Model):
("processed", "Processed"), ("processed", "Processed"),
], ],
) )
sending_result = fields.Text( sending_result = fields.Text(
string="Sending Result", string="Sending Result",
help="Notification sending result", help="Notification sending result",

View File

@@ -32,10 +32,19 @@ XML_PENDING = "5"
CREATE_OPERATION_CODE = "A" CREATE_OPERATION_CODE = "A"
DELETE_OPERATION_CODE = "B" DELETE_OPERATION_CODE = "B"
# Disable insecure request warnings # Disable insecure request warnings
# requests.packages.urllib3.disable_warnings(InsecureRequestWarning) # requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
def clean_string_ses(string):
clean_string = re.sub(r"[^a-zA-Z0-9\s]", "", string).upper()
clean_string = " ".join(clean_string.split())
return clean_string
def _string_to_zip_to_base64(string_data): def _string_to_zip_to_base64(string_data):
zip_buffer = io.BytesIO() zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file: with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
@@ -56,7 +65,7 @@ def _ses_xml_payment_elements(contrato, reservation):
ET.SubElement(pago, "tipoPago").text = tipo_pago ET.SubElement(pago, "tipoPago").text = tipo_pago
def _ses_xml_contract_elements(comunicacion, reservation): def _ses_xml_contract_elements(comunicacion, reservation, people=False):
contrato = ET.SubElement(comunicacion, "contrato") contrato = ET.SubElement(comunicacion, "contrato")
ET.SubElement(contrato, "referencia").text = reservation.name ET.SubElement(contrato, "referencia").text = reservation.name
ET.SubElement(contrato, "fechaContrato").text = str(reservation.date_order)[:10] ET.SubElement(contrato, "fechaContrato").text = str(reservation.date_order)[:10]
@@ -66,6 +75,9 @@ def _ses_xml_contract_elements(comunicacion, reservation):
ET.SubElement( ET.SubElement(
contrato, "fechaSalida" contrato, "fechaSalida"
).text = f"{str(reservation.checkout)[:10]}T00:00:00" ).text = f"{str(reservation.checkout)[:10]}T00:00:00"
if people:
ET.SubElement(contrato, "numPersonas").text = str(people)
else:
ET.SubElement(contrato, "numPersonas").text = str(reservation.adults) ET.SubElement(contrato, "numPersonas").text = str(reservation.adults)
_ses_xml_payment_elements(contrato, reservation) _ses_xml_payment_elements(contrato, reservation)
@@ -90,37 +102,37 @@ def _ses_xml_map_document_type(code):
def _ses_xml_person_names_elements(persona, reservation, checkin_partner): def _ses_xml_person_names_elements(persona, reservation, checkin_partner):
if reservation: if reservation:
name = False ses_firstname = False
if reservation.partner_id.firstname: if reservation.partner_id.firstname:
name = reservation.partner_id.firstname ses_firstname = clean_string_ses(reservation.partner_id.firstname)
elif reservation.partner_name: elif reservation.partner_name:
name = reservation.partner_name.split(" ")[0] ses_firstname = clean_string_ses(reservation.partner_name).split(" ")[0]
_ses_xml_text_element_and_validate( _ses_xml_text_element_and_validate(
persona, persona,
"nombre", "nombre",
name, ses_firstname,
_("The reservation does not have a name."), _("The reservation does not have a name."),
) )
if reservation.partner_id.lastname: if reservation.partner_id.lastname:
firstname = reservation.partner_id.lastname ses_lastname = clean_string_ses(reservation.partner_id.lastname)
elif reservation.partner_name and len(reservation.partner_name.split(" ")) > 1: elif reservation.partner_name and len(reservation.partner_name.split(" ")) > 1:
firstname = reservation.partner_name.split(" ")[1] ses_lastname = clean_string_ses(reservation.partner_name).split(" ")[1]
else: else:
firstname = "No aplica" ses_lastname = "No aplica"
ET.SubElement(persona, "apellido1").text = firstname ET.SubElement(persona, "apellido1").text = ses_lastname
elif checkin_partner: elif checkin_partner:
_ses_xml_text_element_and_validate( _ses_xml_text_element_and_validate(
persona, persona,
"nombre", "nombre",
checkin_partner.firstname, clean_string_ses(checkin_partner.firstname),
_("The guest does not have a name."), _("The guest does not have a name."),
) )
_ses_xml_text_element_and_validate( _ses_xml_text_element_and_validate(
persona, persona,
"apellido1", "apellido1",
checkin_partner.lastname, clean_string_ses(checkin_partner.lastname),
_("The guest does not have a lastname."), _("The guest does not have a lastname."),
) )
@@ -128,7 +140,7 @@ def _ses_xml_person_names_elements(persona, reservation, checkin_partner):
_ses_xml_text_element_and_validate( _ses_xml_text_element_and_validate(
persona, persona,
"apellido2", "apellido2",
checkin_partner.partner_id.lastname2, clean_string_ses(checkin_partner.partner_id.lastname2),
_("The guest does not have a second lastname."), _("The guest does not have a second lastname."),
) )
@@ -242,7 +254,12 @@ def _ses_xml_person_contact_elements(persona, reservation, checkin_partner=False
for contact in contact_methods: for contact in contact_methods:
if contact: if contact:
tag = "telefono" if "@" not in contact else "correo" if "@" in contact:
tag = "correo"
contact = contact[0:50]
else:
tag = "telefono"
contact = contact[0:20]
ET.SubElement(persona, tag).text = contact ET.SubElement(persona, tag).text = contact
break break
else: else:
@@ -324,6 +341,8 @@ def _handle_request_exception(communication, e):
communication.sending_result = f"Request error: {e}" communication.sending_result = f"Request error: {e}"
else: else:
communication.processing_result = f"Request error: {e}" communication.processing_result = f"Request error: {e}"
else:
communication.sending_result = f"Unexpected error: {e}"
class TravellerReport(models.TransientModel): class TravellerReport(models.TransientModel):
@@ -751,8 +770,9 @@ class TravellerReport(models.TransientModel):
) )
def send_file_institution(self, pms_property=False, offset=0, date_target=False): def send_file_institution(self, pms_property=False, offset=0, date_target=False):
try:
called_from_user = False called_from_user = False
log = False
try:
if not pms_property: if not pms_property:
called_from_user = True called_from_user = True
pms_property = self.env["pms.property"].search( pms_property = self.env["pms.property"].search(
@@ -932,21 +952,23 @@ class TravellerReport(models.TransientModel):
) )
return self.generate_xml_reservations_travellers_report(reservation_ids) return self.generate_xml_reservations_travellers_report(reservation_ids)
def generate_xml_reservation_travellers_report(self, solicitud, reservation_id): def generate_xml_reservation_travellers_report(
self, solicitud, reservation_id, people=False
):
reservation = self.env["pms.reservation"].browse(reservation_id) reservation = self.env["pms.reservation"].browse(reservation_id)
comunicacion = ET.SubElement(solicitud, "comunicacion") comunicacion = ET.SubElement(solicitud, "comunicacion")
_ses_xml_contract_elements(comunicacion, reservation) _ses_xml_contract_elements(comunicacion, reservation, people)
for checkin_partner in reservation.checkin_partner_ids.filtered( for checkin_partner in reservation.checkin_partner_ids.filtered(
lambda x: x.state == "onboard" lambda x: x.state == "onboard"
): ):
_ses_xml_person_elements(comunicacion, checkin_partner) _ses_xml_person_elements(comunicacion, checkin_partner)
def generate_xml_reservations_travellers_report(self, reservation_ids): def generate_xml_reservations_travellers_report(
self, reservation_ids, ignore_some_not_onboard=False
):
if not reservation_ids: if not reservation_ids:
raise ValidationError(_("Theres's no reservation to generate the XML")) raise ValidationError(_("Theres's no reservation to generate the XML"))
elif (
if (
len( len(
self.env["pms.reservation"] self.env["pms.reservation"]
.browse(reservation_ids) .browse(reservation_ids)
@@ -955,43 +977,58 @@ class TravellerReport(models.TransientModel):
> 1 > 1
): ):
raise ValidationError(_("The reservations must be from the same property.")) raise ValidationError(_("The reservations must be from the same property."))
if not any( elif all(
state == "onboard" state != "onboard"
for state in self.env["pms.reservation"] for state in self.env["pms.reservation"]
.browse(reservation_ids) .browse(reservation_ids)
.mapped("checkin_partner_ids") .mapped("checkin_partner_ids")
.mapped("state") .mapped("state")
): ):
raise ValidationError( raise ValidationError(_("There are no guests onboard."))
_("There are no guests to generate the travellers report.") elif not ignore_some_not_onboard and any(
) state != "onboard"
for state in self.env["pms.reservation"]
.browse(reservation_ids)
.mapped("checkin_partner_ids")
.mapped("state")
):
raise ValidationError(_("There are some guests not onboard."))
else:
# SOLICITUD # SOLICITUD
solicitud = ET.Element("solicitud") solicitud = ET.Element("solicitud")
pms_property = ( pms_property = (
self.env["pms.reservation"].browse(reservation_ids[0]).pms_property_id self.env["pms.reservation"].browse(reservation_ids[0]).pms_property_id
) )
if not pms_property.institution_property_id: if not pms_property.institution_property_id:
raise ValidationError( raise ValidationError(
_("The property does not have an institution property id.") _("The property does not have an institution property id.")
) )
# SOLICITUD -> CODIGO ESTABLECIMIENTO # SOLICITUD -> CODIGO ESTABLECIMIENTO
ET.SubElement( ET.SubElement(
solicitud, "codigoEstablecimiento" solicitud, "codigoEstablecimiento"
).text = pms_property.institution_property_id ).text = pms_property.institution_property_id
for reservation_id in reservation_ids: for reservation_id in reservation_ids:
if ignore_some_not_onboard:
num_people_on_board = len(
self.env["pms.reservation"]
.browse(reservation_id)
.checkin_partner_ids.filtered(lambda x: x.state == "onboard")
)
ET.SubElement( ET.SubElement(
solicitud, solicitud,
self.generate_xml_reservation_travellers_report( self.generate_xml_reservation_travellers_report(
solicitud, reservation_id solicitud, reservation_id, people=num_people_on_board
),
)
else:
ET.SubElement(
solicitud,
self.generate_xml_reservation_travellers_report(
solicitud,
reservation_id,
), ),
) )
xml_str = ET.tostring(solicitud, encoding="unicode") xml_str = ET.tostring(solicitud, encoding="unicode")
xml_str = ( xml_str = (
'<ns2:peticion xmlns:ns2="http://www.neg.hospedajes.mir.es/altaParteHospedaje">' '<ns2:peticion xmlns:ns2="http://www.neg.hospedajes.mir.es/altaParteHospedaje">'
+ xml_str + xml_str
@@ -1008,10 +1045,30 @@ class TravellerReport(models.TransientModel):
("entity", "=", entity), ("entity", "=", entity),
] ]
): ):
data = False data = False
try:
if communication.operation == DELETE_OPERATION_CODE:
communication_to_cancel = self.env["pms.ses.communication"].search(
[
("reservation_id", "=", communication.reservation_id.id),
("state", "!=", "to_send"),
("entity", "=", communication.entity),
("operation", "=", CREATE_OPERATION_CODE),
]
)
data = (
"<anul:comunicaciones "
'xmlns:anul="http://www.neg.hospedajes.mir.es/anularComunicacion">'
+ "<anul:codigoComunicacion>"
+ communication_to_cancel.communication_id
+ "</anul:codigoComunicacion>"
+ "</anul:comunicaciones>"
)
elif communication.operation == CREATE_OPERATION_CODE:
if communication.entity == "RH": if communication.entity == "RH":
data = self.generate_xml_reservations([communication.reservation_id.id]) data = self.generate_xml_reservations(
[communication.reservation_id.id]
)
elif communication.entity == "PV": elif communication.entity == "PV":
data = self.generate_xml_reservations_travellers_report( data = self.generate_xml_reservations_travellers_report(
[communication.reservation_id.id] [communication.reservation_id.id]
@@ -1026,13 +1083,13 @@ class TravellerReport(models.TransientModel):
) )
communication.communication_soap = payload communication.communication_soap = payload
communication.communication_time = fields.Datetime.now() communication.communication_time = fields.Datetime.now()
try:
soap_response = requests.request( soap_response = requests.request(
"POST", "POST",
communication.reservation_id.pms_property_id.ses_url, communication.reservation_id.pms_property_id.ses_url,
headers=_get_auth_headers(communication), headers=_get_auth_headers(communication),
data=payload, data=payload,
verify=get_module_resource("pms_l10n_es", "static", "ses_cert.pem"), verify=get_module_resource("pms_l10n_es", "static", "cert.pem"),
) )
root = ET.fromstring(soap_response.text) root = ET.fromstring(soap_response.text)
communication.sending_result = root.find(".//descripcion").text communication.sending_result = root.find(".//descripcion").text
@@ -1049,6 +1106,72 @@ class TravellerReport(models.TransientModel):
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
_handle_request_exception(communication, e) _handle_request_exception(communication, e)
except Exception as e:
_handle_request_exception(communication, e)
@api.model
def ses_send_incomplete_traveller_reports(
self, hours_after_first_checkin_to_inform
):
# iterate through incomplete communications
for communication in self.env["pms.ses.communication"].search(
[
("state", "=", "incomplete"),
("entity", "=", "PV"),
]
):
try:
if (
fields.Datetime.now() - communication.create_date
).hours > hours_after_first_checkin_to_inform:
# add a note to the reservation
communication.reservation_id.sudo().message_post(
body=_(
"There was't enough guests in the reservation when data "
"was sent to SES. Sent to SES with onboard guests"
)
)
data = self.generate_xml_reservations_travellers_report(
[communication.reservation_id.id],
ignore_some_not_onboard=True,
)
communication.communication_xml = data
data = _string_to_zip_to_base64(data)
payload = _generate_payload(
communication.reservation_id.pms_property_id.institution_lessor_id,
communication.operation,
communication.entity,
data,
)
communication.communication_soap = payload
communication.communication_time = fields.Datetime.now()
soap_response = requests.request(
"POST",
communication.reservation_id.pms_property_id.ses_url,
headers=_get_auth_headers(communication),
data=payload,
verify=get_module_resource(
"pms_l10n_es", "static", "ses_cert.pem"
),
)
root = ET.fromstring(soap_response.text)
communication.sending_result = root.find(".//descripcion").text
communication.response_communication_soap = soap_response.text
result_code = root.find(".//codigo").text
if result_code == REQUEST_CODE_OK:
communication.communication_id = root.find(".//lote").text
if communication.operation == CREATE_OPERATION_CODE:
communication.state = "to_process"
else:
communication.state = "processed"
else:
communication.state = "error_sending"
except requests.exceptions.RequestException as e:
_handle_request_exception(communication, e)
except Exception as e:
_handle_request_exception(communication, e)
@api.model @api.model
def ses_process_communications(self): def ses_process_communications(self):
@@ -1058,6 +1181,7 @@ class TravellerReport(models.TransientModel):
("operation", "!=", DELETE_OPERATION_CODE), ("operation", "!=", DELETE_OPERATION_CODE),
] ]
): ):
try:
var_xml_get_batch = f""" var_xml_get_batch = f"""
<con:lotes <con:lotes
xmlns:con="http://www.neg.hospedajes.mir.es/consultarComunicacion"> xmlns:con="http://www.neg.hospedajes.mir.es/consultarComunicacion">
@@ -1074,7 +1198,7 @@ class TravellerReport(models.TransientModel):
) )
communication.query_status_soap = payload communication.query_status_soap = payload
communication.query_status_time = fields.Datetime.now() communication.query_status_time = fields.Datetime.now()
try:
soap_response = requests.request( soap_response = requests.request(
"POST", "POST",
communication.reservation_id.pms_property_id.ses_url, communication.reservation_id.pms_property_id.ses_url,
@@ -1105,6 +1229,8 @@ class TravellerReport(models.TransientModel):
communication.processing_result = root.find(".//descripcion").text communication.processing_result = root.find(".//descripcion").text
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
_handle_request_exception(communication, e) _handle_request_exception(communication, e)
except Exception as e:
_handle_request_exception(communication, e)
@api.model @api.model
def create_pending_notifications_traveller_report(self): def create_pending_notifications_traveller_report(self):