diff --git a/call_center_report/README.rst b/call_center_report/README.rst new file mode 100644 index 000000000..ec06d16ac --- /dev/null +++ b/call_center_report/README.rst @@ -0,0 +1,6 @@ +CALL CENTER REPORT +============= + +Export call center report in xls format + + diff --git a/call_center_report/__init__.py b/call_center_report/__init__.py new file mode 100644 index 000000000..351d1ee57 --- /dev/null +++ b/call_center_report/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018 Alexandre Díaz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from . import wizard diff --git a/call_center_report/__manifest__.py b/call_center_report/__manifest__.py new file mode 100644 index 000000000..8204bbb7e --- /dev/null +++ b/call_center_report/__manifest__.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018 Alexandre Díaz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': 'Call Center Report', + 'version': '1.0', + 'author': "Dario Lodeiros", + 'website': 'https://www.eiqui.com', + 'category': 'reports', + 'summary': "Export services and reservation report in xls format", + 'description': "Call Center Report", + 'depends': [ + 'hotel', + ], + 'external_dependencies': { + 'python': ['xlsxwriter'] + }, + 'data': [ + 'wizard/call_center_report.xml', + 'data/menus.xml', + ], + 'qweb': [], + 'test': [ + ], + + 'installable': True, + 'auto_install': False, + 'application': False, + 'license': 'AGPL-3', +} diff --git a/call_center_report/data/menus.xml b/call_center_report/data/menus.xml new file mode 100644 index 000000000..d48c886eb --- /dev/null +++ b/call_center_report/data/menus.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/call_center_report/i18n/es.po b/call_center_report/i18n/es.po new file mode 100644 index 000000000..0f9b0fa66 --- /dev/null +++ b/call_center_report/i18n/es.po @@ -0,0 +1,171 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cash_daily_report +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-05-20 07:33+0000\n" +"PO-Revision-Date: 2018-05-20 09:34+0200\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"Language: es\n" +"X-Generator: Poedit 1.8.7.1\n" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:85 +#, python-format +msgid "Amount" +msgstr "Importe" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:78 +#: model:ir.ui.view,arch_db:cash_daily_report.view_cash_daily_report_wizard +#, python-format +msgid "Cash Daily Report" +msgstr "Informe de caja" + +#. module: cash_daily_report +#: model:ir.actions.act_window,name:cash_daily_report.action_open_cash_daily_report_wizard +#: model:ir.ui.menu,name:cash_daily_report.cash_daily_report_wizard +msgid "Cash Daily Report Wizard" +msgstr "Informe de caja diaria" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:82 +#, python-format +msgid "Client" +msgstr "Cliente" + +#. module: cash_daily_report +#: model:ir.ui.view,arch_db:cash_daily_report.view_cash_daily_report_wizard +msgid "Close" +msgstr "Cerrar" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_create_date +msgid "Created on" +msgstr "Creado en" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:83 +#, python-format +msgid "Date" +msgstr "Fecha" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_display_name +msgid "Display Name" +msgstr "Mostrar Nombre" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_date_end +msgid "End Date" +msgstr "Fecha finalización" + +#. module: cash_daily_report +#: model:ir.ui.view,arch_db:cash_daily_report.view_cash_daily_report_wizard +msgid "Generate XLS" +msgstr "Generar XLS" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_id +msgid "ID" +msgstr "ID" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:84 +#, python-format +msgid "Journal" +msgstr "Diario" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard___last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:80 +#, python-format +msgid "Name" +msgstr "Nombre" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:128 +#, python-format +msgid "Not Any Payments" +msgstr "No hay movimientos" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:81 +#, python-format +msgid "Reference" +msgstr "Referencia" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_date_start +msgid "Start Date" +msgstr "Fecha de inicio" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:143 +#, python-format +msgid "TOTAL" +msgstr "TOTAL" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:139 +#, python-format +msgid "TOTAL PAYMENT RETURNS" +msgstr "TOTAL DEVOLUCIONES" + +#. module: cash_daily_report +#: code:addons/cash_daily_report/wizard/cash_daily_report.py:134 +#, python-format +msgid "TOTAL PAYMENTS" +msgstr "TOTAL PAGOS" + +#. module: cash_daily_report +#: model:ir.ui.menu,name:cash_daily_report.menu_account_finance_xls_reports +msgid "XLS Reports" +msgstr "XLS Reports" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_xls_binary +msgid "Xls binary" +msgstr "Xls archivo" + +#. module: cash_daily_report +#: model:ir.model.fields,field_description:cash_daily_report.field_cash_daily_report_wizard_xls_filename +msgid "Xls filename" +msgstr "Xls nombre de archivo" + +#. module: cash_daily_report +#: model:ir.model,name:cash_daily_report.model_cash_daily_report_wizard +msgid "cash.daily.report.wizard" +msgstr "cash.daily.report.wizard" + +#. module: cash_daily_report +#: model:ir.ui.view,arch_db:cash_daily_report.view_cash_daily_report_wizard +msgid "or" +msgstr "o" diff --git a/call_center_report/wizard/__init__.py b/call_center_report/wizard/__init__.py new file mode 100644 index 000000000..f3c151d2d --- /dev/null +++ b/call_center_report/wizard/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018 Alexandre Díaz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from . import call_center_report diff --git a/call_center_report/wizard/call_center_report.py b/call_center_report/wizard/call_center_report.py new file mode 100644 index 000000000..d3da53d1d --- /dev/null +++ b/call_center_report/wizard/call_center_report.py @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2018 Alexandre Díaz +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +############################################################################## +from io import BytesIO +from datetime import datetime, date +import xlsxwriter +import base64 +from odoo import api, fields, models, _ +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT + + +class CallCenterReportWizard(models.TransientModel): + _name = 'call.center.report.wizard' + + @api.model + def _get_default_date_start(self): + return datetime.now().strftime(DEFAULT_SERVER_DATE_FORMAT) + + @api.model + def _get_default_date_end(self): + return datetime.now().strftime(DEFAULT_SERVER_DATE_FORMAT) + + date_start = fields.Date("Start Date", default=_get_default_date_start) + date_end = fields.Date("End Date", default=_get_default_date_end) + xls_filename = fields.Char() + xls_binary = fields.Binary() + + @api.model + def _export(self): + date_format = "%d-%m-%Y" + time_format = "%H:%M" + file_data = BytesIO() + workbook = xlsxwriter.Workbook(file_data, { + 'strings_to_numbers': True, + 'default_date_format': 'dd/mm/yyyy' + }) + + company_id = self.env.user.company_id + workbook.set_properties({ + 'title': 'Exported data from ' + company_id.name, + 'subject': 'Payments Data from Odoo of ' + company_id.name, + 'author': 'Odoo', + 'manager': u'Call Center', + 'company': company_id.name, + 'category': 'Hoja de Calculo', + 'keywords': 'payments, odoo, data, ' + company_id.name, + 'comments': 'Created with Python in Odoo and XlsxWriter'}) + workbook.use_zip64() + + xls_cell_format_date = workbook.add_format({ + 'num_format': 'dd/mm/yyyy' + }) + xls_cell_format_money = workbook.add_format({ + 'num_format': '#,##0.00' + }) + xls_cell_format_header = workbook.add_format({ + 'bg_color': '#CCCCCC' + }) + + worksheet = workbook.add_worksheet(_('Call Center Report - Production')) + + worksheet.write('A1', _('Ficha'), xls_cell_format_header) + worksheet.write('B1', _('Fecha de Pedido'), xls_cell_format_header) + worksheet.write('C1', _('Cliente'), xls_cell_format_header) + worksheet.write('D1', _('Producto'), xls_cell_format_header) + worksheet.write('E1', _('Noches/Uds'), xls_cell_format_header) + worksheet.write('F1', _('Adultos'), xls_cell_format_header) + worksheet.write('G1', _('Checkin'), xls_cell_format_header) + worksheet.write('H1', _('In-Hora'), xls_cell_format_header) + worksheet.write('I1', _('Checkout'), xls_cell_format_header) + worksheet.write('J1', _('Creado por'), xls_cell_format_header) + worksheet.write('K1', _('Total'), xls_cell_format_header) + + worksheet.set_column('B:B', 20) + worksheet.set_column('C:C', 20) + worksheet.set_column('D:D', 20) + worksheet.set_column('E:E', 20) + worksheet.set_column('F:F', 13) + + reservations_obj = self.env['hotel.reservation'] + reservations = reservations_obj.search([ + ('checkout', '>=', self.date_start), + ('checkout', '<=', self.date_end), + ('state', '=', 'done'), + ('channel_type', '=', 'call'), + ('folio_id.pending_amount', '<', 1), + ]) + offset = 1 + total_reservation_amount = 0.0 + for k_res, v_res in enumerate(reservations): + checkin_date = datetime.strptime(v_res.checkin, DEFAULT_SERVER_DATE_FORMAT) + checkout_date = datetime.strptime(v_res.checkout, DEFAULT_SERVER_DATE_FORMAT) + worksheet.write(k_res+offset, 0, v_res.folio_id.name) + worksheet.write(k_res+offset, 1, v_res.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_res+offset, 2, v_res.partner_id.name) + worksheet.write(k_res+offset, 3, v_res.room_type_id.name) + worksheet.write(k_res+offset, 4, v_res.nights) + worksheet.write(k_res+offset, 5, v_res.adults) + worksheet.write(k_res+offset, 6, checkin_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 7, v_res.arrival_hour) + worksheet.write(k_res+offset, 8, checkout_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 9, v_res.create_uid.name) + worksheet.write(k_res+offset, 10, v_res.price_total, + xls_cell_format_money) + total_reservation_amount += v_res.price_total + + folio_ids = reservations.mapped('folio_id.id') + folios = self.env['hotel.folio'].browse(folio_ids) + services = self.env['hotel.service'].browse() + for folio in folios: + services += folio.service_ids.filtered(lambda r: + r.channel_type == 'call' and r.folio_id.pending_amount < 1) + offset += len(reservations) + total_service_amount = k_line = 0.0 + for k_service, v_service in enumerate(services): + worksheet.write(k_service+offset, 0, v_service.folio_id.name) + worksheet.write(k_service+offset, 1, v_service.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_service+offset, 2, v_service.folio_id.partner_id.name) + worksheet.write(k_service+offset, 3, v_service.product_id.name) + worksheet.write(k_service+offset, 4, v_service.product_qty) + worksheet.write(k_service+offset, 5, '') + worksheet.write(k_service+offset, 6, '') + worksheet.write(k_service+offset, 7, '') + worksheet.write(k_service+offset, 8, '') + worksheet.write(k_service+offset, 9, v_service .create_uid.name) + worksheet.write(k_service+offset, 10, v_service.price_total, + xls_cell_format_money) + total_service_amount += v_service.price_total + offset += len(services) + #~ if total_reservation_amount == 0 and total_service_amount == 0: + #~ raise UserError(_('No Hay reservas de Call Center')) + line = offset + if k_line: + line = k_line + offset + if total_reservation_amount > 0: + line += 1 + worksheet.write(line, 9, _('TOTAL RESERVAS')) + worksheet.write(line, 10, total_reservation_amount, + xls_cell_format_money) + if total_service_amount > 0: + line += 1 + worksheet.write(line, 9, _('TOTAL SERVICIOS')) + worksheet.write(line, 10, total_service_amount, + xls_cell_format_money) + line += 1 + worksheet.write(line, 9, _('TOTAL')) + worksheet.write(line, 10 , total_reservation_amount + total_service_amount, + xls_cell_format_money) + + worksheet = workbook.add_worksheet(_('Call Center Report - Sales')) + + worksheet.write('A1', _('Estado'), xls_cell_format_header) + worksheet.write('B1', _('Ficha'), xls_cell_format_header) + worksheet.write('C1', _('Fecha de Pedido'), xls_cell_format_header) + worksheet.write('D1', _('Cliente'), xls_cell_format_header) + worksheet.write('E1', _('Producto'), xls_cell_format_header) + worksheet.write('F1', _('Noches/Uds'), xls_cell_format_header) + worksheet.write('G1', _('Adultos'), xls_cell_format_header) + worksheet.write('H1', _('Checkin'), xls_cell_format_header) + worksheet.write('I1', _('In-Hora'), xls_cell_format_header) + worksheet.write('J1', _('Checkout'), xls_cell_format_header) + worksheet.write('K1', _('Creado por'), xls_cell_format_header) + worksheet.write('L1', _('Total'), xls_cell_format_header) + + worksheet.set_column('B:B', 20) + worksheet.set_column('C:C', 20) + worksheet.set_column('D:D', 20) + worksheet.set_column('E:E', 20) + worksheet.set_column('F:F', 13) + + reservations_obj = self.env['hotel.reservation'] + reservations = reservations_obj.search([ + ('folio_id.date_order', '>=', self.date_start), + ('folio_id.date_order', '<=', self.date_end), + ('channel_type','=','call'), + ]) + offset = 1 + total_reservation_amount = 0.0 + for k_res, v_res in enumerate(reservations): + checkin_date = datetime.strptime(v_res.checkin, DEFAULT_SERVER_DATE_FORMAT) + checkout_date = datetime.strptime(v_res.checkout, DEFAULT_SERVER_DATE_FORMAT) + worksheet.write(k_res+offset, 0, v_res.state) + worksheet.write(k_res+offset, 1, v_res.folio_id.name) + worksheet.write(k_res+offset, 2, v_res.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_res+offset, 3, v_res.partner_id.name) + worksheet.write(k_res+offset, 4, v_res.room_type_id.name) + worksheet.write(k_res+offset, 5, v_res.nights) + worksheet.write(k_res+offset, 6, v_res.adults) + worksheet.write(k_res+offset, 7, checkin_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 8, v_res.arrival_hour) + worksheet.write(k_res+offset, 9, checkout_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 10, v_res.create_uid.name) + worksheet.write(k_res+offset, 11, v_res.price_total, + xls_cell_format_money) + total_reservation_amount += v_res.price_total + + folio_ids = reservations.mapped('folio_id.id') + folios = self.env['hotel.folio'].browse(folio_ids) + services = self.env['hotel.service'].browse() + for folio in folios: + services += folio.service_ids.filtered(lambda r: + r.channel_type == 'call' and r.folio_id.pending_amount < 1) + offset += len(reservations) + total_service_amount = k_line = 0.0 + for k_service, v_service in enumerate(services): + worksheet.write(k_service+offset, 1, v_service.folio_id.state) + worksheet.write(k_service+offset, 1, v_service.folio_id.name) + worksheet.write(k_service+offset, 2, v_service.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_service+offset, 3, v_service.folio_id.partner_id.name) + worksheet.write(k_service+offset, 4, v_service.product_id.name) + worksheet.write(k_service+offset, 5, v_service.product_qty) + worksheet.write(k_service+offset, 6, '') + worksheet.write(k_service+offset, 7, '') + worksheet.write(k_service+offset, 8, '') + worksheet.write(k_service+offset, 9, '') + worksheet.write(k_service+offset, 10, v_service .create_uid.name) + worksheet.write(k_service+offset, 11, v_service.price_total, + xls_cell_format_money) + total_service_amount += v_service.price_total + offset += len(services) + #~ if total_reservation_amount == 0 and total_service_amount == 0: + #~ raise UserError(_('No Hay reservas de Call Center')) + line = offset + if k_line: + line = k_line + offset + if total_reservation_amount > 0: + line += 1 + worksheet.write(line, 10, _('TOTAL RESERVAS')) + worksheet.write(line, 11, total_reservation_amount, + xls_cell_format_money) + if total_service_amount > 0: + line += 1 + worksheet.write(line, 10, _('TOTAL SERVICIOS')) + worksheet.write(line, 11, total_service_amount, + xls_cell_format_money) + line += 1 + worksheet.write(line, 10, _('TOTAL')) + worksheet.write(line, 11 , total_reservation_amount + total_service_amount, + xls_cell_format_money) + + worksheet = workbook.add_worksheet(_('Call Center Report - Cancelations')) + + worksheet.write('A1', _('Estado'), xls_cell_format_header) + worksheet.write('B1', _('Ficha'), xls_cell_format_header) + worksheet.write('C1', _('Fecha de Pedido'), xls_cell_format_header) + worksheet.write('D1', _('Cliente'), xls_cell_format_header) + worksheet.write('E1', _('Producto'), xls_cell_format_header) + worksheet.write('F1', _('Noches/Uds'), xls_cell_format_header) + worksheet.write('G1', _('Adultos'), xls_cell_format_header) + worksheet.write('H1', _('Checkin'), xls_cell_format_header) + worksheet.write('I1', _('In-Hora'), xls_cell_format_header) + worksheet.write('J1', _('Checkout'), xls_cell_format_header) + worksheet.write('K1', _('Creado por'), xls_cell_format_header) + worksheet.write('L1', _('Precio Final'), xls_cell_format_header) + worksheet.write('M1', _('Precio Original'), xls_cell_format_header) + + worksheet.set_column('B:B', 20) + worksheet.set_column('C:C', 20) + worksheet.set_column('D:D', 20) + worksheet.set_column('E:E', 20) + worksheet.set_column('F:F', 13) + + reservations_obj = self.env['hotel.reservation'] + reservations = reservations_obj.search([ + ('last_updated_res', '>=', self.date_start), + ('last_updated_res', '<=', self.date_end), + ('channel_type','=','call'), + ('state','=','cancelled'), + ]) + offset = 1 + total_reservation_amount = 0.0 + for k_res, v_res in enumerate(reservations): + checkin_date = datetime.strptime(v_res.checkin, DEFAULT_SERVER_DATE_FORMAT) + checkout_date = datetime.strptime(v_res.checkout, DEFAULT_SERVER_DATE_FORMAT) + worksheet.write(k_res+offset, 0, v_res.state) + worksheet.write(k_res+offset, 1, v_res.folio_id.name) + worksheet.write(k_res+offset, 2, v_res.folio_id.date_order, + xls_cell_format_date) + worksheet.write(k_res+offset, 3, v_res.partner_id.name) + worksheet.write(k_res+offset, 4, v_res.room_type_id.name) + worksheet.write(k_res+offset, 5, v_res.nights) + worksheet.write(k_res+offset, 6, v_res.adults) + worksheet.write(k_res+offset, 7, checkin_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 8, v_res.arrival_hour) + worksheet.write(k_res+offset, 9, checkout_date.strftime(date_format), + xls_cell_format_date) + worksheet.write(k_res+offset, 10, v_res.create_uid.name) + worksheet.write(k_res+offset, 11, v_res.price_total, + xls_cell_format_money) + worksheet.write(k_res+offset, 12, v_res.price_total - v_res.discount, + xls_cell_format_money) + total_reservation_amount += v_res.price_total + + offset += len(reservations) + + #~ if total_reservation_amount == 0 and total_service_amount == 0: + #~ raise UserError(_('No Hay reservas de Call Center')) + line = offset + if k_line: + line = k_line + offset + if total_reservation_amount > 0: + line += 1 + worksheet.write(line, 11, _('TOTAL RESERVAS')) + worksheet.write(line, 12, total_reservation_amount, + xls_cell_format_money) + + workbook.close() + file_data.seek(0) + tnow = fields.Datetime.now().replace(' ', '_') + return { + 'xls_filename': 'call_%s.xlsx' %self.env.user.company_id.property_name, + 'xls_binary': base64.encodestring(file_data.read()), + } + + @api.multi + def export(self): + self.write(self._export()) + return { + "type": "ir.actions.do_nothing", + } diff --git a/call_center_report/wizard/call_center_report.xml b/call_center_report/wizard/call_center_report.xml new file mode 100644 index 000000000..79439ea96 --- /dev/null +++ b/call_center_report/wizard/call_center_report.xml @@ -0,0 +1,42 @@ + + + + + call.center.report.wizard + call.center.report.wizard + +
+ + + + + + + + + + + + + + +
+
+
+
+
+ + + Call Center Report Wizard + ir.actions.act_window + call.center.report.wizard + + form + form + new + + +
diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index dd9d641b8..161a55904 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -41,6 +41,7 @@ 'views/hotel_room_type_class_views.xml', 'views/general.xml', 'views/inherited_product_template_views.xml', + 'views/inherited_product_pricelist_views.xml', 'views/hotel_room_amenities_type_views.xml', 'views/hotel_room_amenities_views.xml', 'views/hotel_room_type_restriction_views.xml', @@ -52,6 +53,7 @@ 'views/hotel_board_service_views.xml', 'views/hotel_checkin_partner_views.xml', 'views/hotel_board_service_room_type_views.xml', + 'views/hotel_cancelation_rule_views.xml', 'data/cron_jobs.xml', 'data/records.xml', 'data/email_template_cancel.xml', diff --git a/hotel/data/email_template_cancel.xml b/hotel/data/email_template_cancel.xml index 32c0bc91f..757f77348 100644 --- a/hotel/data/email_template_cancel.xml +++ b/hotel/data/email_template_cancel.xml @@ -12,524 +12,698 @@ + /*Global Styles*/ + .global { + margin: 0; + padding: 0; + min-width: 100% !important; + } + a { + color: #5e96ea; + text-decoration: none; + font-weight: bold; + } + img { + height: auto; + } + .content { + border: 1px solid #eeeeee; + } + .logo { + font-family: sans-serif; + font-size: 36px; + font-weight: bold; + color: #ffffff; + } + .link a { + font-family: sans-serif; + font-size: 12px; + color: #ffffff; + } + .subheading { + font-size: 14px; + color: #cccccc; + font-family: sans-serif; + font-weight: bold; + padding: 0 0 0 0; + text-transform: uppercase; + letter-spacing: 1px; + } + .h1 { + font-family: sans-serif; + font-size: 48px; + font-weight: bold; + line-height: 56px; + color: #ffffff; + padding: 0 0 0 0; + } + .h2 { + font-family: sans-serif; + font-size: 18px; + font-weight: bold; + color: #444444; + padding: 0 0 0 0; + text-transform: uppercase; + letter-spacing: 0.5px; + } + .h3 { + font-family: sans-serif; + font-size: 24px; + font-weight: regular; + color: #555555; + padding: 0 0 0 0; + } + .h4 { + font-family: sans-serif; + font-size: 18px; + font-weight: bold; + color: #666666; + padding: 0 0 0 0; + } + .paragraph { + font-family: sans-serif; + font-size: 14px; + line-height: 22px; + color: #666666; + font-weight: 200; + padding: 20px 0 0 0; + } + .listitem { + font-family: sans-serif; + font-size: 15px; + color: #666666; + font-weight: 200; + padding: 0 0 20px 0; + } + .smalltext { + font-family: sans-serif; + font-size: 14px; + color: #cccccc; + padding: 3px 0 0 0; + } + .borderbottom { + border-bottom: 1px solid #f2eeed; + } + /*Media Queries*/ + @media only screen and (max-width: 651px) { + .columns { + width: 100% !important; + } + .columncontainer { + display: block !important; + width: 100% !important; + } + .paragraph, + .listitem { + font-size: 18px; + } + .link { + float: left; + } + } + @media only screen and (min-width: 651px) { + .content { + width: 650px !important; + } + } + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ Alda Hotels + + + + + + +
+ + + + + + +
+ + + + + + + +
+
+
+ + + + + + + + + + + + + + + +
Tu reserva se ha cancelado en ${object.company_id.property_name}
+ + + + + + +
- /*Global Styles*/ - .global {margin: 0; padding: 0; min-width: 100%!important;} - a { color: #5e96ea; text-decoration: none; font-weight: bold;} - img {height: auto;} - .content { border: 1px solid #eeeeee; } - .logo {font-family: sans-serif; font-size: 36px; font-weight: bold; color: #ffffff;} - .link a {font-family: sans-serif; font-size: 12px; color: #ffffff;} - .subheading {font-size: 14px; color: #cccccc; font-family: sans-serif; font-weight: bold; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 1px;} - .h1 {font-family: sans-serif; font-size: 48px; font-weight: bold; line-height: 56px; color: #ffffff; padding: 0 0 0 0;} - .h2 {font-family: sans-serif; font-size: 18px; font-weight: bold; color: #444444; padding: 0 0 0 0; text-transform: uppercase; letter-spacing: 0.5px;} - .h3 {font-family: sans-serif; font-size: 24px; font-weight: regular; color: #555555; padding: 0 0 0 0;} - .h4 {font-family: sans-serif; font-size: 18px; font-weight: bold; color: #666666; padding: 0 0 0 0;} - .paragraph {font-family: sans-serif; font-size: 14px; line-height: 22px; color: #666666; font-weight: 200; padding: 20px 0 0 0;} - .listitem {font-family: sans-serif; font-size: 15px; color: #666666; font-weight: 200; padding: 0 0 20px 0;} - .smalltext { font-family: sans-serif; font-size: 14px; color: #cccccc; padding: 3px 0 0 0; } - .borderbottom {border-bottom: 1px solid #f2eeed;} - - /*Media Queries*/ - @media only screen and (max-width: 651px){ - .columns{width:100% !important;} - .columncontainer{display:block !important; width:100% !important;} - .paragraph, .listitem {font-size: 18px;} - .link { float: left;} - } - - @media only screen and (min-width: 651px) { - .content {width: 650px !important;} - } - - -
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
- Alda Hotels - - - - -
- - - - -
- - - - - -
-
-
- - - - - - - - - - - - -
Tu reserva se ha cancelado en ${object.company_id.property_name}
- - - - -
  - -
-
Hola ${object.partner_id.firstname}
- Tu reserva en ${object.company_id.property_name} se ha anulado correctamente. No es necesario que hagas nada más. - Si la cancelación conlleva la devolución de alguna cantidad, nos pondremos en contacto contigo. - En caso de que tengas alguna duda, estaremos encantados de atenderte. -
- - - - -
- -
Contactar -
-
-
-
-
- - - - - -
- - - - -
- Cancelación -
-
- - - - -
- - - - - - - - - - - - -
Datos de tu reserva cancelada
- ${object.partner_id.name} -
- % if object.partner_id.contact_address: - ${object.partner_id.contact_address}
- % endif -
- % for rline in object.get_grouped_reservations_json('cancelled'): - ${rline['num']} x ${rline['room_type']['name']} - % if rline['childrens'] > 0: - (${rline['adults']} Adults + ${rline['childrens']} Childrens) - % else: - (${rline['adults']} Adults) - %endif - -
- Entrada: ${format_tz(rline['checkin'], format="%d de %B de %Y")}
- Salida: ${format_tz(rline['checkout'], format="%d de %B de %Y")}
- Nº de noches: ${rline['nights']}

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

+ % endfor +
+
+
+
+ + + + + + + +
+ + + + + + +
+ Pago +
+
+ + + + + + +
+ + + + + + + + + + + + + + +
IMPORTES
+ Noches: ${len(object.room_lines[0].reservation_lines)}
+ Base imponible: ${object.amount_untaxed} €
+ I.V.A (10%): ${object.amount_tax} €
+ Precio total: ${object.amount_total} €
+ Coste de cancelación: [[importe]]
+
+
+
+
+
+
+ + + + + + + + + +
+ + + + + + +
+ + + + + +
+
+
+ + + + + +
+
+
+ + + + + + +
+ + + + + + + + + + + +
NUESTRAS REDES SOCIALES
+
+
+ + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + Facebook + +
Facebook
+ + + + + + +
+
+
Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.
+ + + + + + +
+ + +
Dale + a Me gusta
+
+ +
+
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + Instagram + +
Instagram
+ + + + + + +
+
+
Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.
+ + + + + + +
+ + +
#Entra +
+
+ +
+
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + Twitter + +
Twitter
+ + + + + + +
+
+
Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.
+ + + + + + +
+ + +
Síguenos +
+
+ +
+
+
+
+
+ + + + + + + + +
¡Esperamos verte + pronto!
+
+ + + + + +
+ Alda Hotels + + + + + +
+
+ +
+
]]>
diff --git a/hotel/data/email_template_reserv.xml b/hotel/data/email_template_reserv.xml index 9fff963d4..d2b5ec979 100644 --- a/hotel/data/email_template_reserv.xml +++ b/hotel/data/email_template_reserv.xml @@ -5,942 +5,1036 @@ Confirm Reservation-Send by Email - Confirmación de los detalles de su reserva en ${object.company_id.display_name} + Confirmación de los detalles de su reserva en ${object.company_id.property_name} ${(object.partner_id.id or '')} + /*Global Styles*/ + .marco { + bgcolor: #f6f6f6; + margin: 0; + padding: 0; + min-width: 100% !important; + } + + a { + color: #5e96ea; + text-decoration: none; + font-weight: bold; + } + + img { + height: auto; + } + + .content { + border: 1px solid #eeeeee; + } + + .logo { + font-family: sans-serif; + font-size: 36px; + font-weight: bold; + color: #ffffff; + } + + .link a { + font-family: sans-serif; + font-size: 12px; + color: #ffffff; + } + + .subheading { + font-size: 14px; + color: #cccccc; + font-family: sans-serif; + font-weight: bold; + padding: 0 0 0 0; + text-transform: uppercase; + letter-spacing: 1px; + } + + .h1 { + font-family: sans-serif; + font-size: 48px; + font-weight: bold; + line-height: 56px; + color: #ffffff; + padding: 0 0 0 0; + } + + .h2 { + font-family: sans-serif; + font-size: 18px; + font-weight: bold; + color: #444444; + padding: 0 0 0 0; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .h3 { + font-family: sans-serif; + font-size: 24px; + font-weight: regular; + color: #555555; + padding: 0 0 0 0; + } + + .h4 { + font-family: sans-serif; + font-size: 18px; + font-weight: bold; + color: #666666; + padding: 0 0 0 0; + } + + .paragraph { + font-family: sans-serif; + font-size: 14px; + line-height: 22px; + color: #666666; + font-weight: 200; + padding: 20px 0 0 0; + } + + .listitem { + font-family: sans-serif; + font-size: 15px; + color: #666666; + font-weight: 200; + padding: 0 0 20px 0; + } + + .smalltext { + font-family: sans-serif; + font-size: 14px; + color: #cccccc; + padding: 3px 0 0 0; + } + + .borderbottom { + border-bottom: 1px solid #f2eeed; + } + + /*Media Queries*/ + @media only screen and (max-width: 651px) { + .columns { + width: 100% !important; + } + + .columncontainer { + display: block !important; + width: 100% !important; + } + + .paragraph, + .listitem { + font-size: 18px; + } + + .link { + float: left; + } + } + + @media only screen and (min-width: 651px) { + .content { + width: 650px !important; + } + } +
- - - - -
- - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - - -
- Alda Hotels - - - - -
- - - - -
- - - - - -
-
-
- - - - - - - - - - - - - -
Confirmación de reserva en - ${object.company_id.display_name}
-

__

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

__

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

__

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

__

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

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

-
- -

+
+

Información importante

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

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

__

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


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

+

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

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

__

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

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

+ +

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

+
+
+
+
+ + + + + +
+ + + + +
+ Bus +
+
+ + + + +
+ + + + + + + + + + + + +
Cómo acceder
+ En autobús o tren +
Si vienes en autobús podrás venir desde A Coruña en las líneas 20, 22 y 1A, que te dejarán cerca de Ponte da Pasaxe. Desde allí, tendrás unos 15 minutos andando hasta el hotel.
+
+
+
+
+ + + + + + + + + +
+ + + + +
+ + + + +
+ + + + + + + + + + +
${object.company_id.city}
Santa Cristina es un lugar privilegiado. Playas de arena fina y blanca, paisajes impresionantes y unos alrededores dignos de conocer.
+ + + + +
+ + + +
+
+
+
+
+ + + + +
+ +
+
+ + + + +
+ +
+
+ + + + +
+ + + + +
+ + + + + + + + + +
${object.company_id.property_name}
El Hotel Alda Santa Cristina se encuentra a pie de playa, por lo que disfruta de unas vistas impresionantes de la ría de O Burgo. Además, a escasos minutos en coche, podrás estar en el centro de A Coruña.
+
+
+
+
+ + + + +
+ + + + + + + + + +
NUESTRAS REDES SOCIALES
+
+
+ + + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + Facebook + +
+ Facebook
+ + + + +
+ +
+
Toda la actualidad de nuestros alojamientos, así como ofertas y promociones.
+ + + + +
+ + +
Dale + a Me gusta
+
+ +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + Instagram + +
+ Instagram
+ + + + +
+ +
+
Cada detalle cuenta, y es por eso que tratamos de reflejarlo en nuestras fotos.
+ + + + +
+ + +
#Entra +
+
+ +
+
+
+
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + Twitter + +
+ Twitter
+ + + + +
+ +
+
Propuestas al minuto para hacer de tu viaje una experiencia inmejorable.
+ + + + +
+ + +
Síguenos +
+
+ +
+
+
+
+
+ + + + + + +
¡Muchas gracias por + alojarte + con nosotros!
+
+ + + +
+ Alda Hotels + + + +
+
+
- - - - -
- + + + ]]>
diff --git a/hotel/models/__init__.py b/hotel/models/__init__.py index dbb314957..ae46acfd3 100644 --- a/hotel/models/__init__.py +++ b/hotel/models/__init__.py @@ -31,3 +31,4 @@ from . import hotel_board_service from . import hotel_board_service_room_type_line from . import hotel_board_service_line from . import inherited_account_invoice_line +from . import hotel_cancelation_rule diff --git a/hotel/models/hotel_cancelation_rule.py b/hotel/models/hotel_cancelation_rule.py new file mode 100644 index 000000000..0415d3b56 --- /dev/null +++ b/hotel/models/hotel_cancelation_rule.py @@ -0,0 +1,30 @@ +# Copyright 2017 Alexandre Díaz +# Copyright 2017 Dario Lodeiros +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from odoo import models, fields + + +class HotelCancelationRule(models.Model): + _name = 'hotel.cancelation.rule' + _description = 'Cancelation Rules' + + name = fields.Char('Amenity Name', translate=True, required=True) + active = fields.Boolean('Active', default=True) + pricelist_ids = fields.One2many('product.pricelist', + 'cancelation_rule_id', + 'Pricelist that use this rule') + days_intime = fields.Integer( + 'Days Late', + help='Maximum number of days for free cancellation before Checkin') + penalty_late = fields.Integer('% Penalty Late', defaul="100") + apply_on_late = fields.Selection([ + ('first', 'First Day'), + ('all', 'All Days'), + ('days', 'Specify days')], 'Late apply on', default='first') + days_late = fields.Integer('Late first days', default="2") + penalty_noshow = fields.Integer('% Penalty No Show', default="100") + apply_on_noshow = fields.Selection([ + ('first', 'First Day'), + ('all', 'All Days'), + ('days', 'Specify days')], 'No Show apply on', default='all') + days_noshow = fields.Integer('NoShow first days', default="2") diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index daa3236db..23558e7c7 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -276,16 +276,17 @@ class HotelFolio(models.Model): 'amount_total': amount_untaxed + amount_tax, }) - @api.depends('amount_total', 'payment_ids', 'return_ids', 'reservation_type') + @api.depends('amount_total', 'payment_ids', 'return_ids', + 'reservation_type', 'state') @api.multi def compute_amount(self): acc_pay_obj = self.env['account.payment'] for record in self: if record.reservation_type in ('staff', 'out'): vals = { - 'pending_amount': 0, - 'invoices_paid': 0, - 'refund_amount': 0, + 'pending_amount': 0, + 'invoices_paid': 0, + 'refund_amount': 0, } record.update(vals) else: @@ -294,10 +295,17 @@ class HotelFolio(models.Model): ('folio_id', '=', record.id) ]) total_paid = sum(pay.amount for pay in payments) - return_lines = self.env['payment.return.line'].search([('move_line_ids','in',payments.mapped('move_line_ids.id')),('return_id.state','=', 'done')]) + return_lines = self.env['payment.return.line'].search([ + ('move_line_ids', 'in', payments.mapped('move_line_ids.id')), + ('return_id.state', '=', 'done') + ]) total_inv_refund = sum(pay_return.amount for pay_return in return_lines) + total = record.amount_total + # REVIEW: Must We ignored services in cancelled folios pending amount? + if record.state == 'cancelled': + total = total - sum(record.service_ids.mapped('price_total')) vals = { - 'pending_amount': record.amount_total - total_paid + total_inv_refund, + 'pending_amount': total - total_paid + total_inv_refund, 'invoices_paid': total_paid, 'refund_amount': total_inv_refund, } @@ -408,7 +416,6 @@ class HotelFolio(models.Model): else: vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') or _('New') - # Makes sure partner_invoice_id' and 'pricelist_id' are defined lfields = ('partner_invoice_id', 'partner_shipping_id', 'pricelist_id') if any(f not in vals for f in lfields): @@ -471,7 +478,7 @@ class HotelFolio(models.Model): @api.onchange('partner_diff_invoicing') def onchange_partner_diff_invoicing(self): - if self.partner_diff_invoicing == False: + if self.partner_diff_invoicing is False: self.update({'partner_invoice_id': self.partner_id.id}) elif self.partner_id == self.partner_invoice_id: self.update({'partner_invoice_id': self.partner_id.address_get(['invoice'])['invoice'] or None}) @@ -515,17 +522,14 @@ class HotelFolio(models.Model): @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() + for folio in self: + for reservation in folio.room_lines.filtered(lambda res: + res.state != 'cancelled'): + reservation.action_cancel() + self.write({ + 'state': 'cancel', + }) + return True @api.multi def print_quotation(self): @@ -544,13 +548,14 @@ class HotelFolio(models.Model): 'state': 'confirm', 'confirmation_date': fields.Datetime.now() }) - #~ if self.env.context.get('send_email'): - #~ self.force_quotation_send() + + # if self.env.context.get('send_email'): + # self.force_quotation_send() # create an analytic account if at least an expense product - #~ if any([expense_policy != 'no' for expense_policy in self.order_line.mapped('product_id.expense_policy')]): - #~ if not self.analytic_account_id: - #~ self._create_analytic_account() + # if any([expense_policy != 'no' for expense_policy in self.order_line.mapped('product_id.expense_policy')]): + # if not self.analytic_account_id: + # self._create_analytic_account() return True diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index fa2fcb7df..662d9f872 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -170,12 +170,15 @@ class HotelReservation(models.Model): track_visibility='onchange', help='Number of children there in guest list.') to_assign = fields.Boolean('To Assign', track_visibility='onchange') - state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'), - ('booking', 'On Board'), ('done', 'Out'), - ('cancelled', 'Cancelled')], - 'State', readonly=True, - default=lambda *a: 'draft', - track_visibility='onchange') + state = fields.Selection([ + ('draft', 'Pre-reservation'), + ('confirm', 'Pending Entry'), + ('booking', 'On Board'), + ('done', 'Out'), + ('cancelled', 'Cancelled') + ], string='State', readonly=True, + default=lambda *a: 'draft', copy=False, + track_visibility='onchange') reservation_type = fields.Selection(related='folio_id.reservation_type', default=lambda *a: 'normal') invoice_count = fields.Integer(related='folio_id.invoice_count') @@ -488,7 +491,7 @@ class HotelReservation(models.Model): def _prepare_add_missing_fields(self, values): """ Deduce missing required fields from the onchange """ res = {} - onchange_fields = ['room_id', 'reservation_type', + onchange_fields = ['room_id', 'reservation_type', 'tax_ids', 'currency_id', 'name', 'service_ids'] if values.get('room_type_id'): line = self.new(values) @@ -635,6 +638,11 @@ class HotelReservation(models.Model): write_vals.update({'room_type_id': self.room_id.room_type_id.id}) self.update(write_vals) + @api.onchange('cancelled_reason') + def onchange_cancelled_reason(self): + for record in self: + record._compute_cancelled_discount() + @api.onchange('partner_id') def onchange_partner_id(self): addr = self.partner_id.address_get(['invoice']) @@ -815,6 +823,9 @@ class HotelReservation(models.Model): else: vals.update({'state': 'confirm'}) record.write(vals) + record.reservation_line_ids.update({ + 'cancel_discount': 0 + }) if record.splitted: master_reservation = record.parent_reservation or record @@ -846,7 +857,9 @@ class HotelReservation(models.Model): for record in self: record.write({ 'state': 'cancelled', + 'cancelled_reason': record.compute_cancelation_reason() }) + record._compute_cancelled_discount() if record.splitted: master_reservation = record.parent_reservation or record splitted_reservs = self.env['hotel.reservation'].search([ @@ -861,6 +874,25 @@ class HotelReservation(models.Model): splitted_reservs.action_cancel() record.folio_id.compute_amount() + @api.multi + def compute_cancelation_reason(self): + self.ensure_one() + pricelist = self.pricelist_id + if pricelist and pricelist.cancelation_rule_id: + tz_hotel = self.env['ir.default'].sudo().get( + 'res.config.settings', 'tz_hotel') + today = fields.Date.context_today(self.with_context( + tz=tz_hotel)) + days_diff = (fields.Date.from_string(self.real_checkin) - + fields.Date.from_string(today)).days + if days_diff < 0: + return 'noshow' + elif days_diff < pricelist.cancelation_rule_id.days_intime: + return 'late' + else: + return 'intime' + return False + @api.multi def draft(self): for record in self: @@ -904,11 +936,17 @@ class HotelReservation(models.Model): return True return False - @api.depends('reservation_line_ids.discount') + @api.depends('reservation_line_ids.discount', + 'reservation_line_ids.cancel_discount') def _compute_discount(self): for record in self: - record.discount = sum(line.price * ((line.discount or 0.0) * 0.01) \ - for line in record.reservation_line_ids) + discount = 0 + for line in record.reservation_line_ids: + first_discount = line.price * ((line.discount or 0.0) * 0.01) + price = line.price - first_discount + cancel_discount = price * ((line.cancel_discount or 0.0) * 0.01) + discount += first_discount + cancel_discount + record.discount = discount @api.depends('reservation_line_ids.price', 'discount', 'tax_ids') def _compute_amount_reservation(self): @@ -927,6 +965,51 @@ class HotelReservation(models.Model): 'price_subtotal': taxes['total_excluded'], }) + @api.multi + def _compute_cancelled_discount(self): + self.ensure_one() + pricelist = self.pricelist_id + if self.state == 'cancelled': + if self.cancelled_reason and pricelist and pricelist.cancelation_rule_id: + date_start_dt = fields.Date.from_string(self.real_checkin or self.checkin) + date_end_dt = fields.Date.from_string(self.real_checkout or self.checkout) + days = abs((date_end_dt - date_start_dt).days) + rule = pricelist.cancelation_rule_id + if self.cancelled_reason == 'late': + discount = 100 - rule.penalty_late + if rule.apply_on_late == 'first': + days = 1 + elif rule.apply_on_late == 'days': + days = rule.days_late + elif self.cancelled_reason == 'noshow': + discount = 100 - rule.penalty_noshow + if rule.apply_on_noshow == 'first': + days = 1 + elif rule.apply_on_noshow == 'days': + days = rule.days_late - 1 + elif self.cancelled_reason == 'intime': + discount = 100 + + checkin = self.real_checkin or self.checkin + dates = [] + for i in range(0, days): + dates.append((fields.Date.from_string(checkin) + timedelta(days=i)).strftime( + DEFAULT_SERVER_DATE_FORMAT)) + self.reservation_line_ids.filtered(lambda r: r.date in dates).update({ + 'cancel_discount': discount + }) + self.reservation_line_ids.filtered(lambda r: r.date not in dates).update({ + 'cancel_discount': 100 + }) + else: + self.reservation_line_ids.update({ + 'cancel_discount': 0 + }) + else: + self.reservation_line_ids.update({ + 'cancel_discount': 0 + }) + @api.model def prepare_reservation_lines(self, dfrom, days, pricelist_id, vals=False, update_old_prices=False): total_price = 0.0 @@ -1164,7 +1247,6 @@ class HotelReservation(models.Model): 'price_total': tprice[1], 'parent_reservation': parent_res.id, 'room_type_id': parent_res.room_type_id.id, - 'discount': parent_res.discount, 'state': parent_res.state, 'reservation_line_ids': reservation_lines[1], 'preconfirm': False, diff --git a/hotel/models/hotel_reservation_line.py b/hotel/models/hotel_reservation_line.py index 11432fe80..afce0fe0f 100644 --- a/hotel/models/hotel_reservation_line.py +++ b/hotel/models/hotel_reservation_line.py @@ -22,9 +22,13 @@ class HotelReservationLine(models.Model): ondelete='cascade', required=True, copy=False) date = fields.Date('Date') + state = fields.Selection(related='reservation_id.state') price = fields.Float( string='Price', digits=dp.get_precision('Product Price')) + cancel_discount = fields.Float( + string='Cancel Discount (%)', + digits=dp.get_precision('Discount'), default=0.0) discount = fields.Float( string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) diff --git a/hotel/models/hotel_service.py b/hotel/models/hotel_service.py index c507b78fc..cd71ff1ed 100644 --- a/hotel/models/hotel_service.py +++ b/hotel/models/hotel_service.py @@ -117,6 +117,7 @@ class HotelService(models.Model): folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade', default=_default_folio_id) + state = fields.Selection(related='folio_id.state') ser_room_line = fields.Many2one('hotel.reservation', 'Room', default=_default_ser_room_line) per_day = fields.Boolean(related='product_id.per_day', related_sudo=True) diff --git a/hotel/models/hotel_service_line.py b/hotel/models/hotel_service_line.py index e7e986b84..c5b257387 100644 --- a/hotel/models/hotel_service_line.py +++ b/hotel/models/hotel_service_line.py @@ -4,6 +4,7 @@ from odoo import models, fields, api, _ from odoo.exceptions import ValidationError + class HotelServiceLine(models.Model): _name = "hotel.service.line" _order = "date" @@ -11,6 +12,7 @@ class HotelServiceLine(models.Model): service_id = fields.Many2one('hotel.service', string='Service Room', ondelete='cascade', required=True, copy=False) + active = fields.Boolean('Active', compute="_compute_active") date = fields.Date('Date') day_qty = fields.Integer('Units') product_id = fields.Many2one(related='service_id.product_id', store=True) diff --git a/hotel/models/inherited_product_pricelist.py b/hotel/models/inherited_product_pricelist.py index cae65df63..2296f9855 100644 --- a/hotel/models/inherited_product_pricelist.py +++ b/hotel/models/inherited_product_pricelist.py @@ -11,6 +11,9 @@ class ProductPricelist(models.Model): pricelist_type = fields.Selection([ ('daily', 'Daily Plan'), ], string='Pricelist Type', default='daily') + cancelation_rule_id = fields.Many2one( + 'hotel.cancelation.rule', + string="Cancelation Policy") @api.multi @api.depends('name') diff --git a/hotel/views/hotel_cancelation_rule_views.xml b/hotel/views/hotel_cancelation_rule_views.xml new file mode 100644 index 000000000..d765373bc --- /dev/null +++ b/hotel/views/hotel_cancelation_rule_views.xml @@ -0,0 +1,63 @@ + + + + + + + hotel.cancelation.rule.form + hotel.cancelation.rule + +
+ +

