First Commit
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.pyc
|
||||
|
||||
.settings/
|
||||
|
||||
45
.travis.yml
Normal 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
@@ -0,0 +1,27 @@
|
||||
# HOOTEL PROJECT MODULES [](https://travis-ci.org/hootel/hootel) [](https://codecov.io/gh/hootel/hootel) 
|
||||
|
||||
|
||||
**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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
538
hotel/data/email_template_cancel.xml
Normal 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;">
|
||||
|
||||
</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;"> </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;"> ¡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>
|
||||
580
hotel/data/email_template_exit.xml
Normal 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;">
|
||||
</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" /><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>
|
||||
972
hotel/data/email_template_reserv.xml
Normal 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
|
After Width: | Height: | Size: 6.3 KiB |
BIN
hotel/data/hotel-grey.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
192
hotel/data/hotel_data.xml
Normal 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=""Ground"" name="name"/>
|
||||
</record>
|
||||
<record id="hotel_floor_first0" model="hotel.floor">
|
||||
<field eval=""First"" name="name"/>
|
||||
<field eval="1" name="sequence"/>
|
||||
</record>
|
||||
<record id="hotel_floor_second0" model="hotel.floor">
|
||||
<field eval=""Second"" name="name"/>
|
||||
<field eval="2" name="sequence"/>
|
||||
</record>
|
||||
<record id="hotel_floor_third0" model="hotel.floor">
|
||||
<field eval=""Third"" 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
@@ -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
@@ -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
@@ -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
7673
hotel/i18n/es.po
Normal file
33
hotel/models/__init__.py
Normal 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
@@ -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)
|
||||
150
hotel/models/currency_exchange.py
Normal 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
|
||||
251
hotel/models/hotel_dashboard.py
Normal 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
|
||||
32
hotel/models/hotel_floor.py
Normal 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
@@ -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'])
|
||||
1298
hotel/models/hotel_reservation.py
Normal file
35
hotel/models/hotel_reservation_line.py
Normal 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
@@ -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()
|
||||
30
hotel/models/hotel_room_amenities.py
Normal 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()
|
||||
27
hotel/models/hotel_room_amenities_type.py
Normal 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()
|
||||
141
hotel/models/hotel_room_type.py
Normal 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()
|
||||
78
hotel/models/hotel_service.py
Normal 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()
|
||||
246
hotel/models/hotel_service_line.py
Normal 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()
|
||||
25
hotel/models/hotel_service_type.py
Normal 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()
|
||||
70
hotel/models/hotel_virtual_room_availability.py
Normal 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"))
|
||||
50
hotel/models/hotel_virtual_room_restriction.py
Normal 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
|
||||
90
hotel/models/hotel_virtual_room_restriction_item.py
Normal 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"))
|
||||
87
hotel/models/inherit_account_invoice.py
Normal 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
|
||||
94
hotel/models/inherit_account_payment.py
Normal 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
|
||||
39
hotel/models/inherit_payment_return.py
Normal 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()
|
||||
14
hotel/models/inherit_product_category.py
Normal 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')
|
||||
41
hotel/models/inherit_product_pricelist.py
Normal 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
|
||||
14
hotel/models/inherit_product_product.py
Normal 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')
|
||||
19
hotel/models/inherit_res_company.py
Normal 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')
|
||||
28
hotel/models/inherit_res_partner.py
Normal 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)
|
||||
])
|
||||
45
hotel/models/inherited_mail_compose_message.py
Normal 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
@@ -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)"))
|
||||
137
hotel/models/virtual_room.py
Normal 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
@@ -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
|
||||
41
hotel/report/hotel_report.py
Normal 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
|
||||
13
hotel/report/hotel_report.xml
Normal 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>
|
||||
19
hotel/report/report_parte_viajero.xml
Normal 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>
|
||||
1
hotel/security/.~lock.ir.model.access.csv#
Normal file
@@ -0,0 +1 @@
|
||||
,slimbook,slimbook-PRO,26.07.2018 11:51,file:///home/slimbook/.config/libreoffice/4;
|
||||
22
hotel/security/hotel_security.xml
Normal 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>
|
||||
54
hotel/security/ir.model.access.csv
Normal 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
|
||||
|
BIN
hotel/static/description/bed.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
hotel/static/description/book.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
hotel/static/description/booking.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
hotel/static/description/car.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
hotel/static/description/checkin.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
hotel/static/description/currency.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
hotel/static/description/fast-food-icons.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
hotel/static/description/gym.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
hotel/static/description/head.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
hotel/static/description/hotel.png
Normal file
|
After Width: | Height: | Size: 69 KiB |
BIN
hotel/static/description/hotel1.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
hotel/static/description/hotel2.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
hotel/static/description/icon-book.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
hotel/static/description/icon.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
hotel/static/description/inc1.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
hotel/static/description/inc2.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
hotel/static/description/inc3.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
hotel/static/description/inc4.png
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
hotel/static/description/inc5.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
hotel/static/description/inc6.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
hotel/static/description/inc7.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
hotel/static/description/inc8.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
hotel/static/description/inc9.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
56
hotel/static/description/index.html
Normal 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>
|
||||
BIN
hotel/static/description/key.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
hotel/static/description/menu.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
hotel/static/description/menu_waitor.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
hotel/static/description/money.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
hotel/static/description/opt.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
hotel/static/description/plate.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
hotel/static/description/pool_icon.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
hotel/static/description/rent.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
hotel/static/description/report.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
hotel/static/description/report1.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
hotel/static/description/revenue.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
hotel/static/description/room.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
hotel/static/description/star_icon.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
hotel/static/description/swim.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
hotel/static/description/travel.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
hotel/static/description/vodna.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
hotel/static/description/waiter.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
24
hotel/tests/__init__.py
Normal 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
@@ -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
@@ -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')
|
||||
260
hotel/tests/test_reservation.py
Normal 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)
|
||||