From a8cfe40b18104852af79e8d3bd6fbf223aae5400 Mon Sep 17 00:00:00 2001 From: Jose Luis Date: Mon, 18 Mar 2019 18:34:56 +0100 Subject: [PATCH 01/18] [FIX] Children and Adult calculate. --- hotel/data/email_template_cancel.xml | 1208 +++++++++++++++----------- 1 file changed, 691 insertions(+), 517 deletions(-) diff --git a/hotel/data/email_template_cancel.xml b/hotel/data/email_template_cancel.xml index 32c0bc91f..d4b7eabff 100644 --- a/hotel/data/email_template_cancel.xml +++ b/hotel/data/email_template_cancel.xml @@ -12,524 +12,698 @@ + /*Global Styles*/ + .global { + margin: 0; + padding: 0; + min-width: 100% !important; + } + a { + color: #5e96ea; + text-decoration: none; + font-weight: bold; + } + img { + height: auto; + } + .content { + border: 1px solid #eeeeee; + } + .logo { + font-family: sans-serif; + font-size: 36px; + font-weight: bold; + color: #ffffff; + } + .link a { + font-family: sans-serif; + font-size: 12px; + color: #ffffff; + } + .subheading { + font-size: 14px; + color: #cccccc; + font-family: sans-serif; + font-weight: bold; + padding: 0 0 0 0; + text-transform: uppercase; + letter-spacing: 1px; + } + .h1 { + font-family: sans-serif; + font-size: 48px; + font-weight: bold; + line-height: 56px; + color: #ffffff; + padding: 0 0 0 0; + } + .h2 { + font-family: sans-serif; + font-size: 18px; + font-weight: bold; + color: #444444; + padding: 0 0 0 0; + text-transform: uppercase; + letter-spacing: 0.5px; + } + .h3 { + font-family: sans-serif; + font-size: 24px; + font-weight: regular; + color: #555555; + padding: 0 0 0 0; + } + .h4 { + font-family: sans-serif; + font-size: 18px; + font-weight: bold; + color: #666666; + padding: 0 0 0 0; + } + .paragraph { + font-family: sans-serif; + font-size: 14px; + line-height: 22px; + color: #666666; + font-weight: 200; + padding: 20px 0 0 0; + } + .listitem { + font-family: sans-serif; + font-size: 15px; + color: #666666; + font-weight: 200; + padding: 0 0 20px 0; + } + .smalltext { + font-family: sans-serif; + font-size: 14px; + color: #cccccc; + padding: 3px 0 0 0; + } + .borderbottom { + border-bottom: 1px solid #f2eeed; + } + /*Media Queries*/ + @media only screen and (max-width: 651px) { + .columns { + width: 100% !important; + } + .columncontainer { + display: block !important; + width: 100% !important; + } + .paragraph, + .listitem { + font-size: 18px; + } + .link { + float: left; + } + } + @media only screen and (min-width: 651px) { + .content { + width: 650px !important; + } + } + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ Alda Hotels + + + + + + +
+ + + + + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
Tu reserva se ha cancelado en ${object.company_id.property_name}
+ + + + + + +
- /*Global Styles*/ - .global {margin: 0; padding: 0; min-width: 100%!important;} - a { color: #5e96ea; text-decoration: none; font-weight: bold;} - img {height: auto;} - .content { border: 1px solid #eeeeee; } - .logo {font-family: sans-serif; font-size: 36px; font-weight: bold; color: #ffffff;} - .link a {font-family: sans-serif; font-size: 12px; color: #ffffff;} - .subheading {font-size: 14px; color: #cccccc; font-family: sans-serif; font-weight: bold; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 1px;} - .h1 {font-family: sans-serif; font-size: 48px; font-weight: bold; line-height: 56px; color: #ffffff; padding: 0 0 0 0;} - .h2 {font-family: sans-serif; font-size: 18px; font-weight: bold; color: #444444; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;} - .h3 {font-family: sans-serif; font-size: 24px; font-weight: regular; color: #555555; padding: 0 0 0 0;} - .h4 {font-family: sans-serif; font-size: 18px; font-weight: bold; color: #666666; padding: 0 0 0 0;} - .paragraph {font-family: sans-serif; font-size: 14px; line-height: 22px; color: #666666; font-weight: 200; padding: 20px 0 0 0;} - .listitem {font-family: sans-serif; font-size: 15px; color: #666666; font-weight: 200; padding: 0 0 20px 0;} - .smalltext { font-family: sans-serif; font-size: 14px; color: #cccccc; padding: 3px 0 0 0; } - .borderbottom {border-bottom: 1px solid #f2eeed;} - - /*Media Queries*/ - @media only screen and (max-width: 651px){ - .columns{width:100% !important;} - .columncontainer{display:block !important; width:100% !important;} - .paragraph, .listitem {font-size: 18px;} - .link { float: left;} - } - - @media only screen and (min-width: 651px) { - .content {width: 650px !important;} - } - - -
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- Alda Hotels - - - - -
- - - - -
- - - - - -
-
-
- - - - - - - - - - - - -
Tu reserva se ha cancelado en ${object.company_id.property_name}
- - - - -
  - -
-
Hola ${object.partner_id.firstname}
- Tu reserva en ${object.company_id.property_name} se ha anulado correctamente. No es necesario que hagas nada más. - Si la cancelación conlleva la devolución de alguna cantidad, nos pondremos en contacto contigo. - En caso de que tengas alguna duda, estaremos encantados de atenderte. -
- - - - -
- -
Contactar -
-
-
-
-
- - - - - -
- - - - -
- Cancelación -
-
- - - - -
- - - - - - - - - - - - -
Datos de tu reserva cancelada
- ${object.partner_id.name} -
- % if object.partner_id.contact_address: - ${object.partner_id.contact_address}
- % endif -
- % for rline in object.get_grouped_reservations_json('cancelled'): - ${rline['num']} x ${rline['room_type']['name']} - % if rline['childrens'] > 0: - (${rline['adults']} Adults + ${rline['childrens']} Childrens) - % else: - (${rline['adults']} Adults) - %endif - -
- Entrada: ${format_tz(rline['checkin'], format="%d de %B de %Y")}
- Salida: ${format_tz(rline['checkout'], format="%d de %B de %Y")}
- Nº de noches: ${rline['nights']}

- % endfor -
-
-
-
- - - - - -
- - - - -
- Pago -
-
- - - - -
- - - - - - - - - - - - -
IMPORTES
- Noches: ${len(object.room_lines[0].reservation_lines)}
- Base imponible: ${object.amount_untaxed} €
- I.V.A (10%): ${object.amount_tax} €
- Precio total: ${object.amount_total} €
- Coste de cancelación: [[importe]]
-
-
-
-
-
-
- - - - - - - -
- - - - -
- - - -
-
-
- - - -
-
-
- - - - -
- - - - - - - - - -
NUESTRAS REDES SOCIALES 
-
-
- - - - - - -
- - - - -
- - - - - - - - - - - - - - - - -
- - Facebook - -
Facebook
- - - - -
  -
-
Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.
- - - - -
- - -
Dale a Me gusta
-
- -
-
-
-
- - - - -
- - - - - - - - - - - - - - - - -
- - Instagram - -
Instagram
- - - - -
  -
-
Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.
- - - - -
- - -
#Entra -
-
- -
-
-
-
- - - - -
- - - - - - - - - - - - - - - - -
- - Twitter - -
Twitter
- - - - -
  -
-
Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.
- - - - -
- - -
Síguenos -
-
- -
-
-
-
-
- - - - - - -
¡Esperamos verte pronto!
-
- - - -
- Alda Hotels - - - -
-
- -
-
+
+
Hola ${object.partner_id.firstname}
+ Tu reserva en ${object.company_id.property_name} se ha anulado correctamente. No es necesario que hagas nada más. + Si la cancelación conlleva la devolución de alguna cantidad, nos pondremos en contacto contigo. + En caso de que tengas alguna duda, estaremos encantados de atenderte. +
+ + + + + + +
+ +
Contactar +
+
+
+
+
+ + + + + + + +
+ + + + + + +
+ Cancelación +
+
+ + + + + + +
+ + + + + + + + + + + + + + +
Datos de tu reserva cancelada
+ ${object.partner_id.name} +
+ % if object.partner_id.contact_address: + ${object.partner_id.contact_address}
+ % endif +
+ % for rline in object.get_grouped_reservations_json('cancelled'): + + ${rline['num']} x ${rline['room_type']['name']} + % if rline['childrens'] == 0: + (${rline['adults']} Adultos) + % else: + (${rline['adults']} Adultos + ${rline['childrens']} Niños) + % endif +
+
+
+ Entrada: ${format_tz(rline['checkin'], format="%d de %B de %Y")}
+ Salida: ${format_tz(rline['checkout'], format="%d de %B de %Y")}
+ Nº de noches: ${rline['nights']}

+ % endfor +
+
+
+
+ + + + + + + +
+ + + + + + +
+ Pago +
+
+ + + + + + +
+ + + + + + + + + + + + + + +
IMPORTES
+ Noches: ${len(object.room_lines[0].reservation_lines)}
+ Base imponible: ${object.amount_untaxed} €
+ I.V.A (10%): ${object.amount_tax} €
+ Precio total: ${object.amount_total} €
+ Coste de cancelación: [[importe]]
+
+
+
+
+
+
+ + + + + + + + + +
+ + + + + + +
+ + + + + +
+
+
+ + + + + +
+
+
+ + + + + + +
+ + + + + + + + + + + +
NUESTRAS REDES SOCIALES
+
+
+ + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + Facebook + +
Facebook
+ + + + + + +
+
+
Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.
+ + + + + + +
+ + +
Dale + a Me gusta
+
+ +
+
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + Instagram + +
Instagram
+ + + + + + +
+
+
Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.
+ + + + + + +
+ + +
#Entra +
+
+ +
+
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + Twitter + +
Twitter
+ + + + + + +
+
+
Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.
+ + + + + + +
+ + +
Síguenos +
+
+ +
+
+
+
+
+ + + + + + + + +
¡Esperamos verte + pronto!
+
+ + + + + +
+ Alda Hotels + + + + + +
+
+ +
+
]]>
From ee41732182b730b5bc6387c26d236e156e144526 Mon Sep 17 00:00:00 2001 From: Jose Luis Date: Mon, 18 Mar 2019 19:51:52 +0100 Subject: [PATCH 02/18] [FIX] DateTime problems in templates --- hotel/data/email_template_cancel.xml | 4 +- hotel/data/email_template_reserv.xml | 1930 ++++++++++++++------------ 2 files changed, 1014 insertions(+), 920 deletions(-) diff --git a/hotel/data/email_template_cancel.xml b/hotel/data/email_template_cancel.xml index d4b7eabff..757f77348 100644 --- a/hotel/data/email_template_cancel.xml +++ b/hotel/data/email_template_cancel.xml @@ -279,8 +279,8 @@

- Entrada: ${format_tz(rline['checkin'], format="%d de %B de %Y")}
- Salida: ${format_tz(rline['checkout'], format="%d de %B de %Y")}
+ Entrada: ${format_tz(rline['checkin']+ ' 00:00:00', format="%d de %B de %Y")}
+ Salida: ${format_tz(rline['checkout']+ ' 00:00:00', format="%d de %B de %Y")}
Nº de noches: ${rline['nights']}

