[IMP]pms_l10n_es: improvements in ses traveller report

This commit is contained in:
Darío Lodeiros
2024-12-02 18:01:01 +01:00
parent 0877438b0a
commit c0fb76e095
7 changed files with 216 additions and 131 deletions

View File

@@ -45,9 +45,9 @@
SES Automatic Sending Pending Traveller Reports Communications SES Automatic Sending Pending Traveller Reports Communications
</field> </field>
<field name="active" eval="False" /> <field name="active" eval="False" />
<field name="interval_number">1</field> <field name="interval_number">30</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">minutes</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>

View File

@@ -42,7 +42,6 @@ class PmsProperty(models.Model):
string="Institution lessor id", string="Institution lessor id",
help="Id provided by institution to send data from lessor.", help="Id provided by institution to send data from lessor.",
) )
ine_tourism_number = fields.Char( ine_tourism_number = fields.Char(
"Tourism number", "Tourism number",
help="Registration number in the Ministry of Tourism. Used for INE statistics.", help="Registration number in the Ministry of Tourism. Used for INE statistics.",

View File

@@ -15,6 +15,38 @@ class PmsReservation(models.Model):
string="Is SES", string="Is SES",
readonly=True, readonly=True,
compute="_compute_is_ses", compute="_compute_is_ses",
store=True,
)
ses_status_reservation = fields.Selection(
string="SES Status",
help="Status of the reservation in SES",
selection=[
("not_applicable", "Not Applicable"),
("to_send", "Pending Notification"),
("to_process", "Pending Processing"),
("error_create", "Error Creating"),
("error_sending", "Error Sending"),
("error_processing", "Error Processing"),
("processed", "Processed"),
],
compute="_compute_ses_status_reservation",
store=True,
)
ses_status_traveller_report = fields.Selection(
string="SES Status traveller",
help="Status of the traveller report in SES",
selection=[
("not_applicable", "Not Applicable"),
("incomplete", "Incomplete checkin data"),
("to_send", "Pending Notification"),
("to_process", "Pending Processing"),
("error_create", "Error Creating"),
("error_sending", "Error Sending"),
("error_processing", "Error Processing"),
("processed", "Processed"),
],
compute="_compute_ses_status_traveller_report",
store=True,
) )
@api.depends("pms_property_id") @api.depends("pms_property_id")
@@ -22,6 +54,38 @@ class PmsReservation(models.Model):
for record in self: for record in self:
record.is_ses = record.pms_property_id.institution == "ses" record.is_ses = record.pms_property_id.institution == "ses"
@api.depends("ses_communication_ids", "ses_communication_ids.state")
def _compute_ses_status_reservation(self):
for record in self:
if record.pms_property_id.institution != "ses":
record.ses_status_reservation = "not_applicable"
continue
communication = record.ses_communication_ids.filtered(
lambda x: x.entity == "RH"
)
if len(communication) > 1:
# get the last communication
communication = communication.sorted(key=lambda x: x.create_date)[-1]
record.ses_status_reservation = (
communication.state if communication else "error_create"
)
@api.depends("ses_communication_ids", "ses_communication_ids.state")
def _compute_ses_status_traveller_report(self):
for record in self:
if record.pms_property_id.institution != "ses":
record.ses_status_traveller_report = "not_applicable"
continue
communication = record.ses_communication_ids.filtered(
lambda x: x.entity == "PV"
)
if len(communication) > 1:
# get the last communication
communication = communication.sorted(key=lambda x: x.create_date)[-1]
record.ses_status_traveller_report = (
communication.state if communication else "error_create"
)
@api.model @api.model
def create_communication(self, reservation_id, operation, entity): def create_communication(self, reservation_id, operation, entity):
self.env["pms.ses.communication"].create( self.env["pms.ses.communication"].create(
@@ -100,6 +164,9 @@ class PmsReservation(models.Model):
def write(self, vals): def write(self, vals):
for record in self: for record in self:
if record.pms_property_id.institution == "ses": if (
record.pms_property_id.institution == "ses"
and record.reservation_type != "out"
):
self.create_communication_after_update_reservation(record, vals) self.create_communication_after_update_reservation(record, vals)
return super(PmsReservation, self).write(vals) return super(PmsReservation, self).write(vals)

View File

@@ -5,6 +5,8 @@ from odoo import fields, models
class PmsSesCommunication(models.Model): class PmsSesCommunication(models.Model):
_name = "pms.ses.communication" _name = "pms.ses.communication"
_description = "SES Communication" _description = "SES Communication"
_order = "create_date desc"
reservation_id = fields.Many2one( reservation_id = fields.Many2one(
string="Reservation", string="Reservation",
help="Reservation related to this communication", help="Reservation related to this communication",
@@ -12,6 +14,14 @@ class PmsSesCommunication(models.Model):
required=True, required=True,
comodel_name="pms.reservation", comodel_name="pms.reservation",
) )
pms_property_id = fields.Many2one(
comodel_name="pms.property",
string="Property",
help="Property related to this communication",
related="reservation_id.pms_property_id",
index=True,
store=True,
)
communication_id = fields.Char( communication_id = fields.Char(
string="Communication ID", string="Communication ID",
help="ID of the communication", help="ID of the communication",
@@ -37,7 +47,6 @@ class PmsSesCommunication(models.Model):
string="Query status time", string="Query status time",
help="Date and time of the last state query", help="Date and time of the last state query",
) )
state = fields.Selection( state = fields.Selection(
string="State", string="State",
help="State of the communication", help="State of the communication",
@@ -52,7 +61,6 @@ 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",
@@ -86,3 +94,10 @@ class PmsSesCommunication(models.Model):
string="SOAP Resp. Status", string="SOAP Resp. Status",
help="SOAP response status query", help="SOAP response status query",
) )
def force_send_communication(self):
for record in self:
self.env["traveller.report.wizard"].ses_send_communication(
entity=record.entity,
communication_id=record.communication_id,
)

View File

@@ -221,65 +221,3 @@ class TestPmsSesCommunication(TestPms):
reservation_communications, reservation_communications,
"Update adults should create 2 notifications with operations A and B", "Update adults should create 2 notifications with operations A and B",
) )
def test_create_notification_when_checkin_partner_on_board(self):
# ARRANGE
partner = self.env["res.partner"].create(
{
"name": "name test",
"firstname": "firstname test",
"lastname": "lastname test",
"lastname2": "lastname2 test",
"birthdate_date": "1995-12-10",
"gender": "male",
"nationality_id": self.env.ref("base.es").id,
"residence_street": "street test",
"residence_city": "city test",
"residence_zip": "zip test",
"residence_country_id": self.env.ref("base.us").id,
}
)
reservation = self.env["pms.reservation"].create(
{
"pms_property_id": self.pms_property1.id,
"room_type_id": self.room_type.id,
"checkin": fields.date.today(),
"checkout": fields.date.today() + datetime.timedelta(days=13),
"adults": 1,
"children": 0,
"sale_channel_origin_id": self.sale_channel_direct1.id,
"partner_name": "Test reservation",
}
)
document_type_dni = self.env["res.partner.id_category"].search(
[("code", "=", "D")], limit=1
)
checkin_partner = self.env["pms.checkin.partner"].create(
{
"reservation_id": reservation.id,
"partner_id": partner.id,
"document_number": "11111111H",
"document_type": document_type_dni.id,
"document_expedition_date": fields.date.today()
+ datetime.timedelta(days=1),
"support_number": "123456",
}
)
checkin_partner.action_on_board()
# ACT
self.env[
"traveller.report.wizard"
].create_pending_notifications_traveller_report()
# ASSERT
last_notification = self.env["pms.ses.communication"].search(
[
("reservation_id", "=", reservation.id),
("operation", "=", "A"),
("state", "=", "to_send"),
("entity", "=", "PV"),
],
)
self.assertTrue(
last_notification,
"Notification should be created when checkin partner is on board",
)

