Merge branch '11.0' into pr_roommatik

This commit is contained in:
Unknown
2019-04-02 11:18:52 +02:00
46 changed files with 3131 additions and 1554 deletions

View File

@@ -0,0 +1,6 @@
CALL CENTER REPORT
=============
Export call center report in xls format

View File

@@ -0,0 +1,21 @@
# -*- 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 . import wizard

View File

@@ -0,0 +1,48 @@
# -*- 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/>.
#
##############################################################################
{
'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',
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem id="call_center_report_wizard" name="Call Center Report"
parent="hotel.hotel_management_menu"
groups="hotel.group_hotel_call,hotel.group_hotel_manager"
action="action_open_call_center_report_wizard" sequence="1000" />
</data>
</odoo>

View File

@@ -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"

View File

@@ -0,0 +1,21 @@
# -*- 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 . import call_center_report

View File

@@ -0,0 +1,346 @@
# -*- 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 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",
}

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" ?>
<odoo>
<record model="ir.ui.view" id="view_call_center_report_wizard">
<field name="name">call.center.report.wizard</field>
<field name="model">call.center.report.wizard</field>
<field name="arch" type="xml">
<form string="Call Center Report" >
<group>
<group>
<field name="date_start" required="1" />
</group>
<group>
<field name="date_end" required="1" />
</group>
</group>
<group>
<group>
<field name="xls_filename" invisible="1"/>
<field name="xls_binary" filename="xls_filename" readonly="1" />
</group>
</group>
<footer>
<button name="export" string="Generate XLS" type="object" class="oe_highlight" />
or
<button string="Close" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_open_call_center_report_wizard" model="ir.actions.act_window">
<field name="name">Call Center Report Wizard</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">call.center.report.wizard</field>
<field name="view_id" ref="view_call_center_report_wizard"/>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>

View File

@@ -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',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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")

View File

@@ -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

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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')

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!--==================================================== Cancelation Rules ==================================================== -->
<!-- Form view of cancelation rules -->
<record model="ir.ui.view" id="hotel_cancelation_rule_form">
<field name="name">hotel.cancelation.rule.form</field>
<field name="model">hotel.cancelation.rule</field>
<field name="arch" type="xml">
<form string="Cancelation Rules">
<sheet>
<h3>
<field name="name" />
<separator />
<label for="name" string="Max. days InTime before Checkin" />
<field name="days_intime" />
</h3>
<group>
<group string="Late">
<field name="penalty_late" />
<field name="apply_on_late" />
<field name="days_late" attrs="{'invisible': [('apply_on_late','not in',('days'))]}" />
</group>
<group string="No Show">
<field name="penalty_noshow" />
<field name="apply_on_noshow" />
<field name="days_noshow" attrs="{'invisible': [('apply_on_noshow','not in',('days'))]}" />
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Tree view of cancelation rules -->
<record model="ir.ui.view" id="hotel_cancelation_rule_view_tree">
<field name="name">hotel.cancelation.rule.tree</field>
<field name="model">hotel.cancelation.rule</field>
<field name="arch" type="xml">
<tree string="Cancelation Rules">
<field name="name" />
<field name="days_intime" />
<field name="penalty_late" />
<field name="apply_on_late" />
<field name="penalty_noshow" />
<field name="apply_on_noshow" />
</tree>
</field>
</record>
<!-- Action of hotel cancelation rules -->
<record model="ir.actions.act_window" id="action_hotel_cancelation_rule">
<field name="name">Cancelation Rules</field>
<field name="res_model">hotel.cancelation.rule</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem name="Cancelation Rules" id="menu_hotel_cancelation_rule"
action="action_hotel_cancelation_rule" sequence="25"
parent="hotel.configuration_others" />
</odoo>

View File