% endfor diff --git a/hotel/data/email_template_reserv.xml b/hotel/data/email_template_reserv.xml index 9fff963d4..d2b5ec979 100644 --- a/hotel/data/email_template_reserv.xml +++ b/hotel/data/email_template_reserv.xml @@ -5,942 +5,1036 @@ Confirm Reservation-Send by Email - Confirmación de los detalles de su reserva en ${object.company_id.display_name} + Confirmación de los detalles de su reserva en ${object.company_id.property_name} ${(object.partner_id.id or '')} + /*Global Styles*/ + .marco { + bgcolor: #f6f6f6; + margin: 0; + padding: 0; + min-width: 100% !important; + } + + a { + color: #5e96ea; + text-decoration: none; + font-weight: bold; + } + + img { + height: auto; + } + + .content { + border: 1px solid #eeeeee; + } + + .logo { + font-family: sans-serif; + font-size: 36px; + font-weight: bold; + color: #ffffff; + } + + .link a { + font-family: sans-serif; + font-size: 12px; + color: #ffffff; + } + + .subheading { + font-size: 14px; + color: #cccccc; + font-family: sans-serif; + font-weight: bold; + padding: 0 0 0 0; + text-transform: uppercase; + letter-spacing: 1px; + } + + .h1 { + font-family: sans-serif; + font-size: 48px; + font-weight: bold; + line-height: 56px; + color: #ffffff; + padding: 0 0 0 0; + } + + .h2 { + font-family: sans-serif; + font-size: 18px; + font-weight: bold; + color: #444444; + padding: 0 0 0 0; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .h3 { + font-family: sans-serif; + font-size: 24px; + font-weight: regular; + color: #555555; + padding: 0 0 0 0; + } + + .h4 { + font-family: sans-serif; + font-size: 18px; + font-weight: bold; + color: #666666; + padding: 0 0 0 0; + } + + .paragraph { + font-family: sans-serif; + font-size: 14px; + line-height: 22px; + color: #666666; + font-weight: 200; + padding: 20px 0 0 0; + } + + .listitem { + font-family: sans-serif; + font-size: 15px; + color: #666666; + font-weight: 200; + padding: 0 0 20px 0; + } + + .smalltext { + font-family: sans-serif; + font-size: 14px; + color: #cccccc; + padding: 3px 0 0 0; + } + + .borderbottom { + border-bottom: 1px solid #f2eeed; + } + + /*Media Queries*/ + @media only screen and (max-width: 651px) { + .columns { + width: 100% !important; + } + + .columncontainer { + display: block !important; + width: 100% !important; + } + + .paragraph, + .listitem { + font-size: 18px; + } + + .link { + float: left; + } + } + + @media only screen and (min-width: 651px) { + .content { + width: 650px !important; + } + } +
- - - - -
- - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - - -
- Alda Hotels - - - - -
- - - - -
- - - - - -
-
-
- - - - - - - - - - - - - -
Confirmación de reserva en - ${object.company_id.display_name}
-

__

-
Hola ${object.partner_id.firstname}
- Tu reserva en ${object.company_id.display_name} queda confirmada. Te esperamos el día ${object.room_lines[0].checkin[8:10]} del ${object.room_lines[0].checkin[5:7]} de - ${object.room_lines[0].checkin[0:4]}. Si podemos ayudarte en cualquier tipo de gestión, no dudes en hacérnoslo saber.
-
- - - - -
- -
Contactar -
-
-
-
-
- - - - - -
- - - - -
- - - - - - - - - - - - - -
¿Llegarás más - tarde de las 17:00 horas?
-

__

-
En ese caso te rogamos que te pongas en contacto con nosotros para facilitarte las instrucciones - necesarias.
-
- - - - -
- - -
Contactar -
-
- -
-
-
-
- - - - -
- - - - -
- - - - - - - -
- Servicios
- - - - - - - - - - - - - - - - - -
- Info - Información turística
- Wifi - Wi-Fi gratuito
- Restauracion - Restauración
- Parking - Parking concertado
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - -
- - - - -
- Alda Hotels -
-
- - - - - - -
- - - - - - - - - - - - - - - - - - -
Datos de tu reserva
- ${object.partner_id.name} -
- % if object.partner_id.contact_address: - ${object.partner_id.contact_address}
- % endif - % if object.partner_id.phone: - Tel.: ${object.partner_id.phone}
- % endif - % if object.partner_id.mobile: - Mov.: ${object.partner_id.mobile}
- % endif -
- % for rline in object.get_grouped_reservations_json('confirm'): - - ${rline['num']} x ${rline['room_type']['name']} - % if rline['childrens'] == 0: - (${rline['adults']} Adultos) - % else: - (${rline['adults']} Adultos + ${rline['childrens']} Niños) +
+ + + - - - - - - -
+ + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ + + +
+ Alda Hotels + + + + +
+ + + + +
+ + + + + +
+
+
+ + + + + + + + + + + + + +
Confirmación de reserva + en + ${object.company_id.property_name}
+

__

+
Hola ${object.partner_id.firstname}
+ Tu reserva en ${object.company_id.property_name} queda confirmada. Te esperamos el día ${object.room_lines[0].checkin[8:10]} del ${object.room_lines[0].checkin[5:7]} de + ${object.room_lines[0].checkin[0:4]}. Si podemos ayudarte en cualquier tipo de gestión, no dudes en hacérnoslo saber.
+
+ + + + +
+ +
Contactar +
+
+
+
+
+ + + + + +
+ + + + +
+ + + + + + + + + + + + + +
¿Llegarás más + tarde de las 17:00 horas?
+

__

+
En ese caso te rogamos que te pongas en contacto con nosotros para facilitarte las instrucciones + necesarias.
+
+ + + + +
+ + +
Contactar +
+
+ +
+
+
+
+ + + + +
+ + + + +
+ + + + + + + +
+ Servicios
+ + + + + + + + + + + + + + + + + +
+ Info + Información turística
+ Wifi + Wi-Fi gratuito
+ Restauracion + Restauración
+ Parking + Parking
+
+
+
+
+
+ + + + - -
+ + + + +
+ Alda Hotels +
+
+ + + - -
+ + + + + + + + + - - - -
Datos de tu + reserva
+ ${object.partner_id.name} +
+ % if object.partner_id.contact_address: + ${object.partner_id.contact_address}
+ % endif + % if object.partner_id.phone: + Tel.: ${object.partner_id.phone}
+ % endif + % if object.partner_id.mobile: + Mov.: ${object.partner_id.mobile}
% endif
- - % endfor + % for rline in object.get_grouped_reservations_json('confirm'): + + ${rline['num']} x ${rline['room_type']['name']} + % if rline['childrens'] == 0: + (${rline['adults']} Adultos) + % else: + (${rline['adults']} Adultos + ${rline['childrens']} Niños) + % endif +
+
+ Entrada: ${format_tz(rline['checkin']+ ' 00:00:00', format="%d de %B de %Y")}
+ Salida: ${format_tz(rline['checkout']+ ' 00:00:00', format="%d de %B de %Y")}
+ Nº de noches: ${rline['nights']}

-
+ % endfor - Recuerda que puedas cancelar gratuitamente esta reserva hasta las 12:00 h del día anterior a tu llegada. -
-
-
-
- - - + +
- - - - - - - - + +
Mapa
- - - - - -
- Ver mapa -
-

-
- -

+
+

Información importante

+ En caso de cancelar tu reserva, podrás hacerlo de manera gratuita hasta las 23:59 horas del día anterior a tu llegada. +
Esta reserva está sujeta al pago de la primera noche antes de las 15:00 h del día de entrada. En caso que la tarjeta no sea válida, nos pondremos en contacto contigo para que nos facilites un nuevo método de pago. De no poder verificar correctamente la tarjeta, tu reserva podrá ser cancelada.
+ En reservas con tarifas no reembolsables no se podrá ni cancelar ni modificar, por ello no se reembolsará el dinero. En este tipo de tarifas, se procederá a cargar el importe total de la reserva en el momento de su confirmación, en la tarjeta facilitada. +
+ + +
+
+
+
+ + + - -
+ + + + + + + + - -
Mapa
+ + + + + +
+ Ver mapa
-

- - - - -
- - - - -
- - -
-
-
-
-
- - - - - -
- - - - -
- Importes -
-
- - - - -
- - - - - - - - - - - - -
IMPORTES
- Noches: ${object.get_grouped_reservations_json('confirm')|sum(attribute='nights')} -
- Base imponible: ${object.amount_untaxed} €
- I.V.A (10%): ${object.amount_tax} €
- Precio total: ${object.amount_total} €
-
-
-
-
-
- - - - - -
- - - - -
- Habitacion -
-
- - - - -
-
-
Información de la - habitación
- % set room_type_ids = object.room_lines.filtered('to_send').mapped('room_type_id.id') - % set room_types = user.env['hotel.room.type'].browse(room_type_ids) - % for room_type in room_types: - - % if room_type.product_id.name: -
- ${room_type.product_id.name} -
- % else: -
- Habitación ${loop.index} -
- % endif -
- Esta habitación cuenta con TV, Wi-Fi gratuita, calefacción y baño privado. -
- % endfor -
-
-
-
- - - - - -
- - - - -
- Peticiones -
-
- - - - -
- - - - - - - - - - - - -
Peticiones especiales
- Estamos a tu servicio -
- [[petición]] -
-
-
-
-
- - - - -
- - - - -
- - - - - - - - - - - -
Información - adicional
- - -

__

- -
-
-
-
-
- - - - - -
- - - - -
- Coche -
-
- - - - -
- - - - - - - - - - - - -
Cómo acceder
- En coche -


- Pulse en este enlace, para - conocer como llegar desde su ubicación actual + +

+

- Si vienes en coche, queremos informarte de que nos encontramos en una calle peatonal. Para aparcar, te recomendamos nuestro parking concertado, a tan sólo 3 minutos caminando. Es el - Parking La Salle, en la calle Ramón del Valle Inclán. Tiene un coste de 10€/día por ser cliente de Alda Hotels. -
-
-
-
- - - - - -
- - - - -
- Bus -
-
- - - - -
- - - - - - - - - - - - -
Cómo acceder
- En autobús o tren -
Si vienes en autobús nos encontramos a 15 minutos caminando. Si prefieres coger un bus urbano, te recomendamos las líneas - P1 y P2 y parar en la Praciña das Penas. Desde la parada solo tendrás que caminar 2 minutos.

- Si llegas a la ciudad en tren, puedes coger las líneas de bus C5, C6, 6 o 9, y parar en Praciña das Penas, muy cerca de nuestra ubicación.
-
-
-
-
- - - + + + + + + + + - -
- - - + +
- - - + +
- - - - - - - - - + +
${object.company_id.city}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac lobortis sem. Donec in tincidunt diam, id - ultrices risus. Fusce ultrices posuere lectus vitae commodo.
- - - + +
- - - + +
+
+
+
+
+ + + + + +
+ + + + +
+ Importes +
+
+ + + + +
+ + + + + + + + + + + + +
IMPORTES
+ Noches: ${object.get_grouped_reservations_json('confirm')|sum(attribute='nights')} +
+ Base imponible: ${object.amount_untaxed} €
+ I.V.A (10%): ${object.amount_tax} €
+ Precio total: ${object.amount_total} €
+
+
+
+
+
+ + + + - -
+ + + + +
+ Habitacion +
+
+ + + - -
+
+
Información de la + habitación
+ % set room_type_ids = object.room_lines.filtered('to_send').mapped('room_type_id.id') + % set room_types = user.env['hotel.room.type'].browse(room_type_ids) + % for room_type in room_types: + + % if room_type.product_id.name: +
+ ${room_type.product_id.name} +
+ % else: +
+ Habitación ${loop.index} +
+ % endif +
+ Esta habitación cuenta con TV, Wi-Fi gratuita, calefacción y baño privado. +
+ % endfor
- -
-
-
-
-
- - - - -
- -
-
- - - - -
- -
-
- - - - -
- - - - -
- - - - - - - - - -
${object.company_id.display_name}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ac lobortis sem. Donec in tincidunt - diam, id ultrices risus. Fusce ultrices posuere lectus vitae commodo. Nulla facilisi. Donec condimentum gravida ex et dapibus.
-
-
-
-
- - - - -
- - - - - - - - - -
NUESTRAS REDES SOCIALES
-
-
- - - - - - - - - - -
- - - + - - -
- - - - - - - - - + +
- - Facebook - -
- Facebook
- - - + +
+
+
+
-
Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.
- - - - -
- - -
Dale - a Me gusta
-
- -
-
-
-
- - - - -
- - - - - - - - - - - - - - - - -
- - Instagram - -
- Instagram
- - - - -
-
-
Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.
- - - - -
- - -
#Entra -
-
- -
-
-
-
- - - - -
- - - - - - - - - - - - - - - - -
- - Twitter - -
- Twitter
- - - - -
-
-
Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.
- - - - -
- - -
Síguenos -
-
- -
-
-
-
-
- - - - - - -
¡Muchas gracias por alojarte - con nosotros!
-
- - - -
- Alda Hotels - - - -
-
- +
+ + + + +
+ + + + +
+ + + + + + + + + + + +
Información + adicional
+ + +

__

+ +
+
+
+
+
+ + + + + +
+ + + + +
+ Coche +
+
+ + + + +
+ + + + + + + + + + + + +
Cómo acceder
+ En coche +
+

+
+ Pulse en este enlace, + para + conocer como llegar desde su ubicación actual +

+ +

Si vienes en coche, podrás aparcar en los alrededores del hotel, aunque la zona de playa cuenta con zona azul. También disponemos de parking propio para clientes (bajo disponibilidad).

+
+
+
+
+ + + + + +
+ + + + +
+ Bus +
+
+ + + + +
+ + + + + + + + + + + + +
Cómo acceder
+ En autobús o tren +
Si vienes en autobús podrás venir desde A Coruña en las líneas 20, 22 y 1A, que te dejarán cerca de Ponte da Pasaxe. Desde allí, tendrás unos 15 minutos andando hasta el hotel.
+
+
+
+
+ + + + + + + + + +
+ + + + +
+ + + + +
+ + + + + + + + + + +
${object.company_id.city}
Santa Cristina es un lugar privilegiado. Playas de arena fina y blanca, paisajes impresionantes y unos alrededores dignos de conocer.
+ + + + +
+ + + +
+
+
+
+
+ + + + +
+ +
+
+ + + + +
+ +
+
+ + + + +
+ + + + +
+ + + + + + + + + +
${object.company_id.property_name}
El Hotel Alda Santa Cristina se encuentra a pie de playa, por lo que disfruta de unas vistas impresionantes de la ría de O Burgo. Además, a escasos minutos en coche, podrás estar en el centro de A Coruña.
+
+
+
+
+ + + + +
+ + + + + + + + + +
NUESTRAS REDES SOCIALES
+
+
+ + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + Facebook + +
+ Facebook
+ + + + +
+ +
+
Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.
+ + + + +
+ + +
Dale + a Me gusta
+
+ +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + Instagram + +
+ Instagram
+ + + + +
+ +
+
Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.
+ + + + +
+ + +
#Entra +
+
+ +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + Twitter + +
+ Twitter
+ + + + +
+ +
+
Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.
+ + + + +
+ + +
Síguenos +
+
+ +
+
+
+
+
+ + + + + + +
¡Muchas gracias por + alojarte + con nosotros!
+
+ + + +
+ Alda Hotels + + + +
+
+
- - - - -
- + + + ]]>
From 2ab1663cb2d6ea7603a3e5294aa5bcfbf39a5aa9 Mon Sep 17 00:00:00 2001 From: Jose Luis Date: Wed, 20 Mar 2019 19:30:12 +0100 Subject: [PATCH 03/18] [ADD] Door Codes in Hotels --- hotel_door_codes/README.rst | 13 +++ hotel_door_codes/__init__.py | 22 +++++ hotel_door_codes/__manifest__.py | 47 +++++++++++ hotel_door_codes/data/menus.xml | 13 +++ hotel_door_codes/models/__init__.py | 23 ++++++ .../models/inherit_hotel_reservation.py | 71 ++++++++++++++++ .../models/inherit_res_company.py | 29 +++++++ hotel_door_codes/static/description/icon.png | Bin 0 -> 24484 bytes .../views/inherit_hotel_reservation.xml | 23 ++++++ .../views/inherit_report_viajero.xml | 14 ++++ .../views/inherit_res_company.xml | 18 ++++ hotel_door_codes/wizard/__init__.py | 21 +++++ hotel_door_codes/wizard/door_code.py | 78 ++++++++++++++++++ hotel_door_codes/wizard/door_code.xml | 33 ++++++++ .../views/report_viajero_document.xml | 2 + 15 files changed, 407 insertions(+) create mode 100644 hotel_door_codes/README.rst create mode 100644 hotel_door_codes/__init__.py create mode 100644 hotel_door_codes/__manifest__.py create mode 100644 hotel_door_codes/data/menus.xml create mode 100644 hotel_door_codes/models/__init__.py create mode 100644 hotel_door_codes/models/inherit_hotel_reservation.py create mode 100644 hotel_door_codes/models/inherit_res_company.py create mode 100644 hotel_door_codes/static/description/icon.png create mode 100644 hotel_door_codes/views/inherit_hotel_reservation.xml create mode 100644 hotel_door_codes/views/inherit_report_viajero.xml create mode 100644 hotel_door_codes/views/inherit_res_company.xml create mode 100644 hotel_door_codes/wizard/__init__.py create mode 100644 hotel_door_codes/wizard/door_code.py create mode 100644 hotel_door_codes/wizard/door_code.xml diff --git a/hotel_door_codes/README.rst b/hotel_door_codes/README.rst new file mode 100644 index 000000000..39b189275 --- /dev/null +++ b/hotel_door_codes/README.rst @@ -0,0 +1,13 @@ +DOOR CODES +========== + +Generate HOTEL DOOR CODES + + +Credits +======= + +Creator +------------ + +* Jose Luis Algara Toledo diff --git a/hotel_door_codes/__init__.py b/hotel_door_codes/__init__.py new file mode 100644 index 000000000..d81805226 --- /dev/null +++ b/hotel_door_codes/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018-2019 Jose Luis Algara Toledo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from . import models +from . import wizard diff --git a/hotel_door_codes/__manifest__.py b/hotel_door_codes/__manifest__.py new file mode 100644 index 000000000..299e75b83 --- /dev/null +++ b/hotel_door_codes/__manifest__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, Open Source Management Solution +# Copyright (C) 2018-2019 Jose Luis Algara Toledo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': 'Hotel Door Codes', + 'version': '2.1', + 'author': "Jose Luis Algara Toledo ", + 'website': 'https://www.aldahotels.com', + 'category': 'hotel code', + 'summary': "Generate Hotel door codes, in Pseudo random system", + 'description': "Hotel Door Codes", + 'depends': [ + 'hotel', 'hotel_l10n_es' + ], + 'data': [ + 'wizard/door_code.xml', + 'data/menus.xml', + 'views/inherit_res_company.xml', + 'views/inherit_hotel_reservation.xml', + 'views/inherit_report_viajero.xml', + ], + 'qweb': [], + 'test': [ + ], + 'installable': True, + 'auto_install': False, + 'application': False, + 'license': 'AGPL-3', +} diff --git a/hotel_door_codes/data/menus.xml b/hotel_door_codes/data/menus.xml new file mode 100644 index 000000000..ce3b25d84 --- /dev/null +++ b/hotel_door_codes/data/menus.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/hotel_door_codes/models/__init__.py b/hotel_door_codes/models/__init__.py new file mode 100644 index 000000000..71658e650 --- /dev/null +++ b/hotel_door_codes/models/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018-2019 Alda Hotels +# Jose Luis Algara +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from . import inherit_res_company +from . import inherit_hotel_reservation diff --git a/hotel_door_codes/models/inherit_hotel_reservation.py b/hotel_door_codes/models/inherit_hotel_reservation.py new file mode 100644 index 000000000..0ed077f4f --- /dev/null +++ b/hotel_door_codes/models/inherit_hotel_reservation.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018-2019 Alda Hotels +# Jose Luis Algara +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp import models, fields, api +from datetime import datetime, date, time, timedelta +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT + + +class Inherit_hotel_reservation(models.Model): + _inherit = 'hotel.reservation' + + @api.multi + def doorcode4(self, fecha): + # Calculate de Door Code... need a date in String format "%Y-%m-%d" + compan = self.env.user.company_id + d = datetime.strptime(fecha, DEFAULT_SERVER_DATE_FORMAT) + dia_semana = datetime.weekday(d) # Dias a restar y ponerlo en lunes + d = d - timedelta(days=dia_semana) + dtxt = d.strftime('%s.%%06d') % d.microsecond + dtxt = compan.precode + dtxt[4:8] + compan.postcode + return dtxt + + @api.multi + def _compute_door_codes(self): + for res in self: + entrada = datetime.strptime( + res.checkin[:10], DEFAULT_SERVER_DATE_FORMAT) + if datetime.weekday(entrada) == 0: + entrada = entrada + timedelta(days=1) + salida = datetime.strptime( + res.checkout[:10], DEFAULT_SERVER_DATE_FORMAT) + if datetime.weekday(salida) == 0: + salida = salida - timedelta(days=1) + codes = (u'Código de entrada: ' + + '' + + res.doorcode4(datetime.strftime(entrada, "%Y-%m-%d")) + + '') + while entrada <= salida: + if datetime.weekday(entrada) == 0: + codes += ("
" + + u'Cambiará el Lunes ' + + datetime.strftime(entrada, "%d-%m-%Y") + + ' a: ' + + res.doorcode4(datetime.strftime( + entrada, "%Y-%m-%d")) + + '') + entrada = entrada + timedelta(days=1) + res.door_codes = codes + + door_codes = fields.Html(u'Códigos de entrada', + compute='_compute_door_codes') + box_number = fields.Integer ('Numero de Caja') + box_code = fields.Char ('Cod. Caja') diff --git a/hotel_door_codes/models/inherit_res_company.py b/hotel_door_codes/models/inherit_res_company.py new file mode 100644 index 000000000..181d6424c --- /dev/null +++ b/hotel_door_codes/models/inherit_res_company.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018-2019 Alda Hotels +# Jose Luis Algara +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp import models, fields, api + + +class Inherit_res_company(models.Model): + _inherit = 'res.company' + + precode = fields.Char('Characters before the door code', default='') + postcode = fields.Char('Characters after the code', default='') diff --git a/hotel_door_codes/static/description/icon.png b/hotel_door_codes/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0d7c99f06fafe4f012cab618e056e8652ec140d5 GIT binary patch literal 24484 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 KjTy_-E#^N^!qF`N literal 0 HcmV?d00001 diff --git a/hotel_door_codes/views/inherit_hotel_reservation.xml b/hotel_door_codes/views/inherit_hotel_reservation.xml new file mode 100644 index 000000000..3c236a4e4 --- /dev/null +++ b/hotel_door_codes/views/inherit_hotel_reservation.xml @@ -0,0 +1,23 @@ + + + + + + + door_code.reservation_form + hotel.reservation + + + + + + + + + + + + + + + diff --git a/hotel_door_codes/views/inherit_report_viajero.xml b/hotel_door_codes/views/inherit_report_viajero.xml new file mode 100644 index 000000000..0e1257ddf --- /dev/null +++ b/hotel_door_codes/views/inherit_report_viajero.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/hotel_door_codes/views/inherit_res_company.xml b/hotel_door_codes/views/inherit_res_company.xml new file mode 100644 index 000000000..b1638a40d --- /dev/null +++ b/hotel_door_codes/views/inherit_res_company.xml @@ -0,0 +1,18 @@ + + + + + + + door_code.config.view_company_form + res.company + + + + + + + + + + diff --git a/hotel_door_codes/wizard/__init__.py b/hotel_door_codes/wizard/__init__.py new file mode 100644 index 000000000..180966b56 --- /dev/null +++ b/hotel_door_codes/wizard/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018-2019 Jose Luis Algara Toledo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from . import door_code diff --git a/hotel_door_codes/wizard/door_code.py b/hotel_door_codes/wizard/door_code.py new file mode 100644 index 000000000..334be679a --- /dev/null +++ b/hotel_door_codes/wizard/door_code.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Odoo, Open Source Management Solution +# Copyright (C) 2018-2019 Jose Luis Algara Toledo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +import datetime +from datetime import datetime, date, time, timedelta +from odoo import api, fields, models, _ +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT + + +class DoorCodeWizard(models.TransientModel): + _name = 'door_code' + + @api.model + def _get_default_date_start(self): + return datetime.now().strftime(DEFAULT_SERVER_DATE_FORMAT) + + date_start = fields.Date("Inicio periodo", + default=_get_default_date_start) + date_end = fields.Date("Fin del periodo", + default=_get_default_date_start) + door_code = fields.Html(u'Código para la puerta') + + @api.multi + def doorcode4(self, fecha): + # Calculate de Door Code... need a date in String format "%Y-%m-%d" + compan = self.env.user.company_id + d = datetime.strptime(fecha, DEFAULT_SERVER_DATE_FORMAT) + dia_semana = datetime.weekday(d) # Dias a restar y ponerlo en lunes + d = d - timedelta(days=dia_semana) + dtxt = d.strftime('%s.%%06d') % d.microsecond + dtxt = compan.precode + dtxt[4:8] + compan.postcode + return dtxt + + @api.multi + def check_code(self): + entrada = datetime.strptime( + self.date_start, DEFAULT_SERVER_DATE_FORMAT) + if datetime.weekday(entrada) == 0: + entrada = entrada + timedelta(days=1) + salida = datetime.strptime( + self.date_end, DEFAULT_SERVER_DATE_FORMAT) + if datetime.weekday(salida) == 0: + salida = salida - timedelta(days=1) + codes = (u'Código de entrada: ' + + '' + + self.doorcode4(self.date_start) + + '') + while entrada <= salida: + if datetime.weekday(entrada) == 0: + codes += ("
" + + u'Cambiará el Lunes ' + + datetime.strftime(entrada, "%d-%m-%Y") + + ' a: ' + + self.doorcode4(datetime.strftime( + entrada, "%Y-%m-%d")) + + '') + entrada = entrada + timedelta(days=1) + + return self.write({ + 'door_code': codes + }) diff --git a/hotel_door_codes/wizard/door_code.xml b/hotel_door_codes/wizard/door_code.xml new file mode 100644 index 000000000..f0506a79c --- /dev/null +++ b/hotel_door_codes/wizard/door_code.xml @@ -0,0 +1,33 @@ + + + + + door_code.view + door_code + +
+ + + + + + + + + + +
+
+
+
+
+
+ +
+
diff --git a/hotel_l10n_es/views/report_viajero_document.xml b/hotel_l10n_es/views/report_viajero_document.xml index e3b6f194f..e66c18ce7 100755 --- a/hotel_l10n_es/views/report_viajero_document.xml +++ b/hotel_l10n_es/views/report_viajero_document.xml @@ -56,6 +56,8 @@ Asimismo hemos solicitado que confirme esta autorización para ofrecerle nuestros servicios y poder fidelizarle como cliente.

