First Commit

This commit is contained in:
Dario Lodeiros
2018-07-26 13:06:48 +02:00
commit a9a4e59882
142 changed files with 19956 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.pyc
.settings/

45
.travis.yml Normal file
View File

@@ -0,0 +1,45 @@
language: python
python:
- "2.7"
sudo: false
cache: pip
addons:
postgresql: "9.2" # minimal postgresql version for the daterange method
apt:
packages:
- expect-dev # provides unbuffer utility
- python-lxml # because pip installation is slow
# needed because server-tools is loaded in the dependency chain
- unixodbc-dev
- python-mysqldb
env:
global:
- VERSION="10.0" TESTS="0" LINT_CHECK="0" TRANSIFEX="0" UNIT_TEST="0"
# - TRANSIFEX_USER='transbot@odoo-community.org'
# - secure: "XLhGdCIh86zcqww9qBpnk8Xqsf1Pcgw9SKr7X0KYBHJofHj4Z6Kq/oVFjpZ1LSjadsaABKbwY7h4hvKEpxZwptCv+fNTOKYy7hXFLGYnDeNeWu4zA4LI7TA5uPvyZjZ+g2xc+9dzR/VbfRHNqjvmgiEidxxqLeOnNFZ5CHdOdCw="
matrix:
# Option temporarily disabled
#- LINT_CHECK="1"
- TESTS="1" ODOO_REPO="odoo/odoo"
- TESTS="1" ODOO_REPO="OCA/OCB"
virtualenv:
system_site_packages: true
install:
- git clone https://github.com/OCA/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools --depth=1
- export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
- travis_install_nightly
- git clone -b ${VERSION} https://github.com/OCA/web.git ${HOME}/dependencies/web --depth=1
- git clone -b ${VERSION} https://github.com/OCA/partner-contact.git ${HOME}/dependencies/partner-contact --depth=1
- git clone -b ${VERSION} https://github.com/OCA/account-payment.git ${HOME}/dependencies/account_payment_return --depth=1
script:
- travis_wait travis_run_tests
after_success:
- travis_after_tests_success

27
README.md Normal file
View File

@@ -0,0 +1,27 @@
# HOOTEL PROJECT MODULES [![Build Status](https://travis-ci.org/hootel/hootel.svg?branch=10.0)](https://travis-ci.org/hootel/hootel) [![codecov](https://codecov.io/gh/hootel/hootel/branch/10.0/graph/badge.svg)](https://codecov.io/gh/hootel/hootel) ![Unstable](https://img.shields.io/badge/stability-unstable-yellow.svg)
**IMPORTANT:**
- Set time zone of users that use the calendar
**MODULES:**
- [x] hotel: Base module (Inspired by the work of SerpentCS Hotel Module)
- [x] hotel_calendar: Adds calendar for manage hotel reservations and rooms configuration
- [x] hotel_calendar_wubook: Unify 'hotel_wubook_prototype' and 'hotel_calendar' modules
- [x] hotel_data_bi: Export reservations data for Revenue to MyDataBI
- [x] hotel_l10n_es: Procedures for check-in process in Spain
- [ ] hotel_wubook: NOTHING... the idea is use Odoo Connector
- [x] hotel_wubook_prototype: Current implementation of Wubook Connector... sync data with wubook.net account.
- [ ] hotel_node_slave: Configure a node as a slave to serve and get information from a master one
- [ ] hotel_node_master: Configure a node as a master
- [ ] glasof_exporter: Export Odoo data to Glasof xls format
- [x] hotel_revenue: Export Odoo data for Revenue in xls format
- [x] cash_daily_report: Export Odoo Payments & Payment Returns to xls format
- [x] invoice_payments_report: Add payments info in invoices
- [x] theme_chatter_right: Puts chatter to the right
- [x] report_qweb_pdf_preview: Adds new report_type to generate pdf and launch preview/print process
- [x] l10n_es_events_scraper: Gets info about relevant events in Spain
**HOW WORKS?**
- The idea is... the hotel sell 'virtual rooms' and the customer is assigned to one 'normal room'.
- The folio have all reservation lines, used services...

28
hotel/Doc/ChangeLog.txt Normal file
View File

@@ -0,0 +1,28 @@
============================================================================================================================
Version Change Log (hotel)
============================================================================================================================
0.07 on 2013-10-31 by Murtuza Saleh
*Improved hotel.room kanban view as per in v9.
0.06 on 2013-10-29 by Ashish Thakkar
*Set the product_category_tree_view in the hotel_view.xml file.
*Improved the code to get the hierarchy in rooms,amenities and services.
0.05 on 2013-10-28 by Anu Patel
* Set the default value of check in date and check out date.
* Improved calculation of duration for hotel check in - checkout.
0.04 on 2013-10-28 by Anu Patel
* Improved ir.sequence for hotel.folio.
* Improved hotel folio line one2many field as faced problem because of product_uos field.
0.03 on 2013-10-25 by Anu Patel
* Improved on_change in hotel folio line where product_id onchange is not working.
* Removed on_chage from .xml file as there is no need to define there.
0.02 on 2013-10-26 by Anu Patel
* Improved the code for removed the workflow in hotel folio as workflow is no longer used in v9.
* Improved states used in hotel folio from sale order as per in v9.
0.01 on 2013-10-16 by Ashish Thakkar
* Made the module installable in v9.

53
hotel/README.rst Normal file
View File

@@ -0,0 +1,53 @@
hotel
This Module is for Providing Hotel management Features.
You can manage:
-Hotel Booking,
-Hotel Facilities and Amenities,
-RESTURANTS,
-Currency Exchange,
-REPORTS
-Different reports are also provided, mainly for hotel.
Installation
To install this module, you need to:
install 'product_uos', 'sale_stock', 'point_of_sale', 'report' modules
Configuration
To configure this module, you need to:
have a Hotel management functionality.
Usage
To use this module, you need to:
go to apps, then install module to apply this functionality.
Try me on Runbot
Known issues / Roadmap
...
Bug Tracker
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us smashing it by providing a detailed and welcomed feedback here.
Credits
Contributors
Serpent Consulting Services PVT. LTD. <http://serpentcs.com>
Maintainer
Serpent Consulting Services PVT. LTD.
This module is maintained by the SerpentCS.
To contribute to this module, please visit https://github.com/JayVora-SerpentCS/hotelmgmt_v8.

8
hotel/__init__.py Normal file
View File

@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Tecnotel - Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import wizard
from . import report
from . import date_utils

67
hotel/__manifest__.py Normal file
View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
'name': 'Hotel Management',
'version': '0.07',
'author': 'Odoo Community Association (OCA),\
Darío Lodeiros,\
Jose Luis Algara,\
Alexandre Díaz',
'images': [],
'category': 'Generic Modules/Hotel Management',
'website': '',
'depends': [
'sale_stock',
'account_payment_return',
'cash_daily_report',
],
'license': "",
'demo': ['data/hotel_data.xml'],
'data': [
'security/hotel_security.xml',
'security/ir.model.access.csv',
'wizard/massive_changes.xml',
'wizard/split_reservation.xml',
'wizard/duplicate_reservation.xml',
'views/res_config.xml',
'data/menus.xml',
'views/inherit_account_payment_views.xml',
'views/inherit_account_invoice_views.xml',
'wizard/hotel_wizard.xml',
'wizard/checkinwizard.xml',
'wizard/massive_price_reservation_days.xml',
'wizard/folio_make_invoice_advance_views.xml',
'views/hotel_sequence.xml',
'views/hotel_report.xml',
'views/report_hotel_management.xml',
'views/currency_exchange.xml',
'views/hotel_floor.xml',
'views/hotel_folio.xml',
'views/inherit_res_partner.xml',
# 'views/hotel_service_type.xml',
# 'views/hotel_service_line.xml',
'views/hotel_room_type.xml',
'views/hotel_room.xml',
# 'views/hotel_service.xml',
'views/inherit_product_product.xml',
'views/hotel_room_amenities_type.xml',
'views/hotel_room_amenities.xml',
'views/reservation_restriction_views.xml',
'views/reservation_restriction_item_views.xml',
'views/hotel_reservation.xml',
# 'views/virtual_room_views.xml',
'views/cardex.xml',
'views/virtual_room_availability.xml',
# 'views/hotel_dashboard.xml',
'data/cron_jobs.xml',
'data/records.xml',
'data/email_template_cancel.xml',
'data/email_template_reserv.xml',
'data/email_template_exit.xml',
],
'css': ['static/src/css/room_kanban.css'],
'auto_install': False,
'installable': True
}

