[WIP][MIG][11.0] Hotel module review

This commit is contained in:
QS5ELkMu
2018-09-03 21:08:06 +02:00
parent d2363b7736
commit 0c7b3edd77
79 changed files with 675 additions and 962 deletions

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import currency_exchange
@@ -21,11 +21,11 @@ from . import inherit_product_product
from . import inherit_res_company
# from . import virtual_room
from . import inherit_account_payment
from . import hotel_virtual_room_restriction
from . import hotel_virtual_room_restriction_item
from . import hotel_room_type_restriction
from . import hotel_room_type_restriction_item
from . import hotel_reservation_line
from . import cardex
from . import hotel_virtual_room_availability
from . import hotel_room_type_availability
from . import inherit_product_pricelist
from . import res_config
from . import inherit_res_partner

View File

@@ -1,25 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Dario Lodeiros <>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Copyright 2017 Dario Lodeiros
# Copyright 2018 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import datetime
from openerp import models, fields, api, _
from openerp.exceptions import except_orm, ValidationError

View File

@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Dario Lodeiros
# Copyright 2018 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from decimal import Decimal
import time
# For Python 3.0 and later
from urllib.request import urlopen
from openerp import models, fields, api, _
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
class CurrencyExchangeRate(models.Model):

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Alexandre Díaz
# Copyright 2018 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import json
from datetime import datetime, timedelta
@@ -11,17 +10,19 @@ from odoo.tools.misc import formatLang
class HotelDashboard(models.Model):
_name = "hotel.dashboard"
# FIXME
def _get_count(self):
resevations_count = self.env['hotel.reservation'].search(
resevations_count = self.env['hotel.reservation'].search_count(
[('sate', '=', 'confirm')])
folios_count = self.env['hotel.folio'].search(
folios_count = self.env['hotel.folio'].search_count(
[('sate', '=', 'sales_order')])
next_arrivals_count = self.env['hotel.reservation'].search(
next_arrivals_count = self.env['hotel.reservation'].search_count(
[('is_checkin', '=', True)])
self.orders_count = len(orders_count)
self.quotations_count = len(quotations_count)
self.orders_done_count = len(orders_done_count)
@api.one
def _kanban_dashboard(self):
if self.graph_type == 'bar':
@@ -40,25 +41,33 @@ class HotelDashboard(models.Model):
color = fields.Integer(string='Color Index')
name = fields.Char(string="Name")
type = fields.Char(default="sale")
graph_type = fields.Selection([('line','Line'),('bar','Bar'),('none','None')])
reservations_count = fields.Integer(compute = '_get_count')
folios_count = fields.Integer(compute= '_get_count')
next_arrivals_count = fields.Integer(compute= '_get_count')
graph_type = fields.Selection([
('line', 'Line'),
('bar', 'Bar'),
('none', 'None')])
reservations_count = fields.Integer(compute='_get_count')
folios_count = fields.Integer(compute='_get_count')
next_arrivals_count = fields.Integer(compute='_get_count')
kanban_dashboard = fields.Text(compute='_kanban_dashboard')
kanban_dashboard_graph = fields.Text(compute='_kanban_dashboard_graph')
show_on_dashboard = fields.Boolean(string='Show journal on dashboard', help="Whether this journal should be displayed on the dashboard or not", default=True)
show_on_dashboard = fields.Boolean(
string='Show journal on dashboard',
help="Whether this journal should be displayed on the dashboard or not",
default=True)
@api.multi
def get_bar_graph_datas(self):
data = []
today = datetime.strptime(fields.Date.context_today(self), DF)
day_of_week = int(format_datetime(today, 'e', locale=self._context.get('lang') or 'en_US'))
for i in range(0,15):
if i==0:
for i in range(0, 15):
if i == 0:
label = _('Today')
else:
label = format_date(today + timedelta(days=i) , 'd', locale=self._context.get('lang') or 'en_US')
data.append({'label':label,'value':0.0, 'type': 'past' if i<0 else 'future'})
label = format_date(today + timedelta(days=i),
'd',
locale=self._context.get('lang') or 'en_US')
data.append({'label':label, 'value':0.0, 'type': 'past' if i < 0 else 'future'})
# Build SQL query to find amount aggregated by week
select_sql_clause = """SELECT count(id) as total from hotel_reservation where state != 'cancelled'"""
query = "("+select_sql_clause+" and date(checkin) = '"+today.strftime(DF)+"')"
@@ -68,8 +77,8 @@ class HotelDashboard(models.Model):
self.env.cr.execute(query)
query_results = self.env.cr.dictfetchall()
for index in range(0, len(query_results)):
data[index]['value'] = query_results[index].get('total')
for index_k, index_v in enumerate(query_results):
data[index_k]['value'] = index_v.get('total')
return [{'values': data}]
@api.multi

View File

@@ -1,30 +1,9 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Dario Lodeiros <>
#
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api, _
class HotelFloor(models.Model):
_name = "hotel.floor"
_description = "Ubication"

View File

@@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017-2018 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import datetime
from datetime import datetime
import time
import pytz
import logging
@@ -78,9 +77,9 @@ class HotelFolio(models.Model):
help="Hotel room reservation detail.",)
service_line_ids = fields.One2many('hotel.service', 'folio_id',
readonly=False,
states={'done': [('readonly', True)]},
help="Hotel services detail provide to "
readonly=False,
states={'done': [('readonly', True)]},
help="Hotel services detail provide to "
"customer and it will include in "
"main Invoice.")
hotel_invoice_id = fields.Many2one('account.invoice', 'Invoice')
@@ -98,23 +97,21 @@ class HotelFolio(models.Model):
'sent': [('readonly', False)]},
help="Pricelist for current folio.")
pending_amount = fields.Monetary(compute='compute_amount',
store=True,
string="Pending in Folio")
store=True,
string="Pending in Folio")
refund_amount = fields.Monetary(compute='compute_amount',
store=True,
string="Payment Returns")
invoices_paid = fields.Monetary(compute='compute_amount',
store=True, track_visibility='onchange',
string="Payments")
store=True, track_visibility='onchange',
string="Payments")
booking_pending = fields.Integer('Booking pending',
compute='_compute_cardex_count')
cardex_count = fields.Integer('Cardex counter',
compute='_compute_cardex_count')
cardex_pending = fields.Boolean('Cardex Pending',
compute='_compute_cardex_count')
cardex_pending_num = fields.Integer('Cardex Pending',
compute='_compute_cardex_count')
cardex_pending_count = fields.Integer('Cardex Pending',
compute='_compute_cardex_count')
checkins_reservations = fields.Integer('checkins reservations')
checkouts_reservations = fields.Integer('checkouts reservations')
partner_internal_comment = fields.Text(string='Internal Partner Notes',
@@ -173,12 +170,11 @@ class HotelFolio(models.Model):
sequence = fields.Integer(string='Sequence', default=10)
# sale.order
amount_total = fields.Float(string='Total', store=True, readonly=True,
track_visibility='always')
track_visibility='always')
def _computed_rooms_char(self):
for record in self:
rooms = ', '.join(record.mapped('room_lines.room_id.name'))
record.rooms_char = rooms
record.rooms_char = ', '.join(record.mapped('room_lines.room_id.name'))
@api.multi
def _compute_num_invoices(self):
@@ -219,9 +215,8 @@ class HotelFolio(models.Model):
def action_payments(self):
self.ensure_one()
payments_obj = self.env['account.payment']
payments = payments_obj.search([('folio_id','=',self.id)])
payment_ids = payments.mapped('id')
invoices = self.mapped('invoice_ids.id')
payments = payments_obj.search([('folio_id', '=', self.id)])
#invoices = self.mapped('invoice_ids.id')
return{
'name': _('Payments'),
'view_type': 'form',
@@ -229,7 +224,7 @@ class HotelFolio(models.Model):
'res_model': 'account.payment',
'target': 'new',
'type': 'ir.actions.act_window',
'domain': [('id', 'in', payment_ids)],
'domain': [('id', 'in', payments.ids)],
}
@api.multi
@@ -251,16 +246,16 @@ class HotelFolio(models.Model):
return_move_ids = []
acc_pay_obj = self.env['account.payment']
payments = acc_pay_obj.search([
'|',
('invoice_ids', 'in', self.invoice_ids.ids),
('folio_id', '=', self.id)
])
'|',
('invoice_ids', 'in', self.invoice_ids.ids),
('folio_id', '=', self.id)
])
return_move_ids += self.invoice_ids.filtered(
lambda invoice: invoice.type == 'out_refund').mapped(
'payment_move_line_ids.move_id.id')
return_lines = self.env['payment.return.line'].search([(
'move_line_ids','in',payments.mapped(
'move_line_ids.id'))])
'payment_move_line_ids.move_id.id')
return_lines = self.env['payment.return.line'].search([
('move_line_ids', 'in', payments.mapped('move_line_ids.id')),
])
return_move_ids += return_lines.mapped('return_id.move_id.id')
return{
@@ -323,19 +318,24 @@ class HotelFolio(models.Model):
# }
@api.model
def create(self, vals, check=True):
def create(self, vals):
if vals.get('name', _('New')) == _('New'):
if 'company_id' in vals:
vals['name'] = self.env['ir.sequence'].with_context(force_company=vals['company_id']).next_by_code('sale.order') or _('New')
vals['name'] = self.env['ir.sequence'].with_context(
force_company=vals['company_id']
).next_by_code('sale.order') or _('New')
else:
vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') or _('New')
# Makes sure partner_invoice_id' and 'pricelist_id' are defined
if any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']):
lfields = ('partner_invoice_id', 'partner_shipping_id', 'pricelist_id')
if any(f not in vals for f in lfields):
partner = self.env['res.partner'].browse(vals.get('partner_id'))
addr = partner.address_get(['delivery', 'invoice'])
vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice'])
vals['pricelist_id'] = vals.setdefault('pricelist_id', partner.property_product_pricelist and partner.property_product_pricelist.id)
vals['pricelist_id'] = vals.setdefault(
'pricelist_id',
partner.property_product_pricelist and partner.property_product_pricelist.id)
result = super(HotelFolio, self).create(vals)
return result
@@ -358,12 +358,15 @@ class HotelFolio(models.Model):
addr = self.partner_id.address_get(['invoice'])
values = {
'pricelist_id': self.partner_id.property_product_pricelist and self.partner_id.property_product_pricelist.id or False,
'pricelist_id': self.partner_id.property_product_pricelist and \
self.partner_id.property_product_pricelist.id or False,
'partner_invoice_id': addr['invoice'],
'user_id': self.partner_id.user_id.id or self.env.uid
}
if self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note') and self.env.user.company_id.sale_note:
values['note'] = self.with_context(lang=self.partner_id.lang).env.user.company_id.sale_note
if self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note') and \
self.env.user.company_id.sale_note:
values['note'] = self.with_context(
lang=self.partner_id.lang).env.user.company_id.sale_note
if self.partner_id.team_id:
values['team_id'] = self.partner_id.team_id.id
@@ -409,10 +412,11 @@ class HotelFolio(models.Model):
@api.multi
def action_done(self):
for line in self.room_lines:
room_lines = self.mapped('room_lines')
for line in room_lines:
if line.state == "booking":
line.action_reservation_checkout()
@api.multi
def action_cancel(self):
'''
@@ -457,7 +461,7 @@ class HotelFolio(models.Model):
'domain': [('reservation_id', 'in', rooms)],
'target': 'new',
}
@api.model
def daily_plan(self):
_logger.info('daily_plan')
@@ -486,26 +490,19 @@ class HotelFolio(models.Model):
@api.multi
def _compute_cardex_count(self):
_logger.info('_compute_cardex_amount')
for fol in self:
num_cardex = 0
pending = False
if fol.reservation_type == 'normal':
for reser in fol.room_lines:
if reser.state != 'cancelled' and \
not reser.parent_reservation:
num_cardex += len(reser.cardex_ids)
fol.cardex_count = num_cardex
pending = 0
for reser in fol.room_lines:
if reser.state != 'cancelled' and \
not reser.parent_reservation:
pending += (reser.adults + reser.children) \
- len(reser.cardex_ids)
if pending <= 0:
fol.cardex_pending = False
else:
fol.cardex_pending = True
fol.cardex_pending_num = pending
for record in self:
if record.reservation_type == 'normal':
write_vals = {}
filtered_reservs = record.filtered(
lambda x: x.room_lines.state != 'cancelled' and \
not x.room_lines.parent_reservation)
mapped_cardex = filtered_reservs.mapped('cardex_ids.id')
write_vals.update({'cardex_count': len(mapped_cardex)})
mapped_cardex_count = filtered_reservs.mapped(
lambda x: (x.adults + x.children) - len(x.cardex_ids))
write_vals.update({'cardex_pending_count': sum(mapped_cardex_count)})
record.write(write_vals)
"""
MAILING PROCESS
@@ -586,15 +583,15 @@ class HotelFolio(models.Model):
self.ensure_one()
ir_model_data = self.env['ir.model.data']
try:
template_id = (ir_model_data.get_object_reference
('hotel',
'mail_template_hotel_reservation')[1])
template_id = ir_model_data.get_object_reference(
'hotel',
'mail_template_hotel_reservation')[1]
except ValueError:
template_id = False
try:
compose_form_id = (ir_model_data.get_object_reference
('mail',
'email_compose_message_wizard_form')[1])
compose_form_id = ir_model_data.get_object_reference(
'mail',
'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
ctx = dict()
@@ -632,15 +629,15 @@ class HotelFolio(models.Model):
self.ensure_one()
ir_model_data = self.env['ir.model.data']
try:
template_id = (ir_model_data.get_object_reference
('hotel',
'mail_template_hotel_exit')[1])
template_id = ir_model_data.get_object_reference(
'hotel',
'mail_template_hotel_exit')[1]
except ValueError:
template_id = False
try:
compose_form_id = (ir_model_data.get_object_reference
('mail',
'email_compose_message_wizard_form')[1])
compose_form_id = ir_model_data.get_object_reference(
'mail',
'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
ctx = dict()
@@ -679,15 +676,15 @@ class HotelFolio(models.Model):
self.ensure_one()
ir_model_data = self.env['ir.model.data']
try:
template_id = (ir_model_data.get_object_reference
('hotel',
'mail_template_hotel_cancel')[1])
template_id = ir_model_data.get_object_reference(
'hotel',
'mail_template_hotel_cancel')[1]
except ValueError:
template_id = False
try:
compose_form_id = (ir_model_data.get_object_reference
('mail',
'email_compose_message_wizard_form')[1])
compose_form_id = ir_model_data.get_object_reference(
'mail',
'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
ctx = dict()
@@ -722,15 +719,14 @@ class HotelFolio(models.Model):
@param self: The object pointer
@return: send a mail
"""
now_str = time.strftime(dt)
now_date = datetime.strptime(now_str, dt)
now_date = fields.Datetime.now()
ir_model_data = self.env['ir.model.data']
template_id = (ir_model_data.get_object_reference
('hotel_reservation',
'mail_template_reservation_reminder_24hrs')[1])
template_id = ir_model_data.get_object_reference(
'hotel_reservation',
'mail_template_reservation_reminder_24hrs')[1]
template_rec = self.env['mail.template'].browse(template_id)
for reserv_rec in self.search([]):
checkin_date = (datetime.strptime(reserv_rec.checkin, dt))
checkin_date = datetime.strptime(reserv_rec.checkin, dt)
difference = relativedelta(now_date, checkin_date)
if(difference.days == -1 and reserv_rec.partner_id.email and
reserv_rec.state == 'confirm'):
@@ -742,11 +738,17 @@ class HotelFolio(models.Model):
self.ensure_one()
info_grouped = []
for rline in self.room_lines:
if (import_all or rline.to_send) and not rline.parent_reservation and rline.state == state:
if (import_all or rline.to_send) and \
not rline.parent_reservation and rline.state == state:
dates = rline.get_real_checkin_checkout()
vals = {
'num': len(
self.room_lines.filtered(lambda r: r.get_real_checkin_checkout()[0] == dates[0] and r.get_real_checkin_checkout()[1] == dates[1] and r.room_type_id.id == rline.room_type_id.id and (r.to_send or import_all) and not r.parent_reservation and r.state == rline.state)
self.room_lines.filtered(
lambda r: r.get_real_checkin_checkout()[0] == dates[0] and \
r.get_real_checkin_checkout()[1] == dates[1] and \
r.room_type_id.id == rline.room_type_id.id and \
(r.to_send or import_all) and not r.parent_reservation and \
r.state == rline.state)
),
'room_type': {
'id': rline.room_type_id.id,
@@ -760,10 +762,13 @@ class HotelFolio(models.Model):
}
founded = False
for srline in info_grouped:
if srline['num'] == vals['num'] and srline['room_type']['id'] == vals['room_type']['id'] and srline['checkin'] == vals['checkin'] and srline['checkout'] == vals['checkout']:
if srline['num'] == vals['num'] and \
srline['room_type']['id'] == vals['room_type']['id'] and \
srline['checkin'] == vals['checkin'] and \
srline['checkout'] == vals['checkout']:
founded = True
break
if not founded:
info_grouped.append(vals)
return sorted(sorted(info_grouped, key=lambda k: k['num'], reverse=True), key=lambda k: k['room_type']['id'])
return sorted(sorted(info_grouped,key=lambda k: k['num'], reverse=True),
key=lambda k: k['room_type']['id'])

View File

@@ -1,24 +1,19 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017-2018 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.exceptions import except_orm, UserError, ValidationError
import logging
import time
from datetime import timedelta
from odoo.exceptions import UserError, ValidationError
from odoo.tools import (
misc,
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from odoo import models, fields, api, _
from decimal import Decimal
from dateutil.relativedelta import relativedelta
from dateutil import tz
from datetime import datetime, timedelta, date
from odoo.addons import decimal_precision as dp
from odoo.addons.hotel import date_utils
import pytz
import time
import logging
_logger = logging.getLogger(__name__)
from odoo.addons import decimal_precision as dp
class HotelReservation(models.Model):
@@ -48,7 +43,8 @@ class HotelReservation(models.Model):
tz_hotel = self.env['ir.default'].sudo().get(
'res.config.settings', 'tz_hotel')
today = fields.Date.context_today(self.with_context(tz=tz_hotel))
return (fields.Date.from_string(today) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT)
return (fields.Date.from_string(today) + timedelta(days=1)).strftime(
DEFAULT_SERVER_DATE_FORMAT)
def _get_default_arrival_hour(self):
folio = False
@@ -102,18 +98,17 @@ class HotelReservation(models.Model):
# Has this reservation more charges associates in folio?, Yes?, then, this is share folio ;)
for record in self:
if record.folio_id:
if len(record.folio_id.room_lines) > 1 or \
record.folio_id.service_line_ids.filtered(lambda x: (
x.ser_room_line != record.id)):
record.shared_folio = True
else:
record.shared_folio = False
record.shared_folio = len(record.folio_id.room_lines) > 1 or \
any(record.folio_id.service_line_ids.filtered(
lambda x: x.ser_room_line != record.id))
@api.depends('checkin', 'checkout')
def _computed_nights(self):
for res in self:
if res.checkin and res.checkout:
res.nights = (fields.Date.from_string(res.checkout) - fields.Date.from_string(res.checkin)).days
res.nights = (
fields.Date.from_string(res.checkout) - fields.Date.from_string(res.checkin)
).days
_name = 'hotel.reservation'
_description = 'Hotel Reservation'
@@ -191,11 +186,9 @@ class HotelReservation(models.Model):
# The value is a method name returning a Domains
cardex_count = fields.Integer('Cardex counter',
compute='_compute_cardex_count')
cardex_pending = fields.Boolean('Cardex Pending',
compute='_compute_cardex_count',
search='_search_cardex_pending')
cardex_pending_num = fields.Integer('Cardex Pending Num',
compute='_compute_cardex_count')
cardex_pending_count = fields.Integer('Cardex Pending Num',
compute='_compute_cardex_count',
search='_search_cardex_pending')
# check_rooms = fields.Boolean('Check Rooms')
is_checkin = fields.Boolean()
is_checkout = fields.Boolean()
@@ -226,29 +219,31 @@ class HotelReservation(models.Model):
preconfirm = fields.Boolean('Auto confirm to Save', default=True)
to_send = fields.Boolean('To Send', default=True)
has_confirmed_reservations_to_send = fields.Boolean(
related='folio_id.has_confirmed_reservations_to_send',
readonly=True)
related='folio_id.has_confirmed_reservations_to_send',
readonly=True)
has_cancelled_reservations_to_send = fields.Boolean(
related='folio_id.has_cancelled_reservations_to_send',
readonly=True)
related='folio_id.has_cancelled_reservations_to_send',
readonly=True)
has_checkout_to_send = fields.Boolean(
related='folio_id.has_checkout_to_send',
readonly=True)
related='folio_id.has_checkout_to_send',
readonly=True)
# order_line = fields.One2many('sale.order.line', 'order_id', string='Order Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True, auto_join=True)
# product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
# product_uom = fields.Many2one('product.uom', string='Unit of Measure', required=True)
# product_uom_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
currency_id = fields.Many2one('res.currency',
related='pricelist_id.currency_id',
string='Currency', readonly=True, required=True)
related='pricelist_id.currency_id',
string='Currency', readonly=True, required=True)
# invoice_status = fields.Selection([
# ('upselling', 'Upselling Opportunity'),
# ('invoiced', 'Fully Invoiced'),
# ('to invoice', 'To Invoice'),
# ('no', 'Nothing to Invoice')
# ], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no')
tax_id = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
tax_id = fields.Many2many('account.tax',
string='Taxes',
domain=['|', ('active', '=', False), ('active', '=', True)])
# qty_to_invoice = fields.Float(
# string='To Invoice', store=True, readonly=True,
# digits=dp.get_precision('Product Unit of Measure'))
@@ -257,9 +252,18 @@ class HotelReservation(models.Model):
# digits=dp.get_precision('Product Unit of Measure'))
# qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0)
# qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True)
price_subtotal = fields.Monetary(string='Subtotal', readonly=True, store=True, compute='_compute_amount_reservation')
price_total = fields.Monetary(string='Total', readonly=True, store=True, compute='_compute_amount_reservation')
price_tax = fields.Float(string='Taxes', readonly=True, store=True, compute='_compute_amount_reservation')
price_subtotal = fields.Monetary(string='Subtotal',
readonly=True,
store=True,
compute='_compute_amount_reservation')
price_total = fields.Monetary(string='Total',
readonly=True,
store=True,
compute='_compute_amount_reservation')
price_tax = fields.Float(string='Taxes',
readonly=True,
store=True,
compute='_compute_amount_reservation')
# FIXME discount per night
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
@@ -286,11 +290,13 @@ class HotelReservation(models.Model):
#~ 'reserve_color_text': colors[1],
})
if self.compute_price_out_vals(vals):
days_diff = (fields.Date.from_string(vals['checkout']) - fields.Date.from_string(vals['checkin'])).days
vals.update(record.prepare_reservation_lines(
days_diff = (
fields.Date.from_string(vals['checkout']) - fields.Date.from_string(vals['checkin'])
).days
vals.update(self.prepare_reservation_lines(
vals['checkin'],
days_diff,
vals = vals)) #REVISAR el unlink
vals=vals)) #REVISAR el unlink
record = super(HotelReservation, self).create(vals)
#~ if (record.state == 'draft' and record.folio_id.state == 'sale') or \
#~ record.preconfirm:
@@ -301,18 +307,22 @@ class HotelReservation(models.Model):
def write(self, vals):
if self.notify_update(vals):
vals.update({
'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
'last_updated_res': date_utils.now(hours=True).strftime(
DEFAULT_SERVER_DATETIME_FORMAT)
})
for record in self:
if record.compute_price_out_vals(vals):
days_diff = (fields.Date.from_string(record.checkout) - fields.Date.from_string(record.checkin)).days
days_diff = (
fields.Date.from_string(record.checkout) - \
fields.Date.from_string(record.checkin)
).days
record.update(record.prepare_reservation_lines(
vals['checkin'],
days_diff,
vals = vals)) #REVISAR el unlink
vals=vals)) #REVISAR el unlink
if ('checkin' in vals and record.checkin != vals['checkin']) or \
('checkout' in vals and record.checkout != vals['checkout']) or \
('state' in vals and record.state != vals['state']) :
('state' in vals and record.state != vals['state']):
vals.update({'to_send': True})
res = super(HotelReservation, self).write(vals)
return res
@@ -321,9 +331,9 @@ class HotelReservation(models.Model):
def _prepare_add_missing_fields(self, values):
""" Deduce missing required fields from the onchange """
res = {}
onchange_fields = ['room_id', 'pricelist_id',
'reservation_type', 'currency_id']
if values.get('partner_id') and values.get('room_type_id') and any(f not in values for f in onchange_fields):
onchange_fields = ['room_id', 'pricelist_id', 'reservation_type', 'currency_id']
if values.get('partner_id') and values.get('room_type_id') and \
any(f not in values for f in onchange_fields):
line = self.new(values)
line.onchange_room_id()
for field in onchange_fields:
@@ -345,7 +355,7 @@ class HotelReservation(models.Model):
@api.multi
def overbooking_button(self):
self.ensure_one()
return self.write({'overbooking': not self.overbooking})
self.overbooking = not self.overbooking
@api.multi
def open_folio(self):
@@ -388,8 +398,8 @@ class HotelReservation(models.Model):
def _check_adults(self):
for record in self:
if record.adults > record.room_id.capacity:
raise ValidationError(
_("Reservation persons can't be higher than room capacity"))
raise ValidationError(
_("Reservation persons can't be higher than room capacity"))
if record.adults == 0:
raise ValidationError(_("Reservation has no adults"))
@@ -399,22 +409,24 @@ class HotelReservation(models.Model):
@api.onchange('adults', 'room_id')
def onchange_room_id(self):
# TODO: Usar vals y write
if self.room_id:
write_vals = {}
if self.room_id.capacity < self.adults:
self.adults = self.room_id.capacity
raise UserError(
_('%s people do not fit in this room! ;)') % (persons))
_('%s people do not fit in this room! ;)') % (self.adults))
if self.adults == 0:
self.adults = self.room_id.capacity
if not self.room_type_id: #Si el registro no existe, modificar room_type aunque ya esté establecido
self.room_type_id = self.room_id.room_type_id
write_vals.update({'adults': self.room_id.capacity})
#Si el registro no existe, modificar room_type aunque ya esté establecido
if not self.room_type_id:
write_vals.update({'room_type_id': self.room_id.room_type_id.id})
self.write(write_vals)
@api.onchange('partner_id')
def onchange_partner_id(self):
#TODO: Change parity pricelist by default pricelist
values = {
'pricelist_id': self.partner_id.property_product_pricelist and self.partner_id.property_product_pricelist.id or \
'pricelist_id': self.partner_id.property_product_pricelist and \
self.partner_id.property_product_pricelist.id or \
self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id'),
}
self.update(values)
@@ -423,11 +435,13 @@ class HotelReservation(models.Model):
@api.onchange('room_type_id', 'pricelist_id', 'reservation_type')
def onchange_overwrite_price_by_day(self):
if self.room_type_id and self.checkin and self.checkout:
days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days
days_diff = (
fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)
).days
self.update(self.prepare_reservation_lines(
self.checkin,
days_diff,
update_old_prices = True))
update_old_prices=True))
# When we need to update prices respecting those that were already established
@api.onchange('checkin', 'checkout')
@@ -439,15 +453,16 @@ class HotelReservation(models.Model):
checkin_dt = fields.Date.from_string(self.checkin)
checkout_dt = fields.Date.from_string(self.checkout)
if checkin_dt >= checkout_dt:
self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT)
self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(
DEFAULT_SERVER_DATE_FORMAT)
if self.room_type_id:
days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days
days_diff = (
fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)
).days
self.update(self.prepare_reservation_lines(
self.checkin,
days_diff,
update_old_prices = False))
update_old_prices=False))
@api.onchange('checkin', 'checkout', 'room_type_id')
def onchange_compute_reservation_description(self):
@@ -468,8 +483,9 @@ class HotelReservation(models.Model):
return
occupied = self.env['hotel.reservation'].get_reservations(
self.checkin,
fields.Date.from_string(self.checkout).strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered(
lambda r: r.id != self._origin.id)
fields.Date.from_string(self.checkout).strftime(
DEFAULT_SERVER_DATE_FORMAT)).filtered(
lambda r: r.id != self._origin.id)
rooms_occupied = occupied.mapped('room_id.id')
if self.room_id and self.room_id.id in rooms_occupied:
warning_msg = _('You tried to change \
@@ -488,7 +504,7 @@ class HotelReservation(models.Model):
@api.multi
def _generate_color(self):
self.ensure_one()
now_utc_dt = date_utils.now()
now_utc_dt = fields.Datetime.now()
# unused variables
# diff_checkin_now = date_utils.date_diff(now_utc_dt, self.checkin,
# hours=False)
@@ -502,26 +518,22 @@ class HotelReservation(models.Model):
return ('#4E9DC4', '#000000')
if self.reservation_type == 'staff':
reserv_color = ir_values_obj.get('res.config.settings',
'color_staff')
reserv_color = ir_values_obj.get('res.config.settings', 'color_staff')
reserv_color_text = ir_values_obj.get(
'res.config.settings',
'color_letter_staff')
elif self.reservation_type == 'out':
reserv_color = ir_values_obj.get('res.config.settings',
'color_dontsell')
reserv_color = ir_values_obj.get('res.config.settings', 'color_dontsell')
reserv_color_text = ir_values_obj.get(
'res.config.settings',
'color_letter_dontsell')
elif self.to_assign:
reserv_color = ir_values_obj.get('res.config.settings',
'color_to_assign')
reserv_color = ir_values_obj.get('res.config.settings', 'color_to_assign')
reserv_color_text = ir_values_obj.get(
'res.config.settings',
'color_letter_to_assign')
elif self.state == 'draft':
reserv_color = ir_values_obj.get('res.config.settings',
'color_pre_reservation')
reserv_color = ir_values_obj.get('res.config.settings', 'color_pre_reservation')
reserv_color_text = ir_values_obj.get(
'res.config.settings',
'color_letter_pre_reservation')
@@ -563,13 +575,13 @@ class HotelReservation(models.Model):
@api.depends('state', 'reservation_type', 'folio_id.pending_amount', 'to_assign')
def _compute_color(self):
_logger.info('_compute_color')
for rec in self:
colors = rec._generate_color()
rec.update({
for record in self:
colors = record._generate_color()
record.update({
'reserve_color': colors[0],
'reserve_color_text': colors[1],
})
rec.folio_id.color = colors[0]
record.folio_id.color = colors[0]
# hotel_reserv_obj = self.env['hotel.reservation']
# if rec.splitted:
@@ -595,27 +607,28 @@ class HotelReservation(models.Model):
_logger.info('confirm')
hotel_folio_obj = self.env['hotel.folio']
hotel_reserv_obj = self.env['hotel.reservation']
for r in self:
for record in self:
vals = {}
if r.cardex_ids:
if record.cardex_ids:
vals.update({'state': 'booking'})
else:
vals.update({'state': 'confirm'})
if r.checkin_is_today():
if record.checkin_is_today():
vals.update({'is_checkin': True})
folio = hotel_folio_obj.browse(r.folio_id.id)
folio = hotel_folio_obj.browse(record.folio_id.id)
folio.checkins_reservations = folio.room_lines.search_count([
('folio_id', '=', folio.id), ('is_checkin', '=', True)])
r.write(vals)
record.write(vals)
if r.splitted:
master_reservation = r.parent_reservation or r
if record.splitted:
master_reservation = record.parent_reservation or record
splitted_reservs = hotel_reserv_obj.search([
('splitted', '=', True),
'|', ('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
('folio_id', '=', r.folio_id.id),
('id', '!=', r.id),
'|',
('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
('folio_id', '=', record.folio_id.id),
('id', '!=', record.id),
('state', '!=', 'confirm')
])
splitted_reservs.confirm()
@@ -626,8 +639,8 @@ class HotelReservation(models.Model):
'''
@param self: object pointer
'''
for res in self:
res.action_reservation_checkout()
for record in self:
record.action_reservation_checkout()
return True
@api.multi
@@ -649,8 +662,9 @@ class HotelReservation(models.Model):
master_reservation = record.parent_reservation or record
splitted_reservs = self.env['hotel.reservation'].search([
('splitted', '=', True),
'|', ('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
'|',
('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
('folio_id', '=', record.folio_id.id),
('id', '!=', record.id),
('state', '!=', 'cancelled')
@@ -661,14 +675,14 @@ class HotelReservation(models.Model):
@api.multi
def draft(self):
for record in self:
record.write({'state': 'draft'})
record.state = 'draft'
if record.splitted:
master_reservation = record.parent_reservation or record
splitted_reservs = self.env['hotel.reservation'].search([
('splitted', '=', True),
'|', ('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
'|',
('parent_reservation', '=', master_reservation.id),
('id', '=', master_reservation.id),
('folio_id', '=', record.folio_id.id),
('id', '!=', record.id),
('state', '!=', 'draft')
@@ -696,21 +710,19 @@ class HotelReservation(models.Model):
"""
Compute the amounts of the reservation.
"""
for line in self:
amount_room = 0
for day in line.reservation_line_ids:
amount_room += day.price
for record in self:
amount_room = sum(record.reservation_line_ids.mapped('price'))
if amount_room > 0:
product = line.room_type_id.product_id
price = amount_room * (1 - (line.discount or 0.0) / 100.0)
taxes = line.tax_id.compute_all(price, line.currency_id, 1, product=product)
line.update({
product = record.room_type_id.product_id
price = amount_room * (1 - (record.discount or 0.0) * 0.01)
taxes = record.tax_id.compute_all(price, record.currency_id, 1, product=product)
record.update({
'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])),
'price_total': taxes['total_included'],
'price_subtotal': taxes['total_excluded'],
})
@api.multi
@api.model
def prepare_reservation_lines(self, dfrom, days, vals=False, update_old_prices=False):
total_price = 0.0
cmds = [(5, 0, 0)]
@@ -719,23 +731,24 @@ class HotelReservation(models.Model):
pricelist_id = self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_pricelist_id')
#~ pricelist_id = vals.get('pricelist_id') or self.pricelist_id.id
product = self.env['hotel.room.type'].browse(vals.get('room_type_id') or self.room_type_id.id).product_id
room_type_id = vals.get('room_type_id') or self.room_type_id.id
product = self.env['hotel.room.type'].browse(room_type_id).product_id
old_lines_days = self.mapped('reservation_line_ids.date')
partner = self.env['res.partner'].browse(vals.get('partner_id') or self.partner_id.id)
total_price = 0
for i in range(0, days):
idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(DEFAULT_SERVER_DATE_FORMAT)
idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(
DEFAULT_SERVER_DATE_FORMAT)
old_line = self.reservation_line_ids.filtered(lambda r: r.date == idate)
if update_old_prices or (idate not in old_lines_days):
_logger.info("PASA 3")
product = product.with_context(
lang=partner.lang,
partner=partner.id,
quantity=1,
date=idate,
pricelist=pricelist_id,
uom=product.uom_id.id)
line_price = self.env['account.tax']._fix_tax_included_price_company(product.price, product.taxes_id, self.tax_id, self.company_id)
lang=partner.lang,
partner=partner.id,
quantity=1,
date=idate,
pricelist=pricelist_id,
uom=product.uom_id.id)
line_price = self.env['account.tax']._fix_tax_included_price_company(
product.price, product.taxes_id, self.tax_id, self.company_id)
if old_line:
cmds.append((1, old_line.id, {
'price': line_price
@@ -748,7 +761,6 @@ class HotelReservation(models.Model):
else:
line_price = old_line.price
cmds.append((4, old_line.id))
total_price += line_price
return {'reservation_line_ids': cmds}
@api.multi
@@ -798,7 +810,6 @@ class HotelReservation(models.Model):
('reservation_line_ids.date', '<', dto),
('state', '!=', 'cancelled'),
('overbooking', '=', False)]
reservations = self.env['hotel.reservation'].search(domain)
return self.env['hotel.reservation'].search(domain)
@api.model
@@ -860,21 +871,19 @@ class HotelReservation(models.Model):
@api.multi
def _compute_cardex_count(self):
_logger.info('_compute_cardex_count')
for res in self:
res.cardex_count = len(res.cardex_ids)
res.cardex_pending_num = (res.adults + res.children) \
- len(res.cardex_ids)
if (res.adults + res.children - len(res.cardex_ids)) <= 0:
res.cardex_pending = False
else:
res.cardex_pending = True
for record in self:
record.write({
'cardex_count': len(record.cardex_ids),
'cardex_pending_count': (record.adults + record.children) \
- len(record.cardex_ids)
})
# https://www.odoo.com/es_ES/forum/ayuda-1/question/calculated-fields-in-search-filter-possible-118501
@api.multi
def _search_cardex_pending(self, operator, value):
recs = self.search([]).filtered(lambda x: x.cardex_pending is True)
if recs:
return [('id', 'in', [x.id for x in recs])]
self.ensure_one()
recs = self.search([]).filtered(lambda x: x.cardex_pending_count > 0)
return [('id', 'in', [x.id for x in recs])] if recs else []
@api.multi
def action_reservation_checkout(self):
@@ -908,8 +917,7 @@ class HotelReservation(models.Model):
res.action_reservation_checkout()
reservations = self.env['hotel.reservation'].search([
('reservation_line_ids.date', 'in', [today_str,
yesterday_str]),
('reservation_line_ids.date', 'in', [today_str, yesterday_str]),
('state', 'in', ['confirm', 'booking'])
])
self._cr.execute("update hotel_reservation set is_checkin = False, \
@@ -932,8 +940,7 @@ class HotelReservation(models.Model):
@api.model
def checkin_is_today(self):
self.ensure_one()
tz_hotel = self.env['ir.default'].sudo().get(
'res.config.settings', 'tz_hotel')
tz_hotel = self.env['ir.default'].sudo().get('res.config.settings', 'tz_hotel')
today = fields.Date.context_today(self.with_context(tz=tz_hotel))
return self.checkin == today

View File

@@ -1,26 +1,8 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo import models, fields, api, _
# Copyright 2017-2018 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields
from odoo.addons import decimal_precision as dp
from odoo.exceptions import except_orm, UserError, ValidationError
class HotelReservationLine(models.Model):
_name = "hotel.reservation.line"

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -12,7 +11,7 @@ class HotelRoom(models.Model):
_name = 'hotel.room'
_description = 'Hotel Room'
_order = "sequence, room_type_id, name"
name = fields.Char('Room Name', required=True)
active = fields.Boolean('Active', default=True)
sequence = fields.Integer('Sequence', default=0)

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -6,7 +5,6 @@ from odoo import models, fields, api, _
class HotelRoomAmenities(models.Model):
_name = 'hotel.room.amenities'
_description = 'Room amenities'
# The record's name

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -6,7 +5,6 @@ from odoo import models, fields, api, _
class HotelRoomAmenitiesType(models.Model):
_name = 'hotel.room.amenities.type'
_description = 'Amenities Type'
# The record's name

View File

@@ -1,20 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from decimal import Decimal
from datetime import datetime, timedelta
import dateutil.parser
# For Python 3.0 and later
from urllib.request import urlopen
import time
from odoo.exceptions import except_orm, UserError, ValidationError
from odoo.tools import (
misc,
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from odoo import models, fields, api, _
from odoo.addons.hotel import date_utils
from odoo import models, fields, api
class HotelRoomType(models.Model):
""" Before creating a 'room type', you need to consider the following:
@@ -68,7 +55,7 @@ class HotelRoomType(models.Model):
"""
self.ensure_one()
capacities = self.room_ids.mapped('capacity')
return any(capacities) and min(capacities) or 0
return min(capacities) if any(capacities) else 0
@api.model
# TODO Rename to check_availability_room_type
@@ -93,7 +80,7 @@ class HotelRoomType(models.Model):
room_type_id = self.env['hotel.room.type'].search([
('id', '=', room_type_id)
])
# QUESTION What linked represent? Rooms in this type ?
# QUESTION What linked represent? Rooms in this type ?
rooms_linked = self.room_ids
free_rooms = free_rooms & rooms_linked
return free_rooms.sorted(key=lambda r: r.sequence)
@@ -106,9 +93,11 @@ class HotelRoomType(models.Model):
@param vals: dictionary of fields value.
@return: new record set for hotel room type.
"""
vals.update({'is_room_type': True})
vals.update({'purchase_ok': False})
vals.update({'type': 'service'})
vals.update({
'is_room_type': True,
'purchase_ok': False,
'type': 'service',
})
return super().create(vals)
@api.multi

View File

@@ -1,33 +1,14 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <alex@aloxa.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
class HotelVirtualRoomAvailability(models.Model):
class HotelRoomTypeAvailability(models.Model):
_inherit = 'mail.thread'
_name = 'hotel.virtual.room.availability'
_name = 'hotel.room.type.availability'
# virtual_room_id = fields.Many2one('hotel.virtual.room', 'Virtual Room',
# required=True, track_visibility='always',
@@ -41,16 +22,19 @@ class HotelVirtualRoomAvailability(models.Model):
track_visibility='always')
date = fields.Date('Date', required=True, track_visibility='always')
_sql_constraints = [('vroom_registry_unique', 'unique(room_type_id, date)',
'Only can exists one availability in the same day for the same room type!')]
_sql_constraints = [
('room_type_registry_unique',
'unique(room_type_id, date)',
'Only can exists one availability in the same day for the same room type!')
]
@api.constrains('avail')
def _check_avail(self):
if self.avail < 0:
self.avail = 0
vroom_obj = self.env['hotel.room.type']
cavail = len(vroom_obj.check_availability_virtual_room(
room_type_obj = self.env['hotel.room.type']
cavail = len(room_type_obj.check_availability_virtual_room(
self.date,
self.date,
room_type_id=self.room_type_id.id))

View File

@@ -0,0 +1,31 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api
class HotelRoomTypeRestriction(models.Model):
_name = 'hotel.room.type.restriction'
name = fields.Char('Restriction Plan Name', required=True)
item_ids = fields.One2many('hotel.room.type.restriction.item',
'restriction_id', string='Restriction Items',
copy=True)
active = fields.Boolean('Active',
help='If unchecked, it will allow you to hide the \
restriction plan without removing it.',
default=True)
@api.multi
@api.depends('name')
def name_get(self):
restriction_id = self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id')
if restriction_id:
restriction_id = int(restriction_id)
names = []
for record in self:
if record.id == restriction_id:
names.append((record.id, '%s (Parity)' % record.name))
else:
names.append((record.id, record.name))
return names

View File

@@ -1,24 +1,5 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <alex@aloxa.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import datetime
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
@@ -26,10 +7,10 @@ from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo.addons.hotel import date_utils
class HotelVirtualRoomRestrictionItem(models.Model):
_name = 'hotel.virtual.room.restriction.item'
class HotelRoomTypeRestrictionItem(models.Model):
_name = 'hotel.room.type.restriction.item'
restriction_id = fields.Many2one('hotel.virtual.room.restriction',
restriction_id = fields.Many2one('hotel.room.type.restriction',
'Restriction Plan', ondelete='cascade',
index=True)
# virtual_room_id = fields.Many2one('hotel.virtual.room', 'Virtual Room',
@@ -54,7 +35,7 @@ class HotelVirtualRoomRestrictionItem(models.Model):
closed_departure = fields.Boolean('Closed Departure')
closed_arrival = fields.Boolean('Closed Arrival')
_sql_constraints = [('vroom_registry_unique',
_sql_constraints = [('room_type_registry_unique',
'unique(restriction_id, room_type_id, date_start, date_end)',
'Only can exists one restriction in the same day for the same room type!')]

View File

@@ -1,14 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import time
import datetime
import logging
from odoo import models, fields, api, _
from odoo.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.addons.hotel import date_utils
from odoo.addons import decimal_precision as dp
from odoo import models, fields, api
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
_logger = logging.getLogger(__name__)
class HotelService(models.Model):
@@ -29,8 +25,9 @@ class HotelService(models.Model):
def _default_ser_room_line(self):
if 'room_lines' in self.env.context and self.env.context['room_lines']:
ids = [item[1] for item in self.env.context['room_lines']]
return self.env['hotel.reservation'].search([('id', 'in', ids)],
limit=1)
return self.env['hotel.reservation'].search([
('id', 'in', ids),
], limit=1)
return False
_name = 'hotel.service'
@@ -56,9 +53,9 @@ class HotelService(models.Model):
('web', 'Web')], 'Sales Channel')
ser_checkin = fields.Datetime('From Date', required=True,
default=_service_checkin)
default=_service_checkin)
ser_checkout = fields.Datetime('To Date', required=True,
default=_service_checkout)
default=_service_checkout)
# TODO Hierarchical relationship for parent-child tree

View File

@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import time
import datetime
import logging
from odoo import models, fields, api, _
from odoo.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.exceptions import UserError
from odoo.addons.hotel import date_utils
_logger = logging.getLogger(__name__)
@@ -14,6 +13,8 @@ from odoo.addons import decimal_precision as dp
class HotelServiceLine(models.Model):
_name = 'hotel.service.line'
_description = 'hotel Service line'
@api.one
def copy(self, default=None):
@@ -32,10 +33,11 @@ class HotelServiceLine(models.Model):
@param field_name: Names of fields.
@param arg: User defined arguments
'''
for folio in self:
line = folio.service_line_id
x = line._amount_line(field_name, arg)
return x
total_amount = 0
for record in self:
line = record.service_line_id
total_amount += line._amount_line(field_name, arg)
return total_amount
@api.multi
def _number_packages(self, field_name, arg):
@@ -44,10 +46,11 @@ class HotelServiceLine(models.Model):
@param field_name: Names of fields.
@param arg: User defined arguments
'''
for folio in self:
line = folio.service_line_id
x = line._number_packages(field_name, arg)
return x
total_packages = 0
for record in self:
line = record.service_line_id
total_packages = line._number_packages(field_name, arg)
return total_packages
@api.model
def _service_checkin(self):
@@ -69,8 +72,6 @@ class HotelServiceLine(models.Model):
limit=1)
return False
_name = 'hotel.service.line'
_description = 'hotel Service line'
# The record's name
name = fields.Char('Service line', required=True)
# services in the hotel are products
@@ -93,13 +94,14 @@ class HotelServiceLine(models.Model):
('web','Web')], 'Sales Channel')
ser_checkin = fields.Datetime('From Date', required=True,
default=_service_checkin)
default=_service_checkin)
ser_checkout = fields.Datetime('To Date', required=True,
default=_service_checkout)
ser_room_line = fields.Many2one('hotel.reservation','Room', default=_default_ser_room_line)
default=_service_checkout)
ser_room_line = fields.Many2one('hotel.reservation', 'Room',
default=_default_ser_room_line)
@api.model
def create(self, vals, check=True):
def create(self, vals):
"""
Overrides orm create method.
@param self: The object pointer
@@ -134,14 +136,17 @@ class HotelServiceLine(models.Model):
@param self: object pointer
'''
if self.product_id:
write_vals = {}
if not (self.folio_id and self.folio_id.partner_id) and \
self.ser_room_line:
self.folio_id = self.ser_room_line.folio_id
self.name = self.product_id.name
self.price_unit = self.product_id.lst_price
self.product_uom = self.product_id.uom_id
self.price_unit = self.product_id.price
write_vals.update({'folio_id': self.ser_room_line.folio_id.id})
write_vals.update({
'name': self.product_id.name,
'price_unit': self.product_id.lst_price,
'product_uom': self.product_id.uom_id,
'price_unit': self.product_id.price,
})
self.write(write_vals)
#~ self.price_unit = tax_obj._fix_tax_included_price(prod.price,
#~ prod.taxes_id,
@@ -208,26 +213,23 @@ class HotelServiceLine(models.Model):
if self.ser_checkin and self.ser_checkout:
diffDate = date_utils.date_diff(self.ser_checkin,
self.ser_checkout, hours=False) + 1
# FIXME: Finalize method!
@api.multi
def button_confirm(self):
'''
@param self: object pointer
'''
for folio in self:
line = folio.service_line_id
x = line.button_confirm()
return x
self.ensure_one()
self.service_line_id.button_confirm()
@api.multi
def button_done(self):
'''
@param self: object pointer
'''
for folio in self:
line = folio.service_line_id
x = line.button_done()
return x
self.ensure_one()
self.service_line_id.button_done()
@api.one
def copy_data(self, default=None):
@@ -235,8 +237,7 @@ class HotelServiceLine(models.Model):
@param self: object pointer
@param default: dict of default values to be set
'''
sale_line_obj = self.env['sale.order.line'
].browse(self.service_line_id.id)
sale_line_obj = self.env['sale.order.line'].browse(self.service_line_id.id)
return sale_line_obj.copy_data(default=default)
@api.multi

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -6,10 +5,9 @@ from odoo import models, fields, api, _
class HotelServiceType(models.Model):
_name = "hotel.service.type"
_description = "Service Type"
# The record's name
name = fields.Char('Service Type', required=True)
# Used for activate records
active = fields.Boolean('Active?', default=True)
@@ -19,7 +17,7 @@ class HotelServiceType(models.Model):
service_ids = fields.One2many('hotel.services', 'service_type_id',
'Services in this category')
@api.multi
def unlink(self):
# self.ser_id.unlink()
return super(HotelServiceType, self).unlink()
# @api.multi
# def unlink(self):
# # self.ser_id.unlink()
# return super(HotelServiceType, self).unlink()

View File

@@ -1,50 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <alex@aloxa.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo import models, fields, api
class VirtualRoomRestriction(models.Model):
_name = 'hotel.virtual.room.restriction'
name = fields.Char('Restriction Plan Name', required=True)
item_ids = fields.One2many('hotel.virtual.room.restriction.item',
'restriction_id', string='Restriction Items',
copy=True)
active = fields.Boolean('Active',
help='If unchecked, it will allow you to hide the \
restriction plan without removing it.',
default=True)
@api.multi
@api.depends('name')
def name_get(self):
restriction_id = self.env['ir.default'].sudo().get(
'res.config.settings', 'parity_restrictions_id')
if restriction_id:
restriction_id = int(restriction_id)
names = []
for record in self:
if record.id == restriction_id:
names.append((record.id, '%s (Parity)' % record.name))
else:
names.append((record.id, record.name))
return names

View File

@@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import logging
from openerp import models, fields, api, _
from openerp.exceptions import UserError, ValidationError
import logging
_logger = logging.getLogger(__name__)
class AccountInvoice(models.Model):
_inherit = 'account.invoice'
@api.model
@@ -64,7 +61,8 @@ class AccountInvoice(models.Model):
@api.multi
def action_invoice_open(self):
to_open_invoices_without_vat = self.filtered(lambda inv: inv.state != 'open' and inv.partner_id.vat == False)
to_open_invoices_without_vat = self.filtered(
lambda inv: inv.state != 'open' and inv.partner_id.vat == False)
if to_open_invoices_without_vat:
vat_error = _("We need the VAT of the following companies")
for invoice in to_open_invoices_without_vat:

View File

@@ -1,15 +1,7 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from decimal import Decimal
import datetime
# For Python 3.0 and later
from urllib.request import urlopen
import time
import logging
from openerp.exceptions import except_orm, UserError, ValidationError
from openerp.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
from openerp.exceptions import except_orm
from openerp import models, fields, api, _
_logger = logging.getLogger(__name__)
@@ -40,7 +32,7 @@ class AccountPayment(models.Model):
}
return_vals = {
'journal_id': journal.id,
'line_ids': [(0,0,return_line_vals)],
'line_ids': [(0, 0, return_line_vals)],
}
return_pay = self.env['payment.return'].create(return_vals)
return {
@@ -73,6 +65,7 @@ class AccountPayment(models.Model):
@api.multi
@api.depends('state')
def _compute_folio_amount(self):
# FIXME: Finalize method
res = []
fol = ()
for payment in self:
@@ -84,7 +77,7 @@ class AccountPayment(models.Model):
])
else:
return
if len(fol) == 0:
if not any(fol):
return
elif len(fol) > 1:
raise except_orm(_('Warning'), _('This pay is related with \

View File

@@ -1,28 +1,9 @@
# -*- coding: utf-8 -*-
# --------------------------------------------------------------------------
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2018-Darío Lodeiros Vázquez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
# ---------------------------------------------------------------------------
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, fields, api, _
class PaymentReturn(models.Model):
_inherit = 'payment.return'
folio_id = fields.Many2one('hotel.folio', string='Folio')
@@ -33,7 +14,9 @@ class PaymentReturn(models.Model):
if pay:
folio_ids = []
for line in self.line_ids:
payments = self.env['account.payment'].search([('move_line_ids','in',line.move_line_ids.ids)])
folio_ids += payments.mapped('folio_id.id')
payments = self.env['account.payment'].search([
('move_line_ids', 'in', line.move_line_ids.ids)
])
folio_ids += payments.mapped('folio_id.id')
folios = self.env['hotel.folio'].browse(folio_ids)
folios.compute_amount()

View File

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

View File

@@ -1,23 +1,5 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from openerp import models, api

View File

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

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -6,7 +5,6 @@ from openerp import models, fields, api, _
class ResCompany(models.Model):
_inherit = 'res.company'
additional_hours = fields.Integer('Additional Hours',

View File

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

View File

@@ -1,24 +1,5 @@
# -*- 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/>.
#
##############################################################################
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, models
@@ -28,13 +9,14 @@ class MailComposeMessage(models.TransientModel):
@api.multi
def send_mail(self, auto_commit=False):
if self._context.get('default_model') == 'hotel.folio' and self._context.get('default_res_id') and self._context.get('mark_so_as_sent'):
if self._context.get('default_model') == 'hotel.folio' and \
self._context.get('default_res_id') and self._context.get('mark_so_as_sent'):
folio = self.env['hotel.folio'].browse([
self._context['default_res_id']
])
if folio:
cmds = []
for lid in folio.room_lines._ids:
for lid in folio.room_lines.ids:
cmds.append((
1,
lid,

View File

@@ -1,24 +1,6 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# Alexandre Díaz <alex@aloxa.eu>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Copyright 2017-2018 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import re
import pytz
from openerp import models, fields, api, _
@@ -39,7 +21,7 @@ class HotelConfiguration(models.TransientModel):
parity_pricelist_id = fields.Many2one('product.pricelist',
'Product Pricelist')
parity_restrictions_id = fields.Many2one('hotel.virtual.room.restriction',
parity_restrictions_id = fields.Many2one('hotel.room.type.restriction',
'Restrictions')
default_arrival_hour = fields.Char('Default Arrival Hour (GMT)',
help="HH:mm Format", default="14:00")