+
+
From b9c1159b37b623870e997f55ee9cbbb0f321a2e6 Mon Sep 17 00:00:00 2001 From: Jose Luis Date: Fri, 22 Mar 2019 19:14:25 +0100 Subject: [PATCH 04/18] [ADD] Gender control and partner link in police generator error --- hotel_l10n_es/wizard/police_wizard.py | 8 ++++++-- hotel_l10n_es/wizard/police_wizard.xml | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/hotel_l10n_es/wizard/police_wizard.py b/hotel_l10n_es/wizard/police_wizard.py index 4ee0a847e..d8e85b638 100755 --- a/hotel_l10n_es/wizard/police_wizard.py +++ b/hotel_l10n_es/wizard/police_wizard.py @@ -37,6 +37,7 @@ class PoliceWizard(models.TransientModel): txt_binary = fields.Binary() txt_message = fields.Char() log_police = fields.Char() + error_partner = fields.Many2one('res.partner') @api.one def generate_file(self): @@ -56,6 +57,7 @@ class PoliceWizard(models.TransientModel): if ((line.partner_id.document_type is not False) and (line.partner_id.document_number is not False) and (line.partner_id.firstname is not False) + and (line.partner_id.gender is not False) and (line.partner_id.lastname is not False)): log_police += 1 @@ -101,11 +103,13 @@ class PoliceWizard(models.TransientModel): content += """ """ else: + self.error_partner = line.partner_id + return self.write({ + 'error_partner': line.partner_id.id, 'txt_message': _('Problem generating the file. \ Checkin without data, \ - or incorrect data: - ' + - line.partner_id.name)}) + or incorrect data: ')}) log_police = str(log_police) + _(' records added from ') log_police += str(len(lines)) + _(' records processed.') return self.write({ diff --git a/hotel_l10n_es/wizard/police_wizard.xml b/hotel_l10n_es/wizard/police_wizard.xml index 0fa0e002d..34ddf55a4 100755 --- a/hotel_l10n_es/wizard/police_wizard.xml +++ b/hotel_l10n_es/wizard/police_wizard.xml @@ -26,7 +26,7 @@