@@ -21,20 +21,18 @@
<!-- <button name="send_exit_mail" type="object" string="Send Exit Email"
attrs="{'invisible': [('has_checkout_to_send', '=', False)]}" class="oe_highlight"/> -->
<button name="%(hotel.action_view_folio_advance_payment_inv)d"
string="Create Invoice" type="action" class="btn-primary" states="sale"
string="Create Invoice" type="action" class="btn-primary"
attrs="{'invisible': [('state', '!=', 'confirm')]}"/>
<!-- <button name="action_cancel_draft" states="cancel,sale" string="Set to Draft"
type="object" icon="fa-undo" class="oe_highlight" /> -->
<button name="action_cancel" string="Cancel Folio" states="sale"
type="object" icon="fa-minus-square" class="oe_highlight" />
<button name="action_cancel" string="Cancel Folio" states="draft"
icon="fa-minus-square" type="object" class="oe_highlight" />
<button name="action_cancel" string="Cancel Folio"
attrs="{'invisible': [('state', 'not in', ('confirm','draft'))]}"
type="object" />
<button name="action_done" type="object" string="Set to Done"
help="If a Hotel Folio is done, you cannot modify it manually anymore. However, you will still be able to invoice or deliver. This is used to freeze the Hotel Folio." />
<!-- states="sale" attrs="{'invisible': [('invoice_status', '!=', 'invoiced')]}" -->
help="If a Hotel Folio is done, you cannot modify it manually anymore. However, you will still be able to invoice. This is used to freeze the Hotel Folio." />
<!-- <button name="print_quotation" string="Print" type="object" states="sent,sale"/> -->
<field name="state" select="2" widget="statusbar"
statusbar_visible="draft,sent,sale,done" invisible="1"/>
statusbar_visible="draft,sent,sale,done" />
</header>
<sheet>
@@ -66,7 +64,6 @@
</button>
</div>
<!-- <field name="image" widget="image" class="oe_avatar" options="{&quot;preview_image&quot;: &quot;image_medium&quot;, &quot;size&quot;: [90, 90]}"/> -->
<h2><field name="name"/></h2>
<h1>
<field name="partner_id" default_focus="1" placeholder="Guest" attrs="{'invisible':[('reservation_type','in',('out'))]}"/>

View File