+ + +

+ + + + + + + + + + + + +
+
+
+
+ + + + hotel.cancelation.rule.tree + hotel.cancelation.rule + + + + + + + + + + + + + + + Cancelation Rules + hotel.cancelation.rule + form + tree,form + + + + +
diff --git a/hotel/views/hotel_folio_views.xml b/hotel/views/hotel_folio_views.xml index 9dbbd7d5c..cbdd0134f 100644 --- a/hotel/views/hotel_folio_views.xml +++ b/hotel/views/hotel_folio_views.xml @@ -21,20 +21,18 @@ -

diff --git a/hotel/views/hotel_reservation_views.xml b/hotel/views/hotel_reservation_views.xml index d50458d53..871e6f252 100644 --- a/hotel/views/hotel_reservation_views.xml +++ b/hotel/views/hotel_reservation_views.xml @@ -16,7 +16,7 @@ hotel.reservation 20 -
+
@@ -61,21 +61,21 @@ attrs="{'invisible':[('splitted', '=', False)]}" />
- - - + + + +
+
+
diff --git a/hotel_l10n_es/wizard/police_wizard.py b/hotel_l10n_es/wizard/police_wizard.py index 4ee0a847e..d8e85b638 100755 --- a/hotel_l10n_es/wizard/police_wizard.py +++ b/hotel_l10n_es/wizard/police_wizard.py @@ -37,6 +37,7 @@ class PoliceWizard(models.TransientModel): txt_binary = fields.Binary() txt_message = fields.Char() log_police = fields.Char() + error_partner = fields.Many2one('res.partner') @api.one def generate_file(self): @@ -56,6 +57,7 @@ class PoliceWizard(models.TransientModel): if ((line.partner_id.document_type is not False) and (line.partner_id.document_number is not False) and (line.partner_id.firstname is not False) + and (line.partner_id.gender is not False) and (line.partner_id.lastname is not False)): log_police += 1 @@ -101,11 +103,13 @@ class PoliceWizard(models.TransientModel): content += """ """ else: + self.error_partner = line.partner_id + return self.write({ + 'error_partner': line.partner_id.id, 'txt_message': _('Problem generating the file. \ Checkin without data, \ - or incorrect data: - ' + - line.partner_id.name)}) + or incorrect data: ')}) log_police = str(log_police) + _(' records added from ') log_police += str(len(lines)) + _(' records processed.') return self.write({ diff --git a/hotel_l10n_es/wizard/police_wizard.xml b/hotel_l10n_es/wizard/police_wizard.xml index 0fa0e002d..34ddf55a4 100755 --- a/hotel_l10n_es/wizard/police_wizard.xml +++ b/hotel_l10n_es/wizard/police_wizard.xml @@ -26,7 +26,7 @@

-

+