View File

@@ -42,7 +42,15 @@
<field name="name">pms.ses.communication.tree</field> <field name="name">pms.ses.communication.tree</field>
<field name="model">pms.ses.communication</field> <field name="model">pms.ses.communication</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree name="Property Ubications" create="false"> <tree
name="Property Ubications"
create="false"
decoration-danger="state in ('error_sending', 'error_processing')"
decoration-muted="state=='processed'"
decoration-info="state=='incomplete'"
decoration-success="state=='to_process'"
>
<field name="pms_property_id" />
<field name="reservation_id" /> <field name="reservation_id" />
<field name="communication_id" /> <field name="communication_id" />
<field name="operation" /> <field name="operation" />
@@ -64,17 +72,84 @@
<field name="model">pms.ses.communication</field> <field name="model">pms.ses.communication</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search string="Log SES Communications"> <search string="Log SES Communications">
<field name="pms_property_id" widget="selection" />
<field name="communication_id" /> <field name="communication_id" />
<field name="reservation_id" /> <field name="reservation_id" />
<!-- times --> <!-- filters -->
<field name="create_date" /> <separator string="Results" colspan="4" />
<field name="communication_time" /> <filter
<field name="query_status_time" /> string="To Process"
<field name="state" /> name="to_process"
domain="[('state', '=', 'to_process')]"
/>
<filter
string="Processed"
name="processed"
domain="[('state', '=', 'processed')]"
/>
<filter
string="Error Sending"
name="error_sending"
domain="[('state', '=', 'error_sending')]"
/>
<filter
string="Error Processing"
name="error_processing"
domain="[('state', '=', 'error_processing')]"
/>
<filter
string="Wait checkins"
name="incomplete"
domain="[('state', '=', 'incomplete')]"
/>
<separator string="Times" colspan="4" />
<filter
string="Today"
name="today"
domain="[('create_date', '&gt;=', (context_today() + ' 00:00:00'))]"
/>
<filter
string="This Week"
name="this_week"
domain="[('create_date', '&gt;=', (context_today() + ' 00:00:00'))]"
/>
<filter
string="This Month"
name="this_month"
domain="[('create_date', '&gt;=', (context_today() + ' 00:00:00'))]"
/>
<!-- groups -->
<group expand="0" string="Group By">
<filter
name="group_by_pms_property_id"
string="Property"
context="{'group_by': 'pms_property_id'}"
/>
<filter
name="group_by_state"
string="State"
context="{'group_by': 'state'}"
/>
</group>
</search> </search>
</field> </field>
</record> </record>
<!-- action to force_send_communication -->
<record
model="ir.actions.server"
id="pms_ses_communication_force_send_communication"
>
<field name="name">Force Send Communication</field>
<field name="model_id" ref="model_pms_ses_communication" />
<field name="state">code</field>
<field name="code">
records.force_send_communication()
</field>
<field name="binding_model_id" ref="model_pms_ses_communication" />
<field name="binding_view_types">form,list</field>
</record>
<record model="ir.actions.act_window" id="open_pms_ses_communication_form_tree"> <record model="ir.actions.act_window" id="open_pms_ses_communication_form_tree">
<field name="name">SES Communications</field> <field name="name">SES Communications</field>
<field name="res_model">pms.ses.communication</field> <field name="res_model">pms.ses.communication</field>