32
hotel/data/cron_jobs.xml Normal file
View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="daily_plan_checkin_out" model="ir.cron">
<field name="name">Daily Plan</field>
<field name="active" eval="True" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall">0</field>
<field name="nextcall" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 05:00:00')"/>
<field name="model_id" ref="model_hotel_reservation" />
<field name="code">model.daily_plan()</field>
</record>
<!-- Scheduler For To Inform Guest About Reservation Before 24 Hours -->
<record model="ir.cron" id="Guest_reservation_reminder_24hrs">
<field name="name">Inform Guest About Reservation Before 24 Hours</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="model_id" ref="model_hotel_reservation" />
<field name="code">model.reservation_reminder_24hrs()</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,538 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xml>
<odoo>
<data noupdate="1">
<!-- Email Template For Hotel Reservation -->
<record id="mail_template_hotel_cancel" model="mail.template">
<field name="name">Cancel Reservation-Send by Email</field>
<field name="email_from">${(object.warehouse_id.partner_id.email or'')}</field>
<field name="subject">Cancelación de su reserva en ${object.company_id.property_name}</field>
<field name="partner_to">${(object.partner_id.id or '')}</field>
<field name="model_id" ref="hotel.model_hotel_folio"/>
<field name="auto_delete" eval="True" />
<field name="body_html">
<![CDATA[
<style type="text/css">
/*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;}
}
</style>
<div style="bgcolor:#ffffff; margin: 0; padding: 0; min-width: 100%!important;">
<table width="100%" bgcolor="#ffffff" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<!--[if (gte mso 9)|(IE)]>
<table width="540" align="center" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<![endif]-->
<!--Content Wrapper-->
<table class="content" background-color="#f6f6f6" align="center" cellpadding="0" cellspacing="0" border="0" style="width: 650px !important; border: 1px solid #eeeeee; background-color:#f6f6f6;">
<!--Header-->
<tr>
<td style="padding: 30px 30px 20px 30px;background-color:#1B1B1B">
<table border="0x" cellpadding="0" cellspacing="0" width="100%" class="columns">
<tr valign="top">
</tr>
</table>
<img src="https://www.aldahotels.es/firma/email/llegada/logobl.png" width="251" height="57" alt="Alda Hotels"/>
<table border="0x" cellpadding="0" cellspacing="0" width="100%" class="columns">
<tr valign="top">
<td width="50%" valign="middle" class="columncontainer" >
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td align="right" valign="middle" style="padding: 0px 0px 0px 0px;">
<table border="0" cellspacing="0" cellpadding="0" class="link">
<tr>
<td style="padding: 0px 15px 0px 0px;">
<a href="#" style="color:#FFFFFF" >www.aldahotels.com</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
</tr>
<!--1 Column-->
<tr>
<td class="borderbottom" style="padding: 60px 30px 60px 30px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="h2"> Tu reserva se ha cancelado en ${object.company_id.property_name}</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 0 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td bgcolor="#C50967" height="5px" style="font-size: 1px; line-height: 5px;">&nbsp;
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">Hola ${object.partner_id.firstname}<br />
Tu reserva en <b>${object.company_id.property_name}</b> 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.
</tr>
<tr>
<td align="center" style="padding: 20px 0 0px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<a href="tel:${object.company_id.phone}" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Contactar
</div>
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Section with sidebar-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="http://www.aldahotels.es/firma/email/llegada/cancelacion.png" border="0" alt="Cancelación" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="color: #C50967">Datos de tu reserva cancelada</td>
</tr>
<tr>
<td align="justify" class="subheading" style="color: #555555">
<strong>${object.partner_id.name}</strong>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">
% if object.partner_id.contact_address:
${object.partner_id.contact_address}<br />
% endif
<br />
% for rline in object.get_grouped_reservations_json('cancelled'):
<strong>${rline['num']} x ${rline['virtual_room']['name']}
% if rline['childrens'] > 0:
(${rline['adults']} Adults + ${rline['childrens']} Childrens)
% else:
(${rline['adults']} Adults)
%endif
</strong>
<br />
<strong style="margin-left:2em">Entrada</strong>: ${format_tz(rline['checkin'], format="%d de %B de %Y")}<br />
<strong style="margin-left:2em">Salida</strong>: ${format_tz(rline['checkout'], format="%d de %B de %Y")}<br />
<strong style="margin-left:2em"2>Nº de noches</strong>: ${rline['nights']}<br /><br />
% endfor
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--2 Column-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="http://www.aldahotels.es/firma/email/llegada/pago.png" border="0" alt="Pago" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="color: #C50967">IMPORTES</td>
</tr>
<tr>
<td align="justify" class="subheading" style="color: #555555">
<strong>Noches</strong>: ${len(object.room_lines[0].reservation_lines)}</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">
<strong>Base imponible</strong>: ${object.amount_untaxed} €<br />
<strong>I.V.A (10%)</strong>: ${object.amount_tax} €<br />
<strong>Precio total</strong>: ${object.amount_total} €<br />
<strong>Coste de cancelación</strong>: [[importe]]<br />
<br />
<br />
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--Section with sidebar-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--Block Layout-->
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" width="50%" bgcolor="#f9f9f9">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
</table>
</td>
</tr>
<tr valign="top">
</tr>
</table>
</td>
</tr>
<!--2 Column-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 0px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td class="h2" style="color: #C50967">NUESTRAS REDES SOCIALES</td>
<td style="padding: 0 0 30px 0;">&nbsp;</td>
</tr>
<tr>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--3 Column-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 0px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;">
<a href="https://www.facebook.com/aldahotels" target="_blank">
<img src="http://www.aldahotels.es/firma/email/llegada/fb.png" width="51" height="50" border="0" alt="Facebook" />
</a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #3B5998; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Facebook</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#3B5998" height="5px" style="font-size: 1px; line-height: 5px;">&nbsp;
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.facebook.com/aldahotels" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Dale a Me gusta</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;">
<a href="https://www.instagram.com/aldahotels" target="_blank">
<img src="http://www.aldahotels.es/firma/email/llegada/ig.png" width="50" height="50" border="0" alt="Instagram" />
</a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #E56459; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Instagram</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#E56459" height="5px" style="font-size: 1px; line-height: 5px;">&nbsp;
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.instagram.com/aldahotels/" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">#Entra
</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;">
<a href="https://www.twitter.com/aldahotels" target="_blank">
<img src="http://www.aldahotels.es/firma/email/llegada/tw.png" width="50" height="50" border="0" alt="Twitter" />
</a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #1DA1F2; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Twitter</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#1DA1F2" height="5px" style="font-size: 1px; line-height: 5px;">&nbsp;
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.twitter.com/aldahotels" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Síguenos
</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--2 Column-->
<tr>
</tr>
<!--1 Column-->
<tr>
<td style="padding: 30px 30px 20px 30px;background-color:#C50967">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #FFFFFF; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> ¡Esperamos verte pronto!</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" valign="middle" class="borderbottom" style="padding: 45px 0px 30px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
</tr>
</table>
<img src="http://www.aldahotels.es/firma/email/llegada/logoft.png" width="300" height="75" alt="Alda Hotels"/>
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
</tr>
</table>
</td>
</tr>
<!--Footer-->
<tr>
<td class="footer">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="justify" class="smalltext" style="color: #828282; padding: 20px 20px 20px 20px;">En cumplimiento de la Ley 34/2002 de Servicios de la Sociedad de la Información y del Comercio Electrónico, así como de la Ley Orgánica 15/1999 del 13 de Diciembre de Protección de Datos de Carácter Personal y demás legislación concordante, se le informa que sus datos personales figuran en un fichero automatizado cuya responsabilidad es de ALDA COMPOSTELA S.L. Praza da Algalia de Arriba, 3 C.P. 15704 Santiago de Compostela. Los datos personales que existen en nuestro poder están protegidos por nuestra Política de Privacidad y solo serán utilizados para los fines propios de nuestra actividad. Para ejercer sus derechos de acceso, rectificación, cancelación u oposición debe enviar un correo electrónico a info@aldahotels.com indicándonos la opción a realizar. Este correo podría ser confidencial. Si recibe este e-mail por error, por favor elimínelo, así como cualquier documento adjunto, y notifíquelo a su emisor. Si usted no es el destinatario del mensaje, sepa que no está permitida ninguna difusión, copia o utilización no autorizada.</td>
</tr>
<tr>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" valign="middle" class="listitem" style="color: #0A5F19; padding: 0px 0px 0px 0px">
<img src="http://www.aldahotels.es/firma/email/llegada/eco.png" width="30" height="30" />
<strong>Antes de imprimir este mensaje, compruebe que es verdaderamente necesario. El medioambiente es cosa de todos. </strong>
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</table>
</div>
]]>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,580 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xml>
<odoo>
<data noupdate="1">
<!-- Email Template For Hotel Reservation -->
<record id="mail_template_hotel_exit" model="mail.template">
<field name="name">Exit Reservation-Send by Email</field>
<field name="email_from">${(object.warehouse_id.partner_id.email or'')}</field>
<field name="subject">Gracias por alojarse con nosotros en ${object.company_id.property_name}</field>
<field name="partner_to">${(object.partner_id.id or '')}</field>
<field name="model_id" ref="hotel.model_hotel_folio"/>
<field name="auto_delete" eval="True" />
<field name="body_html"><![CDATA[<style type="text/css">/*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;}
}</style>
<div style="background-color:#ffffff; margin: 0; padding: 0; min-width: 100%!important;font-family: sans-serif; font-size: 16px; line-height: 22px; color: #666666; font-weight: 200; padding: 20px 0 0 0;">
<table width="100%" bgcolor="#ffffff" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<!--[if (gte mso 9)|(IE)]>
<table width="540" align="center" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<![endif]-->
<!--Content Wrapper-->
<table class="content" background-color="#f6f6f6" align="center" cellpadding="0" cellspacing="0" border="0" style="width: 650px !important; border: 1px solid #eeeeee; background-color:#f6f6f6;">
<!--Header-->
<tr>
<td style="padding: 30px 30px 20px 30px;background-color:#1B1B1B">
<table border="0x" cellpadding="0" cellspacing="0" width="100%" class="columns">
<tr valign="top">
</tr>
</table>
<img src="https://www.aldahotels.es/firma/email/llegada/logobl.png" width="251" height="57" alt="Alda Hotels"/>
<table border="0x" cellpadding="0" cellspacing="0" width="100%" class="columns">
<tr valign="top">
<td width="50%" valign="middle" class="columncontainer" >
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td align="right" valign="middle" style="padding: 0px 0px 0px 0px;">
<table border="0" cellspacing="0" cellpadding="0" class="link">
<tr>
<td style="padding: 0px 15px 0px 0px;"><a href="https://www.aldahotels.es/" style="color:#FFFFFF" >www.aldahotels.com</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
</tr>
<!--1 Column-->
<tr>
<td class="borderbottom" style="padding: 60px 30px 40px 30px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #444444; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> ¡Muchas gracias por tu visita!
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 0 0;">
<p style="font-size: 2em; line_height=0px; color: #C50967;">__</p>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">Hola ${object.partner_id.firstname},<br />
Esperamos que hayas disfrutado de la ciudad, y que muy especialmente te hayas sentido a gusto en nuestro alojamiento. Todo el equipo de <strong>${object.company_id.property_name}</strong> te agradece tu estancia en nuestro centro y te desea un feliz regreso.<br />
Recibe un cordial saludo y esperamos volver a verte.<br />
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
<!--Section with sidebar-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 10px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 0px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #444444; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Disfruta de tu descuento de cliente
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 0 0;">
<p style="font-size: 2em; line_height=0px; color: #C50967;">__</p>
</td>
</tr>
<tr>
</tr>
</table>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">Solo por ser cliente de Alda Hotels disfruta, automáticamente, de <strong>hasta un 10% de descuento en cualquiera de nuestros establecimientos</strong>, totalmente transferible y sin fecha de caducidad.<br />
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
<img src="https://www.aldahotels.es/firma/email/salida/descuento.png" width="600" height="170" alt="Descuento 10%"/>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Section with sidebar-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 10px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 0px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #444444; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> ¿Tienes algo que comentarnos?
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 0 0;">
<p style="font-size: 2em; line_height=0px; color: #C50967;">__</p>
</td>
</tr>
<tr>
</tr>
</table>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 30px 0;">Cada día intentamos dar un mejor servicio, es por ello que si quieres aportar alguna propuesta o crítica constructiva, te invitamos a utilizar el siguiente formulario. <strong>Estamos encantados de recibir tus propuestas</strong>. Todas serán leídas y tenidas en cuenta, y atenderemos aquellas que nos permitan nuestros medios y posibilidades.<br />
</td>
</tr>
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://goo.gl/forms/GbhPNgcGGSMQT2rG2" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Entrar al formulario ▶
</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
<table border="0" cellspacing="0" cellpadding="0">
<tr>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--2 Column-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="http://www.aldahotels.es/firma/email/llegada/localizacion.png" border="0" alt="Localización" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="color: #C50967"> nuestros alojamientos</td>
</tr>
<tr>
<td align="justify" class="subheading" style="color: #555555"><strong>Descubre dónde estamos</strong>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 10px 0;">Estamos presentes a lo largo del territorio peninsular. Si quieres saber dónde puedes alojarte con nosotros, pincha en el mapa.<br />
</td>
</tr>
</table>
<center>
<a href="https://www.aldahotels.es/wpresscorporate/nuestros-alojamientos/"><img src="http://www.aldahotels.es/firma/email/salida/mapaalojamientos.png" border="0" alt="Mapa" />
</a>
<table border="0" cellspacing="0" cellpadding="0">
</center>
<tr>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Section with sidebar-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--Article with thumbnail-->
<tr>
</tr>
<!--Block Layout-->
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" width="50%" bgcolor="#f9f9f9">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
</table>
</td>
</tr>
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
</table>
</td>
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" width="50%" bgcolor="#C50967">
<table border="0" cellpadding="0" cellspacing="0" width="100%" >
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--2 Column-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 0px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">NUESTRAS REDES SOCIALES
</td>
<td style="padding: 0 0 50px 0;">&nbsp;
</td>
</tr>
<tr>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--3 Column-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 0px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;"><a href="https://www.facebook.com/aldahotels" target="_blank"><img src="http://www.aldahotels.es/firma/email/llegada/fb.png" width="51" height="50" border="0" alt="Facebook" /></a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #3B5998; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Facebook</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#3B5998" height="5px" style="font-size: 1px; line-height: 5px;">&nbsp;
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.facebook.com/aldahotels" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Dale a Me gusta</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;"><a href="https://www.instagram.com/aldahotels" target="_blank"><img src="http://www.aldahotels.es/firma/email/llegada/ig.png" width="50" height="50" border="0" alt="Instagram" /></a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #E56459; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Instagram</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#E56459" height="5px" style="font-size: 1px; line-height: 5px;">&nbsp;
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.instagram.com/aldahotels/" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">#Entra
</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;"><a href="https://www.twitter.com/aldahotels" target="_blank"><img src="http://www.aldahotels.es/firma/email/llegada/tw.png" width="50" height="50" border="0" alt="Twitter" /></a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #1DA1F2; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Twitter
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#1DA1F2" height="5px" style="font-size: 1px; line-height: 5px;">&nbsp;
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.twitter.com/aldahotels" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Síguenos
</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--2 Column-->
<tr>
</tr>
<!--1 Column-->
<tr>
<td style="padding: 30px 30px 20px 30px;background-color:#C50967">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #FFFFFF; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> ¡Muchas gracias por alojarte con nosotros!
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" valign="middle" class="borderbottom" style="padding: 45px 0px 30px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
</tr>
</table>
<img src="https://www.aldahotels.es/firma/email/llegada/logoft.png" width="300" height="75" alt="Alda Hotels"/>
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
</tr>
</table>
</td>
</tr>
<!--Footer-->
<tr>
<td class="footer">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="justify" class="smalltext" style="color: #828282; padding: 20px 20px 20px 20px;">En cumplimiento de la Ley 34/2002 de Servicios de la Sociedad de la Información y del Comercio Electrónico, así como de la Ley Orgánica 15/1999 del 13 de Diciembre de Protección de Datos de Carácter Personal y demás legislación concordante, se le informa que sus datos personales figuran en un fichero automatizado cuya responsabilidad es de ALDA COMPOSTELA S.L. Praza da Algalia de Arriba, 3 C.P. 15704 Santiago de Compostela. Los datos personales que existen en nuestro poder están protegidos por nuestra Política de Privacidad y solo serán utilizados para los fines propios de nuestra actividad. Para ejercer sus derechos de acceso, rectificación, cancelación u oposición debe enviar un correo electrónico a info@aldahotels.com indicándonos la opción a realizar. Este correo podría ser confidencial. Si recibe este e-mail por error, por favor elimínelo, así como cualquier documento adjunto, y notifíquelo a su emisor. Si usted no es el destinatario del mensaje, sepa que no está permitida ninguna difusión, copia o utilización no autorizada.</td>
</tr>
<tr>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" valign="middle" class="listitem" style="color: #0A5F19; padding: 0px 0px 0px 0px"><img src="https://www.aldahotels.es/firma/email/llegada/eco.png" width="30" height="30" /><strong>Antes de imprimir este mensaje, compruebe que es verdaderamente necesario. El medioambiente es cosa de todos. </strong>
</td>
</tr>
<tr> </tr>
</table>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</table>
</div>]]></field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,972 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xml>
<odoo>
<data noupdate="1">
<!-- Email Template For Hotel Reservation -->
<record id="mail_template_hotel_reservation" model="mail.template">
<field name="name">Confirm Reservation-Send by Email</field>
<field name="email_from">${(object.warehouse_id.partner_id.email or '')}</field>
<field name="subject">Confirmación de los detalles de su reserva en ${object.company_id.property_name}</field>
<field name="partner_to">${(object.partner_id.id or '')}</field>
<field name="model_id" ref="hotel.model_hotel_folio"/>
<field name="auto_delete" eval="True" />
<field name="body_html">
<![CDATA[
<style type="text/css">
/*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;}
}
</style>
<div style="background-color:#ffffff; margin: 0; padding: 0; min-width: 100%!important;font-family: sans-serif; font-size: 16px; line-height: 22px; color: #666666; font-weight: 200; padding: 20px 0 0 0;">
<table width="100%" background-color="#ffffff" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<!--[if (gte mso 9)|(IE)]>
<table width="540" align="center" cellpadding="0" cellspacing="0" border="0">
<tr>
<td>
<![endif]-->
<!--Content Wrapper-->
<table class="content" background-color="#f6f6f6" align="center" cellpadding="0" cellspacing="0" border="0" style="width: 650px !important; border: 1px solid #eeeeee; background-color:#f6f6f6;">
<!--Header-->
<tr>
<td style="padding: 30px 30px 20px 30px;background-color:#1B1B1B">
<table border="0x" cellpadding="0" cellspacing="0" width="100%" class="columns">
<tr valign="top">
</tr>
</table>
<img src="https://www.aldahotels.es/firma/email/llegada/logobl.png" width="251" height="57" alt="Alda Hotels"/>
<table border="0x" cellpadding="0" cellspacing="0" width="100%" class="columns">
<tr valign="top">
<td width="50%" valign="middle" class="columncontainer" >
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td align="right" valign="middle" style="padding: 0px 0px 0px 0px;">
<table border="0" cellspacing="0" cellpadding="0" class="link">
<tr>
<td style="padding: 0px 15px 0px 0px;">
<a href="#" style="color:#FFFFFF" >www.aldahotels.com</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
</tr>
<!--1 Column-->
<tr>
<td class="borderbottom" style="padding: 60px 30px 60px 30px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #444444; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Confirmación de reserva en ${object.company_id.property_name}</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 0 0;">
<p style="font-size: 2em; line_height=0px; color: #C50967;">__</p>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">Hola ${object.partner_id.firstname}<br />
Tu reserva en <b>${object.company_id.property_name}</b> queda confirmada. Te esperamos el día <b>${object.room_lines[0].checkin[8:10]} del ${object.room_lines[0].checkin[5:7]} de ${object.room_lines[0].checkin[0:4]}</b>. Si podemos ayudarte en cualquier tipo de gestión, no dudes en hacérnoslo saber.<br />
</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 0px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<a href="tel:${object.company_id.phone}" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Contactar
</div>
</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Section with sidebar-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 10px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 0px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #444444; padding: 10px 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> ¿Llegarás más tarde de las 17:00 horas?</td>
</tr>
<tr>
<td style="padding: 20px 0 10px 0;">
<p style="font-size: 2em; line_height=0px; color: #C50967;">__</p>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">En ese caso te rogamos que <strong>te pongas en contacto con nosotros</strong> para facilitarte las instrucciones necesarias.<br />
</td>
</tr>
<tr>
<td align="left" valign="middle" style="padding: 30px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="tel:${object.company_id.phone}" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Contactar
</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%" >
<tr>
<td valign="top" style="padding: 20px 30px 20px 0px;">
<table border="0" width="100%" cellspacing="0" cellpadding="0" style="background: #f9f9f9;">
<tr>
<td style="padding: 10px 30px 10px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #666666; padding: 10px 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Servicios</td>
</tr>
<tr>
<td style="padding: 20px 0 10px 0; background-color:#F9F9F9">
<table border="0" cellspacing="0" cellpadding="0" >
<tr>
<td height="50" valign="middle" style="padding: 0 10px 0 0;">
<img src="https://www.aldahotels.es/firma/email/llegada/infoturistica.png" alt="Info" />
</td>
<td class="listitem" style="padding: 10px 10px 0 0;color: #666666; font-weight: 200">Información turística</td>
</tr>
<tr>
<td height="50" valign="middle" style="padding: 0 10px 0 0;">
<img src="https://www.aldahotels.es/firma/email/llegada/wi-fi.png" alt="Wifi" />
</td>
<td class="listitem" style="padding: 10px 10px 0 0;color: #666666; font-weight: 200">Wi-Fi gratuito</td>
</tr>
<tr>
<td height="50" valign="middle" style="padding: 0 10px 0 0;">
<img src="https://www.aldahotels.es/firma/email/llegada/restauracion.png" alt="Restauracion" />
</td>
<td class="listitem" style="padding: 10px 10px 0 0;color: #666666; font-weight: 200">Restauración</td>
</tr>
<tr>
<td height="50" valign="middle" style="padding: 0 10px 0 0;">
<img src="https://www.aldahotels.es/firma/email/llegada/parking.png" alt="Parking" />
</td>
<td class="listitem" style="padding: 10px 10px 0 0;color: #666666; font-weight: 200">Parking concertado</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="https://www.aldahotels.es/firma/email/llegada/hotel.png" border="0" alt="Alda Hotels" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">Datos de tu reserva</td>
</tr>
<tr>
<td align="justify" class="subheading" style="color: #555555">
<strong>${object.partner_id.name}</strong>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">
% if object.partner_id.contact_address:
${object.partner_id.contact_address}<br />
% endif
% if object.partner_id.phone:
Tel.: ${object.partner_id.phone}<br />
% endif
% if object.partner_id.mobile:
Mov.: ${object.partner_id.mobile}<br />
% endif
<br />
% for rline in object.get_grouped_reservations_json('confirm'):
<strong>${rline['num']} x ${rline['virtual_room']['name']}
% if rline['childrens'] > 0:
(${rline['adults']} Adultos + ${rline['childrens']} Niños)
% else:
(${rline['adults']} Adultos)
% endif
</strong>
<br />
<strong style="margin-left:2em">Entrada</strong>: ${format_tz(rline['checkin'], format="%d de %B de %Y")}<br />
<strong style="margin-left:2em">Salida</strong>: ${format_tz(rline['checkout'], format="%d de %B de %Y")}<br />
<strong style="margin-left:2em"2>Nº de noches</strong>: ${rline['nights']}<br /><br />
% endfor
<br />
Recuerda que puedas cancelar gratuitamente esta reserva hasta las 12:00 h del día anterior a tu llegada.
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--2 Column-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 0px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
</tr>
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Mapa</td>
</tr>
<tr>
<td align="center" valign="middle" style="padding: 20px 0px 0px 0px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
</tr>
<tr>
<a href="http://maps.google.es/?q=${object.company_id.property_name}%20${object.company_id.street}%20${object.company_id.city}" style="background-color:transparent" target="_blank" >
<br/>
<img src="https://www.aldahotels.es/firma/email/llegada/mapa_google.png" alt="Ver mapa" border="0">
</a>
<p>
<br/>
<a href="https://www.google.com/maps/dir/?api=1&destination=${object.company_id.property_name}%20${object.company_id.street}%20${object.company_id.city}" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">¿Como llegar desde su ubicación actual?</div>
</a>
</p>
</tr>
<tr>
<td style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="https://www.aldahotels.es/firma/email/llegada/pago.png" border="0" alt="Importes" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">IMPORTES</td>
</tr>
<tr>
<td align="justify" class="subheading" style="color: #555555">
<strong>Noches</strong>: ${len(object.room_lines[0].reservation_lines)}</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">
<strong>Base imponible</strong>: ${object.amount_untaxed} €<br />
<strong>I.V.A (10%)</strong>: ${object.amount_tax} €<br />
<strong>Precio total</strong>: ${object.amount_total} €<br />
<br />
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="https://www.aldahotels.es/firma/email/llegada/habitacion.png" border="0" alt="Habitacion" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">Información de la habitación</td>
</tr>
% set vroom_ids = object.room_lines.filtered('to_send').mapped('virtual_room_id.id')
% set vrooms = user.env['hotel.virtual.room'].browse(vroom_ids)
% for vroom in vrooms:
<tr>
% if vroom.name:
<td align="justify" class="subheading" style="color: #555555">
<strong>${vroom.name}</strong>
</td>
% else:
<td align="justify" class="subheading" style="color: #555555">
<strong>Habitación ${loop.index}</strong>
</td>
% endif
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">
Esta habitación cuenta con TV, Wi-Fi gratuita, calefacción y baño privado.
</td>
</tr>
<tr>
</tr>
<td valign="top" style="padding: 30px 0px 0px 0px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
</td>
</tr>
<tr>
<td>
</td>
</tr>
<tr>
</tr>
</table>
</td>
% endfor
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="https://www.aldahotels.es/firma/email/llegada/peticiones.png" border="0" alt="Peticiones" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">Peticiones especiales</td>
</tr>
<tr>
<td align="justify" class="subheading" style="color: #555555">
<strong>Estamos a tu servicio</strong>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">
<strong>[[petición]]</strong>
<br />
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Section with sidebar-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 10px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 0px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #444444; padding: 10px 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Información adicional</td>
</tr>
<tr>
<td style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<p style="font-size: 2em; line_height=0px; color: #C50967;">__</p>
</tr>
</table>
</td>
</tr>
<tr>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="https://www.aldahotels.es/firma/email/llegada/coche.png" width="115" height="115" border="0" alt="Coche" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">Cómo acceder</td>
</tr>
<tr>
<td align="justify" class="subheading" style="color: #555555">
<strong> En coche</strong>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">
<p>
<br/>
<a href="https://www.google.com/maps/dir/?api=1&destination=${object.company_id.property_name}%20${object.company_id.street}%20${object.company_id.city}">Pulse en este enlace, para conocer como llegar desde su ubicación actual</a>
</p>
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.
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Article with thumbnail-->
<tr>
<td class="borderbottom" style="padding: 30px 0px 0px 0px;">
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="10%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td height="115" style="padding: 20px 0px 0px 30px;">
<img src="https://www.aldahotels.es/firma/email/llegada/bus.png" width="116" height="115" border="0" alt="Bus" />
</td>
</tr>
</table>
</td>
<td width="90%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 30px 20px 30px;">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">Cómo acceder</td>
</tr>
<tr>
<td class="subheading" style="color: #555555">
<strong> En autobús o tren</strong>
</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;"> 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.<br /><br />
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. <br />
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--Block Layout-->
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" width="650" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer" style = "background-color:#f9f9f9">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" width="50%" background-color="#f9f9f9">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 30px 30px 20px 30px; height: 230px;" height="230px">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="justify" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 15px 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">${object.company_id.city}</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;">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.</td>
</tr>
<tr>
<td style="padding: 20px 0 20px 0; background-color:#F9F9F9">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<div style="border: 2px solid #C50967; border-radius: 3px; -webkit-border-radius: 3px; -moz-border-radius: 3px;">
<a href="http://www.santiagoturismo.com/" target="_blank" style="font-size: 12px; font-family: sans-serif; text-transform: uppercase; color: #C50967; border-color: #C50967; border: 2px solid #C50967; background-color: #ffffff; text-decoration: none; border-top: 12px solid #ffffff; border-bottom: 12px solid #ffffff; border-left: 12px solid #ffffff; border-right: 12px solid #ffffff; padding: 0 0 0 0; display: inline-block;">Más información</a>
</div>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" width="50%" height="280px">
<img src="https://www.aldahotels.es/firma/email/llegada/aa.jpg" alt="" width="330" border="0" />
</td>
</tr>
</table>
</td>
</tr>
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" width="50%" height="280px">
<img src="https://www.aldahotels.es/firma/email/llegada/aa2.jpg" width="330" border="0" alt=""/>
</td>
</tr>
</table>
</td>
<td width="50%" class="columncontainer" style = "background-color:#C50967">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" width="50%" >
<table border="0" cellpadding="0" cellspacing="0" width="100%" >
<tr>
<td valign="top" style="padding: 30px 30px 20px 30px; height: 230px; background-color:#C50967 ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="justify" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #FFFFFF; padding: 15px 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">${object.company_id.property_name}</td>
</tr>
<tr>
<td align="justify" class="paragraph" style="padding: 10px 0 0 0;color: #FFFFFF;">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.</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--2 Column-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 0px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="50%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #C50967; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;">NUESTRAS REDES SOCIALES</td>
<td style="padding: 0 0 50px 0;"> </td>
</tr>
<tr>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--3 Column-->
<tr>
<td class="borderbottom" style="padding: 10px 0px 0px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;">
<a href="https://www.facebook.com/aldahotels" target="_blank">
<img src="http://www.aldahotels.es/firma/email/llegada/fb.png" width="51" height="50" border="0" alt="Facebook" />
</a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #3B5998; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Facebook</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#3B5998" height="5px" style="font-size: 1px; line-height: 5px;">
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.facebook.com/aldahotels" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Dale a Me gusta</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;">
<a href="https://www.instagram.com/aldahotels" target="_blank">
<img src="http://www.aldahotels.es/firma/email/llegada/ig.png" width="50" height="50" border="0" alt="Instagram" />
</a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #E56459; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Instagram</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#E56459" height="5px" style="font-size: 1px; line-height: 5px;">
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.instagram.com/aldahotels/" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">#Entra
</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
<td width="33%" class="columncontainer">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="padding: 20px 15px 15px 0px;">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" style="padding: 0 0 20px 0;">
<a href="https://www.twitter.com/aldahotels" target="_blank">
<img src="http://www.aldahotels.es/firma/email/llegada/tw.png" width="50" height="50" border="0" alt="Twitter" />
</a>
</td>
</tr>
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #1DA1F2; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> Twitter</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 10px 0;">
<table width="50px" style="width: 50px;" height="5px" border="0" cellspacing="0" cellpadding="0" >
<tr>
<td background-color="#1DA1F2" height="5px" style="font-size: 1px; line-height: 5px;">
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" class="paragraph" style="padding: 10px 0 0 0;"> Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.</td>
</tr>
<tr>
<td align="center" style="padding: 20px 0 20px 0; ">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center">
<!--[if gte mso 12>
<div style="border: 2px solid #5e96ea;">
<![endif]-->
<a href="https://www.twitter.com/aldahotels" target="_blank" style="text-decoration: none; color: #FFFFFF; font-size: 20px; padding: 10px 20px 10px 20px;">
<div style="padding: 0.5em; background-color: #C50967; border-color: #C50967; border-width: 2px;border-style:solid; border-bottom-style: solid;border-left-style: solid;border-right-style: solid;border-top-style: solid;-webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px;">Síguenos
</div>
</a>
<!--[if gte mso 12>
</div>
<![endif]-->
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!--2 Column-->
<tr>
</tr>
<!--1 Column-->
<tr>
<td style="padding: 30px 30px 20px 30px;background-color:#C50967">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" class="h2" style="font-family: sans-serif; font-size: 18px; font-weight: bold; color: #FFFFFF; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;"> ¡Muchas gracias por alojarte con nosotros!</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
<tr>
<td align="center" valign="middle" class="borderbottom" style="padding: 45px 0px 30px 30px;">
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
</tr>
</table>
<img src="https://www.aldahotels.es/firma/email/llegada/logoft.png" width="300" height="75" alt="Alda Hotels"/>
<table border="0" cellpadding="0" cellspacing="0" width="620" class="columns">
<tr valign="top">
</tr>
</table>
</td>
</tr>
<!--Footer-->
<tr>
<td class="footer">
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="justify" class="smalltext" style="color: #828282; padding: 20px 20px 20px 20px;">En cumplimiento de la Ley 34/2002 de Servicios de la Sociedad de la Información y del Comercio Electrónico, así como de la Ley Orgánica 15/1999 del 13 de Diciembre de Protección de Datos de Carácter Personal y demás legislación concordante, se le informa que sus datos personales figuran en un fichero automatizado cuya responsabilidad es de ALDA COMPOSTELA S.L. Praza da Algalia de Arriba, 3 C.P. 15704 Santiago de Compostela. Los datos personales que existen en nuestro poder están protegidos por nuestra Política de Privacidad y solo serán utilizados para los fines propios de nuestra actividad. Para ejercer sus derechos de acceso, rectificación, cancelación u oposición debe enviar un correo electrónico a info@aldahotels.com indicándonos la opción a realizar. Este correo podría ser confidencial. Si recibe este e-mail por error, por favor elimínelo, así como cualquier documento adjunto, y notifíquelo a su emisor. Si usted no es el destinatario del mensaje, sepa que no está permitida ninguna difusión, copia o utilización no autorizada.</td>
</tr>
<tr>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="center" valign="middle" class="listitem" style="color: #0A5F19; padding: 0px 0px 0px 0px">
<img src="https://www.aldahotels.es/firma/email/llegada/eco.png" width="30" height="30" alt="ECO" />
<strong>Antes de imprimir este mensaje, compruebe que es verdaderamente necesario. El medioambiente es cosa de todos. </strong>
</td>
</tr>
<tr>
</tr>
</table>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
</table>
</div>
]]>
</field>
</record>
</data>
</odoo>

BIN
hotel/data/hotel-color.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

BIN
hotel/data/hotel-grey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

192
hotel/data/hotel_data.xml Normal file
View File

@@ -0,0 +1,192 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Floor -->
<record id="hotel_floor_ground0" model="hotel.floor">
<field eval="&quot;Ground&quot;" name="name"/>
</record>
<record id="hotel_floor_first0" model="hotel.floor">
<field eval="&quot;First&quot;" name="name"/>
<field eval="1" name="sequence"/>
</record>
<record id="hotel_floor_second0" model="hotel.floor">
<field eval="&quot;Second&quot;" name="name"/>
<field eval="2" name="sequence"/>
</record>
<record id="hotel_floor_third0" model="hotel.floor">
<field eval="&quot;Third&quot;" name="name"/>
<field eval="3" name="sequence"/>
</record>
<!-- hotel_room_amenities_type -->
<record id="hotel_room_amenities_type_0" model="hotel.room.amenities.type">
<field name="name">All Aminities</field>
<field name="isamenitytype">1</field>
</record>
<record id="hotel_room_amenities_type_1" model="hotel.room.amenities.type">
<field name="parent_id" search="[('isamenitytype','=',True), ('name', '=', 'All Aminities')]"/>
<field name="name">Beds</field>
<field name="isamenitytype">1</field>
</record>
<record id="hotel_room_amenities_type_2" model="hotel.room.amenities.type">
<field name="parent_id" search="[('isamenitytype','=',True), ('name', '=', 'All Aminities')]"/>
<field name="name">Tables</field>
<field name="isamenitytype">1</field>
</record>
<record id="hotel_room_amenities_type_3" model="hotel.room.amenities.type">
<field name="parent_id" search="[('isamenitytype','=',True), ('name', '=', 'Beds')]"/>
<field name="name">Single Bed</field>
<field name="isamenitytype">1</field>
</record>
<record id="hotel_room_amenities_type_4" model="hotel.room.amenities.type">
<field name="parent_id" search="[('isamenitytype','=',True), ('name', '=', 'Beds')]"/>
<field name="name">Double Bed</field>
<field name="isamenitytype">1</field>
</record>
<!-- hotel_room_type -->
<record id="rooms" model="hotel.room.type">
<field name="name">All Rooms</field>
<field name="code_type">ALL</field>
<field name="isroomtype">1</field>
</record>
<record id="hotel_room_type_1" model="hotel.room.type">
<!-- <field name="parent_id" search="[('isroomtype','=',True), ('name', '=', 'All Rooms')]"/> -->
<field name="name">Single</field>
<field name="code_type">SNG</field>
<field name="isroomtype">1</field>
</record>
<record id="hotel_room_type_2" model="hotel.room.type">
<!-- <field name="parent_id" search="[('isroomtype','=',True), ('name', '=', 'All Rooms')]"/> -->
<field name="name">Double</field>
<field name="code_type">DBL</field>
<field name="isroomtype">1</field>
</record>
<record id="hotel_room_type_3" model="hotel.room.type">
<!-- <field name="parent_id" search="[('isroomtype','=',True), ('name', '=', 'All Rooms')]"/> -->
<field name="name">Triple</field>
<field name="code_type">TRP</field>
<field name="isroomtype">1</field>
</record>
<!-- hotel_service_type -->
<record id="hotel_service_type_0" model="hotel.service.type">
<field name="name">All Services</field>
<field name="isservicetype">1</field>
</record>
<record id="hotel_service_type_1" model="hotel.service.type">
<field name="parent_id" search="[('isservicetype','=',True), ('name', '=', 'All Services')]"/>
<field name="isservicetype">1</field>
<field name="name">Fixed</field>
</record>
<record id="hotel_service_type_2" model="hotel.service.type">
<field name="parent_id" search="[('isservicetype','=',True), ('name', '=', 'All Services')]"/>
<field name="isservicetype">1</field>
<field name="name">Variable</field>
</record>
<!-- hotel_room -->
<record id="hotel_room_0" model="hotel.room">
<field name="name">Single-101</field>
<field name="isroom">1</field>
<!-- <field name="categ_id" search="[('isroomtype','=',True), ('name', '=', 'Single')]"/> -->
<field name="list_price">100.00</field>
<field name="capacity">2</field>
</record>
<record id="hotel_room_1" model="hotel.room">
<field name="name">Single-102</field>
<field name="isroom">1</field>
<!-- <field name="categ_id" search="[('isroomtype','=',True), ('name', '=', 'Single')]"/> -->
<field name="list_price">100.00</field>
<field name="capacity">2</field>
</record>
<record id="hotel_room_2" model="hotel.room">
<field name="name">Single-103</field>
<field name="isroom">1</field>
<!-- <field name="categ_id" search="[('isroomtype','=',True), ('name', '=', 'Single')]"/> -->
<field name="list_price">100.00</field>
<field name="capacity">2</field>
</record>
<record id="hotel_room_3" model="hotel.room">
<field name="name">Double-201</field>
<field name="isroom">1</field>
<!-- <field name="categ_id" search="[('isroomtype','=',True), ('name', '=', 'Double')]"/> -->
<field name="list_price">200.00</field>
<field name="capacity">5</field>
</record>
<record id="hotel_room_4" model="hotel.room">
<field name="name">Double-202</field>
<field name="isroom">1</field>
<!-- <field name="categ_id" search="[('isroomtype','=',True), ('name', '=', 'Double')]"/> -->
<field name="list_price">200.00</field>
<field name="capacity">5</field>
</record>
<record id="hotel_room_5" model="hotel.room">
<field name="name">Double-203</field>
<field name="isroom">1</field>
<!-- <field name="categ_id" search="[('isroomtype','=',True), ('name', '=', 'Double')]"/> -->
<field name="list_price">200.00</field>
<field name="capacity">5</field>
</record>
<record id="hotel_room_6" model="hotel.room">
<field name="name">Triple-101</field>
<field name="isroom">1</field>
<!-- <field name="categ_id" search="[('isroomtype','=',True), ('name', '=', 'Triple')]"/> -->
<field name="list_price">300.00</field>
<field name="capacity">6</field>
</record>
<record id="hotel_room_61" model="hotel.room">
<field name="name">Triple-102</field>
<field name="isroom">1</field>
<!-- <field name="categ_id" search="[('isroomtype','=',True), ('name', '=', 'Triple')]"/> -->
<field name="list_price">300.00</field>
<field name="capacity">6</field>
</record>
<!-- hotel_services -->
<record id="hotel_service_6" model="hotel.services">
<field name="name">Internet</field>
<field name="isservice">1</field>
<field name="categ_id" search="[('isservicetype','=',True), ('name', '=', 'Fixed')]"/>
<field name="list_price">200.00</field>
</record>
<record id="hotel_service_0" model="hotel.services">
<field name="name">Taxi</field>
<field name="isservice">1</field>
<field name="categ_id" search="[('isservicetype','=',True), ('name', '=', 'Variable')]"/>
<field name="list_price">500.00</field>
</record>
<record id="hotel_service_1" model="hotel.services">
<field name="name">Laundry</field>
<field name="isservice">1</field>
<field name="categ_id" search="[('isservicetype','=',True), ('name', '=', 'Fixed')]"/>
<field name="list_price">150.00</field>
</record>
<!--Amenities-->
<record id="hotel_room_amenities_1" model="hotel.room.amenities">
<field name="name">Toiletries</field>
<field name="iscategid">1</field>
<field name="categ_id" search="[('isamenitytype','=',True), ('name', '=', 'All Aminities')]"/>
</record>
<record id="hotel_room_amenities_2" model="hotel.room.amenities">
<field name="name">Iron</field>
<field name="iscategid">1</field>
<field name="categ_id" search="[('isamenitytype','=',True), ('name', '=', 'All Aminities')]"/>
</record>
<record id="hotel_room_amenities_3" model="hotel.room.amenities">
<field name="name">Irons Boards</field>
<field name="iscategid">1</field>
<field name="categ_id" search="[('isamenitytype','=',True), ('name', '=', 'All Aminities')]"/>
</record>
</data>
</odoo>

28
hotel/data/menus.xml Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="hotel_management_menu" name="Hotel Management"
sequence="8" web_icon="hotel,static/description/icon.png"
groups="hotel.group_hotel_user,hotel.group_hotel_call"/>
<menuitem id="hotel_configuration_menu" name="Configuration"
sequence="20" parent="hotel_management_menu"
groups="hotel.group_hotel_user"/>
<menuitem id="hotel_reports_menu" name="Reports"
sequence="15" parent="hotel_management_menu"
groups="hotel.group_hotel_user"/>
<menuitem id="menu_account_finance_xls_reports" name="XLS Reports" parent="hotel.hotel_reports_menu" sequence="50"/>
<menuitem id="cash_daily_report_wizard" name="Cash Daily Report Wizard"
parent="hotel.menu_account_finance_xls_reports"
action="cash_daily_report.action_open_cash_daily_report_wizard" sequence="1" />
<menuitem id="configuration_others" name="Configuration"
parent="hotel.hotel_configuration_menu" sequence="10"
groups="hotel.group_hotel_manager" />
<menuitem id="hotel_massive_change" parent="hotel.configuration_others"
sequence="10" action="action_hotel_massive_change" name="Massive Changes"/>
</odoo>

26
hotel/data/records.xml Normal file
View File

@@ -0,0 +1,26 @@
<odoo>
<data noupdate="1">
<record model="hotel.virtual.room.restriction">
<field name="name">Default Restrictions</field>
</record>
<function id="default_generate_parity_pricelist_id"
model="ir.default" name="set"
eval="('res.config.settings', 'parity_pricelist_id', 1)"/>
<function id="default_generate_parity_restrictions_id"
model="ir.default" name="set"
eval="('res.config.settings', 'parity_restrictions_id', 1)"/>
<function id="default_generate_default_arrival_hour"
model="ir.default" name="set"
eval="('res.config.settings', 'default_arrival_hour', '14:00')"/>
<function id="default_generate_default_departure_hour"
model="ir.default" name="set"
eval="('res.config.settings', 'default_departure_hour', '12:00')"/>
</data>
</odoo>

206
hotel/date_utils.py Normal file
View File

@@ -0,0 +1,206 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime, timedelta
from dateutil import tz
from openerp.tools import (
DEFAULT_SERVER_DATETIME_FORMAT,
DEFAULT_SERVER_DATE_FORMAT)
from openerp import fields, _
from openerp.exceptions import ValidationError
# Generate a 'datetime' object from 'str_date' string with 'dtformat' format.
def _generate_datetime(str_date, dtformat, stz=False):
ndate = False
try:
ndate = datetime.strptime(str_date, dtformat)
ndate = ndate.replace(tzinfo=tz.gettz(stz and str(stz) or 'UTC'))
except ValueError:
return False
return ndate
# Try generate a 'datetime' object from 'str_date' string
# using all odoo formats
def get_datetime(str_date, dtformat=False, hours=True, end_day=False,
stz=False):
if dtformat:
date_dt = _generate_datetime(str_date, dtformat, stz=stz)
else:
date_dt = _generate_datetime(
str_date,
DEFAULT_SERVER_DATETIME_FORMAT,
stz=stz)
if not date_dt:
date_dt = _generate_datetime(
str_date,
DEFAULT_SERVER_DATE_FORMAT,
stz=stz)
if date_dt:
if end_day:
date_dt = dt_no_hours(date_dt, end_day=True)
elif not hours:
date_dt = dt_no_hours(date_dt)
return date_dt
# Compare two dates
def date_compare(str_date_a, str_date_b, hours=True):
date_dt_a = get_datetime(str_date_a)
date_dt_b = get_datetime(str_date_b)
if not hours:
date_dt_a = dt_no_hours(date_dt_a)
date_dt_b = dt_no_hours(date_dt_b)
return date_dt_a == date_dt_b
# Set hours to zero
def dt_no_hours(new_start_date_dt, end_day=False):
if not end_day:
return new_start_date_dt.replace(hour=0, minute=0, second=0,
microsecond=0)
else:
return new_start_date_dt.replace(hour=23, minute=59, second=59,
microsecond=999999)
# Get now 'datetime' object
def now(hours=False):
now_utc_dt = fields.datetime.now().replace(tzinfo=tz.tzutc())
if not hours:
now_utc_dt = now_utc_dt.replace(hour=0, minute=0, second=0,
microsecond=0)
return now_utc_dt
# Get the difference in days between 'str_date_start' and 'str_date_end'
def date_diff(date_start, date_end, hours=True, stz=False):
if not isinstance(date_start, datetime):
date_start_dt = get_datetime(date_start, stz=stz)
else:
date_start_dt = date_start
if not isinstance(date_end, datetime):
date_end_dt = get_datetime(date_end, stz=stz)
else:
date_end_dt = date_end
if not date_start_dt or not date_end_dt:
raise ValidationError(_("Invalid date. Can't compare it!"))
if not hours:
date_start_dt = dt_no_hours(date_start_dt)
date_end_dt = dt_no_hours(date_end_dt)
return abs((date_end_dt - date_start_dt).days)
# Get a new 'datetime' object from 'date_dt' usign the 'tz' timezone
def dt_as_timezone(date_dt, stz):
return date_dt.astimezone(tz.gettz(stz and str(stz) or 'UTC'))
# Generate a list of days start in 'cdate'
def generate_dates_list(cdate,
num_days,
outformat=DEFAULT_SERVER_DATE_FORMAT, stz=False):
ndate = get_datetime(cdate, stz=stz) if not isinstance(cdate, datetime) \
else cdate
return [(ndate + timedelta(days=i)).strftime(outformat)
for i in range(0, num_days)]
# Check if 'str_date' is between 'str_start_date' and 'str_end_date'
# 0 Inside
# -1 'str_date' is before 'str_start_date'
# 1 'str_date' is after 'str_end_date'
def date_in(str_date, str_start_date, str_end_date, hours=True, stz=False):
if not isinstance(str_date, datetime):
date_dt = get_datetime(str_date, stz=stz)
else:
date_dt = str_date
if not isinstance(str_start_date, datetime):
date_start_dt = get_datetime(str_date_start, stz=stz)
else:
date_start_dt = str_start_date
if not isinstance(str_end_date, datetime):
date_end_dt = get_datetime(str_end_date, stz=stz)
else:
date_end_dt = str_end_date
if not date_start_dt or not date_end_dt or not date_dt:
raise ValidationError(_("Invalid date. Can't compare it!"))
if not hours:
date_start_dt = dt_no_hours(date_start_dt)
date_end_dt = dt_no_hours(date_end_dt)
res = -2
if date_dt >= date_start_dt and date_dt <= date_end_dt:
res = 0
elif date_dt > date_end_dt:
res = 1
elif date_dt < date_start_dt:
res = -1
return res
# Check if 'str_start_date_a' and 'str_start_date_b'
# is between 'str_start_date_b' and 'str_end_date_b'
# 0 Inside
# -1 'str_date' is before 'str_start_date'
# 1 'str_date' is after 'str_end_date'
def range_dates_in(str_start_date_a,
str_end_date_a,
str_start_date_b,
str_end_date_b,
hours=True, stz=False):
date_start_dt_a = get_datetime(str_start_date_a, stz=stz)
date_end_dt_a = get_datetime(str_end_date_a, stz=stz)
date_start_dt_b = get_datetime(str_start_date_b, stz=stz)
date_end_dt_b = get_datetime(str_end_date_b, stz=stz)
if not date_start_dt_a or not date_end_dt_a \
or not date_start_dt_b or not date_end_dt_b:
raise ValidationError(_("Invalid date. Can't compare it!"))
if not hours:
date_start_dt_b = dt_no_hours(date_start_dt_b)
date_end_dt_b = dt_no_hours(date_end_dt_b)
res = -2
if date_start_dt_a >= date_start_dt_b and date_end_dt_a <= date_end_dt_b:
res = 0
elif date_start_dt_a < date_start_dt_b \
and date_end_dt_a >= date_start_dt_b:
res = -1
elif date_start_dt_a <= date_end_dt_b and date_end_dt_a > date_end_dt_b:
res = 1
return res

BIN
hotel/i18n/es.mo Normal file

Binary file not shown.

7673
hotel/i18n/es.po Normal file

File diff suppressed because it is too large Load Diff

33
hotel/models/__init__.py Normal file
View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import currency_exchange
#~ from . import folio_room_line
from . import inherit_payment_return
from . import hotel_floor
from . import hotel_folio
from . import hotel_reservation
from . import hotel_room
from . import hotel_room_amenities
from . import hotel_room_amenities_type
from . import hotel_room_type
# from . import hotel_service_line
from . import hotel_service
# from . import hotel_service_type
from . import inherit_account_invoice
# from . import inherit_product_category
from . import inherit_product_product
from . import inherit_res_company
# from . import virtual_room
from . import inherit_account_payment
from . import hotel_virtual_room_restriction
from . import hotel_virtual_room_restriction_item
from . import hotel_reservation_line
from . import cardex
from . import hotel_virtual_room_availability
from . import inherit_product_pricelist
from . import res_config
from . import inherit_res_partner
from . import inherited_mail_compose_message
#~ from . import hotel_dashboard

88
hotel/models/cardex.py Normal file
View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Dario Lodeiros <>
#
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import datetime
from openerp import models, fields, api, _
from openerp.exceptions import except_orm, ValidationError
from odoo.addons.hotel import date_utils
class Cardex(models.Model):
_name = 'cardex'
# Validation for Departure date is after arrival date.
@api.constrains('exit_date')
def validation_dates(self):
if self.exit_date < self.enter_date:
raise models.ValidationError(
_('Departure date (%s) is prior to arrival on %s') %
(self.exit_date, self.enter_date))
def default_reservation_id(self):
if 'reservation_id' in self.env.context:
reservation = self.env['hotel.reservation'].search([
('id', '=', self.env.context['reservation_id'])
])
return reservation
return False
def default_enter_date(self):
if 'reservation_id' in self.env.context:
reservation = self.env['hotel.reservation'].search([
('id', '=', self.env.context['reservation_id'])
])
return reservation.checkin
return False
def default_exit_date(self):
if 'reservation_id' in self.env.context:
reservation = self.env['hotel.reservation'].search([
('id', '=', self.env.context['reservation_id'])
])
return reservation.checkout
return False
def default_partner_id(self):
if 'reservation_id' in self.env.context:
reservation = self.env['hotel.reservation'].search([
('id', '=', self.env.context['reservation_id'])
])
return reservation.partner_id
return False
@api.onchange('enter_date', 'exit_date')
def check_change_dates(self):
if self.exit_date <= self.enter_date:
date_1 = date_utils.get_datetime(self.enter_date)
date_2 = date_1 + datetime.timedelta(days=1)
self.update({'exit_date': date_2, })
raise ValidationError(
_('Departure date, is prior to arrival. Check it now. %s') %
(date_2))
partner_id = fields.Many2one('res.partner', default=default_partner_id,
required=True)
reservation_id = fields.Many2one(
'hotel.reservation',
default=default_reservation_id, readonly=True)
enter_date = fields.Date(default=default_enter_date, required=True)
exit_date = fields.Date(default=default_exit_date, required=True)

View File

@@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from decimal import Decimal
# For Python 3.0 and later
from urllib.request import urlopen
from openerp import models, fields, api, _
class CurrencyExchangeRate(models.Model):
_name = "currency.exchange"
_description = "currency"
name = fields.Char('Reg Number', readonly=True, default='New')
today_date = fields.Datetime('Date Ordered',
required=True,
default=(lambda *a:
time.strftime
(DEFAULT_SERVER_DATETIME_FORMAT)))
input_curr = fields.Many2one('res.currency', string='Input Currency',
track_visibility='always')
in_amount = fields.Float('Amount Taken', size=64, default=1.0)
out_curr = fields.Many2one('res.currency', string='Output Currency',
track_visibility='always')
out_amount = fields.Float('Subtotal', size=64)
folio_no = fields.Many2one('hotel.folio', 'Folio Number')
guest_name = fields.Many2one('res.partner', string='Guest Name')
room_number = fields.Char(string='Room Number')
state = fields.Selection([('draft', 'Draft'), ('done', 'Done'),
('cancel', 'Cancel')], 'State', default='draft')
rate = fields.Float('Rate(per unit)', size=64)
hotel_id = fields.Many2one('stock.warehouse', 'Hotel Name')
type = fields.Selection([('cash', 'Cash')], 'Type', default='cash')
tax = fields.Selection([('2', '2%'), ('5', '5%'), ('10', '10%')],
'Service Tax', default='2')
total = fields.Float('Amount Given')
@api.model
def create(self, vals):
"""
Overrides orm create method.
@param self: The object pointer
@param vals: dictionary of fields value.
"""
if not vals:
vals = {}
if self._context is None:
self._context = {}
seq_obj = self.env['ir.sequence']
vals['name'] = seq_obj.next_by_code('currency.exchange') or 'New'
return super(CurrencyExchangeRate, self).create(vals)
@api.onchange('folio_no')
def get_folio_no(self):
'''
When you change folio_no, based on that it will update
the guest_name,hotel_id and room_number as well
---------------------------------------------------------
@param self: object pointer
'''
for rec in self:
self.guest_name = False
self.hotel_id = False
self.room_number = False
if rec.folio_no and len(rec.folio_no.room_lines) != 0:
self.guest_name = rec.folio_no.partner_id.id
self.hotel_id = rec.folio_no.warehouse_id.id
self.room_number = rec.folio_no.room_lines[0].product_id.name
@api.multi
def act_cur_done(self):
"""
This method is used to change the state
to done of the currency exchange
---------------------------------------
@param self: object pointer
"""
self.write({'state': 'done'})
return True
@api.multi
def act_cur_cancel(self):
"""
This method is used to change the state
to cancel of the currency exchange
---------------------------------------
@param self: object pointer
"""
self.write({'state': 'cancel'})
return True
@api.multi
def act_cur_cancel_draft(self):
"""
This method is used to change the state
to draft of the currency exchange
---------------------------------------
@param self: object pointer
"""
self.write({'state': 'draft'})
return True
@api.model
def get_rate(self, a, b):
'''
Calculate rate between two currency
-----------------------------------
@param self: object pointer
'''
try:
url = 'http://finance.yahoo.com/d/quotes.csv?s=%s%s=X&f=l1' % (a,
b)
rate = urllib2.urlopen(url).read().rstrip()
return Decimal(rate)
except:
return Decimal('-1.00')
@api.onchange('input_curr', 'out_curr', 'in_amount')
def get_currency(self):
'''
When you change input_curr, out_curr or in_amount
it will update the out_amount of the currency exchange
------------------------------------------------------
@param self: object pointer
'''
self.out_amount = 0.0
if self.input_curr:
for rec in self:
result = rec.get_rate(self.input_curr.name,
self.out_curr.name)
if self.out_curr:
self.rate = result
if self.rate == Decimal('-1.00'):
raise except_orm(_('Warning'),
_('Please Check Your \
Network Connectivity.'))
self.out_amount = (float(result) * float(self.in_amount))
@api.onchange('out_amount', 'tax')
def tax_change(self):
'''
When you change out_amount or tax
it will update the total of the currency exchange
-------------------------------------------------
@param self: object pointer
'''
if self.out_amount:
ser_tax = ((self.out_amount) * (float(self.tax))) / 100
self.total = self.out_amount - ser_tax

View File

@@ -0,0 +1,251 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import json
from datetime import datetime, timedelta
from babel.dates import format_datetime, format_date
from odoo import models, api, _, fields
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
from odoo.tools.misc import formatLang
class HotelDashboard(models.Model):
_name = "hotel.dashboard"
def _get_count(self):
resevations_count = self.env['hotel.reservation'].search(
[('sate', '=', 'confirm')])
folios_count = self.env['hotel.folio'].search(
[('sate', '=', 'sales_order')])
next_arrivals_count = self.env['hotel.reservation'].search(
[('is_checkin', '=', True)])
self.orders_count = len(orders_count)
self.quotations_count = len(quotations_count)
self.orders_done_count = len(orders_done_count)
@api.one
def _kanban_dashboard(self):
if self.graph_type == 'bar':
self.kanban_dashboard_graph = json.dumps(self.get_bar_graph_datas())
elif self.graph_type == 'line':
self.kanban_dashboard_graph = json.dumps(self.get_line_graph_datas())
@api.one
def _kanban_dashboard_graph(self):
self.kanban_dashboard_graph = json.dumps(self.get_bar_graph_datas())
#~ if (self.type in ['sale', 'purchase']):
#~ self.kanban_dashboard_graph = json.dumps(self.get_bar_graph_datas())
#~ elif (self.type in ['cash', 'bank']):
#~ self.kanban_dashboard_graph = json.dumps(self.get_line_graph_datas())
color = fields.Integer(string='Color Index')
name = fields.Char(string="Name")
type = fields.Char(default="sale")
graph_type = fields.Selection([('line','Line'),('bar','Bar'),('none','None')])
reservations_count = fields.Integer(compute = '_get_count')
folios_count = fields.Integer(compute= '_get_count')
next_arrivals_count = fields.Integer(compute= '_get_count')
kanban_dashboard = fields.Text(compute='_kanban_dashboard')
kanban_dashboard_graph = fields.Text(compute='_kanban_dashboard_graph')
show_on_dashboard = fields.Boolean(string='Show journal on dashboard', help="Whether this journal should be displayed on the dashboard or not", default=True)
@api.multi
def get_bar_graph_datas(self):
data = []
today = datetime.strptime(fields.Date.context_today(self), DF)
day_of_week = int(format_datetime(today, 'e', locale=self._context.get('lang') or 'en_US'))
for i in range(0,15):
if i==0:
label = _('Today')
else:
label = format_date(today + timedelta(days=i) , 'd', locale=self._context.get('lang') or 'en_US')
data.append({'label':label,'value':0.0, 'type': 'past' if i<0 else 'future'})
# Build SQL query to find amount aggregated by week
select_sql_clause = """SELECT count(id) as total from hotel_reservation where state != 'cancelled'"""
query = "("+select_sql_clause+" and date(checkin) = '"+today.strftime(DF)+"')"
for i in range(1,15):
next_date = today + timedelta(days=i)
query += " UNION ALL ("+select_sql_clause+" and date(checkin) = '"+next_date.strftime(DF)+"')"
self.env.cr.execute(query)
query_results = self.env.cr.dictfetchall()
for index in range(0, len(query_results)):
data[index]['value'] = query_results[index].get('total')
return [{'values': data}]
@api.multi
def get_journal_dashboard_datas(self):
#~ currency = self.currency_id or self.company_id.currency_id
#~ number_to_reconcile = last_balance = account_sum = 0
#~ ac_bnk_stmt = []
#~ title = ''
#~ number_draft = number_waiting = number_late = 0
#~ sum_draft = sum_waiting = sum_late = 0.0
#~ if self.type in ['bank', 'cash']:
#~ last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1)
#~ last_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0
#~ #Get the number of items to reconcile for that bank journal
#~ self.env.cr.execute("""SELECT COUNT(DISTINCT(statement_line_id))
#~ FROM account_move where statement_line_id
#~ IN (SELECT line.id
#~ FROM account_bank_statement_line AS line
#~ LEFT JOIN account_bank_statement AS st
#~ ON line.statement_id = st.id
#~ WHERE st.journal_id IN %s and st.state = 'open')""", (tuple(self.ids),))
#~ already_reconciled = self.env.cr.fetchone()[0]
#~ self.env.cr.execute("""SELECT COUNT(line.id)
#~ FROM account_bank_statement_line AS line
#~ LEFT JOIN account_bank_statement AS st
#~ ON line.statement_id = st.id
#~ WHERE st.journal_id IN %s and st.state = 'open'""", (tuple(self.ids),))
#~ all_lines = self.env.cr.fetchone()[0]
#~ number_to_reconcile = all_lines - already_reconciled
#~ # optimization to read sum of balance from account_move_line
#~ account_ids = tuple(filter(None, [self.default_debit_account_id.id, self.default_credit_account_id.id]))
#~ if account_ids:
#~ amount_field = 'balance' if (not self.currency_id or self.currency_id == self.company_id.currency_id) else 'amount_currency'
#~ query = """SELECT sum(%s) FROM account_move_line WHERE account_id in %%s AND date <= %%s;""" % (amount_field,)
#~ self.env.cr.execute(query, (account_ids, fields.Date.today(),))
#~ query_results = self.env.cr.dictfetchall()
#~ if query_results and query_results[0].get('sum') != None:
#~ account_sum = query_results[0].get('sum')
#~ #TODO need to check if all invoices are in the same currency than the journal!!!!
#~ elif self.type in ['sale', 'purchase']:
#~ title = _('Bills to pay') if self.type == 'purchase' else _('Invoices owed to you')
#~ # optimization to find total and sum of invoice that are in draft, open state
#~ query = """SELECT state, amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %s AND state NOT IN ('paid', 'cancel');"""
#~ self.env.cr.execute(query, (self.id,))
#~ query_results = self.env.cr.dictfetchall()
#~ today = datetime.today()
#~ query = """SELECT amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %s AND date < %s AND state = 'open';"""
#~ self.env.cr.execute(query, (self.id, today))
#~ late_query_results = self.env.cr.dictfetchall()
#~ for result in query_results:
#~ if result['type'] in ['in_refund', 'out_refund']:
#~ factor = -1
#~ else:
#~ factor = 1
#~ cur = self.env['res.currency'].browse(result.get('currency'))
#~ if result.get('state') in ['draft', 'proforma', 'proforma2']:
#~ number_draft += 1
#~ sum_draft += cur.compute(result.get('amount_total'), currency) * factor
#~ elif result.get('state') == 'open':
#~ number_waiting += 1
#~ sum_waiting += cur.compute(result.get('amount_total'), currency) * factor
#~ for result in late_query_results:
#~ if result['type'] in ['in_refund', 'out_refund']:
#~ factor = -1
#~ else:
#~ factor = 1
#~ cur = self.env['res.currency'].browse(result.get('currency'))
#~ number_late += 1
#~ sum_late += cur.compute(result.get('amount_total'), currency) * factor
#~ difference = currency.round(last_balance-account_sum) + 0.0
return {
'graph': self.graph_type,
'number_to_reconcile': 11,
'account_balance': 4314,
'last_balance': 252,
'difference': 432,
'number_draft': 32,
'number_waiting': 44,
'number_late': 23,
'sum_draft': 2424245,
'sum_waiting': 3124312,
'sum_late': 23123,
'currency_id': 1,
'bank_statements_source': 'fonte',
'title': 'titulo',
}
@api.multi
def get_line_graph_datas(self):
data = []
today = datetime.strptime(fields.Date.context_today(self), DF)
days=30
for i in range(-1, days + 1):
ndate = today + timedelta(days=i)
ndate_str = ndate.strftime(DF)
day_onboard = self.env['hotel.reservation.line'].search_count([('date','=',ndate)])
locale = self._context.get('lang') or 'en_US'
short_name = format_date(ndate, 'd', locale=locale)
name = format_date(ndate, 'd LLLL Y', locale=locale)
data.append({'x':short_name,'y':day_onboard, 'name':name})
return [{'values': data, 'area': True}]
@api.multi
def action_create_new(self):
#~ ctx = self._context.copy()
#~ model = 'account.invoice'
#~ if self.type == 'sale':
#~ ctx.update({'journal_type': self.type, 'default_type': 'out_invoice', 'type': 'out_invoice', 'default_journal_id': self.id})
#~ if ctx.get('refund'):
#~ ctx.update({'default_type':'out_refund', 'type':'out_refund'})
#~ view_id = self.env.ref('account.invoice_form').id
#~ elif self.type == 'purchase':
#~ ctx.update({'journal_type': self.type, 'default_type': 'in_invoice', 'type': 'in_invoice', 'default_journal_id': self.id})
#~ if ctx.get('refund'):
#~ ctx.update({'default_type': 'in_refund', 'type': 'in_refund'})
#~ view_id = self.env.ref('account.invoice_supplier_form').id
#~ else:
#~ ctx.update({'default_journal_id': self.id})
#~ view_id = self.env.ref('account.view_move_form').id
#~ model = 'account.move'
model = "hotel.folio"
view_id = self.env.ref('hotel.view_hotel_folio1_form').id
ctx=''
return {
'name': _('Create invoice/bill'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': model,
'view_id': view_id,
'context': ctx,
}
@api.multi
def open_action(self):
"""return action based on type for related journals"""
#~ action_name = self._context.get('action_name', False)
#~ if not action_name:
#~ if self.type == 'bank':
#~ action_name = 'action_bank_statement_tree'
#~ elif self.type == 'cash':
#~ action_name = 'action_view_bank_statement_tree'
#~ elif self.type == 'sale':
#~ action_name = 'action_invoice_tree1'
#~ elif self.type == 'purchase':
#~ action_name = 'action_invoice_tree2'
#~ else:
#~ action_name = 'action_move_journal_line'
#~ _journal_invoice_type_map = {
#~ ('sale', None): 'out_invoice',
#~ ('purchase', None): 'in_invoice',
#~ ('sale', 'refund'): 'out_refund',
#~ ('purchase', 'refund'): 'in_refund',
#~ ('bank', None): 'bank',
#~ ('cash', None): 'cash',
#~ ('general', None): 'general',
#~ }
#~ invoice_type = _journal_invoice_type_map[(self.type, self._context.get('invoice_type'))]
#~ ctx = self._context.copy()
#~ ctx.pop('group_by', None)
#~ ctx.update({
#~ 'journal_type': self.type,
#~ 'default_journal_id': self.id,
#~ 'search_default_journal_id': self.id,
#~ 'default_type': invoice_type,
#~ 'type': invoice_type
#~ })
#~ [action] = self.env.ref('account.%s' % action_name).read()
#~ action['context'] = ctx
#~ action['domain'] = self._context.get('use_domain', [])
#~ if action_name in ['action_bank_statement_tree', 'action_view_bank_statement_tree']:
#~ action['views'] = False
#~ action['view_id'] = False
return False

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Dario Lodeiros <>
#
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, fields, api, _
class HotelFloor(models.Model):
_name = "hotel.floor"
_description = "Ubication"
name = fields.Char('Ubication Name', size=64, required=True, index=True)
sequence = fields.Integer('Sequence', size=64)

884
hotel/models/hotel_folio.py Normal file
View File

@@ -0,0 +1,884 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import datetime
import time
import pytz
import logging
from decimal import Decimal
from dateutil.relativedelta import relativedelta
from odoo.exceptions import except_orm, UserError, ValidationError
from odoo.tools import (
misc,
DEFAULT_SERVER_DATETIME_FORMAT,
DEFAULT_SERVER_DATE_FORMAT)
from odoo import models, fields, api, _
from odoo.addons.hotel import date_utils
_logger = logging.getLogger(__name__)
from odoo.addons import decimal_precision as dp
class HotelFolio(models.Model):
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
if args is None:
args = []
args += ([('name', operator, name)])
mids = self.search(args, limit=100)
return mids.name_get()
@api.model
def _needaction_count(self, domain=None):
"""
Show a count of draft state folio on the menu badge.
@param self: object pointer
"""
return self.search_count([('state', '=', 'draft')])
@api.multi
def copy(self, default=None):
'''
@param self: object pointer
@param default: dict of default values to be set
'''
return super(HotelFolio, self).copy(default=default)
@api.multi
def _invoiced(self, name, arg):
'''
@param self: object pointer
@param name: Names of fields.
@param arg: User defined arguments
'''
pass
# return self.env['sale.order']._invoiced(name, arg)
@api.multi
def _invoiced_search(self, obj, name, args):
'''
@param self: object pointer
@param name: Names of fields.
@param arg: User defined arguments
'''
pass
# return self.env['sale.order']._invoiced_search(obj, name, args)
# @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity')
def _get_invoice_qty(self):
pass
# @api.depends('product_id.invoice_policy', 'order_id.state')
def _compute_qty_delivered_updateable(self):
pass
# @api.depends('state', 'order_line.invoice_status')
def _get_invoiced(self):
pass
# @api.depends('state', 'product_uom_qty', 'qty_delivered', 'qty_to_invoice', 'qty_invoiced')
def _compute_invoice_status(self):
pass
# @api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_id')
def _compute_amount(self):
pass
# @api.depends('order_line.price_total')
def _amount_all(self):
pass
_name = 'hotel.folio'
_description = 'Hotel Folio'
_order = 'id'
_inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
name = fields.Char('Folio Number', readonly=True, index=True,
default='New')
partner_id = fields.Many2one('res.partner',
track_visibility='onchange')
# partner_invoice_id = fields.Many2one('res.partner',
# string='Invoice Address',
# readonly=True, required=True,
# states={'draft': [('readonly', False)],
# 'sent': [('readonly', False)]},
# help="Invoice address for current sales order.")
# For being used directly in the Folio views
email = fields.Char('E-mail', related='partner_id.email')
mobile = fields.Char('Mobile', related='partner_id.mobile')
phone = fields.Char('Phone', related='partner_id.phone')
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')
room_lines = fields.One2many('hotel.reservation', 'folio_id',
readonly=False,
states={'done': [('readonly', True)]},
help="Hotel room reservation detail.",)
service_line_ids = fields.One2many('hotel.service', 'folio_id',
readonly=False,
states={'done': [('readonly', True)]},
help="Hotel services detail provide to "
"customer and it will include in "
"main Invoice.")
# service_line_ids = fields.One2many('hotel.service.line', 'folio_id',
# readonly=False,
# states={'done': [('readonly', True)]},
# help="Hotel services detail provide to"
# "customer and it will include in "
# "main Invoice.")
# has no sense used as this way
hotel_invoice_id = fields.Many2one('account.invoice', 'Invoice')
company_id = fields.Many2one('res.company', 'Company')
# currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id',
# string='Currency', readonly=True, required=True)
# pricelist_id = fields.Many2one('product.pricelist',
# string='Pricelist',
# required=True,
# readonly=True,
# states={'draft': [('readonly', False)],
# 'sent': [('readonly', False)]},
# help="Pricelist for current sales order.")
# Monetary to Float
invoices_amount = fields.Float(compute='compute_invoices_amount',
store=True,
string="Pending in Folio")
# Monetary to Float
refund_amount = fields.Float(compute='compute_invoices_amount',
store=True,
string="Payment Returns")
# Monetary to Float
invoices_paid = fields.Float(compute='compute_invoices_amount',
store=True, track_visibility='onchange',
string="Payments")
booking_pending = fields.Integer('Booking pending',
compute='_compute_cardex_count')
cardex_count = fields.Integer('Cardex counter',
compute='_compute_cardex_count')
cardex_pending = fields.Boolean('Cardex Pending',
compute='_compute_cardex_count')
cardex_pending_num = fields.Integer('Cardex Pending',
compute='_compute_cardex_count')
checkins_reservations = fields.Integer('checkins reservations')
checkouts_reservations = fields.Integer('checkouts reservations')
partner_internal_comment = fields.Text(string='Internal Partner Notes',
related='partner_id.comment')
internal_comment = fields.Text(string='Internal Folio Notes')
cancelled_reason = fields.Text('Cause of cancelled')
payment_ids = fields.One2many('account.payment', 'folio_id',
readonly=True)
return_ids = fields.One2many('payment.return', 'folio_id',
readonly=True)
prepaid_warning_days = fields.Integer(
'Prepaid Warning Days',
help='Margin in days to create a notice if a payment \
advance has not been recorded')
reservation_type = fields.Selection([('normal', 'Normal'),
('staff', 'Staff'),
('out', 'Out of Service')],
'Type', default=lambda *a: 'normal')
channel_type = fields.Selection([('door', 'Door'),
('mail', 'Mail'),
('phone', 'Phone'),
('web', 'Web')], 'Sales Channel', default='door')
num_invoices = fields.Integer(compute='_compute_num_invoices')
rooms_char = fields.Char('Rooms', compute='_computed_rooms_char')
segmentation_ids = fields.Many2many('res.partner.category',
string='Segmentation')
has_confirmed_reservations_to_send = fields.Boolean(
compute='_compute_has_confirmed_reservations_to_send')
has_cancelled_reservations_to_send = fields.Boolean(
compute='_compute_has_cancelled_reservations_to_send')
has_checkout_to_send = fields.Boolean(
compute='_compute_has_checkout_to_send')
# fix_price = fields.Boolean(compute='_compute_fix_price')
date_order = fields.Datetime(
string='Order Date',
required=True, readonly=True, index=True,
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
copy=False, default=fields.Datetime.now)
invoice_ids = fields.Many2many('account.invoice', string='Invoices',
compute='_get_invoiced', readonly=True, copy=False)
invoice_status = fields.Selection([('upselling', 'Upselling Opportunity'),
('invoiced', 'Fully Invoiced'),
('to invoice', 'To Invoice'),
('no', 'Nothing to Invoice')],
string='Invoice Status',
compute='_compute_invoice_status',
store=True, readonly=True, default='no')
client_order_ref = fields.Char(string='Customer Reference', copy=False)
note = fields.Text('Terms and conditions')
# layout_category_id = fields.Many2one('sale.layout_category', string='Section')
user_id = fields.Many2one('res.users', string='Salesperson', index=True,
track_visibility='onchange', default=lambda self: self.env.user)
sequence = fields.Integer(string='Sequence', default=10)
# sale.order
amount_total = fields.Float(string='Total', store=True, readonly=True,
track_visibility='always')
def _compute_fix_price(self):
for record in self:
for res in record.room_lines:
if res.fix_total == True:
record.fix_price = True
break
else:
record.fix_price = False
def action_recalcule_payment(self):
for record in self:
for res in record.room_lines:
res.on_change_checkin_checkout_product_id()
def _computed_rooms_char(self):
for record in self:
rooms = ', '.join(record.mapped('room_lines.room_id.name'))
record.rooms_char = rooms
@api.model
def recompute_amount(self):
folios = self.env['hotel.folio']
if folios:
folios = folios.filtered(lambda x: (
x.name == folio_name))
folios.compute_invoices_amount()
@api.multi
def _compute_num_invoices(self):
pass
# for fol in self:
# fol.num_invoices = len(self.mapped('invoice_ids.id'))
@api.model
def daily_plan(self):
_logger.info('daily_plan')
self._cr.execute("update hotel_folio set checkins_reservations = 0, \
checkouts_reservations = 0 where checkins_reservations > 0 \
or checkouts_reservations > 0")
folios_in = self.env['hotel.folio'].search([
('room_lines.is_checkin', '=', True)
])
folios_out = self.env['hotel.folio'].search([
('room_lines.is_checkout', '=', True)
])
for fol in folios_in:
count_checkin = fol.room_lines.search_count([
('is_checkin', '=', True), ('folio_id.id', '=', fol.id)
])
fol.write({'checkins_reservations': count_checkin})
for fol in folios_out:
count_checkout = fol.room_lines.search_count([
('is_checkout', '=', True),
('folio_id.id', '=', fol.id)
])
fol.write({'checkouts_reservations': count_checkout})
return True
# @api.depends('order_line.price_total', 'payment_ids', 'return_ids')
@api.multi
def compute_invoices_amount(self):
_logger.info('compute_invoices_amount')
@api.multi
def action_pay(self):
self.ensure_one()
partner = self.partner_id.id
amount = self.invoices_amount
view_id = self.env.ref('hotel.view_account_payment_folio_form').id
return{
'name': _('Register Payment'),
'view_type': 'form',
'view_mode': 'form',
'res_model': 'account.payment',
'type': 'ir.actions.act_window',
'view_id': view_id,
'context': {
'default_folio_id': self.id,
'default_amount': amount,
'default_payment_type': 'inbound',
'default_partner_type': 'customer',
'default_partner_id': partner,
'default_communication': self.name,
},
'target': 'new',
}
@api.multi
def action_payments(self):
self.ensure_one()
payments_obj = self.env['account.payment']
payments = payments_obj.search([('folio_id','=',self.id)])
payment_ids = payments.mapped('id')
invoices = self.mapped('invoice_ids.id')
return{
'name': _('Payments'),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'account.payment',
'target': 'new',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', payment_ids)],
}
@api.multi
def open_invoices_folio(self):
invoices = self.mapped('invoice_ids')
action = self.env.ref('account.action_invoice_tree1').read()[0]
if len(invoices) > 1:
action['domain'] = [('id', 'in', invoices.ids)]
elif len(invoices) == 1:
action['views'] = [(self.env.ref('account.invoice_form').id, 'form')]
action['res_id'] = invoices.ids[0]
else:
action = {'type': 'ir.actions.act_window_close'}
return action
@api.multi
def action_return_payments(self):
self.ensure_one()
return_move_ids = []
acc_pay_obj = self.env['account.payment']
payments = acc_pay_obj.search([
'|',
('invoice_ids', 'in', self.invoice_ids.ids),
('folio_id', '=', self.id)
])
return_move_ids += self.invoice_ids.filtered(
lambda invoice: invoice.type == 'out_refund').mapped(
'payment_move_line_ids.move_id.id')
return_lines = self.env['payment.return.line'].search([(
'move_line_ids','in',payments.mapped(
'move_line_ids.id'))])
return_move_ids += return_lines.mapped('return_id.move_id.id')
return{
'name': _('Returns'),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'account.move',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', return_move_ids)],
}
@api.multi
def action_checks(self):
self.ensure_one()
rooms = self.mapped('room_lines.id')
return {
'name': _('Cardexs'),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'cardex',
'type': 'ir.actions.act_window',
'domain': [('reservation_id', 'in', rooms)],
'target': 'new',
}
@api.multi
def action_folios_amount(self):
now_utc_dt = date_utils.now()
now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
reservations = self.env['hotel.reservation'].search([
('checkout', '<=', now_utc_str)
])
folio_ids = reservations.mapped('folio_id.id')
folios = self.env['hotel.folio'].search([('id', 'in', folio_ids)])
folios = folios.filtered(lambda r: r.invoices_amount > 0)
return {
'name': _('Pending'),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'hotel.folio',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', folios.ids)]
}
@api.depends('room_lines')
def _compute_has_confirmed_reservations_to_send(self):
has_to_send = False
for rline in self.room_lines:
if rline.splitted:
master_reservation = rline.parent_reservation or rline
has_to_send = self.env['hotel.reservation'].search_count([
('splitted', '=', True),
('folio_id', '=', self.id),
('to_send', '=', True),
('state', 'in', ('confirm', 'booking')),
'|',
('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
]) > 0
elif rline.to_send and rline.state in ('confirm', 'booking'):
has_to_send = True
break
self.has_confirmed_reservations_to_send = has_to_send
@api.depends('room_lines')
def _compute_has_cancelled_reservations_to_send(self):
has_to_send = False
for rline in self.room_lines:
if rline.splitted:
master_reservation = rline.parent_reservation or rline
has_to_send = self.env['hotel.reservation'].search_count([
('splitted', '=', True),
('folio_id', '=', self.id),
('to_send', '=', True),
('state', '=', 'cancelled'),
'|',
('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
]) > 0
elif rline.to_send and rline.state == 'cancelled':
has_to_send = True
break
self.has_cancelled_reservations_to_send = has_to_send
@api.depends('room_lines')
def _compute_has_checkout_to_send(self):
has_to_send = True
for rline in self.room_lines:
if rline.splitted:
master_reservation = rline.parent_reservation or rline
nreservs = self.env['hotel.reservation'].search_count([
('splitted', '=', True),
('folio_id', '=', self.id),
('to_send', '=', True),
('state', '=', 'done'),
'|',
('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
])
if nreservs != len(self.room_lines):
has_to_send = False
elif not rline.to_send or rline.state != 'done':
has_to_send = False
break
self.has_checkout_to_send = has_to_send
@api.multi
def _compute_cardex_count(self):
_logger.info('_compute_cardex_amount')
for fol in self:
num_cardex = 0
pending = False
if fol.reservation_type == 'normal':
for reser in fol.room_lines:
if reser.state != 'cancelled' and \
not reser.parent_reservation:
num_cardex += len(reser.cardex_ids)
fol.cardex_count = num_cardex
pending = 0
for reser in fol.room_lines:
if reser.state != 'cancelled' and \
not reser.parent_reservation:
pending += (reser.adults + reser.children) \
- len(reser.cardex_ids)
if pending <= 0:
fol.cardex_pending = False
else:
fol.cardex_pending = True
fol.cardex_pending_num = pending
@api.multi
def go_to_currency_exchange(self):
'''
when Money Exchange button is clicked then this method is called.
-------------------------------------------------------------------
@param self: object pointer
'''
_logger.info('go_to_currency_exchange')
pass
# cr, uid, context = self.env.args
# context = dict(context)
# for rec in self:
# if rec.partner_id.id and len(rec.room_lines) != 0:
# context.update({'folioid': rec.id, 'guest': rec.partner_id.id,
# 'room_no': rec.room_lines[0].product_id.name})
# self.env.args = cr, uid, misc.frozendict(context)
# else:
# raise except_orm(_('Warning'), _('Please Reserve Any Room.'))
# return {'name': _('Currency Exchange'),
# 'res_model': 'currency.exchange',
# 'type': 'ir.actions.act_window',
# 'view_id': False,
# 'view_mode': 'form,tree',
# 'view_type': 'form',
# 'context': {'default_folio_no': context.get('folioid'),
# 'default_hotel_id': context.get('hotel'),
# 'default_guest_name': context.get('guest'),
# 'default_room_number': context.get('room_no')
# },
# }
@api.model
def create(self, vals, check=True):
"""
Overrides orm create method.
@param self: The object pointer
@param vals: dictionary of fields value.
@return: new record set for hotel folio.
"""
_logger.info('create')
if not 'service_line_ids' and 'folio_id' in vals:
tmp_room_lines = vals.get('room_lines', [])
vals['order_policy'] = vals.get('hotel_policy', 'manual')
vals.update({'room_lines': []})
for line in (tmp_room_lines):
line[2].update({'folio_id': folio_id})
vals.update({'room_lines': tmp_room_lines})
folio_id = super(HotelFolio, self).create(vals)
else:
if not vals:
vals = {}
vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio')
folio_id = super(HotelFolio, self).create(vals)
return folio_id
@api.multi
def write(self, vals):
if 'room_lines' in vals and vals['room_lines'][0][2] and 'reservation_line_ids' in vals['room_lines'][0][2] and vals['room_lines'][0][2]['reservation_line_ids'][0][0] == 5:
del vals['room_lines']
return super(HotelFolio, self).write(vals)
@api.multi
@api.onchange('partner_id')
def onchange_partner_id(self):
'''
When you change partner_id it will update the partner_invoice_id,
partner_shipping_id and pricelist_id of the hotel folio as well
---------------------------------------------------------------
@param self: object pointer
'''
_logger.info('onchange_partner_id')
pass
# self.update({
# 'currency_id': self.env.ref('base.main_company').currency_id,
# 'partner_invoice_id': self.partner_id and self.partner_id.id or False,
# 'partner_shipping_id': self.partner_id and self.partner_id.id or False,
# 'pricelist_id': self.partner_id and self.partner_id.property_product_pricelist.id or False,
# })
# """
# Warning messajes saved in partner form to folios
# """
# if not self.partner_id:
# return
# warning = {}
# title = False
# message = False
# partner = self.partner_id
#
# # If partner has no warning, check its company
# if partner.sale_warn == 'no-message' and partner.parent_id:
# partner = partner.parent_id
#
# if partner.sale_warn != 'no-message':
# # Block if partner only has warning but parent company is blocked
# if partner.sale_warn != 'block' and partner.parent_id \
# and partner.parent_id.sale_warn == 'block':
# partner = partner.parent_id
# title = _("Warning for %s") % partner.name
# message = partner.sale_warn_msg
# warning = {
# 'title': title,
# 'message': message,
# }
# if self.partner_id.sale_warn == 'block':
# self.update({
# 'partner_id': False,
# 'partner_invoice_id': False,
# 'partner_shipping_id': False,
# 'pricelist_id': False
# })
# return {'warning': warning}
#
# if warning:
# return {'warning': warning}
@api.multi
def button_dummy(self):
'''
@param self: object pointer
'''
# for folio in self:
# folio.order_id.button_dummy()
return True
@api.multi
def action_done(self):
for line in self.room_lines:
if line.state == "booking":
line.action_reservation_checkout()
@api.multi
def action_invoice_create(self, grouped=False, states=None):
'''
@param self: object pointer
'''
pass
# if states is None:
# states = ['confirmed', 'done']
# order_ids = [folio.order_id.id for folio in self]
# sale_obj = self.env['sale.order'].browse(order_ids)
# invoice_id = (sale_obj.action_invoice_create(grouped=False,
# states=['confirmed',
# 'done']))
# for line in self:
# values = {'invoiced': True,
# 'state': 'progress' if grouped else 'progress',
# 'hotel_invoice_id': invoice_id
# }
# line.write(values)
# return invoice_id
@api.multi
def advance_invoice(self):
pass
# order_ids = [folio.order_id.id for folio in self]
# sale_obj = self.env['sale.order'].browse(order_ids)
# invoices = action_invoice_create(self, grouped=True)
# return invoices
@api.multi
def action_cancel(self):
'''
@param self: object pointer
'''
pass
# for sale in self:
# if not sale.order_id:
# raise ValidationError(_('Order id is not available'))
# for invoice in sale.invoice_ids:
# invoice.state = 'cancel'
# sale.room_lines.action_cancel()
# sale.order_id.action_cancel()
@api.multi
def action_confirm(self):
_logger.info('action_confirm')
@api.multi
def print_quotation(self):
pass
# self.order_id.filtered(lambda s: s.state == 'draft').write({
# 'state': 'sent',
# })
# return self.env.ref('sale.report_saleorder').report_action(self, data=data)
@api.multi
def action_cancel_draft(self):
_logger.info('action_confirm')
@api.multi
def send_reservation_mail(self):
'''
This function opens a window to compose an email,
template message loaded by default.
@param self: object pointer
'''
# Debug Stop -------------------
# import wdb; wdb.set_trace()
# Debug Stop -------------------
self.ensure_one()
ir_model_data = self.env['ir.model.data']
try:
template_id = (ir_model_data.get_object_reference
('hotel',
'mail_template_hotel_reservation')[1])
except ValueError:
template_id = False
try:
compose_form_id = (ir_model_data.get_object_reference
('mail',
'email_compose_message_wizard_form')[1])
except ValueError:
compose_form_id = False
ctx = dict()
ctx.update({
'default_model': 'hotel.folio',
'default_res_id': self._ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
'force_send': True,
'mark_so_as_sent': True
})
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form_id, 'form')],
'view_id': compose_form_id,
'target': 'new',
'context': ctx,
'force_send': True
}
@api.multi
def send_exit_mail(self):
'''
This function opens a window to compose an email,
template message loaded by default.
@param self: object pointer
'''
# Debug Stop -------------------
# import wdb; wdb.set_trace()
# Debug Stop -------------------
self.ensure_one()
ir_model_data = self.env['ir.model.data']
try:
template_id = (ir_model_data.get_object_reference
('hotel',
'mail_template_hotel_exit')[1])
except ValueError:
template_id = False
try:
compose_form_id = (ir_model_data.get_object_reference
('mail',
'email_compose_message_wizard_form')[1])
except ValueError:
compose_form_id = False
ctx = dict()
ctx.update({
'default_model': 'hotel.reservation',
'default_res_id': self._ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
'force_send': True,
'mark_so_as_sent': True
})
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form_id, 'form')],
'view_id': compose_form_id,
'target': 'new',
'context': ctx,
'force_send': True
}
@api.multi
def send_cancel_mail(self):
'''
This function opens a window to compose an email,
template message loaded by default.
@param self: object pointer
'''
# Debug Stop -------------------
#import wdb; wdb.set_trace()
# Debug Stop -------------------
self.ensure_one()
ir_model_data = self.env['ir.model.data']
try:
template_id = (ir_model_data.get_object_reference
('hotel',
'mail_template_hotel_cancel')[1])
except ValueError:
template_id = False
try:
compose_form_id = (ir_model_data.get_object_reference
('mail',
'email_compose_message_wizard_form')[1])
except ValueError:
compose_form_id = False
ctx = dict()
ctx.update({
'default_model': 'hotel.reservation',
'default_res_id': self._ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
'force_send': True,
'mark_so_as_sent': True
})
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form_id, 'form')],
'view_id': compose_form_id,
'target': 'new',
'context': ctx,
'force_send': True
}
@api.model
def reservation_reminder_24hrs(self):
"""
This method is for scheduler
every 1day scheduler will call this method to
find all tomorrow's reservations.
----------------------------------------------
@param self: The object pointer
@return: send a mail
"""
now_str = time.strftime(dt)
now_date = datetime.strptime(now_str, dt)
ir_model_data = self.env['ir.model.data']
template_id = (ir_model_data.get_object_reference
('hotel_reservation',
'mail_template_reservation_reminder_24hrs')[1])
template_rec = self.env['mail.template'].browse(template_id)
for reserv_rec in self.search([]):
checkin_date = (datetime.strptime(reserv_rec.checkin, dt))
difference = relativedelta(now_date, checkin_date)
if(difference.days == -1 and reserv_rec.partner_id.email and
reserv_rec.state == 'confirm'):
template_rec.send_mail(reserv_rec.id, force_send=True)
return True
@api.multi
def unlink(self):
# for record in self:
# record.order_id.unlink()
return super(HotelFolio, self).unlink()
@api.multi
def get_grouped_reservations_json(self, state, import_all=False):
self.ensure_one()
info_grouped = []
for rline in self.room_lines:
if (import_all or rline.to_send) and not rline.parent_reservation and rline.state == state:
dates = rline.get_real_checkin_checkout()
vals = {
'num': len(
self.room_lines.filtered(lambda r: r.get_real_checkin_checkout()[0] == dates[0] and r.get_real_checkin_checkout()[1] == dates[1] and r.room_type_id.id == rline.room_type_id.id and (r.to_send or import_all) and not r.parent_reservation and r.state == rline.state)
),
'room_type': {
'id': rline.room_type_id.id,
'name': rline.room_type_id.name,
},
'checkin': dates[0],
'checkout': dates[1],
'nights': len(rline.reservation_line_ids),
'adults': rline.adults,
'childrens': rline.children,
}
founded = False
for srline in info_grouped:
if srline['num'] == vals['num'] and srline['room_type']['id'] == vals['room_type']['id'] and srline['checkin'] == vals['checkin'] and srline['checkout'] == vals['checkout']:
founded = True
break
if not founded:
info_grouped.append(vals)
return sorted(sorted(info_grouped, key=lambda k: k['num'], reverse=True), key=lambda k: k['room_type']['id'])

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo import models, fields, api, _
from odoo.addons import decimal_precision as dp
class HotelReservationLine(models.Model):
_name = "hotel.reservation.line"
_order = "date"
reservation_id = fields.Many2one('hotel.reservation', string='Reservation',
ondelete='cascade', required=True,
copy=False)
date = fields.Date('Date')
price = fields.Float('Price')
discount = fields.Float(
string='Discount (%)',
digits=dp.get_precision('Discount'), default=0.0)

105
hotel/models/hotel_room.py Normal file
View File

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
# 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, api, _
class HotelRoom(models.Model):
""" The rooms for lodging can be for sleeping, usually called rooms, and also
for speeches (conference rooms), parking, relax with cafe con leche, spa...
"""
_name = 'hotel.room'
_description = 'Hotel Room'
# The record's name
name = fields.Char('Room Name', required=True)
# Used for activate records
active = fields.Boolean('Active', default=True)
# Used for ordering
sequence = fields.Integer('Sequence', default=0)
_order = "sequence, room_type_id, name"
# each room has only one type (Many2one)
room_type_id = fields.Many2one('hotel.room.type', 'Hotel Room Type')
floor_id = fields.Many2one('hotel.floor', 'Ubication',
help='At which floor the room is located.')
# TODO Q. Should the amenities be on the Room Type ? -
room_amenities = fields.Many2many('hotel.room.amenities', 'temp_tab',
'room_amenities', 'rcateg_id',
string='Room Amenities',
help='List of room amenities.')
# default price for this room
list_price = fields.Float(store=True,
string='Room Rate',
help='The room rate is fixed unless a room type'
' is selected, in which case the rate is taken from'
' the room type.')
# how to manage the price
# sale_price_type = fields.Selection([
# ('fixed', 'Fixed Price'),
# ('vroom', 'Room Type'),
# ], 'Price Type', default='fixed', required=True)
# max number of adults and children per room
max_adult = fields.Integer('Max Adult')
max_child = fields.Integer('Max Child')
# maximum capacity of the room
capacity = fields.Integer('Capacity')
# FIXME not used
to_be_cleaned = fields.Boolean('To be Cleaned', default=False)
shared_room = fields.Boolean('Shared Room', default=False)
description_sale = fields.Text(
'Sale Description', translate=True,
help="A description of the Product that you want to communicate to "
" your customers. This description will be copied to every Sales "
" Order, Delivery Order and Customer Invoice/Credit Note")
# In case the price is managed from a specific type of room
# price_virtual_room = fields.Many2one(
# 'hotel.virtual.room',
# 'Price Virtual Room',
# help='Price will be based on selected Virtual Room')
# virtual_rooms = fields.Many2many('hotel.virtual.room',
# string='Virtual Rooms')
# categ_id = fields.Selection([('room', 'Room '),
# ('shared_room', 'Shared Room'),
# ('parking', 'Parking')],
# string='Hotel Lodging Type',
# store=True, default='room')
# price_virtual_room_domain = fields.Char(
# compute=_compute_price_virtual_room_domain,
# readonly=True,
# store=False,
# )
# @api.multi
# @api.depends('categ_id')
# def _compute_price_virtual_room_domain(self):
# for rec in self:
# rec.price_virtual_room_domain = json.dumps(
# ['|', ('room_ids.id', '=', rec.id), ('room_type_ids.cat_id.id', '=', rec.categ_id.id)]
# )
# @api.onchange('categ_id')
# def price_virtual_room_domain(self):
# return {
# 'domain': {
# 'price_virtual_room': [
# '|', ('room_ids.id', '=', self._origin.id),
# ('room_type_ids.cat_id.id', '=', self.categ_id.id)
# ]
# }
# }
# @api.multi
# def unlink(self):
# for record in self:
# record.product_id.unlink()
# return super(HotelRoom, self).unlink()

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# 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, api, _
class HotelRoomAmenities(models.Model):
_name = 'hotel.room.amenities'
_description = 'Room amenities'
# The record's name
name = fields.Char('Amenity Name', required=True)
# Used for activate records
active = fields.Boolean('Active', default=True)
default_code = fields.Char('Internal Reference', store=True)
# room_categ_id = fields.Many2one('product.product', 'Product Category',
# required=True, delegate=True,
# ondelete='cascade')
room_amenities_type_id = fields.Many2one('hotel.room.amenities.type',
'Amenity Catagory')
# room_ids = fields.Many2man('hotel.room','Rooms')
# @api.multi
# def unlink(self):
# # self.room_categ_id.unlink()
# return super(HotelRoomAmenities, self).unlink()

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# 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, api, _
class HotelRoomAmenitiesType(models.Model):
_name = 'hotel.room.amenities.type'
_description = 'Amenities Type'
# The record's name
name = fields.Char('Amenity Name', required=True)
# Used for activate records
active = fields.Boolean('Active', default=True)
room_amenities_ids = fields.One2many('hotel.room.amenities',
'room_amenities_type_id',
'Amenities in this category')
# cat_id = fields.Many2one('product.category', 'category', required=True,
# delegate=True, ondelete='cascade')
# @api.multi
# def unlink(self):
# # self.cat_id.unlink()
# return super(HotelRoomAmenitiesType, self).unlink()

View File

@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from decimal import Decimal
from datetime import datetime, timedelta
import dateutil.parser
# For Python 3.0 and later
from urllib.request import urlopen
import time
from odoo.exceptions import except_orm, UserError, ValidationError
from odoo.tools import (
misc,
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from odoo import models, fields, api, _
from odoo.addons.hotel import date_utils
from odoo.addons import decimal_precision as dp
class HotelRoomType(models.Model):
""" Before creating a 'room type', you need to consider the following:
With the term 'room type' is meant a type of residential accommodation: for
example, a Double Room, a Economic Room, an Apartment, a Tent, a Caravan...
"""
_name = "hotel.room.type"
_description = "Room Type"
_inherits = {'product.product': 'product_id'}
# Relationship between models
product_id = fields.Many2one('product.product', 'Product Room Type',
required=True, delegate=True,
ondelete='cascade')
# cat_id = fields.Many2one('product.category', 'category', required=True,
# delegate=True, index=True, ondelete='cascade')
room_ids = fields.One2many('hotel.room', 'room_type_id', 'Rooms')
# TODO Hierarchical relationship for parent-child tree ?
# parent_id = fields.Many2one ...
# Used for activate records
active = fields.Boolean('Active', default=True,
help="The active field allows you to hide the \
category without removing it.")
# Used for ordering
sequence = fields.Integer('Sequence', default=0)
code_type = fields.Char('Code')
_order = "sequence, code_type, name"
_sql_constraints = [('code_type_unique', 'unique(code_type)',
'code must be unique!')]
# total number of rooms in this type
total_rooms_count = fields.Integer(compute='_compute_total_rooms')
# FIXING rename to default rooms ?
max_real_rooms = fields.Integer('Default Max Room Allowed')
@api.depends('room_ids')
def _compute_total_rooms(self):
for record in self:
count = 0
count += len(record.room_ids) # Rooms linked directly
# room_categories = r.room_type_ids.mapped('room_ids.id')
# count += self.env['hotel.room'].search_count([
# ('categ_id.id', 'in', room_categories)
# ]) # Rooms linked through room type
record.total_rooms_count = count
def _check_duplicated_rooms(self):
# FIXME Using a Many2one relationship duplicated should not been possible
pass
@api.constrains('max_real_rooms', 'room_ids')
def _check_max_rooms(self):
warning_msg = ""
# for r in self:
if self.max_real_rooms > self.total_rooms_count:
warning_msg += _('The Maxime rooms allowed can not be greate \
than total rooms count')
raise models.ValidationError(warning_msg)
@api.multi
def get_capacity(self):
# WARNING use selg.capacity directly ?
pass
# self.ensure_one()
# hotel_room_obj = self.env['hotel.room']
# room_categories = self.room_type_ids.mapped('room_ids.id')
# room_ids = self.room_ids + hotel_room_obj.search([
# ('categ_id.id', 'in', room_categories)
# ])
# capacities = room_ids.mapped('capacity')
# return any(capacities) and min(capacities) or 0
@api.model
def check_availability_virtual_room(self, checkin, checkout,
room_type_id=False, notthis=[]):
"""
Check the avalability for an specific type of room
@return: A recordset of free rooms ?
"""
occupied = self.env['hotel.reservation'].occupied(checkin, checkout)
rooms_occupied = occupied.mapped('product_id.id')
free_rooms = self.env['hotel.room'].search([
('product_id.id', 'not in', rooms_occupied),
('id', 'not in', notthis)
])
if room_type_id:
# hotel_room_obj = self.env['hotel.room']
room_type_id = self.env['hotel.room.type'].search([
('id', '=', room_type_id)
])
# room_categories = virtual_room.room_type_ids.mapped('room_ids.id')
# rooms_linked = virtual_room.room_ids | hotel_room_obj.search([
# ('categ_id.id', 'in', room_categories)])
# rooms_linked = room_type_id.room_ids
rooms_linked = self.room_ids
free_rooms = free_rooms & rooms_linked
return free_rooms.sorted(key=lambda r: r.sequence)
@api.model
def create(self, vals):
"""
Overrides orm create method.
@param self: The object pointer
@param vals: dictionary of fields value.
@return: new record set for hotel room type.
"""
vals.update({'is_room_type': True})
vals.update({'purchase_ok': False})
vals.update({'type': 'service'})
return super().create(vals)
@api.multi
def unlink(self):
for record in self:
# Set fixed price to rooms with price from this virtual rooms
# Remove product.product
record.product_id.unlink()
return super().unlink()

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import time
import datetime
import logging
from odoo import models, fields, api, _
from odoo.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.addons.hotel import date_utils
from odoo.addons import decimal_precision as dp
_logger = logging.getLogger(__name__)
class HotelService(models.Model):
@api.model
def _service_checkin(self):
if 'checkin' in self._context:
return self._context['checkin']
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
@api.model
def _service_checkout(self):
if 'checkout' in self._context:
return self._context['checkout']
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
@api.model
def _default_ser_room_line(self):
if 'room_lines' in self.env.context and self.env.context['room_lines']:
ids = [item[1] for item in self.env.context['room_lines']]
return self.env['hotel.reservation'].search([('id', 'in', ids)],
limit=1)
return False
_name = 'hotel.service'
_description = 'Hotel Services and its charges'
name = fields.Char('Service description')
# services in the hotel are products
product_id = fields.Many2one('product.product', 'Service', required=True)
folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade')
ser_room_line = fields.Many2one('hotel.reservation', 'Room',
default=_default_ser_room_line)
list_price = fields.Float(
related='product_id.list_price')
channel_type = fields.Selection([
('door', 'Door'),
('mail', 'Mail'),
('phone', 'Phone'),
('call', 'Call Center'),
('web', 'Web')], 'Sales Channel')
ser_checkin = fields.Datetime('From Date', required=True,
default=_service_checkin)
ser_checkout = fields.Datetime('To Date', required=True,
default=_service_checkout)
# TODO Hierarchical relationship for parent-child tree
# parent_id = fields.Many2one ...
# service_id = fields.Many2one('product.product', 'Service_id',
# required=True, ondelete='cascade',
# delegate=True)
# service_type_id = fields.Many2one('hotel.service.type',
# 'Service Catagory')
# service_line_id = fields.Many2one('hotel.service.line',
# 'Service Line')
# @api.multi
# def unlink(self):
# # for record in self:
# # record.service_id.unlink()
# return super(HotelServices, self).unlink()

View File

@@ -0,0 +1,246 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import time
import datetime
import logging
from odoo import models, fields, api, _
from odoo.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.addons.hotel import date_utils
_logger = logging.getLogger(__name__)
from odoo.addons import decimal_precision as dp
class HotelServiceLine(models.Model):
@api.one
def copy(self, default=None):
'''
@param self: object pointer
@param default: dict of default values to be set
'''
line_id = self.service_line_id.id
sale_line_obj = self.env['sale.order.line'].browse(line_id)
return sale_line_obj.copy(default=default)
@api.multi
def _amount_line(self, field_name, arg):
'''
@param self: object pointer
@param field_name: Names of fields.
@param arg: User defined arguments
'''
for folio in self:
line = folio.service_line_id
x = line._amount_line(field_name, arg)
return x
@api.multi
def _number_packages(self, field_name, arg):
'''
@param self: object pointer
@param field_name: Names of fields.
@param arg: User defined arguments
'''
for folio in self:
line = folio.service_line_id
x = line._number_packages(field_name, arg)
return x
@api.model
def _service_checkin(self):
if 'checkin' in self._context:
return self._context['checkin']
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
@api.model
def _service_checkout(self):
if 'checkout' in self._context:
return self._context['checkout']
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
@api.model
def _default_ser_room_line(self):
if 'room_lines' in self.env.context and self.env.context['room_lines']:
ids = [item[1] for item in self.env.context['room_lines']]
return self.env['hotel.reservation'].search([('id', 'in', ids)],
limit=1)
return False
_name = 'hotel.service.line'
_description = 'hotel Service line'
# The record's name
name = fields.Char('Service line', required=True)
# services in the hotel are products
product_id = fields.Many2one('product.product', 'Service')
list_price = fields.Float(
related='product_id.list_price')
# TODO refactor to services_ids
# services_line_id = fields.Many2one('hotel.services', 'Service Line',
# ondelete='cascade')
# FIXME You can add services to a folio ?
folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade')
channel_type = fields.Selection([
('door', 'Door'),
('mail', 'Mail'),
('phone', 'Phone'),
('call', 'Call Center'),
('web','Web')], 'Sales Channel')
ser_checkin = fields.Datetime('From Date', required=True,
default=_service_checkin)
ser_checkout = fields.Datetime('To Date', required=True,
default=_service_checkout)
ser_room_line = fields.Many2one('hotel.reservation','Room', default=_default_ser_room_line)
@api.model
def create(self, vals, check=True):
"""
Overrides orm create method.
@param self: The object pointer
@param vals: dictionary of fields value.
@return: new record set for hotel service line.
"""
if 'folio_id' in vals:
folio = self.env['hotel.folio'].browse(vals['folio_id'])
vals.update({'order_id': folio.order_id.id})
user = self.env['res.users'].browse(self.env.uid)
if user.has_group('hotel.group_hotel_call'):
vals.update({'channel_type': 'call'})
return super(HotelServiceLine, self).create(vals)
# ~ @api.multi
# ~ def unlink(self):
# ~ """
# ~ Overrides orm unlink method.
# ~ @param self: The object pointer
# ~ @return: True/False.
# ~ """
# ~ s_line_obj = self.env['sale.order.line']
# ~ for line in self:
# ~ if line.service_line_id:
# ~ sale_unlink_obj = s_line_obj.browse([line.service_line_id.id])
# ~ sale_unlink_obj.unlink()
# ~ return super(HotelServiceLine, self).unlink()
@api.onchange('product_id')
def product_id_change_hotel(self):
'''
@param self: object pointer
'''
if self.product_id:
if not (self.folio_id and self.folio_id.partner_id) and \
self.ser_room_line:
self.folio_id = self.ser_room_line.folio_id
self.name = self.product_id.name
self.price_unit = self.product_id.lst_price
self.product_uom = self.product_id.uom_id
self.price_unit = self.product_id.price
#~ self.price_unit = tax_obj._fix_tax_included_price(prod.price,
#~ prod.taxes_id,
#~ self.tax_id)
# ~ _logger.info(self._context)
# ~ if 'folio_id' in self._context:
# ~ _logger.info(self._context)
# ~ domain_rooms = []
# ~ rooms_lines = self.env['hotel.reservation'].search([('folio_id','=',folio_id)])
# ~ room_ids = room_lines.mapped('id')
# ~ domain_rooms.append(('id','in',room_ids))
# ~ return {'domain': {'ser_room_line': domain_rooms}}
#
# ~ @api.onchange('folio_id')
# ~ def folio_id_change(self):
# ~ self.ensure_one()
# ~ _logger.info(self.mapped('folio_id.room_lines'))
# ~ rooms = self.mapped('folio_id.room_lines.id')
# ~ return {'domain': {'ser_room_line': rooms}}
#~ @api.onchange('product_uom')
#~ def product_uom_change(self):
#~ '''
#~ @param self: object pointer
#~ '''
# ~ if not self.product_uom:
# ~ self.price_unit = 0.0
# ~ return
# ~ self.price_unit = self.product_id.lst_price
# ~ if self.folio_id.partner_id:
# ~ prod = self.product_id.with_context(
# ~ lang=self.folio_id.partner_id.lang,
# ~ partner=self.folio_id.partner_id.id,
# ~ quantity=1,
# ~ date_order=self.folio_id.date_order,
# ~ pricelist=self.folio_id.pricelist_id.id,
# ~ uom=self.product_uom.id
# ~ )
# ~ tax_obj = self.env['account.tax']
# ~ self.price_unit = tax_obj._fix_tax_included_price(prod.price,
# ~ prod.taxes_id,
# ~ self.tax_id)
@api.onchange('ser_checkin', 'ser_checkout')
def on_change_checkout(self):
'''
When you change checkin or checkout it will checked it
and update the qty of hotel service line
-----------------------------------------------------------------
@param self: object pointer
'''
now_utc_dt = date_utils.now()
if not self.ser_checkin:
self.ser_checkin = now_utc_dt.strftime(
DEFAULT_SERVER_DATETIME_FORMAT)
if not self.ser_checkout:
self.ser_checkout = now_utc_dt.strftime(
DEFAULT_SERVER_DATETIME_FORMAT)
chkin_utc_dt = date_utils.get_datetime(self.ser_checkin)
chkout_utc_dt = date_utils.get_datetime(self.ser_checkout)
if chkout_utc_dt < chkin_utc_dt:
raise UserError(_('Checkout must be greater or equal checkin date'))
if self.ser_checkin and self.ser_checkout:
diffDate = date_utils.date_diff(self.ser_checkin,
self.ser_checkout, hours=False) + 1
@api.multi
def button_confirm(self):
'''
@param self: object pointer
'''
for folio in self:
line = folio.service_line_id
x = line.button_confirm()
return x
@api.multi
def button_done(self):
'''
@param self: object pointer
'''
for folio in self:
line = folio.service_line_id
x = line.button_done()
return x
@api.one
def copy_data(self, default=None):
'''
@param self: object pointer
@param default: dict of default values to be set
'''
sale_line_obj = self.env['sale.order.line'
].browse(self.service_line_id.id)
return sale_line_obj.copy_data(default=default)
@api.multi
def unlink(self):
for record in self:
record.service_line_id.unlink()
return super(HotelServiceLine, self).unlink()

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# 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, api, _
class HotelServiceType(models.Model):
_name = "hotel.service.type"
_description = "Service Type"
# The record's name
name = fields.Char('Service Type', required=True)
# Used for activate records
active = fields.Boolean('Active?', default=True)
# ser_id = fields.Many2one('product.category', 'category', required=True,
# delegate=True, index=True, ondelete='cascade')
service_ids = fields.One2many('hotel.services', 'service_type_id',
'Services in this category')
@api.multi
def unlink(self):
# self.ser_id.unlink()
return super(HotelServiceType, self).unlink()

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <alex@aloxa.eu>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class HotelVirtualRoomAvailability(models.Model):
_inherit = 'mail.thread'
_name = 'hotel.virtual.room.availability'
# virtual_room_id = fields.Many2one('hotel.virtual.room', 'Virtual Room',
# required=True, track_visibility='always',
# ondelete='cascade')
room_type_id = fields.Many2one('hotel.room.type', 'Room Type',
required=True, track_visibility='always',
ondelete='cascade')
avail = fields.Integer('Avail', default=0, track_visibility='always')
no_ota = fields.Boolean('No OTA', default=False, track_visibility='always')
booked = fields.Boolean('Booked', default=False, readonly=True,
track_visibility='always')
date = fields.Date('Date', required=True, track_visibility='always')
_sql_constraints = [('vroom_registry_unique', 'unique(room_type_id, date)',
'Only can exists one availability in the same day for the same room type!')]
@api.constrains('avail')
def _check_avail(self):
if self.avail < 0:
self.avail = 0
vroom_obj = self.env['hotel.room.type']
cavail = len(vroom_obj.check_availability_virtual_room(
self.date,
self.date,
room_type_id=self.room_type_id.id))
max_avail = min(cavail,
self.room_type_id.total_rooms_count)
if self.avail > max_avail:
self.avail = max_avail
@api.constrains('date', 'room_type_id')
def _check_date_virtual_room_id(self):
count = self.search_count([
('date', '=', self.date),
('room_type_id', '=', self.room_type_id.id)
])
if count > 1:
raise ValidationError(_("can't assign the same date to more than \
one room type"))

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <alex@aloxa.eu>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo import models, fields, api
class VirtualRoomRestriction(models.Model):
_name = 'hotel.virtual.room.restriction'
name = fields.Char('Restriction Plan Name', required=True)
item_ids = fields.One2many('hotel.virtual.room.restriction.item',
'restriction_id', string='Restriction Items',
copy=True)
active = fields.Boolean('Active',
help='If unchecked, it will allow you to hide the \
restriction plan without removing it.',
default=True)
@api.multi
@api.depends('name')
def name_get(self):
restriction_id = self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id')
if restriction_id:
restriction_id = int(restriction_id)
names = []
for record in self:
if record.id == restriction_id:
names.append((record.id, '%s (Parity)' % record.name))
else:
names.append((record.id, record.name))
return names

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <alex@aloxa.eu>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import datetime
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo.addons.hotel import date_utils
class HotelVirtualRoomRestrictionItem(models.Model):
_name = 'hotel.virtual.room.restriction.item'
restriction_id = fields.Many2one('hotel.virtual.room.restriction',
'Restriction Plan', ondelete='cascade',
index=True)
# virtual_room_id = fields.Many2one('hotel.virtual.room', 'Virtual Room',
# required=True, ondelete='cascade')
room_type_id = fields.Many2one('hotel.room.type', 'Room Type',
required=True, ondelete='cascade')
date_start = fields.Date('From')
date_end = fields.Date("To")
applied_on = fields.Selection([
('1_global', 'Global'),
# ('0_virtual_room', 'Virtual Room')], string="Apply On", required=True,
# default='0_virtual_room',
('0_room_type', 'Room Type')], string="Apply On", required=True,
default='0_room_type',
help='Pricelist Item applicable on selected option')
min_stay = fields.Integer("Min. Stay")
min_stay_arrival = fields.Integer("Min. Stay Arrival")
max_stay = fields.Integer("Max. Stay")
max_stay_arrival = fields.Integer("Max. Stay Arrival")
closed = fields.Boolean('Closed')
closed_departure = fields.Boolean('Closed Departure')
closed_arrival = fields.Boolean('Closed Arrival')
_sql_constraints = [('vroom_registry_unique',
'unique(restriction_id, room_type_id, date_start, date_end)',
'Only can exists one restriction in the same day for the same room type!')]
@api.constrains('min_stay', 'min_stay_arrival', 'max_stay',
'max_stay_arrival')
def _check_min_stay_min_stay_arrival_max_stay(self):
if self.min_stay < 0:
raise ValidationError(_("Min. Stay can't be less than zero"))
elif self.min_stay_arrival < 0:
raise ValidationError(
("Min. Stay Arrival can't be less than zero"))
elif self.max_stay < 0:
raise ValidationError(_("Max. Stay can't be less than zero"))
elif self.max_stay_arrival < 0:
raise ValidationError(
("Max. Stay Arrival can't be less than zero"))
@api.constrains('date_start', 'date_end')
def _check_date_start_date_end(self):
if self.applied_on == '1_global':
self.date_start = False
self.date_end = False
elif self.date_start and self.date_end:
date_start_dt = date_utils.get_datetime(self.date_start)
date_end_dt = date_utils.get_datetime(self.date_end)
if date_end_dt < date_start_dt:
raise ValidationError(_("Invalid Dates"))
@api.constrains('applied_on')
def _check_applied_on(self):
count = self.search_count([('applied_on', '=', '1_global')])
if count > 1:
raise ValidationError(_("Already exists an global rule"))

View File

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api, _
from openerp.exceptions import UserError, ValidationError
import logging
_logger = logging.getLogger(__name__)
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
@api.model
def create(self, vals):
cr, uid, context = self.env.args
context = dict(context)
if context.get('invoice_origin', False):
vals.update({'origin': context['invoice_origin']})
return super(AccountInvoice, self).create(vals)
@api.multi
def action_folio_payments(self):
self.ensure_one()
sales = self.mapped('invoice_line_ids.sale_line_ids.order_id')
folios = self.env['hotel.folio'].search([('order_id.id','in',sales.ids)])
payments_obj = self.env['account.payment']
payments = payments_obj.search([('folio_id','in',folios.ids)])
payment_ids = payments.mapped('id')
return{
'name': _('Payments'),
'view_type': 'form',
'view_mode': 'tree,form',
'res_model': 'account.payment',
'target': 'new',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', payment_ids)],
}
dif_customer_payment = fields.Boolean(compute='_compute_dif_customer_payment')
from_folio = fields.Boolean(compute='_compute_dif_customer_payment')
sale_ids = fields.Many2many(
'sale.order', 'sale_order_invoice_rel', 'invoice_id',
'order_id', 'Sale Orders', readonly=True,
help="This is the list of sale orders related to this invoice.")
folio_ids = fields.Many2many(
comodel_name='hotel.folio', compute='_compute_dif_customer_payment')
@api.multi
def _compute_dif_customer_payment(self):
for inv in self:
sales = inv.mapped('invoice_line_ids.sale_line_ids.order_id')
folios = self.env['hotel.folio'].search([('order_id.id','in',sales.ids)])
if folios:
inv.from_folio = True
inv.folio_ids = [(6, 0, folios.ids)]
payments_obj = self.env['account.payment']
payments = payments_obj.search([('folio_id','in',folios.ids)])
for pay in payments:
if pay.partner_id != inv.partner_id:
inv.dif_customer_payment = True
@api.multi
def action_invoice_open(self):
to_open_invoices_without_vat = self.filtered(lambda inv: inv.state != 'open' and inv.partner_id.vat == False)
if to_open_invoices_without_vat:
vat_error = _("We need the VAT of the following companies")
for invoice in to_open_invoices_without_vat:
vat_error += ", " + invoice.partner_id.name
raise ValidationError(vat_error)
return super(AccountInvoice, self).action_invoice_open()
# ~ @api.multi
# ~ def confirm_paid(self):
# ~ '''
# ~ This method change pos orders states to done when folio invoice
# ~ is in done.
# ~ ----------------------------------------------------------
# ~ @param self: object pointer
# ~ '''
# ~ pos_order_obj = self.env['pos.order']
# ~ res = super(AccountInvoice, self).confirm_paid()
# ~ pos_odr_rec = pos_order_obj.search([('invoice_id', 'in', self._ids)])
# ~ pos_odr_rec and pos_odr_rec.write({'state': 'done'})
# ~ return res

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from decimal import Decimal
import datetime
# For Python 3.0 and later
from urllib.request import urlopen
import time
import logging
from openerp.exceptions import except_orm, UserError, ValidationError
from openerp.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
from openerp import models, fields, api, _
_logger = logging.getLogger(__name__)
class AccountPayment(models.Model):
_inherit = 'account.payment'
folio_id = fields.Many2one('hotel.folio', string='Folio')
amount_total_folio = fields.Float(
compute="_compute_folio_amount", store=True,
string="Total amount in folio",
)
@api.multi
def return_payment_folio(self):
journal = self.journal_id
partner = self.partner_id
amount = self.amount
reference = self.communication
account_move_lines = self.move_line_ids.filtered(lambda x: (
x.account_id.internal_type == 'receivable'))
return_line_vals = {
'move_line_ids': [(6, False, [x.id for x in account_move_lines])],
'partner_id': partner.id,
'amount': amount,
'reference': reference,
}
return_vals = {
'journal_id': journal.id,
'line_ids': [(0,0,return_line_vals)],
}
return_pay = self.env['payment.return'].create(return_vals)
return {
'name': 'Folio Payment Return',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'payment.return',
'type': 'ir.actions.act_window',
'res_id': return_pay.id,
}
@api.multi
def modify(self):
self.cancel()
vals = {
'journal_id': self.journal_id,
'partner_id': self.partner_id,
'amount': self.amount,
'payment_date': self.payment_date,
'communication': self.communication,
'folio_id': self.folio_id}
self.update(vals)
self.post()
@api.multi
def delete(self):
self.cancel()
self.move_name = ''
self.unlink()
@api.multi
@api.depends('state')
def _compute_folio_amount(self):
res = []
fol = ()
for payment in self:
amount_pending = 0
total_amount = 0
if payment.folio_id:
fol = payment.env['hotel.folio'].search([
('id', '=', payment.folio_id.id)
])
else:
return
if len(fol) == 0:
return
elif len(fol) > 1:
raise except_orm(_('Warning'), _('This pay is related with \
more than one Reservation.'))
else:
fol.compute_invoices_amount()
return res

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# --------------------------------------------------------------------------
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018-Darío Lodeiros Vázquez
#
# 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 <http://www.gnu.org/licenses/>
#
# ---------------------------------------------------------------------------
from openerp import models, fields, api, _
class PaymentReturn(models.Model):
_inherit = 'payment.return'
folio_id = fields.Many2one('hotel.folio', string='Folio')
@api.multi
def action_confirm(self):
pay = super(PaymentReturn,self).action_confirm()
if pay:
folio_ids = []
for line in self.line_ids:
payments = self.env['account.payment'].search([('move_line_ids','in',line.move_line_ids.ids)])
folio_ids += payments.mapped('folio_id.id')
folios = self.env['hotel.folio'].browse(folio_ids)
folios.compute_invoices_amount()

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api, _
class ProductCategory(models.Model):
_inherit = "product.category"
# isroomtype = fields.Boolean('Is Room Type')
isamenitytype = fields.Boolean('Is Amenities Type')
isservicetype = fields.Boolean('Is Service Type')

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp import models, api
class ProductPricelist(models.Model):
_inherit = 'product.pricelist'
@api.multi
@api.depends('name')
def name_get(self):
pricelist_id = self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_pricelist_id')
if pricelist_id:
pricelist_id = int(pricelist_id)
org_names = super(ProductPricelist, self).name_get()
names = []
for name in org_names:
if name[0] == pricelist_id:
names.append((name[0], '%s (Parity)' % name[1]))
else:
names.append((name[0], name[1]))
return names

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api, _
class ProductProduct(models.Model):
_inherit = "product.product"
is_room_type = fields.Boolean('Is a Room Type', default=False)
# iscategid = fields.Boolean('Is categ id')
# isservice = fields.Boolean('Is Service id')

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api, _
class ResCompany(models.Model):
_inherit = 'res.company'
additional_hours = fields.Integer('Additional Hours',
help="Provide the min hours value for \
check in, checkout days, whatever \
the hours will be provided here based \
on that extra days will be \
calculated.")
default_cancel_policy_days = fields.Integer('Cancelation Days')
default_cancel_policy_percent = fields.Integer('Percent to pay')

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api, _
class ResPartner(models.Model):
_inherit = 'res.partner'
reservations_count = fields.Integer('Reservations',
compute='_compute_reservations_count')
folios_count = fields.Integer('Folios', compute='_compute_folios_count')
def _compute_reservations_count(self):
hotel_reservation_obj = self.env['hotel.reservation']
for partner in self:
partner.reservations_count = hotel_reservation_obj.search_count([
('partner_id.id', '=', partner.id)
])
def _compute_folios_count(self):
hotel_folio_obj = self.env['hotel.folio']
for partner in self:
partner.folios_count = hotel_folio_obj.search_count([
('partner_id.id', '=', partner.id)
])

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018 Alexandre Díaz <dev@redneboa.es>
#
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo import api, models
class MailComposeMessage(models.TransientModel):
_inherit = 'mail.compose.message'
@api.multi
def send_mail(self, auto_commit=False):
if self._context.get('default_model') == 'hotel.folio' and self._context.get('default_res_id') and self._context.get('mark_so_as_sent'):
folio = self.env['hotel.folio'].browse([
self._context['default_res_id']
])
if folio:
cmds = []
for lid in folio.room_lines._ids:
cmds.append((
1,
lid,
{'to_send': False}
))
if cmds:
folio.room_lines = cmds
return super(MailComposeMessage, self).send_mail(auto_commit=auto_commit)

104
hotel/models/res_config.py Normal file
View File

@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <alex@aloxa.eu>
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import re
import pytz
from openerp import models, fields, api, _
from openerp.exceptions import ValidationError
@api.model
def _tz_get(self):
# put POSIX 'Etc/*' entries at the end to avoid confusing users
# see bug 1086728
return [(tz, tz) for tz in sorted(pytz.all_timezones,
key=lambda tz: tz
if not tz.startswith('Etc/') else '_')]
class HotelConfiguration(models.TransientModel):
_inherit = 'res.config.settings'
parity_pricelist_id = fields.Many2one('product.pricelist',
'Product Pricelist')
parity_restrictions_id = fields.Many2one('hotel.virtual.room.restriction',
'Restrictions')
default_arrival_hour = fields.Char('Default Arrival Hour (GMT)',
help="HH:mm Format", default="14:00")
default_departure_hour = fields.Char('Default Departure Hour (GMT)',
help="HH:mm Format", default="12:00")
tz_hotel = fields.Selection(_tz_get, string='Timezone',
default=lambda self: self._context.get('tz'),
help="The hotel's timezone, used to manage \
date and time values in reservations \
It is important to set a value for this \
field.")
@api.multi
def set_values(self):
super(HotelConfiguration, self).set_values()
self.env['ir.default'].sudo().set(
'res.config.settings', 'parity_pricelist_id',
self.parity_pricelist_id.id)
self.env['ir.default'].sudo().set(
'res.config.settings', 'parity_restrictions_id',
self.parity_restrictions_id.id)
self.env['ir.default'].sudo().set(
'res.config.settings', 'tz_hotel', self.tz_hotel)
self.env['ir.default'].sudo().set(
'res.config.settings', 'default_arrival_hour',
self.default_arrival_hour)
self.env['ir.default'].sudo().set(
'res.config.settings', 'default_departure_hour',
self.default_departure_hour)
@api.model
def get_values(self):
res = super(HotelConfiguration, self).get_values()
# ONLY FOR v11. DO NOT FORWARD-PORT
parity_pricelist_id = self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_pricelist_id')
parity_restrictions_id = self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id')
tz_hotel = self.env['ir.default'].sudo().get(
'res.config.settings', 'tz_hotel')
default_arrival_hour = self.env['ir.default'].sudo().get(
'res.config.settings', 'default_arrival_hour')
default_departure_hour = self.env['ir.default'].sudo().get(
'res.config.settings', 'default_departure_hour')
res.update(
parity_pricelist_id=parity_pricelist_id,
parity_restrictions_id=parity_restrictions_id,
tz_hotel=tz_hotel,
default_arrival_hour=default_arrival_hour,
default_departure_hour=default_departure_hour,
)
return res
@api.constrains('default_arrival_hour', 'default_departure_hour')
def _check_hours(self):
r = re.compile('[0-2][0-9]:[0-5][0-9]')
if not r.match(self.default_arrival_hour):
raise ValidationError(_("Invalid arrival hour (Format: HH:mm)"))
if not r.match(self.default_departure_hour):
raise ValidationError(_("Invalid departure hour (Format: HH:mm)"))

View File

@@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Dario Lodeiros <>
# Alexandre Díaz <dev@redneboa.es>
#
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from decimal import Decimal
from datetime import datetime, timedelta
import dateutil.parser
# For Python 3.0 and later
from urllib.request import urlopen
import time
from openerp.exceptions import except_orm, UserError, ValidationError
from openerp.tools import (
misc,
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from openerp import models, fields, api, _
from odoo.addons.hotel import date_utils
class VirtualRoom(models.Model):
_name = 'hotel.virtual.room'
_inherits = {'product.product': 'product_id'}
@api.depends('room_ids', 'room_type_ids')
def _compute_total_rooms(self):
for r in self:
count = 0
count += len(r.room_ids) # Rooms linked directly
room_categories = r.room_type_ids.mapped('room_ids.id')
count += self.env['hotel.room'].search_count([
('categ_id.id', 'in', room_categories)
]) # Rooms linked through room type
r.total_rooms_count = count
@api.constrains('room_ids', 'room_type_ids')
def _check_duplicated_rooms(self):
warning_msg = ""
for r in self:
room_categories = self.room_type_ids.mapped('room_ids.id')
if self.room_ids & self.env['hotel.room'].search([
('categ_id.id', 'in', room_categories)]):
room_ids = self.room_ids & self.env['hotel.room'].search([
('categ_id.id', 'in', room_categories)
])
rooms_name = ','.join(str(x.name) for x in room_ids)
warning_msg += _('You can not enter the same room in duplicate \
(check the room types) %s') % rooms_name
raise models.ValidationError(warning_msg)
@api.constrains('max_real_rooms', 'room_ids', 'room_type_ids')
def _check_max_rooms(self):
warning_msg = ""
for r in self:
if self.max_real_rooms > self.total_rooms_count:
warning_msg += _('The Maxime rooms allowed can not be greate \
than total rooms count')
raise models.ValidationError(warning_msg)
virtual_code = fields.Char('Code') # not used
room_ids = fields.Many2many('hotel.room', string='Rooms')
room_type_ids = fields.Many2many('hotel.room.type', string='Room Types')
total_rooms_count = fields.Integer(compute='_compute_total_rooms')
product_id = fields.Many2one('product.product', 'Product_id',
required=True, delegate=True,
ondelete='cascade')
# FIXME services are related to real rooms
service_ids = fields.Many2many('hotel.services',
string='Included Services')
max_real_rooms = fields.Integer('Default Max Room Allowed')
product_id = fields.Many2one(
'product.product', required=True,
ondelete='cascade')
active = fields.Boolean(default=True, help="The active field allows you to hide the category without removing it.")
@api.multi
def get_capacity(self):
self.ensure_one()
hotel_room_obj = self.env['hotel.room']
room_categories = self.room_type_ids.mapped('room_ids.id')
room_ids = self.room_ids + hotel_room_obj.search([
('categ_id.id', 'in', room_categories)
])
capacities = room_ids.mapped('capacity')
return any(capacities) and min(capacities) or 0
@api.model
def check_availability_virtual_room(self, checkin, checkout,
virtual_room_id=False, notthis=[]):
occupied = self.env['hotel.reservation'].occupied(checkin, checkout)
rooms_occupied = occupied.mapped('product_id.id')
free_rooms = self.env['hotel.room'].search([
('product_id.id', 'not in', rooms_occupied),
('id', 'not in', notthis)
])
if virtual_room_id:
hotel_room_obj = self.env['hotel.room']
virtual_room = self.env['hotel.virtual.room'].search([
('id', '=', virtual_room_id)
])
room_categories = virtual_room.room_type_ids.mapped('room_ids.id')
rooms_linked = virtual_room.room_ids | hotel_room_obj.search([
('categ_id.id', 'in', room_categories)])
free_rooms = free_rooms & rooms_linked
return free_rooms.sorted(key=lambda r: r.sequence)
@api.multi
def unlink(self):
for record in self:
# Set fixed price to rooms with price from this virtual rooms
rooms = self.env['hotel.room'].search([
('sale_price_type', '=', 'vroom'),
('price_virtual_room', '=', record.id)
])
for room in rooms:
room.sale_price_type = 'fixed'
# Remove product.product
record.product_id.unlink()
return super(VirtualRoom, self).unlink()

6
hotel/report/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import hotel_report

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import time
from openerp import models
# Old SXW engine was removed already in v11. You should update your code with
# current engine tools.
# class FolioReport():
# def __init__(self, cr, uid, name, context):
# super(FolioReport, self).__init__(cr, uid, name, context)
# self.localcontext.update({'time': time,
# 'get_data': self.get_data,
# 'get_Total': self.getTotal,
# 'get_total': self.gettotal,
# })
# self.temp = 0.0
#
# def get_data(self, date_start, date_end):
# folio_obj = self.pool.get('hotel.folio')
# tids = folio_obj.search(self.cr, self.uid,
# [('checkin_date', '>=', date_start),
# ('checkout_date', '<=', date_end)])
# res = folio_obj.browse(self.cr, self.uid, tids)
# return res
#
# def gettotal(self, total):
# self.temp = self.temp + float(total)
# return total
#
# def getTotal(self):
# return self.temp
#
#
# class ReportLunchorder(models.AbstractModel):
# _name = 'report.hotel.report_hotel_folio'
# _inherit = 'report.report_xlsx.abstract'
# _template = 'hotel.report_hotel_folio'
# _wrapped_report_class = FolioReport

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<odoo>
<!--Report for hotel folio -->
<report id="hotel_folio_details"
string="Folio Total"
model="hotel.folio"
name="folio.total"
rml="hotel/report/total_folio.rml"
menu="False"
auto="False"/>
</odoo>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<odoo>
<report
id="action_report_viajero"
model="cardex"
string="Parte de Viajero"
report_type="qweb-pdf"
name="report.viajero"
file="report.viajero" />
<record id="action_report_viajero" model="ir.actions.report.xml">
<field name="paperformat_id" ref="report_viajero_paperformat"/>
<field name="report_type">qweb-pdf</field>
<field name="pdfjs_enabled">1</field>
<field name="pdfjs_auto_print">1</field>
<field name="pdfjs_print_dpi">201</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
,slimbook,slimbook-PRO,26.07.2018 11:51,file:///home/slimbook/.config/libreoffice/4;

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!--Group for hotel user -->
<record id="group_hotel_user" model="res.groups">
<field name="name">Hotel Management / User</field>
</record>
<!--Group for hotel manager -->
<record id="group_hotel_manager" model="res.groups">
<field name="name">Hotel Management/ Manager</field>
<field name="implied_ids" eval="[(4, ref('hotel.group_hotel_user'))]"/>
</record>
<!--Group for hotel user -->
<record id="group_hotel_call" model="res.groups">
<field name="name">Hotel Management / CallCenter</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,54 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_currency_exchange_call,hotel.currency_exchange.call,model_currency_exchange,hotel.group_hotel_call,1,1,1,1
access_currency_exchange_user,hotel.currency_exchange.user,model_currency_exchange,hotel.group_hotel_user,1,1,1,1
access_folio_room_line_call,hotel.folio_room_line.call,hotel.model_hotel_reservation_line,hotel.group_hotel_call,1,1,1,1
access_folio_room_line_user,hotel.folio_room_line.user,hotel.model_hotel_reservation_line,hotel.group_hotel_user,1,1,1,1
access_hotel_account_tax_call,hotel.account.tax.call,account.model_account_tax,hotel.group_hotel_call,1,1,1,1
access_hotel_account_tax_user,hotel.account.tax.user,account.model_account_tax,hotel.group_hotel_user,1,1,1,1
access_hotel_floor_group,hotel.floor.user,model_hotel_floor,hotel.group_hotel_user,1,0,0,0
access_hotel_floor_group_call,hotel.floor.call,model_hotel_floor,hotel.group_hotel_call,1,0,0,0
access_hotel_floor_group_manager,hotel.floor.manager,model_hotel_floor,hotel.group_hotel_manager,1,1,1,1
access_hotel_folio,hotel.folio.user,model_hotel_folio,hotel.group_hotel_user,1,1,1,1
access_hotel_folio_call,hotel.folio.call,model_hotel_folio,hotel.group_hotel_call,1,1,1,1
access_hotel_folio_line,hotel_folio.line.user,model_hotel_reservation,hotel.group_hotel_user,1,1,1,1
access_hotel_folio_line_call,hotel_folio.line.call,model_hotel_reservation,hotel.group_hotel_call,1,1,1,1
access_hotel_invoice_call,account.invoice.call,account.model_account_invoice,hotel.group_hotel_call,1,1,1,1
access_hotel_invoice_user,account.invoice.user,account.model_account_invoice,hotel.group_hotel_user,1,1,1,1
access_hotel_model_cardex_call,hotel.currency_exchange.call,hotel.model_cardex,hotel.group_hotel_call,1,1,1,1
access_hotel_model_cardex_user,hotel.currency_exchange.user,hotel.model_cardex,hotel.group_hotel_user,1,1,1,1
access_hotel_order_call,hotel.order.call,sale.model_sale_order,hotel.group_hotel_call,1,1,1,1
access_hotel_order_line_call,hotel.order.line.call,sale.model_sale_order_line,hotel.group_hotel_call,1,1,1,1
access_hotel_order_line_user,hotel.order.line.user,sale.model_sale_order_line,hotel.group_hotel_user,1,1,1,1
access_hotel_order_user,hotel.order.user,sale.model_sale_order,hotel.group_hotel_user,1,1,1,1
access_hotel_restrictions_call,hotel.restriction. All,hotel.model_hotel_virtual_room_restriction,hotel.group_hotel_call,1,0,0,0
access_hotel_restrictions_item_call,hotel.restriction.item.call,hotel.model_hotel_virtual_room_restriction_item,hotel.group_hotel_call,1,0,0,0
access_hotel_restrictions_item_manager,hotel.restriction.item.manager,hotel.model_hotel_virtual_room_restriction_item,hotel.group_hotel_manager,1,1,1,1
access_hotel_restrictions_item_user,hotel.restriction.item.user,hotel.model_hotel_virtual_room_restriction_item,hotel.group_hotel_user,1,0,0,0
access_hotel_restrictions_manager,hotel.restriction.manager,hotel.model_hotel_virtual_room_restriction,hotel.group_hotel_manager,1,1,1,1
access_hotel_restrictions_user,hotel.restriction.user,hotel.model_hotel_virtual_room_restriction,hotel.group_hotel_user,1,0,0,0
access_hotel_room,hotel.room.user,model_hotel_room,hotel.group_hotel_user,1,0,0,0
access_hotel_room_amenities,hotel.room_aminities.user,model_hotel_room_amenities,hotel.group_hotel_user,1,0,0,0
access_hotel_room_amenities_call,hotel.room_aminities.call,model_hotel_room_amenities,hotel.group_hotel_call,1,0,0,0
access_hotel_room_amenities_manager,hotel.room_aminities.manager,model_hotel_room_amenities,hotel.group_hotel_manager,1,1,1,1
access_hotel_room_amenities_type,hotel.room_amenities_type.user,model_hotel_room_amenities_type,hotel.group_hotel_user,1,0,0,0
access_hotel_room_amenities_type_call,hotel.room_amenities_type.call,model_hotel_room_amenities_type,hotel.group_hotel_call,1,0,0,0
access_hotel_room_amenities_type_manager,hotel.room_amenities_type.manager,model_hotel_room_amenities_type,hotel.group_hotel_manager,1,1,1,1
access_hotel_room_call,hotel.room.call,model_hotel_room,hotel.group_hotel_call,1,0,0,0
access_hotel_room_manager,hotel.room.manager,model_hotel_room,hotel.group_hotel_manager,1,1,1,1
access_hotel_room_type,hotel.room_type.user,model_hotel_room_type,hotel.group_hotel_user,1,0,0,0
access_hotel_room_type_call,hotel.room_type.call,model_hotel_room_type,hotel.group_hotel_call,1,0,0,0
access_hotel_room_type_manager,hotel.room_type.manager,model_hotel_room_type,hotel.group_hotel_manager,1,1,1,1
access_hotel_service,hotel_service.user,model_hotel_service,hotel.group_hotel_user,1,1,1,1
access_hotel_service_call,hotel_service.call,model_hotel_service,hotel.group_hotel_call,1,1,1,1
access_hotel_user reconcilie,hotel.user reconcilie,account.model_account_partial_reconcile,hotel.group_hotel_user,1,1,1,1
access_hotel_user_account_full_reconcilie,hotel.user_account_full_reconcilie,account.model_account_full_reconcile,hotel.group_hotel_user,1,1,1,1
access_hotel_user_user,hotel.user_res_user,auth_crypt.model_res_users,hotel.group_hotel_user,1,1,0,0
access_hotel_virtual_room_availability_call,hotel.availability.call,hotel.model_hotel_virtual_room_availability,hotel.group_hotel_call,1,1,1,1
access_hotel_virtual_room_availability_manager,hotel.availability.manager,hotel.model_hotel_virtual_room_availability,hotel.group_hotel_manager,1,1,1,1
access_hotel_virtual_room_availability_user,hotel.availability.user,hotel.model_hotel_virtual_room_availability,hotel.group_hotel_user,1,1,1,1
access_product_category,product.category.user,product.model_product_category,hotel.group_hotel_user,1,0,0,0
access_product_category_call,product.category.call,product.model_product_category,hotel.group_hotel_call,1,0,0,0
access_product_category_manager,product.category.manager,product.model_product_category,hotel.group_hotel_manager,1,1,1,1
access_product_product,product.product.user,product.model_product_product,hotel.group_hotel_user,1,0,0,0
access_product_product_call,product.product.call,product.model_product_product,hotel.group_hotel_call,1,0,0,0
access_product_product_manager,product.product.manager,product.model_product_product,hotel.group_hotel_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_currency_exchange_call hotel.currency_exchange.call model_currency_exchange hotel.group_hotel_call 1 1 1 1
3 access_currency_exchange_user hotel.currency_exchange.user model_currency_exchange hotel.group_hotel_user 1 1 1 1
4 access_folio_room_line_call hotel.folio_room_line.call hotel.model_hotel_reservation_line hotel.group_hotel_call 1 1 1 1
5 access_folio_room_line_user hotel.folio_room_line.user hotel.model_hotel_reservation_line hotel.group_hotel_user 1 1 1 1
6 access_hotel_account_tax_call hotel.account.tax.call account.model_account_tax hotel.group_hotel_call 1 1 1 1
7 access_hotel_account_tax_user hotel.account.tax.user account.model_account_tax hotel.group_hotel_user 1 1 1 1
8 access_hotel_floor_group hotel.floor.user model_hotel_floor hotel.group_hotel_user 1 0 0 0
9 access_hotel_floor_group_call hotel.floor.call model_hotel_floor hotel.group_hotel_call 1 0 0 0
10 access_hotel_floor_group_manager hotel.floor.manager model_hotel_floor hotel.group_hotel_manager 1 1 1 1
11 access_hotel_folio hotel.folio.user model_hotel_folio hotel.group_hotel_user 1 1 1 1
12 access_hotel_folio_call hotel.folio.call model_hotel_folio hotel.group_hotel_call 1 1 1 1
13 access_hotel_folio_line hotel_folio.line.user model_hotel_reservation hotel.group_hotel_user 1 1 1 1
14 access_hotel_folio_line_call hotel_folio.line.call model_hotel_reservation hotel.group_hotel_call 1 1 1 1
15 access_hotel_invoice_call account.invoice.call account.model_account_invoice hotel.group_hotel_call 1 1 1 1
16 access_hotel_invoice_user account.invoice.user account.model_account_invoice hotel.group_hotel_user 1 1 1 1
17 access_hotel_model_cardex_call hotel.currency_exchange.call hotel.model_cardex hotel.group_hotel_call 1 1 1 1
18 access_hotel_model_cardex_user hotel.currency_exchange.user hotel.model_cardex hotel.group_hotel_user 1 1 1 1
19 access_hotel_order_call hotel.order.call sale.model_sale_order hotel.group_hotel_call 1 1 1 1
20 access_hotel_order_line_call hotel.order.line.call sale.model_sale_order_line hotel.group_hotel_call 1 1 1 1
21 access_hotel_order_line_user hotel.order.line.user sale.model_sale_order_line hotel.group_hotel_user 1 1 1 1
22 access_hotel_order_user hotel.order.user sale.model_sale_order hotel.group_hotel_user 1 1 1 1
23 access_hotel_restrictions_call hotel.restriction. All hotel.model_hotel_virtual_room_restriction hotel.group_hotel_call 1 0 0 0
24 access_hotel_restrictions_item_call hotel.restriction.item.call hotel.model_hotel_virtual_room_restriction_item hotel.group_hotel_call 1 0 0 0
25 access_hotel_restrictions_item_manager hotel.restriction.item.manager hotel.model_hotel_virtual_room_restriction_item hotel.group_hotel_manager 1 1 1 1
26 access_hotel_restrictions_item_user hotel.restriction.item.user hotel.model_hotel_virtual_room_restriction_item hotel.group_hotel_user 1 0 0 0
27 access_hotel_restrictions_manager hotel.restriction.manager hotel.model_hotel_virtual_room_restriction hotel.group_hotel_manager 1 1 1 1
28 access_hotel_restrictions_user hotel.restriction.user hotel.model_hotel_virtual_room_restriction hotel.group_hotel_user 1 0 0 0
29 access_hotel_room hotel.room.user model_hotel_room hotel.group_hotel_user 1 0 0 0
30 access_hotel_room_amenities hotel.room_aminities.user model_hotel_room_amenities hotel.group_hotel_user 1 0 0 0
31 access_hotel_room_amenities_call hotel.room_aminities.call model_hotel_room_amenities hotel.group_hotel_call 1 0 0 0
32 access_hotel_room_amenities_manager hotel.room_aminities.manager model_hotel_room_amenities hotel.group_hotel_manager 1 1 1 1
33 access_hotel_room_amenities_type hotel.room_amenities_type.user model_hotel_room_amenities_type hotel.group_hotel_user 1 0 0 0
34 access_hotel_room_amenities_type_call hotel.room_amenities_type.call model_hotel_room_amenities_type hotel.group_hotel_call 1 0 0 0
35 access_hotel_room_amenities_type_manager hotel.room_amenities_type.manager model_hotel_room_amenities_type hotel.group_hotel_manager 1 1 1 1
36 access_hotel_room_call hotel.room.call model_hotel_room hotel.group_hotel_call 1 0 0 0
37 access_hotel_room_manager hotel.room.manager model_hotel_room hotel.group_hotel_manager 1 1 1 1
38 access_hotel_room_type hotel.room_type.user model_hotel_room_type hotel.group_hotel_user 1 0 0 0
39 access_hotel_room_type_call hotel.room_type.call model_hotel_room_type hotel.group_hotel_call 1 0 0 0
40 access_hotel_room_type_manager hotel.room_type.manager model_hotel_room_type hotel.group_hotel_manager 1 1 1 1
41 access_hotel_service hotel_service.user model_hotel_service hotel.group_hotel_user 1 1 1 1
42 access_hotel_service_call hotel_service.call model_hotel_service hotel.group_hotel_call 1 1 1 1
43 access_hotel_user reconcilie hotel.user reconcilie account.model_account_partial_reconcile hotel.group_hotel_user 1 1 1 1
44 access_hotel_user_account_full_reconcilie hotel.user_account_full_reconcilie account.model_account_full_reconcile hotel.group_hotel_user 1 1 1 1
45 access_hotel_user_user hotel.user_res_user auth_crypt.model_res_users hotel.group_hotel_user 1 1 0 0
46 access_hotel_virtual_room_availability_call hotel.availability.call hotel.model_hotel_virtual_room_availability hotel.group_hotel_call 1 1 1 1
47 access_hotel_virtual_room_availability_manager hotel.availability.manager hotel.model_hotel_virtual_room_availability hotel.group_hotel_manager 1 1 1 1
48 access_hotel_virtual_room_availability_user hotel.availability.user hotel.model_hotel_virtual_room_availability hotel.group_hotel_user 1 1 1 1
49 access_product_category product.category.user product.model_product_category hotel.group_hotel_user 1 0 0 0
50 access_product_category_call product.category.call product.model_product_category hotel.group_hotel_call 1 0 0 0
51 access_product_category_manager product.category.manager product.model_product_category hotel.group_hotel_manager 1 1 1 1
52 access_product_product product.product.user product.model_product_product hotel.group_hotel_user 1 0 0 0
53 access_product_product_call product.product.call product.model_product_product hotel.group_hotel_call 1 0 0 0
54 access_product_product_manager product.product.manager product.model_product_product hotel.group_hotel_manager 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,56 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h1 style="font-size:200%; font-family:courier; color:black; text-align:center;"> HOTEL MANAGEMENT SYSTEM </h1>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12" style = "background-color: #009688; height:200px;">
<h1 style="font-family:courier; color:white ; text-align:right ; margin-right:1cm; margin-top: 35px"> HOTEL BOOKING </h1>
<p style="font-size:120%; font-family:courier; color:white ; text-align:right; margin-right: 2cm"> Book your room. </p>
<img style="position:relative;top:-120px; width: 200px; height:200px" src= "hotel1.png">
<img style="top:35px; width: 80px; height:80px; margin-right:2cm" src= "room.png" align="right">
<img style="position:relative; top:-170px; width:75px; height:75px;" src="book.png">
<img style="position:relative; top:-170px; width:70px; height:70px;" src="checkin.png">
<img style="position:relative; top:-170px; width:70px; height:70px;" src="key.png">
</div>
<div class="oe_span12" style = "background-color: #f1c40f; height:200px;">
<h1 style="font-family:courier; color:white ; text-align:left ; margin-top: 35px"> ALL INCLUSIVE </h1>
<p style="font-size:120%; font-family:courier; color:white ; text-align: left;"> Facilities provided </p>
<img style="top:35px;" src= "inc1.png">
<img style="top:35px; width: 50px; height: 50px;" src= "inc2.png" align="right">
<img style="top:35px; width: 50px; height: 50px;" src= "inc3.png" align="right">
<img style="top:35px; width: 50px; height: 50px;" src= "inc4.png" align="right">
<img style="top:50px; width: 50px; height: 50px;" src= "inc5.png" align="right">
</div>
<div class="oe_span12" style = "background-color: #f44336; height:200px;" >
<h1 style="font-family:courier; color:white ; text-align:right ; margin-right: 5cm; margin-top: 35px"> AMENITIES </h1>
<p style="font-size:120%; font-family:courier; color:white ; text-align:right; margin-right:2cm"> Extra class hotel service </p>
<img style="width:100px; height: 100px; margin-right:2cm" src="star_icon.png" align="right" >
<img style="top:35px; width:50px; height:50px; left:200px" src="gym.png">
<img style="width:80px; height:60px;" src="pool_icon.png" align="left">
<img style="width:60px; height:60px;" src="car.png">
</div>
<div class="oe_span12" style = "background-color: #cddc39; height:200px;" >
<h1 style="font-family:courier; color:white; text-align:left ; margin-top: 35px"> RESTURANTS </h1>
<p style="font-size:120%; font-family:courier; color:white ; text-align:left;"> Bon Appetite ! </p>
<img style="bottom:20px; width:250px; height: 150px" src="menu_waitor.png">
<img style="top:35px; width: 60px; height: 60px;" src= "plate.png" align="right">
<img style="top:35px; width: 60px; height: 60px;" src= "menu.png" align="right">
</div>
<div class="oe_span12" style = "background-color: #3ed37d; height:200px;" >
<h1 style="font-family:courier; color:white ; text-align:right ; margin-right:1cm; margin-top: 35px"> Currency Exchange </h1>
<p style="font-size:120%; font-family:courier; color:white ; text-align:right; margin-right:3cm"> Exchange currency on the go </p>
<img style="position:relative; top:-90px; width:110px; heigth:110px" src= "currency.png">
<img style="position:relative; top:-20px; width: 80px; height:80px; margin-right:1cm" src= "money.png" align="right">
</div>
<div class="oe_span12" style = "background-color: #34495e; height:200px;" >
<h1 style="font-family:courier; color:white ; text-align:left ; margin-right: 5cm; margin-top: 35px"> REPORTS </h1>
<p style="font-size:120%; font-family:courier; color:white ; text-align:left; margin-right:3cm"> Manage and Analyze </p>
<img style="position:relative; top:-80px; width: 150px; height:150px" src= "report1.png" align="right">
<img style="position:relative; top:-50px; width:100px; height:100px;" src="report.png" align="right">
</div>
</div>
</section>

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

24
hotel/tests/__init__.py Normal file
View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_reservation
from . import test_folio

251
hotel/tests/common.py Normal file
View File

@@ -0,0 +1,251 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import timedelta
from odoo import api, fields
from odoo.tests import common
from openerp.tools import (
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from odoo.addons.mail.tests.common import TestMail
from odoo.addons.hotel import date_utils
import pytz
import logging
_logger = logging.getLogger(__name__)
# TestMail crea recursos utiles para nuestros test...
# por ejemplo, usuarios con distintos tipos de nivel, etc...
class TestHotel(TestMail):
@classmethod
def _init_mock_hotel(cls):
return True
def create_folio(self, creator, partner):
# Create Folio
folio = self.env['hotel.folio'].sudo(creator).create({
'partner_id': partner.id,
})
self.assertTrue(folio, "Can't create folio")
return folio
def create_reservation(self, creator, folio, checkin, checkout, room,
resname, adults=1, children=0):
# Create Reservation (Special Room)
reservation = self.env['hotel.reservation'].sudo(creator).create({
'name': resname,
'adults': adults,
'children': children,
'checkin': checkin.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'checkout': checkout.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'folio_id': folio.id,
'virtual_room_id': room.price_virtual_room.id,
'product_id': room.product_id.id,
})
self.assertTrue(
reservation,
"Hotel Calendar can't create a new reservation!")
# Create Reservation Lines + Update Reservation Price
days_diff = date_utils.date_diff(checkin, checkout, hours=False)
res = reservation.sudo(creator).prepare_reservation_lines(
checkin.strftime(DEFAULT_SERVER_DATETIME_FORMAT), days_diff)
reservation.sudo(creator).write({
'reservation_lines': res['commands'],
'price_unit': res['total_price'],
})
return reservation
@classmethod
def setUpClass(cls):
super(TestHotel, cls).setUpClass()
cls._init_mock_hotel()
# Restriction Plan
cls.restriction_1 = cls.env['hotel.virtual.room.restriction'].create({
'name': 'Restriction Test #1',
'active': True
})
# Pricelist
cls.pricelist_1 = cls.env['product.pricelist'].create({
'name': 'Pricelist Test #1',
})
# Minimal Hotel Configuration
cls.tz_hotel = 'Europe/Madrid'
cls.parity_pricelist_id = cls.pricelist_1.id
cls.parity_restrictions_id = cls.restriction_1.id
cls.env['ir.values'].sudo().set_default('hotel.config.settings',
'tz_hotel', cls.tz_hotel)
cls.env['ir.values'].sudo().set_default('hotel.config.settings',
'parity_pricelist_id',
cls.parity_pricelist_id)
cls.env['ir.values'].sudo().set_default('hotel.config.settings',
'parity_restrictions_id',
cls.parity_restrictions_id)
# User Groups
user_group_hotel_manager = cls.env.ref('hotel.group_hotel_manager')
user_group_hotel_user = cls.env.ref('hotel.group_hotel_user')
user_group_employee = cls.env.ref('base.group_user')
user_group_public = cls.env.ref('base.group_public')
user_group_account_inv = cls.env.ref('account.group_account_invoice')
user_group_sale_manager = cls.env.ref('sales_team.group_sale_manager')
user_group_base_partner_manager = cls.env.ref(
'base.group_partner_manager')
# Create Test Users
Users = cls.env['res.users'].with_context({
'no_reset_password': True,
'mail_create_nosubscribe': True
})
cls.user_hotel_manager = Users.create({
'name': 'Jeff Hotel Manager',
'login': 'hoteljeff',
'email': 'mynameisjeff@example.com',
'signature': '--\nJeff',
'notify_email': 'always',
'groups_id': [(6, 0, [user_group_hotel_manager.id,
user_group_employee.id,
user_group_account_inv.id,
user_group_sale_manager.id,
user_group_base_partner_manager.id])]
})
cls.user_hotel_user = Users.create({
'name': 'Juancho Hotel User',
'login': 'juancho',
'email': 'juancho@example.com',
'signature': '--\nJuancho',
'notify_email': 'always',
'groups_id': [(6, 0, [user_group_hotel_user.id,
user_group_public.id])]
})
# Create Tests Records
RoomTypes = cls.env['hotel.room.type']
cls.hotel_room_type_simple = RoomTypes.create({
'name': 'Simple',
'code_type': 'TSMP',
})
cls.hotel_room_type_double = RoomTypes.create({
'name': 'Double',
'code_type': 'TDBL',
})
VRooms = cls.env['hotel.virtual.room']
cls.hotel_vroom_budget = VRooms.create({
'name': 'Budget Room',
'virtual_code': '001',
'list_price': 50,
})
cls.hotel_vroom_special = VRooms.create({
'name': 'Special Room',
'virtual_code': '002',
'list_price': 150,
})
Rooms = cls.env['hotel.room']
cls.hotel_room_simple_100 = Rooms.create({
'name': '100',
'sale_price_type': 'vroom',
'price_virtual_room': cls.hotel_vroom_budget.id,
'categ_id': cls.hotel_room_type_simple.cat_id.id,
'capacity': 1,
})
cls.hotel_room_simple_101 = Rooms.create({
'name': '101',
'sale_price_type': 'vroom',
'price_virtual_room': cls.hotel_vroom_budget.id,
'categ_id': cls.hotel_room_type_simple.cat_id.id,
'capacity': 1,
'sequence': 1,
})
cls.hotel_room_double_200 = Rooms.create({
'name': '200',
'sale_price_type': 'vroom',
'price_virtual_room': cls.hotel_vroom_special.id,
'categ_id': cls.hotel_room_type_double.cat_id.id,
'capacity': 2,
})
cls.hotel_vroom_budget.write({
'room_ids': [(6, False, [cls.hotel_room_simple_100.id,
cls.hotel_room_simple_101.id])],
})
cls.hotel_vroom_special.write({
'room_ids': [(6, False, [cls.hotel_room_double_200.id])],
})
# Create a week of fresh data
now_utc_dt = date_utils.now()
cls.avails_tmp = {
cls.hotel_vroom_budget.id: (1, 2, 2, 1, 1, 2, 2),
cls.hotel_vroom_special.id: (1, 1, 1, 1, 1, 1, 1),
}
cls.prices_tmp = {
cls.hotel_vroom_budget.id: (10.0, 80.0, 80.0, 95.0, 90.0, 80.0,
20.0),
cls.hotel_vroom_special.id: (5.0, 15.0, 15.0, 35.0, 35.0, 10.0,
10.0),
}
cls.restrictions_min_stay_tmp = {
cls.hotel_vroom_budget.id: (0, 1, 2, 1, 1, 0, 0),
cls.hotel_vroom_special.id: (3, 1, 0, 2, 0, 1, 4),
}
budget_product_id = cls.hotel_vroom_budget.product_id
special_product_id = cls.hotel_vroom_special.product_id
product_tmpl_ids = {
cls.hotel_vroom_budget.id: budget_product_id.product_tmpl_id.id,
cls.hotel_vroom_special.id: special_product_id.product_tmpl_id.id,
}
vroom_avail_obj = cls.env['hotel.virtual.room.availability']
vroom_rest_item_obj = cls.env['hotel.virtual.room.restriction.item']
pricelist_item_obj = cls.env['product.pricelist.item']
for k_vr, v_vr in cls.avails_tmp.iteritems():
for i in range(0, len(v_vr)):
ndate = now_utc_dt + timedelta(days=i)
vroom_avail_obj.create({
'virtual_room_id': k_vr,
'avail': v_vr[i],
'date': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)
})
vroom_rest_item_obj.create({
'virtual_room_id': k_vr,
'restriction_id': cls.parity_restrictions_id,
'date_start': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
'date_end': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
'applied_on': '0_virtual_room',
'min_stay': cls.restrictions_min_stay_tmp[k_vr][i],
})
pricelist_item_obj.create({
'pricelist_id': cls.parity_pricelist_id,
'date_start': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
'date_end': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
'compute_price': 'fixed',
'applied_on': '1_product',
'product_tmpl_id': product_tmpl_ids[k_vr],
'fixed_price': cls.prices_tmp[k_vr][i],
})

55
hotel/tests/test_folio.py Normal file
View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
from datetime import timedelta
from .common import TestHotel
from odoo.addons.hotel import date_utils
class TestHotelReservations(TestHotel):
def test_cancel_folio(self):
now_utc_dt = date_utils.now()
org_reserv_start_utc_dt = now_utc_dt + timedelta(days=3)
org_reserv_end_utc_dt = org_reserv_start_utc_dt + timedelta(days=6)
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation_a = self.create_reservation(
self.user_hotel_manager,
folio,
org_reserv_start_utc_dt,
org_reserv_end_utc_dt,
self.hotel_room_double_200,
"Reservation Test #1")
reservation_b = self.create_reservation(
self.user_hotel_manager,
folio,
org_reserv_start_utc_dt,
org_reserv_end_utc_dt,
self.hotel_room_simple_100,
"Reservation Test #2")
self.assertEqual(len(folio.room_lines), 2, 'Invalid room lines count')
folio.action_cancel()
self.assertEqual(folio.state, 'cancel', 'Invalid folio state')
for rline in folio.room_lines:
self.assertEqual(rline.state, 'cancelled',
'Invalid reservation state')

View File

@@ -0,0 +1,260 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <dev@redneboa.es>
#
#
# 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 <http://www.gnu.org/licenses/>.
#
##############################################################################
import datetime
from datetime import timedelta
from odoo import fields
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from openerp.exceptions import ValidationError
from .common import TestHotel
from odoo.addons.hotel import date_utils
import pytz
import logging
_logger = logging.getLogger(__name__)
class TestHotelReservations(TestHotel):
def test_create_reservation(self):
now_utc_dt = date_utils.now()
reserv_start_utc_dt = now_utc_dt + timedelta(days=3)
reserv_end_utc_dt = reserv_start_utc_dt + timedelta(days=3)
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Reservation Test #1")
reserv_start_dt = date_utils.dt_as_timezone(reserv_start_utc_dt,
self.tz_hotel)
reserv_end_dt = date_utils.dt_as_timezone(reserv_end_utc_dt -
timedelta(days=1),
self.tz_hotel)
self.assertEqual(reservation.reservation_lines[0].date,
reserv_start_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
"Reservation lines don't start in the correct date")
self.assertEqual(reservation.reservation_lines[-1].date,
reserv_end_dt.strftime(DEFAULT_SERVER_DATE_FORMAT),
"Reservation lines don't end in the correct date")
total_price = 0.0
for rline in reservation.reservation_lines:
total_price += rline.price
self.assertEqual(folio.amount_untaxed, total_price,
"Folio amount doesn't match with reservation lines")
def test_create_reservations(self):
now_utc_dt = date_utils.now()
reserv_start_utc_dt = now_utc_dt + timedelta(days=3)
reserv_end_utc_dt = reserv_start_utc_dt + timedelta(days=3)
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Reservation Test #1")
reserv_start_utc_dt = reserv_end_utc_dt
reserv_end_utc_dt = reserv_start_utc_dt + timedelta(days=3)
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Reservation Test #2")
reserv_end_utc_dt = now_utc_dt + timedelta(days=3)
reserv_start_utc_dt = reserv_end_utc_dt - timedelta(days=1)
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Reservation Test #3")
reserv_start_utc_dt = now_utc_dt + timedelta(days=3)
reserv_end_utc_dt = reserv_start_utc_dt + timedelta(days=3)
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_simple_100,
"Reservation Test #4")
def test_create_invalid_reservations(self):
now_utc_dt = date_utils.now()
org_reserv_start_utc_dt = now_utc_dt + timedelta(days=3)
org_reserv_end_utc_dt = org_reserv_start_utc_dt + timedelta(days=6)
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
org_reserv_start_utc_dt,
org_reserv_end_utc_dt,
self.hotel_room_double_200,
"Original Reservation Test #1")
# Same Dates
reserv_start_utc_dt = now_utc_dt + timedelta(days=3)
reserv_end_utc_dt = reserv_start_utc_dt + timedelta(days=6)
with self.assertRaises(ValidationError):
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Invalid Reservation Test #1")
# Inside Org Reservation (Start Same Date)
reserv_start_utc_dt = now_utc_dt + timedelta(days=3)
reserv_end_utc_dt = reserv_start_utc_dt + timedelta(days=3)
with self.assertRaises(ValidationError):
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Invalid Reservation Test #2")
# Inside Org Reservation (Start after)
reserv_start_utc_dt = now_utc_dt + timedelta(days=4)
reserv_end_utc_dt = reserv_start_utc_dt + timedelta(days=3)
with self.assertRaises(ValidationError):
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Invalid Reservation Test #3")
# Intersect Org Reservation (Start before)
reserv_start_utc_dt = now_utc_dt + timedelta(days=2)
reserv_end_utc_dt = reserv_start_utc_dt + timedelta(days=3)
with self.assertRaises(ValidationError):
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Invalid Reservation Test #4")
# Intersect Org Reservation (End Same)
reserv_start_utc_dt = org_reserv_end_utc_dt - timedelta(days=2)
reserv_end_utc_dt = org_reserv_end_utc_dt
with self.assertRaises(ValidationError):
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Invalid Reservation Test #5")
# Intersect Org Reservation (End after)
reserv_start_utc_dt = org_reserv_end_utc_dt - timedelta(days=2)
reserv_end_utc_dt = org_reserv_end_utc_dt + timedelta(days=3)
with self.assertRaises(ValidationError):
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Invalid Reservation Test #6")
# Overlays Org Reservation
reserv_start_utc_dt = org_reserv_start_utc_dt - timedelta(days=2)
reserv_end_utc_dt = org_reserv_end_utc_dt + timedelta(days=2)
with self.assertRaises(ValidationError):
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
reserv_start_utc_dt,
reserv_end_utc_dt,
self.hotel_room_double_200,
"Invalid Reservation Test #7")
# Checkin > Checkout
with self.assertRaises(ValidationError):
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
org_reserv_end_utc_dt,
org_reserv_start_utc_dt,
self.hotel_room_simple_100,
"Invalid Reservation Test #8")
def test_modify_reservation(self):
now_utc_dt = date_utils.now()
# 5.0, 15.0, 15.0, 35.0, 35.0, 10.0, 10.0
vroom_prices = self.prices_tmp[self.hotel_room_double_200.price_virtual_room.id]
org_reserv_start_utc_dt = now_utc_dt + timedelta(days=1)
org_reserv_end_utc_dt = org_reserv_start_utc_dt + timedelta(days=2)
folio = self.create_folio(self.user_hotel_manager, self.partner_2)
reservation = self.create_reservation(
self.user_hotel_manager,
folio,
org_reserv_start_utc_dt,
org_reserv_end_utc_dt,
self.hotel_room_double_200,
"Original Reservation Test #1")
ndate = org_reserv_start_utc_dt
for r_k, r_v in enumerate(reservation.reservation_lines):
self.assertEqual(r_v.date, ndate.strftime(DEFAULT_SERVER_DATE_FORMAT))
self.assertEqual(r_v.price, vroom_prices[r_k+1])
ndate = ndate + timedelta(days=1)
self.assertEqual(reservation.amount_room, 30.0)
ndate = org_reserv_start_utc_dt + timedelta(days=1)
line = reservation.reservation_lines.filtered(lambda r: r.date == ndate.strftime(DEFAULT_SERVER_DATE_FORMAT))
reservation.reservation_lines = [(1, line.id, {'price': 100.0})]
self.assertEqual(reservation.amount_room, 115.0)
reservation.sudo(self.user_hotel_manager).write({
'checkin': (org_reserv_start_utc_dt + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT),
'checkout': (org_reserv_end_utc_dt + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT),
})
self.assertEqual(reservation.amount_room, 135.0)

Some files were not shown because too many files have changed in this diff Show More