-

+

From bedde6b8a6baf2bb0f309efa30b1b294eaed6a51 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Sat, 23 Mar 2019 11:41:07 +0100 Subject: [PATCH 05/18] [FIX] compute tax_ids missing fields --- hotel/models/hotel_reservation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index fa2fcb7df..af6233398 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -488,7 +488,7 @@ class HotelReservation(models.Model): def _prepare_add_missing_fields(self, values): """ Deduce missing required fields from the onchange """ res = {} - onchange_fields = ['room_id', 'reservation_type', + onchange_fields = ['room_id', 'reservation_type', 'tax_ids', 'currency_id', 'name', 'service_ids'] if values.get('room_type_id'): line = self.new(values) From e73be563d2118fb2477d7909c682fbc137df8b14 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Sat, 23 Mar 2019 15:51:24 +0100 Subject: [PATCH 06/18] [ADD] Pricelist cancelled rules --- hotel/__manifest__.py | 2 + hotel/models/__init__.py | 1 + hotel/models/hotel_cancelation_rule.py | 30 ++++++ hotel/models/hotel_reservation.py | 102 ++++++++++++++++-- hotel/models/hotel_reservation_line.py | 4 + hotel/models/inherited_product_pricelist.py | 3 + hotel/views/hotel_cancelation_rule_views.xml | 63 +++++++++++ hotel/views/hotel_folio_views.xml | 2 +- hotel/views/hotel_reservation_views.xml | 31 +++--- .../inherited_product_pricelist_views.xml | 17 +++ .../inherited_product_pricelist_views.xml | 4 - 11 files changed, 230 insertions(+), 29 deletions(-) create mode 100644 hotel/models/hotel_cancelation_rule.py create mode 100644 hotel/views/hotel_cancelation_rule_views.xml create mode 100644 hotel/views/inherited_product_pricelist_views.xml diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index dd9d641b8..161a55904 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -41,6 +41,7 @@ 'views/hotel_room_type_class_views.xml', 'views/general.xml', 'views/inherited_product_template_views.xml', + 'views/inherited_product_pricelist_views.xml', 'views/hotel_room_amenities_type_views.xml', 'views/hotel_room_amenities_views.xml', 'views/hotel_room_type_restriction_views.xml', @@ -52,6 +53,7 @@ 'views/hotel_board_service_views.xml', 'views/hotel_checkin_partner_views.xml', 'views/hotel_board_service_room_type_views.xml', + 'views/hotel_cancelation_rule_views.xml', 'data/cron_jobs.xml', 'data/records.xml', 'data/email_template_cancel.xml', diff --git a/hotel/models/__init__.py b/hotel/models/__init__.py index dbb314957..ae46acfd3 100644 --- a/hotel/models/__init__.py +++ b/hotel/models/__init__.py @@ -31,3 +31,4 @@ from . import hotel_board_service from . import hotel_board_service_room_type_line from . import hotel_board_service_line from . import inherited_account_invoice_line +from . import hotel_cancelation_rule diff --git a/hotel/models/hotel_cancelation_rule.py b/hotel/models/hotel_cancelation_rule.py new file mode 100644 index 000000000..0415d3b56 --- /dev/null +++ b/hotel/models/hotel_cancelation_rule.py @@ -0,0 +1,30 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models, fields + + +class HotelCancelationRule(models.Model): + _name = 'hotel.cancelation.rule' + _description = 'Cancelation Rules' + + name = fields.Char('Amenity Name', translate=True, required=True) + active = fields.Boolean('Active', default=True) + pricelist_ids = fields.One2many('product.pricelist', + 'cancelation_rule_id', + 'Pricelist that use this rule') + days_intime = fields.Integer( + 'Days Late', + help='Maximum number of days for free cancellation before Checkin') + penalty_late = fields.Integer('% Penalty Late', defaul="100") + apply_on_late = fields.Selection([ + ('first', 'First Day'), + ('all', 'All Days'), + ('days', 'Specify days')], 'Late apply on', default='first') + days_late = fields.Integer('Late first days', default="2") + penalty_noshow = fields.Integer('% Penalty No Show', default="100") + apply_on_noshow = fields.Selection([ + ('first', 'First Day'), + ('all', 'All Days'), + ('days', 'Specify days')], 'No Show apply on', default='all') + days_noshow = fields.Integer('NoShow first days', default="2") diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index af6233398..662d9f872 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -170,12 +170,15 @@ class HotelReservation(models.Model): track_visibility='onchange', help='Number of children there in guest list.') to_assign = fields.Boolean('To Assign', track_visibility='onchange') - state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'), - ('booking', 'On Board'), ('done', 'Out'), - ('cancelled', 'Cancelled')], - 'State', readonly=True, - default=lambda *a: 'draft', - track_visibility='onchange') + state = fields.Selection([ + ('draft', 'Pre-reservation'), + ('confirm', 'Pending Entry'), + ('booking', 'On Board'), + ('done', 'Out'), + ('cancelled', 'Cancelled') + ], string='State', readonly=True, + default=lambda *a: 'draft', copy=False, + track_visibility='onchange') reservation_type = fields.Selection(related='folio_id.reservation_type', default=lambda *a: 'normal') invoice_count = fields.Integer(related='folio_id.invoice_count') @@ -635,6 +638,11 @@ class HotelReservation(models.Model): write_vals.update({'room_type_id': self.room_id.room_type_id.id}) self.update(write_vals) + @api.onchange('cancelled_reason') + def onchange_cancelled_reason(self): + for record in self: + record._compute_cancelled_discount() + @api.onchange('partner_id') def onchange_partner_id(self): addr = self.partner_id.address_get(['invoice']) @@ -815,6 +823,9 @@ class HotelReservation(models.Model): else: vals.update({'state': 'confirm'}) record.write(vals) + record.reservation_line_ids.update({ + 'cancel_discount': 0 + }) if record.splitted: master_reservation = record.parent_reservation or record @@ -846,7 +857,9 @@ class HotelReservation(models.Model): for record in self: record.write({ 'state': 'cancelled', + 'cancelled_reason': record.compute_cancelation_reason() }) + record._compute_cancelled_discount() if record.splitted: master_reservation = record.parent_reservation or record splitted_reservs = self.env['hotel.reservation'].search([ @@ -861,6 +874,25 @@ class HotelReservation(models.Model): splitted_reservs.action_cancel() record.folio_id.compute_amount() + @api.multi + def compute_cancelation_reason(self): + self.ensure_one() + pricelist = self.pricelist_id + if pricelist and pricelist.cancelation_rule_id: + tz_hotel = self.env['ir.default'].sudo().get( + 'res.config.settings', 'tz_hotel') + today = fields.Date.context_today(self.with_context( + tz=tz_hotel)) + days_diff = (fields.Date.from_string(self.real_checkin) - + fields.Date.from_string(today)).days + if days_diff < 0: + return 'noshow' + elif days_diff < pricelist.cancelation_rule_id.days_intime: + return 'late' + else: + return 'intime' + return False + @api.multi def draft(self): for record in self: @@ -904,11 +936,17 @@ class HotelReservation(models.Model): return True return False - @api.depends('reservation_line_ids.discount') + @api.depends('reservation_line_ids.discount', + 'reservation_line_ids.cancel_discount') def _compute_discount(self): for record in self: - record.discount = sum(line.price * ((line.discount or 0.0) * 0.01) \ - for line in record.reservation_line_ids) + discount = 0 + for line in record.reservation_line_ids: + first_discount = line.price * ((line.discount or 0.0) * 0.01) + price = line.price - first_discount + cancel_discount = price * ((line.cancel_discount or 0.0) * 0.01) + discount += first_discount + cancel_discount + record.discount = discount @api.depends('reservation_line_ids.price', 'discount', 'tax_ids') def _compute_amount_reservation(self): @@ -927,6 +965,51 @@ class HotelReservation(models.Model): 'price_subtotal': taxes['total_excluded'], }) + @api.multi + def _compute_cancelled_discount(self): + self.ensure_one() + pricelist = self.pricelist_id + if self.state == 'cancelled': + if self.cancelled_reason and pricelist and pricelist.cancelation_rule_id: + date_start_dt = fields.Date.from_string(self.real_checkin or self.checkin) + date_end_dt = fields.Date.from_string(self.real_checkout or self.checkout) + days = abs((date_end_dt - date_start_dt).days) + rule = pricelist.cancelation_rule_id + if self.cancelled_reason == 'late': + discount = 100 - rule.penalty_late + if rule.apply_on_late == 'first': + days = 1 + elif rule.apply_on_late == 'days': + days = rule.days_late + elif self.cancelled_reason == 'noshow': + discount = 100 - rule.penalty_noshow + if rule.apply_on_noshow == 'first': + days = 1 + elif rule.apply_on_noshow == 'days': + days = rule.days_late - 1 + elif self.cancelled_reason == 'intime': + discount = 100 + + checkin = self.real_checkin or self.checkin + dates = [] + for i in range(0, days): + dates.append((fields.Date.from_string(checkin) + timedelta(days=i)).strftime( + DEFAULT_SERVER_DATE_FORMAT)) + self.reservation_line_ids.filtered(lambda r: r.date in dates).update({ + 'cancel_discount': discount + }) + self.reservation_line_ids.filtered(lambda r: r.date not in dates).update({ + 'cancel_discount': 100 + }) + else: + self.reservation_line_ids.update({ + 'cancel_discount': 0 + }) + else: + self.reservation_line_ids.update({ + 'cancel_discount': 0 + }) + @api.model def prepare_reservation_lines(self, dfrom, days, pricelist_id, vals=False, update_old_prices=False): total_price = 0.0 @@ -1164,7 +1247,6 @@ class HotelReservation(models.Model): 'price_total': tprice[1], 'parent_reservation': parent_res.id, 'room_type_id': parent_res.room_type_id.id, - 'discount': parent_res.discount, 'state': parent_res.state, 'reservation_line_ids': reservation_lines[1], 'preconfirm': False, diff --git a/hotel/models/hotel_reservation_line.py b/hotel/models/hotel_reservation_line.py index 11432fe80..afce0fe0f 100644 --- a/hotel/models/hotel_reservation_line.py +++ b/hotel/models/hotel_reservation_line.py @@ -22,9 +22,13 @@ class HotelReservationLine(models.Model): ondelete='cascade', required=True, copy=False) date = fields.Date('Date') + state = fields.Selection(related='reservation_id.state') price = fields.Float( string='Price', digits=dp.get_precision('Product Price')) + cancel_discount = fields.Float( + string='Cancel Discount (%)', + digits=dp.get_precision('Discount'), default=0.0) discount = fields.Float( string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) diff --git a/hotel/models/inherited_product_pricelist.py b/hotel/models/inherited_product_pricelist.py index cae65df63..2296f9855 100644 --- a/hotel/models/inherited_product_pricelist.py +++ b/hotel/models/inherited_product_pricelist.py @@ -11,6 +11,9 @@ class ProductPricelist(models.Model): pricelist_type = fields.Selection([ ('daily', 'Daily Plan'), ], string='Pricelist Type', default='daily') + cancelation_rule_id = fields.Many2one( + 'hotel.cancelation.rule', + string="Cancelation Policy") @api.multi @api.depends('name') diff --git a/hotel/views/hotel_cancelation_rule_views.xml b/hotel/views/hotel_cancelation_rule_views.xml new file mode 100644 index 000000000..d765373bc --- /dev/null +++ b/hotel/views/hotel_cancelation_rule_views.xml @@ -0,0 +1,63 @@ + + + + + + + hotel.cancelation.rule.form + hotel.cancelation.rule + +
+ +