@@ -16,7 +16,7 @@
<field name="model">hotel.reservation</field>
<field name="priority">20</field>
<field name="arch" type="xml">
<form string="Reservation" >
<form string="Reservation">
<header>
<field name="splitted" invisible="True" />
<field name="tax_ids" invisible="1"/>
@@ -61,21 +61,21 @@
attrs="{'invisible':[('splitted', '=', False)]}"
/>
<button name="open_master" string="Open Master" type="object" class="oe_highlight" icon="fa-file" attrs="{'invisible':['|',['parent_reservation', '=', False]]}" />
<field name="state" widget="statusbar"/>
<field name="state" select="2" widget="statusbar" statusbar_visible="draft,confirm,booking,done" />
</header>
<div class="alert alert-info" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': ['|',('shared_folio','=',False),('splitted', '=', True)]}">
This reservation has other reservantions and/or services in the folio, you can check it in the
<bold><button class="alert-link" type="object" name="open_folio" string="Folio Form"/></bold>
</div>
<div class="alert alert-warning" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': [('splitted','=',False)]}">
This reservation is part of splitted reservation!, you can check it in the
<bold><button class="alert-link" type="object" name="open_folio" string="Folio Form"/></bold>
</div>
<field name="shared_folio" invisible="1"/>
<sheet>
<div class="alert alert-info" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': ['|',('shared_folio','=',False),('splitted', '=', True)]}">
This reservation has other reservantions and/or services in the folio, you can check it in the
<bold><button class="alert-link" type="object" name="open_folio" string="Folio Form"/></bold>
</div>
<div class="alert alert-warning" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': [('splitted','=',False)]}">
This reservation is part of splitted reservation!, you can check it in the
<bold><button class="alert-link" type="object" name="open_folio" string="Folio Form"/></bold>
</div>
<field name="shared_folio" invisible="1"/>
<div class="oe_button_box" attrs="{'invisible': [('folio_id','=',False)]}">
<button type="object" class="oe_stat_button"
icon="fa-file"
@@ -196,6 +196,7 @@
<field name="departure_hour"/>
</group>
<group colspan="4" string="Reservation Details" name="reservation_details">
<field name="cancelled_reason" attrs="{'invisible': [('state', 'not in', ('cancelled'))]}"/>
<field name="nights"/>
<!-- TODO: How to filter to avoid show False (generic) pricelist board when exist a specific pricelist board¿? -->
<field name="board_service_room_id" domain="[
@@ -287,6 +288,8 @@
<field name="date" />
<field name="price" />
<field name="discount" />
<field name="cancel_discount"
attrs="{'column_invisible': [('parent.state','!=','cancelled')]}" />
</tree>
</field>
</page>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<odoo>
<record id="product_pricelist_view_form" model="ir.ui.view">
<field name="model">product.pricelist</field>
<field name="inherit_id" ref="product.product_pricelist_view" />
<field name="arch" type="xml">
<xpath expr="//field[@name='country_group_ids']" position="before">
<field name="pricelist_type" />
<field name="cancelation_rule_id" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,8 +1,8 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import timedelta
from openerp import models, fields, api
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo import models, fields, api
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
class MassiveChangesWizard(models.TransientModel):
@@ -26,8 +26,7 @@ class MassiveChangesWizard(models.TransientModel):
('0', 'Global'),
('1', 'Room Type'),
], string='Applied On', default='0')
# room_type_ids = fields.Many2many('hotel.virtual.room',
# string="Virtual Rooms")
room_type_ids = fields.Many2many('hotel.room.type', string="Room Types")
# Restriction fields
@@ -157,11 +156,10 @@ class MassiveChangesWizard(models.TransientModel):
def _save_restrictions(self, ndate, room_types, record):
hotel_room_type_re_it_obj = self.env['hotel.room.type.restriction.item']
domain = [
('date_start', '>=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)),
('date_end', '<=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)),
('date', '>=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)),
('date', '<=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)),
('restriction_id', '=', record.restriction_id.id),
]
for room_type in room_types:
vals = self._get_restrictions_values(record)
if not any(vals):
@@ -173,8 +171,7 @@ class MassiveChangesWizard(models.TransientModel):
rrest_item_ids.write(vals)
else:
vals.update({
'date_start': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
'date_end': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
'date': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
'restriction_id': record.restriction_id.id,
'room_type_id': room_type.id,
})

View File

@@ -16,13 +16,13 @@
<group colspan="8" col="8">
<field name="date_start" required="1" colspan="4"/>
<field name="date_end" required="1" colspan="4" />
<field name="dmo" colspan="1" />
<field name="dtu" colspan="1" />
<field name="dwe" colspan="1" />
<field name="dth" colspan="1" />
<field name="dfr" colspan="1" />
<field name="dsa" colspan="1" />
<field name="dsu" colspan="1" />
<field name="dmo" />
<field name="dtu" />
<field name="dwe" />
<field name="dth" />
<field name="dfr" />
<field name="dsa" />
<field name="dsu" />
</group>
<!-- Restricion Fields -->
<group col="4" colspan="4" attrs="{'invisible':[('section', '!=', 'restrictions')]}">

View File

@@ -18,18 +18,22 @@ class HotelRoomTypeAvailability(models.Model):
@api.model
def _default_max_avail(self):
room_type_id = self._context.get('room_type_id')
if room_type_id:
room_type_id = self.env['hotel.room_type'].browse(room_type_id)
return room_type_id.default_max_avail if room_type_id else -1
room_type_id = self.room_type_id.id or self._context.get('room_type_id')
channel_room_type = self.env['channel.hotel.room.type'].search([
('odoo_id', '=', room_type_id)
]) or None
if channel_room_type:
return channel_room_type.default_max_avail
return -1
@api.model
def _default_quota(self):
room_type_id = self._context.get('room_type_id')
if room_type_id:
room_type_id = self.env['hotel.room_type'].browse(room_type_id)
return room_type_id.default_quota if room_type_id else -1
room_type_id = self.room_type_id.id or self._context.get('room_type_id')
channel_room_type = self.env['channel.hotel.room.type'].search([
('odoo_id', '=', room_type_id)
]) or None
if channel_room_type:
return channel_room_type.default_quota
return -1
room_type_id = fields.Many2one('hotel.room.type', 'Room Type',
@@ -44,6 +48,7 @@ class HotelRoomTypeAvailability(models.Model):
quota = fields.Integer("Quota", default=_default_quota,
help="Quota assigned to the channel.")
# TODO: WHY max_avail IS READONLY ¿?
max_avail = fields.Integer("Max. Availability", default=-1, readonly=True,
help="Maximum simultaneous availability.")
@@ -72,8 +77,34 @@ class HotelRoomTypeAvailability(models.Model):
@api.onchange('room_type_id')
def onchange_room_type_id(self):
if self.room_type_id:
self.quota = self.room_type_id.default_quota
channel_room_type = self.env['channel.hotel.room.type'].search([
('odoo_id', '=', self.room_type_id.id)
]) or None
if channel_room_type:
self.quota = channel_room_type.default_quota
self.max_avail = channel_room_type.default_max_avail
self.no_ota = 0
@api.model
def create(self, vals):
vals.update(self._prepare_add_missing_fields(vals))
return super().create(vals)
@api.model
def _prepare_add_missing_fields(self, values):
""" Deduce missing required fields from the onchange """
res = {}
onchange_fields = ['quota', 'max_avail']
if values.get('room_type_id'):
record = self.new(values)
if 'quota' not in values:
record.quota = record._default_quota()
if 'max_avail' not in values:
record.max_avail = record._default_max_avail()
for field in onchange_fields:
if field not in values:
res[field] = record._fields[field].convert_to_write(record[field], record)
return res
class ChannelHotelRoomTypeAvailability(models.Model):
@@ -152,6 +183,8 @@ class ChannelHotelRoomTypeAvailability(models.Model):
_logger.info(vals_avail)
if room_type_avail_id.channel_avail != avail:
vals_avail.update({'channel_avail': avail})
if self._context.get('update_no_ota', False):
vals_avail.update({'channel_pushed': False})
if vals_avail:
room_type_avail_id.write(vals_avail)
@@ -209,11 +242,13 @@ class BindingHotelRoomTypeAvailabilityListener(Component):
fields_to_check = ('quota', 'max_avail', 'no_ota')
fields_checked = [elm for elm in fields_to_check if elm in fields]
_logger.info("==[on_record_write] :: hotel.room.type.availability==")
_logger.info(fields)
if any(fields_checked) and any(record.channel_bind_ids):
_logger.info("==[on_record_write] :: hotel.room.type.availability==")
_logger.info(fields)
if 'no_ota' in fields_checked:
self.env.context = dict(self.env.context)
self.env.context.update({'update_no_ota': True})
for binding in record.channel_bind_ids:
binding.refresh_availability(
record.date,
@@ -261,7 +296,7 @@ class ChannelBindingHotelRoomTypeAvailabilityListener(Component):
@skip_if(lambda self, record, **kwargs: self.no_connector_export(record))
def on_record_write(self, record, fields=None):
fields_to_check = ('date', 'channel_avail') # no_ota ¿?
fields_to_check = ('date', 'channel_avail')
fields_checked = [elm for elm in fields_to_check if elm in fields]
_logger.info("==[on_record_write] :: channel.hotel.room.type.availability==")

View File

@@ -21,10 +21,6 @@
confirm="Disconnecting will automatically delete the pricelist in the Channel. Do you want to proceed?"
/>
</xpath>
<xpath expr="//field[@name='country_group_ids']" position="before">
<field name="pricelist_type" />
</xpath>
</field>
</record>

View File

@@ -13,9 +13,9 @@ class MassiveChangesWizard(models.TransientModel):
# Availability fields
change_quota = fields.Boolean(default=False)
quota = fields.Integer('Quota', default=-1)
quota = fields.Integer('Quota', default=0)
change_max_avail = fields.Boolean(default=False)
max_avail = fields.Integer('Max. Avail.', default=-1)
max_avail = fields.Integer('Max. Avail.', default=0)
change_no_ota = fields.Boolean(default=False)
no_ota = fields.Boolean('No OTA', default=False)

View File

@@ -7,27 +7,21 @@
<field name="arch" type="xml">
<xpath expr="//form/group[last()]" position="after">
<!-- Availability Fields -->
<group col="3" colspan="3" attrs="{'invisible':[('section', '!=', 'avail')]}">
<table class="oe_form_group">
<thead>
<th width="12%"></th>
<th></th>
</thead>
<tbody>
<tr class="oe_form_group_row">
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_max_avail" /> <strong> Max. Avail.</strong></td>
<td class="oe_form_group_cell" colspan="3"><field name="max_avail" attrs="{'readonly':[('change_max_avail', '=', False)]}" /></td>
</tr>
<tr class="oe_form_group_row">
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_quota" /> <strong> Quota</strong></td>
<td class="oe_form_group_cell" colspan="3"><field name="quota" attrs="{'readonly':[('change_quota', '=', False)]}" /></td>
</tr>
<tr class="oe_form_group_row">
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_no_ota" /> <strong> No OTA</strong></td>
<td class="oe_form_group_cell"><field name="no_ota" attrs="{'readonly':[('change_no_ota', '=', False)]}" /></td>
</tr>
</tbody>
</table>
<group col="8" colspan="8" attrs="{'invisible':[('section', '!=', 'avail')]}">
<field name="change_max_avail"/>
<field name="max_avail"
attrs="{'readonly':[('change_max_avail', '=', False)],
'invisible':[('change_max_avail', '=', False)]}"/>
<field name="change_quota"/>
<field name="quota"
attrs="{'readonly':[('change_quota', '=', False)],
'invisible':[('change_quota', '=', False)]}"/>
<field name="change_no_ota"/>
<field name="no_ota"
attrs="{'readonly':[('change_no_ota', '=', False)],
'invisible':[('change_no_ota', '=', False)]}"/>
</group>
</xpath>
</field>

View File

@@ -0,0 +1,13 @@
DOOR CODES
==========
Generate HOTEL DOOR CODES
Credits
=======
Creator
------------
* Jose Luis Algara Toledo <osotranquilo@gmail.com>

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018-2019 Jose Luis Algara Toledo <osotranquilo@gmail.com>
#
# 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 models
from . import wizard

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Management Solution
# Copyright (C) 2018-2019 Jose Luis Algara Toledo <osotranquilo@gmail.com>
#
# 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/>.
#
##############################################################################
{
'name': 'Hotel Door Codes',
'version': '2.1',
'author': "Jose Luis Algara Toledo <osotranquilo@gmail.com>",
'website': 'https://www.aldahotels.com',
'category': 'hotel code',
'summary': "Generate Hotel door codes, in Pseudo random system",
'description': "Hotel Door Codes",
'depends': [
'hotel', 'hotel_l10n_es'
],
'data': [
'wizard/door_code.xml',
'data/menus.xml',
'views/inherit_res_company.xml',
'views/inherit_hotel_reservation.xml',
'views/inherit_report_viajero.xml',
],
'qweb': [],
'test': [
],
'installable': True,
'auto_install': False,
'application': False,
'license': 'AGPL-3',
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem
id="menu_hotel_door_code"
name="Codigos para la Puerta"
parent="hotel.hotel_reports_menu"
action="door_code_act"
groups="hotel.group_hotel_user,hotel.group_hotel_manager"
sequence="45"
/>
</data>
</odoo>

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018-2019 Alda Hotels <informatica@aldahotels.com>
# Jose Luis Algara <osotranquilo@gmail.com>
#
# 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 inherit_res_company
from . import inherit_hotel_reservation

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018-2019 Alda Hotels <informatica@aldahotels.com>
# Jose Luis Algara <osotranquilo@gmail.com>
#
# 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
from datetime import datetime, date, time, timedelta
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
class Inherit_hotel_reservation(models.Model):
_inherit = 'hotel.reservation'
@api.multi
def doorcode4(self, fecha):
# Calculate de Door Code... need a date in String format "%Y-%m-%d"
compan = self.env.user.company_id
d = datetime.strptime(fecha, DEFAULT_SERVER_DATE_FORMAT)
dia_semana = datetime.weekday(d) # Dias a restar y ponerlo en lunes
d = d - timedelta(days=dia_semana)
dtxt = d.strftime('%s.%%06d') % d.microsecond
dtxt = compan.precode + dtxt[4:8] + compan.postcode
return dtxt
@api.multi
def _compute_door_codes(self):
for res in self:
entrada = datetime.strptime(
res.checkin[:10], DEFAULT_SERVER_DATE_FORMAT)
if datetime.weekday(entrada) == 0:
entrada = entrada + timedelta(days=1)
salida = datetime.strptime(
res.checkout[:10], DEFAULT_SERVER_DATE_FORMAT)
if datetime.weekday(salida) == 0:
salida = salida - timedelta(days=1)
codes = (u'Código de entrada: ' +
'<strong><span style="font-size: 1.4em;">' +
res.doorcode4(datetime.strftime(entrada, "%Y-%m-%d")) +
'</span></strong>')
while entrada <= salida:
if datetime.weekday(entrada) == 0:
codes += ("<br>" +
u'Cambiará el Lunes ' +
datetime.strftime(entrada, "%d-%m-%Y") +
' a: <strong><span style="font-size: 1.4em;">' +
res.doorcode4(datetime.strftime(
entrada, "%Y-%m-%d")) +
'</span></strong>')
entrada = entrada + timedelta(days=1)
res.door_codes = codes
door_codes = fields.Html(u'Códigos de entrada',
compute='_compute_door_codes')
box_number = fields.Integer ('Numero de Caja')
box_code = fields.Char ('Cod. Caja')

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018-2019 Alda Hotels <informatica@aldahotels.com>
# Jose Luis Algara <osotranquilo@gmail.com>
#
# 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 Inherit_res_company(models.Model):
_inherit = 'res.company'
precode = fields.Char('Characters before the door code', default='')
postcode = fields.Char('Characters after the code', default='')

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Hotel Settings -->
<data>
<!-- Inherit Company view to add 'door_code' in Reservation Form -->
<record id="door_code_reservation_form" model="ir.ui.view">
<field name="name">door_code.reservation_form</field>
<field name="model">hotel.reservation</field>
<field name="inherit_id" ref="hotel.hotel_reservation_view_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='others']" position="after">
<page name="doorcode" string="Códigos entrada">
<group>
<field name="door_codes" />
<field name="box_number" />
<field name="box_code" />
</group>
</page>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Hotel Settings -->
<data>
<!-- Inherit viajero repdort to add 'door_code' -->
<template id="door_code_view_report" inherit_id="hotel_l10n_es.report_viajero_document">
<xpath expr="//div[@class='final']" position="replace">
<div class ="doorcodes" style="font-size:1.8em;">
<span t-field="o.reservation_id.door_codes"/>
</div>
</xpath>
</template>
</data>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Hotel Settings -->
<data>
<!-- Inherit Company view to add 'door_code' -->
<record id="door_code_view_company_form" model="ir.ui.view">
<field name="name">door_code.config.view_company_form</field>
<field name="model">res.company</field>
<field name="inherit_id" ref="base.view_company_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='cardex_warning']" position="after">
<field name="precode" />
<field name="postcode" />
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018-2019 Jose Luis Algara Toledo <osotranquilo@gmail.com>
#
# 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 door_code

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Odoo, Open Source Management Solution
# Copyright (C) 2018-2019 Jose Luis Algara Toledo <osotranquilo@gmail.com>
#
# 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 datetime, date, time, timedelta
from odoo import api, fields, models, _
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT
class DoorCodeWizard(models.TransientModel):
_name = 'door_code'
@api.model
def _get_default_date_start(self):
return datetime.now().strftime(DEFAULT_SERVER_DATE_FORMAT)
date_start = fields.Date("Inicio periodo",
default=_get_default_date_start)
date_end = fields.Date("Fin del periodo",
default=_get_default_date_start)
door_code = fields.Html(u'Código para la puerta')
@api.multi
def doorcode4(self, fecha):
# Calculate de Door Code... need a date in String format "%Y-%m-%d"
compan = self.env.user.company_id
d = datetime.strptime(fecha, DEFAULT_SERVER_DATE_FORMAT)
dia_semana = datetime.weekday(d) # Dias a restar y ponerlo en lunes
d = d - timedelta(days=dia_semana)
dtxt = d.strftime('%s.%%06d') % d.microsecond
dtxt = compan.precode + dtxt[4:8] + compan.postcode
return dtxt
@api.multi
def check_code(self):
entrada = datetime.strptime(
self.date_start, DEFAULT_SERVER_DATE_FORMAT)
if datetime.weekday(entrada) == 0:
entrada = entrada + timedelta(days=1)
salida = datetime.strptime(
self.date_end, DEFAULT_SERVER_DATE_FORMAT)
if datetime.weekday(salida) == 0:
salida = salida - timedelta(days=1)
codes = (u'Código de entrada: ' +
'<strong><span style="font-size: 2em;">' +
self.doorcode4(self.date_start) +
'</span></strong>')
while entrada <= salida:
if datetime.weekday(entrada) == 0:
codes += ("<br>" +
u'Cambiará el Lunes ' +
datetime.strftime(entrada, "%d-%m-%Y") +
' a: <strong><span style="font-size: 2em;">' +
self.doorcode4(datetime.strftime(
entrada, "%Y-%m-%d")) +
'</span></strong>')
entrada = entrada + timedelta(days=1)
return self.write({
'door_code': codes
})

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" ?>
<odoo>
<data>
<record model="ir.ui.view" id="hotel_door_code_view">
<field name="name">door_code.view</field>
<field name="model">door_code</field>
<field name="arch" type="xml">
<form string="Door Code Generator" >
<sheet>
<group>
<group>
<field name="date_start" required="1" />
<field name="date_end" required="1" />
</group>
</group>
<group>
<field name="door_code" readonly="1"/>
</group>
<footer>
<button name="check_code" string="Generate Code" type="object" class="oe_highlight" />
</footer>
</sheet>
</form>
</field>
</record>
<act_window
id="door_code_act"
name="Door Code Generator"
res_model="door_code"
view_mode="form"
/>
</data>
</odoo>

View File

@@ -56,6 +56,8 @@
Asimismo hemos solicitado que confirme esta autorización para ofrecerle nuestros servicios y poder fidelizarle como cliente.</p>
</div>
</div>
<div class="final">
</div>
</template>
</data>
</odoo>

View File

@@ -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({

View File

@@ -26,7 +26,7 @@
<group colspan="1">
<div>
<p><field name="log_police" readonly="1"/></p>
<p><field name="txt_message" readonly="1"/></p>
<p><field name="txt_message" readonly="1"/><strong><field name="error_partner" readonly="1"/></strong></p>
</div>
</group>
<group>