View File

@@ -6,6 +6,7 @@ import json
import logging import logging
import re import re
import time import time
import traceback
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
import zipfile import zipfile
@@ -45,7 +46,7 @@ def replace_multiple_spaces(text: str) -> str:
def clean_string_only_letters(string): def clean_string_only_letters(string):
clean_string = re.sub(r"[^a-zA-Z\s]", "", string).upper() clean_string = re.sub(r"[^a-zA-Z\s]", "", string).upper()
clean_string = " ".join(clean_string.split()) clean_string = " ".join(clean_string.split())
return return clean_string
def clean_string_only_numbers_and_letters(string): def clean_string_only_numbers_and_letters(string):
@@ -200,7 +201,7 @@ def _ses_xml_person_personal_info_elements(persona, checkin_partner):
) )
def _ses_xml_municipality_code(residence_zip): def _ses_xml_municipality_code(residence_zip, pms_property):
with open( with open(
get_module_resource( get_module_resource(
"pms_l10n_es", "static/src/", "pms.ine.zip.municipality.ine.relation.csv" "pms_l10n_es", "static/src/", "pms.ine.zip.municipality.ine.relation.csv"
@@ -212,6 +213,11 @@ def _ses_xml_municipality_code(residence_zip):
for fila in lector: for fila in lector:
if residence_zip in fila[0]: if residence_zip in fila[0]:
return fila[1][:5] return fila[1][:5]
# REVIEW: If the zip code is not found,
# use provisory pms_property zip code
property_zip = pms_property.zip
if property_zip:
return property_zip[:5]
raise ValidationError(_("The guest does not have a valid zip code.")) raise ValidationError(_("The guest does not have a valid zip code."))
@@ -225,7 +231,10 @@ def _ses_xml_person_address_elements(persona, checkin_partner):
) )
if checkin_partner.residence_country_id.code == CODE_SPAIN: if checkin_partner.residence_country_id.code == CODE_SPAIN:
municipio_code = _ses_xml_municipality_code(checkin_partner.residence_zip) municipio_code = _ses_xml_municipality_code(
residence_zip=checkin_partner.residence_zip,
pms_property=checkin_partner.reservation_id.pms_property_id,
)
if municipio_code: if municipio_code:
ET.SubElement(direccion, "codigoMunicipio").text = municipio_code ET.SubElement(direccion, "codigoMunicipio").text = municipio_code
else: else:
@@ -358,11 +367,15 @@ def _handle_request_exception(communication, e):
) )
else: else:
if communication.state == "to_send": if communication.state == "to_send":
communication.sending_result = f"Request error: {e}" communication.sending_result = (
f"Request error: {traceback.format_exc()}"
)
else: else:
communication.processing_result = f"Request error: {e}" communication.processing_result = (
f"Request error: {traceback.format_exc()}"
)
else: else:
communication.sending_result = f"Unexpected error: {e}" communication.sending_result = f"Unexpected error: {traceback.format_exc()}"
class TravellerReport(models.TransientModel): class TravellerReport(models.TransientModel):
@@ -979,7 +992,7 @@ class TravellerReport(models.TransientModel):
comunicacion = ET.SubElement(solicitud, "comunicacion") comunicacion = ET.SubElement(solicitud, "comunicacion")
_ses_xml_contract_elements(comunicacion, reservation, people) _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 in ["onboard", "done"]
): ):
_ses_xml_person_elements(comunicacion, checkin_partner) _ses_xml_person_elements(comunicacion, checkin_partner)
@@ -998,7 +1011,7 @@ class TravellerReport(models.TransientModel):
): ):
raise ValidationError(_("The reservations must be from the same property.")) raise ValidationError(_("The reservations must be from the same property."))
elif all( elif all(
state != "onboard" state not in ["onboard", "done"]
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")
@@ -1006,7 +1019,7 @@ class TravellerReport(models.TransientModel):
): ):
raise ValidationError(_("There are no guests onboard.")) raise ValidationError(_("There are no guests onboard."))
elif not ignore_some_not_onboard and any( elif not ignore_some_not_onboard and any(
state != "onboard" state not in ["onboard", "done"]
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")
@@ -1032,7 +1045,9 @@ class TravellerReport(models.TransientModel):
num_people_on_board = len( num_people_on_board = len(
self.env["pms.reservation"] self.env["pms.reservation"]
.browse(reservation_id) .browse(reservation_id)
.checkin_partner_ids.filtered(lambda x: x.state == "onboard") .checkin_partner_ids.filtered(
lambda x: x.state in ["onboard", "done"]
)
) )
ET.SubElement( ET.SubElement(
solicitud, solicitud,
@@ -1057,14 +1072,14 @@ class TravellerReport(models.TransientModel):
return xml_str return xml_str
@api.model @api.model
def ses_send_communications(self, entity): def ses_send_communications(self, entity, communication_id=False):
domain = [
for communication in self.env["pms.ses.communication"].search( ("state", "=", "to_send"),
[ ("entity", "=", entity),
("state", "=", "to_send"), ]
("entity", "=", entity), if communication_id:
] domain.append(("id", "=", communication_id))
): for communication in self.env["pms.ses.communication"].search(domain):
data = False data = False
try: try:
if communication.operation == DELETE_OPERATION_CODE: if communication.operation == DELETE_OPERATION_CODE:
@@ -1074,7 +1089,9 @@ class TravellerReport(models.TransientModel):
("state", "!=", "to_send"), ("state", "!=", "to_send"),
("entity", "=", communication.entity), ("entity", "=", communication.entity),
("operation", "=", CREATE_OPERATION_CODE), ("operation", "=", CREATE_OPERATION_CODE),
] ],
order="id desc",
limit=1,
) )
data = ( data = (
"<anul:comunicaciones " "<anul:comunicaciones "
@@ -1111,21 +1128,22 @@ class TravellerReport(models.TransientModel):
data=payload, data=payload,
verify=get_module_resource("pms_l10n_es", "static", "cert.pem"), verify=get_module_resource("pms_l10n_es", "static", "cert.pem"),
) )
soap_response.raise_for_status()
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
communication.response_communication_soap = soap_response.text communication.response_communication_soap = soap_response.text
result_code = root.find(".//codigo").text result_code = root.find(".//codigo").text
if result_code == REQUEST_CODE_OK: if result_code == REQUEST_CODE_OK:
communication.communication_id = root.find(".//lote").text communication.communication_id = root.find(".//lote").text
if communication.operation == CREATE_OPERATION_CODE: communication.state = "to_process"
communication.state = "to_process"
else:
communication.state = "processed"
else: else:
communication.state = "error_sending" communication.state = "error_sending"
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
_handle_request_exception(communication, e) _handle_request_exception(communication, e)
except requests.exceptions.HTTPError as http_err:
_handle_request_exception(communication, http_err)
except Exception as e: except Exception as e:
_handle_request_exception(communication, e) _handle_request_exception(communication, e)
@@ -1173,10 +1191,9 @@ class TravellerReport(models.TransientModel):
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( verify=get_module_resource("pms_l10n_es", "static", "cert.pem"),
"pms_l10n_es", "static", "ses_cert.pem"
),
) )
soap_response.raise_for_status()
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
communication.response_communication_soap = soap_response.text communication.response_communication_soap = soap_response.text
@@ -1192,6 +1209,8 @@ 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 requests.exceptions.HTTPError as http_err:
_handle_request_exception(communication, http_err)
except Exception as e: except Exception as e:
_handle_request_exception(communication, e) _handle_request_exception(communication, e)
@@ -1228,6 +1247,7 @@ class TravellerReport(models.TransientModel):
data=payload, data=payload,
verify=get_module_resource("pms_l10n_es", "static", "cert.pem"), verify=get_module_resource("pms_l10n_es", "static", "cert.pem"),
) )
soap_response.raise_for_status()
root = ET.fromstring(soap_response.text) root = ET.fromstring(soap_response.text)
communication.response_communication_soap = soap_response.text communication.response_communication_soap = soap_response.text
result_code = root.find(".//codigo").text result_code = root.find(".//codigo").text
@@ -1251,36 +1271,7 @@ 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 requests.exceptions.HTTPError as http_err:
_handle_request_exception(communication, http_err)
except Exception as e: except Exception as e:
_handle_request_exception(communication, e) _handle_request_exception(communication, e)
@api.model
def create_pending_notifications_traveller_report(self):
domain = [
("state", "=", "onboard"),
("checkin", "=", fields.Datetime.today().date()),
("pms_property_id.institution", "=", "ses"),
]
for reservation in (
self.env["pms.reservation"]
.search(domain)
.filtered(
lambda x: any(
state == "onboard"
for state in x.checkin_partner_ids.mapped("state")
)
)
):
self.env["pms.ses.communication"].search(
[
("reservation_id", "=", reservation.id),
("entity", "=", "PV"),
("state", "=", "to_send"),
]
).unlink()
self.env["pms.reservation"].create_communication(
reservation.id,
CREATE_OPERATION_CODE,
"PV",
)