+ + +

+ + + + + + + + + + + + +
+
+
+
+ + + + hotel.cancelation.rule.tree + hotel.cancelation.rule + + + + + + + + + + + + + + + Cancelation Rules + hotel.cancelation.rule + form + tree,form + + + + +
diff --git a/hotel/views/hotel_folio_views.xml b/hotel/views/hotel_folio_views.xml index 9dbbd7d5c..61086d516 100644 --- a/hotel/views/hotel_folio_views.xml +++ b/hotel/views/hotel_folio_views.xml @@ -34,7 +34,7 @@ + statusbar_visible="draft,sent,sale,done" /> diff --git a/hotel/views/hotel_reservation_views.xml b/hotel/views/hotel_reservation_views.xml index d50458d53..871e6f252 100644 --- a/hotel/views/hotel_reservation_views.xml +++ b/hotel/views/hotel_reservation_views.xml @@ -16,7 +16,7 @@ hotel.reservation 20 -
+
@@ -61,21 +61,21 @@ attrs="{'invisible':[('splitted', '=', False)]}" />
- - - + + + +
-

From 44767febe35ece3fdf0e73288d9e1cd990e6d801 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 28 Mar 2019 11:56:27 +0100 Subject: [PATCH 08/18] [FIX] align days of the week in two lines --- hotel/wizard/massive_changes.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hotel/wizard/massive_changes.xml b/hotel/wizard/massive_changes.xml index 7071b9320..88b3483a5 100644 --- a/hotel/wizard/massive_changes.xml +++ b/hotel/wizard/massive_changes.xml @@ -16,13 +16,13 @@ - - - - - - - + + + + + + + From b97802aeb0bb233eb95f0e575fd6a495fdd36740 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 28 Mar 2019 16:11:06 +0100 Subject: [PATCH 09/18] [FIX] use field date in restriction.item --- hotel/wizard/massive_changes.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/hotel/wizard/massive_changes.py b/hotel/wizard/massive_changes.py index 8facef563..8e6065e44 100644 --- a/hotel/wizard/massive_changes.py +++ b/hotel/wizard/massive_changes.py @@ -26,8 +26,7 @@ class MassiveChangesWizard(models.TransientModel): ('0', 'Global'), ('1', 'Room Type'), ], string='Applied On', default='0') - # room_type_ids = fields.Many2many('hotel.virtual.room', - # string="Virtual Rooms") + room_type_ids = fields.Many2many('hotel.room.type', string="Room Types") # Restriction fields @@ -157,11 +156,10 @@ class MassiveChangesWizard(models.TransientModel): def _save_restrictions(self, ndate, room_types, record): hotel_room_type_re_it_obj = self.env['hotel.room.type.restriction.item'] domain = [ - ('date_start', '>=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)), - ('date_end', '<=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)), + ('date', '>=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)), + ('date', '<=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)), ('restriction_id', '=', record.restriction_id.id), ] - for room_type in room_types: vals = self._get_restrictions_values(record) if not any(vals): @@ -173,8 +171,7 @@ class MassiveChangesWizard(models.TransientModel): rrest_item_ids.write(vals) else: vals.update({ - 'date_start': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT), - 'date_end': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT), + 'date': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT), 'restriction_id': record.restriction_id.id, 'room_type_id': room_type.id, }) From c2922f6cfa02aa6264636261c6c163b3ecca9d33 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 28 Mar 2019 16:14:17 +0100 Subject: [PATCH 10/18] [UPD] update import from odoo --- hotel/wizard/massive_changes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hotel/wizard/massive_changes.py b/hotel/wizard/massive_changes.py index 8e6065e44..4a78a000d 100644 --- a/hotel/wizard/massive_changes.py +++ b/hotel/wizard/massive_changes.py @@ -1,8 +1,8 @@ # Copyright 2017 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import timedelta -from openerp import models, fields, api -from openerp.tools import DEFAULT_SERVER_DATE_FORMAT +from odoo import models, fields, api +from odoo.tools import DEFAULT_SERVER_DATE_FORMAT class MassiveChangesWizard(models.TransientModel): From 3d34ab2f2c768047caa878a66c1f1ff724eac53e Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 28 Mar 2019 16:57:02 +0100 Subject: [PATCH 11/18] [UPD] Update fields layout --- .../wizard/inherited_massive_changes.xml | 33 +++++++------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/hotel_channel_connector/wizard/inherited_massive_changes.xml b/hotel_channel_connector/wizard/inherited_massive_changes.xml index 4377dd910..99c0111a5 100644 --- a/hotel_channel_connector/wizard/inherited_massive_changes.xml +++ b/hotel_channel_connector/wizard/inherited_massive_changes.xml @@ -7,27 +7,18 @@ - - - - - - - - - - - - - - - - - - - - -
Max. Avail.
Quota
No OTA
+ + + + + + + + +
From a5b3ec8f57079c04ce62ef9f072552661913fc2c Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 28 Mar 2019 18:49:41 +0100 Subject: [PATCH 12/18] [IMP] Update fields layout and UX --- hotel/wizard/massive_changes.xml | 14 +++++++------- .../wizard/inherited_massive_changes.xml | 15 +++++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/hotel/wizard/massive_changes.xml b/hotel/wizard/massive_changes.xml index 88b3483a5..0163ec07c 100644 --- a/hotel/wizard/massive_changes.xml +++ b/hotel/wizard/massive_changes.xml @@ -16,13 +16,13 @@ - - - - - - - + + + + + + + diff --git a/hotel_channel_connector/wizard/inherited_massive_changes.xml b/hotel_channel_connector/wizard/inherited_massive_changes.xml index 99c0111a5..964b3935c 100644 --- a/hotel_channel_connector/wizard/inherited_massive_changes.xml +++ b/hotel_channel_connector/wizard/inherited_massive_changes.xml @@ -8,17 +8,20 @@ - + + attrs="{'readonly':[('change_max_avail', '=', False)], + 'invisible':[('change_max_avail', '=', False)]}"/> - + + attrs="{'readonly':[('change_quota', '=', False)], + 'invisible':[('change_quota', '=', False)]}"/> - + + attrs="{'readonly':[('change_no_ota', '=', False)], + 'invisible':[('change_no_ota', '=', False)]}"/>
From 8fc032aca2533788cbbc800fd1bf91caaed69352 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 28 Mar 2019 18:50:22 +0100 Subject: [PATCH 13/18] [FIX] task_54 wrong default values in Availability --- .../wizard/inherited_massive_changes.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/hotel_channel_connector/wizard/inherited_massive_changes.py b/hotel_channel_connector/wizard/inherited_massive_changes.py index c6d56e7c2..d1aa6cf38 100644 --- a/hotel_channel_connector/wizard/inherited_massive_changes.py +++ b/hotel_channel_connector/wizard/inherited_massive_changes.py @@ -13,15 +13,26 @@ class MassiveChangesWizard(models.TransientModel): # Availability fields change_quota = fields.Boolean(default=False) - quota = fields.Integer('Quota', default=-1) + quota = fields.Integer('Quota', default=0) change_max_avail = fields.Boolean(default=False) - max_avail = fields.Integer('Max. Avail.', default=-1) + max_avail = fields.Integer('Max. Avail.', default=0) change_no_ota = fields.Boolean(default=False) no_ota = fields.Boolean('No OTA', default=False) @api.model def _get_availability_values(self, ndate, room_type, record): - vals = {} + room_type_avail = self.env['hotel.room.type.availability'].search([ + ('date', '=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)), + ('room_type_id', '=', room_type.id) + ]) + channel_room_type = self.env['channel.hotel.room.type'].search([ + ('odoo_id', '=', room_type.id) + ]) or None + vals = { + 'quota': room_type_avail.quota or channel_room_type.default_quota, + 'max_avail': room_type_avail.max_avail or channel_room_type.default_max_avail, + 'no_ota': room_type_avail.no_ota or channel_room_type.default_max_avail, + } if record.change_quota: vals.update({ 'quota': record.quota, From 389621990acd06d7f0c8442cdc6001361c26c5c5 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 28 Mar 2019 20:18:16 +0100 Subject: [PATCH 14/18] [FIX] Hot Fix set channel_pushed = False for no_ota This Fix works because no_ota is a Boolean field. Therefore, if `no_ota` is in fields_checked it is because its value has changed from `True` to `False` or from `False` to `True`. Regardless other fields, a push_availability is mandatory --- .../models/hotel_room_type_availability/common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hotel_channel_connector/models/hotel_room_type_availability/common.py b/hotel_channel_connector/models/hotel_room_type_availability/common.py index bf83eb3b4..d06b393ee 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -222,6 +222,9 @@ class BindingHotelRoomTypeAvailabilityListener(Component): binding.backend_id.id, room_type_id=record.room_type_id.id) + if 'no_ota' in fields_checked: + binding.write({'channel_pushed': False}) + @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_create(self, record, fields=None): if not any(record.channel_bind_ids): @@ -261,7 +264,7 @@ class ChannelBindingHotelRoomTypeAvailabilityListener(Component): @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_write(self, record, fields=None): - fields_to_check = ('date', 'channel_avail') # no_ota ¿? + fields_to_check = ('date', 'channel_avail') fields_checked = [elm for elm in fields_to_check if elm in fields] _logger.info("==[on_record_write] :: channel.hotel.room.type.availability==") From 13ec4a1d7b12d889eae594c96a70e6b22d90d1e2 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Fri, 29 Mar 2019 13:48:50 +0100 Subject: [PATCH 15/18] [ADD] Call Center Report --- call_center_report/README.rst | 6 + call_center_report/__init__.py | 21 ++ call_center_report/__manifest__.py | 48 +++ call_center_report/data/menus.xml | 11 + call_center_report/i18n/es.po | 171 +++++++++ call_center_report/wizard/__init__.py | 21 ++ .../wizard/call_center_report.py | 349 ++++++++++++++++++ .../wizard/call_center_report.xml | 42 +++ 8 files changed, 669 insertions(+) create mode 100644 call_center_report/README.rst create mode 100644 call_center_report/__init__.py create mode 100644 call_center_report/__manifest__.py create mode 100644 call_center_report/data/menus.xml create mode 100644 call_center_report/i18n/es.po create mode 100644 call_center_report/wizard/__init__.py create mode 100644 call_center_report/wizard/call_center_report.py create mode 100644 call_center_report/wizard/call_center_report.xml diff --git a/call_center_report/README.rst b/call_center_report/README.rst new file mode 100644 index 000000000..ec06d16ac --- /dev/null +++ b/call_center_report/README.rst @@ -0,0 +1,6 @@ +CALL CENTER REPORT +============= + +Export call center report in xls format + + diff --git a/call_center_report/__init__.py b/call_center_report/__init__.py new file mode 100644 index 000000000..351d1ee57 --- /dev/null +++ b/call_center_report/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018 Alexandre Díaz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from . import wizard diff --git a/call_center_report/__manifest__.py b/call_center_report/__manifest__.py new file mode 100644 index 000000000..8204bbb7e --- /dev/null +++ b/call_center_report/__manifest__.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018 Alexandre Díaz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': 'Call Center Report', + 'version': '1.0', + 'author': "Dario Lodeiros", + 'website': 'https://www.eiqui.com', + 'category': 'reports', + 'summary': "Export services and reservation report in xls format", + 'description': "Call Center Report", + 'depends': [ + 'hotel', + ], + 'external_dependencies': { + 'python': ['xlsxwriter'] + }, + 'data': [ + 'wizard/call_center_report.xml', + 'data/menus.xml', + ], + 'qweb': [], + 'test': [ + ], + + 'installable': True, + 'auto_install': False, + 'application': False, + 'license': 'AGPL-3', +} diff --git a/call_center_report/data/menus.xml b/call_center_report/data/menus.xml new file mode 100644 index 000000000..d48c886eb --- /dev/null +++ b/call_center_report/data/menus.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/call_center_report/i18n/es.po b/call_center_report/i18n/es.po new file mode 100644 index 000000000..0f9b0fa66 --- /dev/null +++ b/call_center_report/i18n/es.po @@ -0,0 +1,171 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cash_daily_report +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-05-20 07:33+0000\n" +"PO-Revision-Date: 2018-05-20 09:34+0200\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"Language: es\n" +"X-Generator: Poedit 1.8.7.1\n" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:85 +#, python-format +msgid "Amount" +msgstr "Importe" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:78 +#: model:ir.ui.view,arch_db:cash_daily_report.view_cash_daily_report_wizard +#, python-format +msgid "Cash Daily Report" +msgstr "Informe de caja" + +#. module: cash_daily_report +#: model:ir.actions.act_window,name:cash_daily_report.action_open_cash_daily_report_wizard +#: model:ir.ui.menu,name:cash_daily_report.cash_daily_report_wizard +msgid "Cash Daily Report Wizard" +msgstr "Informe de caja diaria" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:82 +#, python-format +msgid "Client" +msgstr "Cliente" + +#. module: cash_daily_report +#: model:ir.ui.view,arch_db:cash_daily_report.view_cash_daily_report_wizard +msgid "Close" +msgstr "Cerrar" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_create_date +msgid "Created on" +msgstr "Creado en" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:83 +#, python-format +msgid "Date" +msgstr "Fecha" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_date_end +msgid "End Date" +msgstr "Fecha finalización" + +#. module: cash_daily_report +#: model:ir.ui.view,arch_db:cash_daily_report.view_cash_daily_report_wizard +msgid "Generate XLS" +msgstr "Generar XLS" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_id +msgid "ID" +msgstr "ID" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:84 +#, python-format +msgid "Journal" +msgstr "Diario" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard___last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:80 +#, python-format +msgid "Name" +msgstr "Nombre" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:128 +#, python-format +msgid "Not Any Payments" +msgstr "No hay movimientos" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:81 +#, python-format +msgid "Reference" +msgstr "Referencia" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_date_start +msgid "Start Date" +msgstr "Fecha de inicio" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:143 +#, python-format +msgid "TOTAL" +msgstr "TOTAL" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:139 +#, python-format +msgid "TOTAL PAYMENT RETURNS" +msgstr "TOTAL DEVOLUCIONES" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:134 +#, python-format +msgid "TOTAL PAYMENTS" +msgstr "TOTAL PAGOS" + +#. module: cash_daily_report +#: model:ir.ui.menu,name:cash_daily_report.menu_account_finance_xls_reports +msgid "XLS Reports" +msgstr "XLS Reports" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_xls_binary +msgid "Xls binary" +msgstr "Xls archivo" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_xls_filename +msgid "Xls filename" +msgstr "Xls nombre de archivo" + +#. module: cash_daily_report +#: model:ir.model,name:cash_daily_report.model_cash_daily_report_wizard +msgid "cash.daily.report.wizard" +msgstr "cash.daily.report.wizard" + +#. module: cash_daily_report +#: model:ir.ui.view,arch_db:cash_daily_report.view_cash_daily_report_wizard +msgid "or" +msgstr "o" diff --git a/call_center_report/wizard/__init__.py b/call_center_report/wizard/__init__.py new file mode 100644 index 000000000..f3c151d2d --- /dev/null +++ b/call_center_report/wizard/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018 Alexandre Díaz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from . import call_center_report diff --git a/call_center_report/wizard/call_center_report.py b/call_center_report/wizard/call_center_report.py new file mode 100644 index 000000000..79dab55d2 --- /dev/null +++ b/call_center_report/wizard/call_center_report.py @@ -0,0 +1,349 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018 Alexandre Díaz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from io import BytesIO +from datetime import datetime, date +import xlsxwriter +import base64 +from odoo import api, fields, models, _ +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT + + +class CallCenterReportWizard(models.TransientModel): + _name = 'call.center.report.wizard' + + @api.model + def _get_default_date_start(self): + return datetime.now().strftime(DEFAULT_SERVER_DATE_FORMAT) + + @api.model + def _get_default_date_end(self): + return datetime.now().strftime(DEFAULT_SERVER_DATE_FORMAT) + + date_start = fields.Date("Start Date", default=_get_default_date_start) + date_end = fields.Date("End Date", default=_get_default_date_end) + xls_filename = fields.Char() + xls_binary = fields.Binary() + + @api.model + def _export(self): + date_format = "%d-%m-%Y" + time_format = "%H:%M" + file_data = BytesIO() + workbook = xlsxwriter.Workbook(file_data, { + 'strings_to_numbers': True, + 'default_date_format': 'dd/mm/yyyy' + }) + + company_id = self.env.user.company_id + workbook.set_properties({ + 'title': 'Exported data from ' + company_id.name, + 'subject': 'Payments Data from Odoo of ' + company_id.name, + 'author': 'Odoo', + 'manager': u'Call Center', + 'company': company_id.name, + 'category': 'Hoja de Calculo', + 'keywords': 'payments, odoo, data, ' + company_id.name, + 'comments': 'Created with Python in Odoo and XlsxWriter'}) + workbook.use_zip64() + + xls_cell_format_date = workbook.add_format({ + 'num_format': 'dd/mm/yyyy' + }) + xls_cell_format_money = workbook.add_format({ + 'num_format': '#,##0.00' + }) + xls_cell_format_header = workbook.add_format({ + 'bg_color': '#CCCCCC' + }) + + worksheet = workbook.add_worksheet(_('Call Center Report - Production')) + + worksheet.write('A1', _('Ficha'), xls_cell_format_header) + worksheet.write('B1', _('Fecha de Pedido'), xls_cell_format_header) + worksheet.write('C1', _('Cliente'), xls_cell_format_header) + worksheet.write('D1', _('Producto'), xls_cell_format_header) + worksheet.write('E1', _('Noches/Uds'), xls_cell_format_header) + worksheet.write('F1', _('Adultos'), xls_cell_format_header) + worksheet.write('G1', _('Checkin'), xls_cell_format_header) + worksheet.write('H1', _('In-Hora'), xls_cell_format_header) + worksheet.write('I1', _('Checkout'), xls_cell_format_header) + worksheet.write('J1', _('Creado por'), xls_cell_format_header) + worksheet.write('K1', _('Total'), xls_cell_format_header) + + worksheet.set_column('B:B', 20) + worksheet.set_column('C:C', 20) + worksheet.set_column('D:D', 20) + worksheet.set_column('E:E', 20) + worksheet.set_column('F:F', 13) + + reservations_obj = self.env['hotel.reservation'] + reservations = reservations_obj.search([ + ('checkout', '>=', self.date_start), + ('checkout', '<=', self.date_end), + ('state', '=', 'done'), + ('channel_type', '=', 'call'), + ('folio_id.pending_amount', '<', 1), + ]) + offset = 1 + total_reservation_amount = 0.0 + for k_res, v_res in enumerate(reservations): + checkin_date = datetime.strptime(v_res.checkin, DEFAULT_SERVER_DATE_FORMAT) + checkout_date = datetime.strptime(v_res.checkout, DEFAULT_SERVER_DATE_FORMAT) + worksheet.write(k_res+offset, 0, v_res.folio_id.name) + worksheet.write(k_res+offset, 1, v_res.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_res+offset, 2, v_res.partner_id.name) + worksheet.write(k_res+offset, 3, v_res.room_type_id.name) + worksheet.write(k_res+offset, 4, v_res.nights) + worksheet.write(k_res+offset, 5, v_res.adults) + worksheet.write(k_res+offset, 6, checkin_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 7, checkin_date.strftime(time_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 8, checkout_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 9, v_res.create_uid.name) + worksheet.write(k_res+offset, 10, v_res.price_total, + xls_cell_format_money) + total_reservation_amount += v_res.price_total + + folio_ids = reservations.mapped('folio_id.id') + folios = self.env['hotel.folio'].browse(folio_ids) + services = self.env['hotel.service'].browse() + for folio in folios: + services += folio.service_ids.filtered(lambda r: + r.channel_type == 'call' and r.folio_id.pending_amount < 1) + offset += len(reservations) + total_service_amount = k_line = 0.0 + for k_service, v_service in enumerate(services): + worksheet.write(k_service+offset, 0, v_service.folio_id.name) + worksheet.write(k_service+offset, 1, v_service.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_service+offset, 2, v_service.folio_id.partner_id.name) + worksheet.write(k_service+offset, 3, v_service.product_id.name) + worksheet.write(k_service+offset, 4, v_service.product_qty) + worksheet.write(k_service+offset, 5, '') + worksheet.write(k_service+offset, 6, '') + worksheet.write(k_service+offset, 7, '') + worksheet.write(k_service+offset, 8, '') + worksheet.write(k_service+offset, 9, v_service .create_uid.name) + worksheet.write(k_service+offset, 10, v_service.price_total, + xls_cell_format_money) + total_service_amount += v_service.price_total + offset += len(services) + #~ if total_reservation_amount == 0 and total_service_amount == 0: + #~ raise UserError(_('No Hay reservas de Call Center')) + line = offset + if k_line: + line = k_line + offset + if total_reservation_amount > 0: + line += 1 + worksheet.write(line, 9, _('TOTAL RESERVAS')) + worksheet.write(line, 10, total_reservation_amount, + xls_cell_format_money) + if total_service_amount > 0: + line += 1 + worksheet.write(line, 9, _('TOTAL SERVICIOS')) + worksheet.write(line, 10, total_service_amount, + xls_cell_format_money) + line += 1 + worksheet.write(line, 9, _('TOTAL')) + worksheet.write(line, 10 , total_reservation_amount + total_service_amount, + xls_cell_format_money) + + worksheet = workbook.add_worksheet(_('Call Center Report - Sales')) + + worksheet.write('A1', _('Estado'), xls_cell_format_header) + worksheet.write('B1', _('Ficha'), xls_cell_format_header) + worksheet.write('C1', _('Fecha de Pedido'), xls_cell_format_header) + worksheet.write('D1', _('Cliente'), xls_cell_format_header) + worksheet.write('E1', _('Producto'), xls_cell_format_header) + worksheet.write('F1', _('Noches/Uds'), xls_cell_format_header) + worksheet.write('G1', _('Adultos'), xls_cell_format_header) + worksheet.write('H1', _('Checkin'), xls_cell_format_header) + worksheet.write('I1', _('In-Hora'), xls_cell_format_header) + worksheet.write('J1', _('Checkout'), xls_cell_format_header) + worksheet.write('K1', _('Creado por'), xls_cell_format_header) + worksheet.write('L1', _('Total'), xls_cell_format_header) + + worksheet.set_column('B:B', 20) + worksheet.set_column('C:C', 20) + worksheet.set_column('D:D', 20) + worksheet.set_column('E:E', 20) + worksheet.set_column('F:F', 13) + + reservations_obj = self.env['hotel.reservation'] + reservations = reservations_obj.search([ + ('folio_id.date_order', '>=', self.date_start), + ('folio_id.date_order', '<=', self.date_end), + ('channel_type','=','call'), + ]) + offset = 1 + total_reservation_amount = 0.0 + for k_res, v_res in enumerate(reservations): + checkin_date = datetime.strptime(v_res.checkin, DEFAULT_SERVER_DATE_FORMAT) + checkout_date = datetime.strptime(v_res.checkout, DEFAULT_SERVER_DATE_FORMAT) + worksheet.write(k_res+offset, 0, v_res.state) + worksheet.write(k_res+offset, 1, v_res.folio_id.name) + worksheet.write(k_res+offset, 2, v_res.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_res+offset, 3, v_res.partner_id.name) + worksheet.write(k_res+offset, 4, v_res.room_type_id.name) + worksheet.write(k_res+offset, 5, v_res.nights) + worksheet.write(k_res+offset, 6, v_res.adults) + worksheet.write(k_res+offset, 7, checkin_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 8, checkin_date.strftime(time_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 9, checkout_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 10, v_res.create_uid.name) + worksheet.write(k_res+offset, 11, v_res.price_total, + xls_cell_format_money) + total_reservation_amount += v_res.price_total + + folio_ids = reservations.mapped('folio_id.id') + folios = self.env['hotel.folio'].browse(folio_ids) + services = self.env['hotel.service'].browse() + for folio in folios: + services += folio.service_ids.filtered(lambda r: + r.channel_type == 'call' and r.folio_id.pending_amount < 1) + offset += len(reservations) + total_service_amount = k_line = 0.0 + for k_service, v_service in enumerate(services): + worksheet.write(k_service+offset, 1, v_service.folio_id.state) + worksheet.write(k_service+offset, 1, v_service.folio_id.name) + worksheet.write(k_service+offset, 2, v_service.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_service+offset, 3, v_service.folio_id.partner_id.name) + worksheet.write(k_service+offset, 4, v_service.product_id.name) + worksheet.write(k_service+offset, 5, v_service.product_qty) + worksheet.write(k_service+offset, 6, '') + worksheet.write(k_service+offset, 7, '') + worksheet.write(k_service+offset, 8, '') + worksheet.write(k_service+offset, 9, '') + worksheet.write(k_service+offset, 10, v_service .create_uid.name) + worksheet.write(k_service+offset, 11, v_service.price_total, + xls_cell_format_money) + total_service_amount += v_service.price_total + offset += len(services) + #~ if total_reservation_amount == 0 and total_service_amount == 0: + #~ raise UserError(_('No Hay reservas de Call Center')) + line = offset + if k_line: + line = k_line + offset + if total_reservation_amount > 0: + line += 1 + worksheet.write(line, 10, _('TOTAL RESERVAS')) + worksheet.write(line, 11, total_reservation_amount, + xls_cell_format_money) + if total_service_amount > 0: + line += 1 + worksheet.write(line, 10, _('TOTAL SERVICIOS')) + worksheet.write(line, 11, total_service_amount, + xls_cell_format_money) + line += 1 + worksheet.write(line, 10, _('TOTAL')) + worksheet.write(line, 11 , total_reservation_amount + total_service_amount, + xls_cell_format_money) + + worksheet = workbook.add_worksheet(_('Call Center Report - Cancelations')) + + worksheet.write('A1', _('Estado'), xls_cell_format_header) + worksheet.write('B1', _('Ficha'), xls_cell_format_header) + worksheet.write('C1', _('Fecha de Pedido'), xls_cell_format_header) + worksheet.write('D1', _('Cliente'), xls_cell_format_header) + worksheet.write('E1', _('Producto'), xls_cell_format_header) + worksheet.write('F1', _('Noches/Uds'), xls_cell_format_header) + worksheet.write('G1', _('Adultos'), xls_cell_format_header) + worksheet.write('H1', _('Checkin'), xls_cell_format_header) + worksheet.write('I1', _('In-Hora'), xls_cell_format_header) + worksheet.write('J1', _('Checkout'), xls_cell_format_header) + worksheet.write('K1', _('Creado por'), xls_cell_format_header) + worksheet.write('L1', _('Precio Final'), xls_cell_format_header) + worksheet.write('M1', _('Precio Original'), xls_cell_format_header) + + worksheet.set_column('B:B', 20) + worksheet.set_column('C:C', 20) + worksheet.set_column('D:D', 20) + worksheet.set_column('E:E', 20) + worksheet.set_column('F:F', 13) + + reservations_obj = self.env['hotel.reservation'] + reservations = reservations_obj.search([ + ('last_updated_res', '>=', self.date_start), + ('last_updated_res', '<=', self.date_end), + ('channel_type','=','call'), + ('state','=','cancelled'), + ]) + offset = 1 + total_reservation_amount = 0.0 + for k_res, v_res in enumerate(reservations): + checkin_date = datetime.strptime(v_res.checkin, DEFAULT_SERVER_DATE_FORMAT) + checkout_date = datetime.strptime(v_res.checkout, DEFAULT_SERVER_DATE_FORMAT) + worksheet.write(k_res+offset, 0, v_res.state) + worksheet.write(k_res+offset, 1, v_res.folio_id.name) + worksheet.write(k_res+offset, 2, v_res.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_res+offset, 3, v_res.partner_id.name) + worksheet.write(k_res+offset, 4, v_res.virtual_room_id.name) + worksheet.write(k_res+offset, 5, v_res.nights) + worksheet.write(k_res+offset, 6, v_res.adults) + worksheet.write(k_res+offset, 7, checkin_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 8, checkin_date.strftime(time_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 9, checkout_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 10, v_res.create_uid.name) + worksheet.write(k_res+offset, 11, v_res.price_total, + xls_cell_format_money) + worksheet.write(k_res+offset, 12, v_res.amount_room, + xls_cell_format_money) + total_reservation_amount += v_res.amount_room + + offset += len(reservations) + + #~ if total_reservation_amount == 0 and total_service_amount == 0: + #~ raise UserError(_('No Hay reservas de Call Center')) + line = offset + if k_line: + line = k_line + offset + if total_reservation_amount > 0: + line += 1 + worksheet.write(line, 11, _('TOTAL RESERVAS')) + worksheet.write(line, 12, total_reservation_amount, + xls_cell_format_money) + + workbook.close() + file_data.seek(0) + tnow = fields.Datetime.now().replace(' ', '_') + return { + 'xls_filename': 'call_%s.xlsx' %self.env.user.company_id.property_name, + 'xls_binary': base64.encodestring(file_data.read()), + } + + @api.multi + def export(self): + self.write(self._export()) + return { + "type": "ir.actions.do_nothing", + } diff --git a/call_center_report/wizard/call_center_report.xml b/call_center_report/wizard/call_center_report.xml new file mode 100644 index 000000000..79439ea96 --- /dev/null +++ b/call_center_report/wizard/call_center_report.xml @@ -0,0 +1,42 @@ + + + + + call.center.report.wizard + call.center.report.wizard + + + + + + + + + + + + + + + + +
+
+ +
+
+ + + Call Center Report Wizard + ir.actions.act_window + call.center.report.wizard + + form + form + new + + +
From 2a90f849a2ba6f0cb4bb1c8cad94f6ca664433e4 Mon Sep 17 00:00:00 2001 From: Pablo Date: Fri, 29 Mar 2019 17:49:13 +0100 Subject: [PATCH 16/18] [FIX] wrong default values and no_ota --- .../hotel_room_type_availability/common.py | 66 ++++++++++++++----- .../wizard/inherited_massive_changes.py | 13 +--- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/hotel_channel_connector/models/hotel_room_type_availability/common.py b/hotel_channel_connector/models/hotel_room_type_availability/common.py index d06b393ee..2e4f53c77 100644 --- a/hotel_channel_connector/models/hotel_room_type_availability/common.py +++ b/hotel_channel_connector/models/hotel_room_type_availability/common.py @@ -18,18 +18,22 @@ class HotelRoomTypeAvailability(models.Model): @api.model def _default_max_avail(self): - room_type_id = self._context.get('room_type_id') - if room_type_id: - room_type_id = self.env['hotel.room_type'].browse(room_type_id) - return room_type_id.default_max_avail if room_type_id else -1 + room_type_id = self.room_type_id.id or self._context.get('room_type_id') + channel_room_type = self.env['channel.hotel.room.type'].search([ + ('odoo_id', '=', room_type_id) + ]) or None + if channel_room_type: + return channel_room_type.default_max_avail return -1 @api.model def _default_quota(self): - room_type_id = self._context.get('room_type_id') - if room_type_id: - room_type_id = self.env['hotel.room_type'].browse(room_type_id) - return room_type_id.default_quota if room_type_id else -1 + room_type_id = self.room_type_id.id or self._context.get('room_type_id') + channel_room_type = self.env['channel.hotel.room.type'].search([ + ('odoo_id', '=', room_type_id) + ]) or None + if channel_room_type: + return channel_room_type.default_quota return -1 room_type_id = fields.Many2one('hotel.room.type', 'Room Type', @@ -44,6 +48,7 @@ class HotelRoomTypeAvailability(models.Model): quota = fields.Integer("Quota", default=_default_quota, help="Quota assigned to the channel.") + # TODO: WHY max_avail IS READONLY ¿? max_avail = fields.Integer("Max. Availability", default=-1, readonly=True, help="Maximum simultaneous availability.") @@ -72,8 +77,34 @@ class HotelRoomTypeAvailability(models.Model): @api.onchange('room_type_id') def onchange_room_type_id(self): - if self.room_type_id: - self.quota = self.room_type_id.default_quota + channel_room_type = self.env['channel.hotel.room.type'].search([ + ('odoo_id', '=', self.room_type_id.id) + ]) or None + if channel_room_type: + self.quota = channel_room_type.default_quota + self.max_avail = channel_room_type.default_max_avail + self.no_ota = 0 + + @api.model + def create(self, vals): + vals.update(self._prepare_add_missing_fields(vals)) + return super().create(vals) + + @api.model + def _prepare_add_missing_fields(self, values): + """ Deduce missing required fields from the onchange """ + res = {} + onchange_fields = ['quota', 'max_avail'] + if values.get('room_type_id'): + record = self.new(values) + if 'quota' not in values: + record.quota = record._default_quota() + if 'max_avail' not in values: + record.max_avail = record._default_max_avail() + for field in onchange_fields: + if field not in values: + res[field] = record._fields[field].convert_to_write(record[field], record) + return res class ChannelHotelRoomTypeAvailability(models.Model): @@ -152,6 +183,8 @@ class ChannelHotelRoomTypeAvailability(models.Model): _logger.info(vals_avail) if room_type_avail_id.channel_avail != avail: vals_avail.update({'channel_avail': avail}) + if self._context.get('update_no_ota', False): + vals_avail.update({'channel_pushed': False}) if vals_avail: room_type_avail_id.write(vals_avail) @@ -209,11 +242,13 @@ class BindingHotelRoomTypeAvailabilityListener(Component): fields_to_check = ('quota', 'max_avail', 'no_ota') fields_checked = [elm for elm in fields_to_check if elm in fields] + _logger.info("==[on_record_write] :: hotel.room.type.availability==") + _logger.info(fields) + if any(fields_checked) and any(record.channel_bind_ids): - - _logger.info("==[on_record_write] :: hotel.room.type.availability==") - _logger.info(fields) - + if 'no_ota' in fields_checked: + self.env.context = dict(self.env.context) + self.env.context.update({'update_no_ota': True}) for binding in record.channel_bind_ids: binding.refresh_availability( record.date, @@ -222,9 +257,6 @@ class BindingHotelRoomTypeAvailabilityListener(Component): binding.backend_id.id, room_type_id=record.room_type_id.id) - if 'no_ota' in fields_checked: - binding.write({'channel_pushed': False}) - @skip_if(lambda self, record, **kwargs: self.no_connector_export(record)) def on_record_create(self, record, fields=None): if not any(record.channel_bind_ids): diff --git a/hotel_channel_connector/wizard/inherited_massive_changes.py b/hotel_channel_connector/wizard/inherited_massive_changes.py index d1aa6cf38..7b7597685 100644 --- a/hotel_channel_connector/wizard/inherited_massive_changes.py +++ b/hotel_channel_connector/wizard/inherited_massive_changes.py @@ -21,18 +21,7 @@ class MassiveChangesWizard(models.TransientModel): @api.model def _get_availability_values(self, ndate, room_type, record): - room_type_avail = self.env['hotel.room.type.availability'].search([ - ('date', '=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)), - ('room_type_id', '=', room_type.id) - ]) - channel_room_type = self.env['channel.hotel.room.type'].search([ - ('odoo_id', '=', room_type.id) - ]) or None - vals = { - 'quota': room_type_avail.quota or channel_room_type.default_quota, - 'max_avail': room_type_avail.max_avail or channel_room_type.default_max_avail, - 'no_ota': room_type_avail.no_ota or channel_room_type.default_max_avail, - } + vals = {} if record.change_quota: vals.update({ 'quota': record.quota, From d1a2698c5cd060e32db5517d3fe6efc471c503f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Lodeiros?= Date: Sat, 30 Mar 2019 13:06:02 +0100 Subject: [PATCH 17/18] [FIX] fields call center wizard --- call_center_report/wizard/call_center_report.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/call_center_report/wizard/call_center_report.py b/call_center_report/wizard/call_center_report.py index 79dab55d2..093485616 100644 --- a/call_center_report/wizard/call_center_report.py +++ b/call_center_report/wizard/call_center_report.py @@ -116,8 +116,7 @@ class CallCenterReportWizard(models.TransientModel): worksheet.write(k_res+offset, 5, v_res.adults) worksheet.write(k_res+offset, 6, checkin_date.strftime(date_format), xls_cell_format_date) - worksheet.write(k_res+offset, 7, checkin_date.strftime(time_format), - xls_cell_format_date) + worksheet.write(k_res+offset, 7, v_res.arrival_hour) worksheet.write(k_res+offset, 8, checkout_date.strftime(date_format), xls_cell_format_date) worksheet.write(k_res+offset, 9, v_res.create_uid.name) @@ -211,8 +210,7 @@ class CallCenterReportWizard(models.TransientModel): worksheet.write(k_res+offset, 6, v_res.adults) worksheet.write(k_res+offset, 7, checkin_date.strftime(date_format), xls_cell_format_date) - worksheet.write(k_res+offset, 8, checkin_date.strftime(time_format), - xls_cell_format_date) + worksheet.write(k_res+offset, 8, v_res.arrival_hour) worksheet.write(k_res+offset, 9, checkout_date.strftime(date_format), xls_cell_format_date) worksheet.write(k_res+offset, 10, v_res.create_uid.name) @@ -304,13 +302,12 @@ class CallCenterReportWizard(models.TransientModel): worksheet.write(k_res+offset, 2, v_res.folio_id.date_order, xls_cell_format_date) worksheet.write(k_res+offset, 3, v_res.partner_id.name) - worksheet.write(k_res+offset, 4, v_res.virtual_room_id.name) + worksheet.write(k_res+offset, 4, v_res.room_type_id.name) worksheet.write(k_res+offset, 5, v_res.nights) worksheet.write(k_res+offset, 6, v_res.adults) worksheet.write(k_res+offset, 7, checkin_date.strftime(date_format), xls_cell_format_date) - worksheet.write(k_res+offset, 8, checkin_date.strftime(time_format), - xls_cell_format_date) + worksheet.write(k_res+offset, 8, v_res.arrival_hour) worksheet.write(k_res+offset, 9, checkout_date.strftime(date_format), xls_cell_format_date) worksheet.write(k_res+offset, 10, v_res.create_uid.name) From 3fcd1f5011fb4091f332e013a750c6cfb929921e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Lodeiros?= Date: Sat, 30 Mar 2019 13:23:33 +0100 Subject: [PATCH 18/18] Update call_center_report.py --- call_center_report/wizard/call_center_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/call_center_report/wizard/call_center_report.py b/call_center_report/wizard/call_center_report.py index 093485616..d3da53d1d 100644 --- a/call_center_report/wizard/call_center_report.py +++ b/call_center_report/wizard/call_center_report.py @@ -313,9 +313,9 @@ class CallCenterReportWizard(models.TransientModel): worksheet.write(k_res+offset, 10, v_res.create_uid.name) worksheet.write(k_res+offset, 11, v_res.price_total, xls_cell_format_money) - worksheet.write(k_res+offset, 12, v_res.amount_room, + worksheet.write(k_res+offset, 12, v_res.price_total - v_res.discount, xls_cell_format_money) - total_reservation_amount += v_res.amount_room + total_reservation_amount += v_res.price_total offset += len(reservations)