mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[DEV] Board Service, Wizard refact, hotes services configuration, services lines by day
This commit is contained in:
@@ -15,7 +15,7 @@ from . import hotel_room_type
|
||||
from . import hotel_service
|
||||
from . import inherit_account_invoice
|
||||
# from . import inherit_product_category
|
||||
from . import inherit_product_product
|
||||
from . import inherit_product_template
|
||||
from . import inherit_res_company
|
||||
# from . import room_type
|
||||
from . import inherit_account_payment
|
||||
@@ -30,4 +30,6 @@ from . import inherit_res_partner
|
||||
from . import inherited_mail_compose_message
|
||||
from . import hotel_room_type_class
|
||||
from . import room_closure_reason
|
||||
from . import hotel_service_line
|
||||
from . import hotel_board_service
|
||||
#~ from . import hotel_dashboard
|
||||
|
||||
15
hotel/models/hotel_board_service.py
Normal file
15
hotel/models/hotel_board_service.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from openerp import models, fields, api, _
|
||||
|
||||
|
||||
class HotelBoardService(models.Model):
|
||||
_name = "hotel.board.service"
|
||||
_description = "Board Services"
|
||||
|
||||
name = fields.Char('Board Name', size=64, required=True, index=True)
|
||||
service_ids = fields.Many2many(comodel_name='product.template',
|
||||
relation='board_services_room',
|
||||
column1='board_id',
|
||||
column2='service_id')
|
||||
sequence = fields.Integer('Sequence', size=64)
|
||||
@@ -353,13 +353,12 @@ class HotelFolio(models.Model):
|
||||
#~ })
|
||||
return
|
||||
addr = self.partner_id.address_get(['invoice'])
|
||||
values = {'user_id': self.partner_id.user_id.id or self.env.uid,
|
||||
'pricelist_id':self.partner_id.property_product_pricelist and \
|
||||
pricelist = 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'),
|
||||
'reservation_type': self.env['hotel.folio'].calcule_reservation_type(
|
||||
self.partner_id.is_staff,
|
||||
self.reservation_type)}
|
||||
self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id')
|
||||
values = {'user_id': self.partner_id.user_id.id or self.env.uid,
|
||||
'pricelist_id': pricelist
|
||||
}
|
||||
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(
|
||||
@@ -369,6 +368,15 @@ class HotelFolio(models.Model):
|
||||
values['team_id'] = self.partner_id.team_id.id
|
||||
self.update(values)
|
||||
|
||||
@api.multi
|
||||
@api.onchange('pricelist_id')
|
||||
def onchange_pricelist_id(self):
|
||||
values = {'reservation_type': self.env['hotel.folio'].calcule_reservation_type(
|
||||
self.pricelist_id.is_staff,
|
||||
self.reservation_type)}
|
||||
self.update(values)
|
||||
|
||||
|
||||
@api.model
|
||||
def calcule_reservation_type(self, is_staff, current_type):
|
||||
if current_type == 'out':
|
||||
|
||||
@@ -196,6 +196,7 @@ class HotelReservation(models.Model):
|
||||
parent_reservation = fields.Many2one('hotel.reservation',
|
||||
'Parent Reservation')
|
||||
overbooking = fields.Boolean('Is Overbooking', default=False)
|
||||
reselling = fields.Boolean('Is Reselling', default=False)
|
||||
|
||||
nights = fields.Integer('Nights', compute='_computed_nights', store=True)
|
||||
channel_type = fields.Selection([
|
||||
@@ -408,6 +409,7 @@ class HotelReservation(models.Model):
|
||||
'parent_reservation': self.parent_reservation.id,
|
||||
'state': self.state,
|
||||
'overbooking': self.overbooking,
|
||||
'reselling': self.reselling,
|
||||
'price_unit': self.price_unit,
|
||||
'splitted': self.splitted,
|
||||
# 'room_type_id': self.room_type_id.id,
|
||||
@@ -444,16 +446,22 @@ class HotelReservation(models.Model):
|
||||
@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 \
|
||||
pricelist = 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'),
|
||||
'reservation_type': self.env['hotel.folio'].calcule_reservation_type(
|
||||
self.partner_id.is_staff,
|
||||
self.reservation_type)
|
||||
self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id')
|
||||
values = {
|
||||
'pricelist_id': pricelist,
|
||||
}
|
||||
self.update(values)
|
||||
|
||||
@api.multi
|
||||
@api.onchange('pricelist_id')
|
||||
def onchange_pricelist_id(self):
|
||||
values = {'reservation_type': self.env['hotel.folio'].calcule_reservation_type(
|
||||
self.pricelist_id.is_staff,
|
||||
self.reservation_type)}
|
||||
self.update(values)
|
||||
|
||||
@api.onchange('reservation_type')
|
||||
def assign_partner_company_on_out_service(self):
|
||||
if self.reservation_type == 'out':
|
||||
@@ -507,7 +515,7 @@ class HotelReservation(models.Model):
|
||||
def onchange_room_availabiltiy_domain(self):
|
||||
self.ensure_one()
|
||||
if self.checkin and self.checkout:
|
||||
if self.overbooking:
|
||||
if self.overbooking or self.reselling:
|
||||
return
|
||||
occupied = self.env['hotel.reservation'].get_reservations(
|
||||
self.checkin,
|
||||
@@ -842,7 +850,8 @@ class HotelReservation(models.Model):
|
||||
domain = [('reservation_line_ids.date', '>=', dfrom),
|
||||
('reservation_line_ids.date', '<', dto),
|
||||
('state', '!=', 'cancelled'),
|
||||
('overbooking', '=', False)]
|
||||
('overbooking', '=', False),
|
||||
('reselling', '=', False),]
|
||||
return domain
|
||||
|
||||
@api.model
|
||||
@@ -872,7 +881,7 @@ class HotelReservation(models.Model):
|
||||
return reservations_dates
|
||||
|
||||
# TODO: Use default values on checkin /checkout is empty
|
||||
@api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking')
|
||||
@api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking', 'reselling')
|
||||
def check_dates(self):
|
||||
"""
|
||||
1.-When date_order is less then checkin date or
|
||||
|
||||
@@ -10,18 +10,6 @@ _logger = logging.getLogger(__name__)
|
||||
class HotelService(models.Model):
|
||||
_name = 'hotel.service'
|
||||
_description = 'Hotel Services and its charges'
|
||||
|
||||
@api.model
|
||||
def _service_checkin(self):
|
||||
if 'checkin' in self._context:
|
||||
return self._context['checkin']
|
||||
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
@api.model
|
||||
def _service_checkout(self):
|
||||
if 'checkout' in self._context:
|
||||
return self._context['checkout']
|
||||
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
@api.model
|
||||
def _default_ser_room_line(self):
|
||||
@@ -33,42 +21,116 @@ class HotelService(models.Model):
|
||||
return False
|
||||
|
||||
name = fields.Char('Service description')
|
||||
# services in the hotel are products
|
||||
product_id = fields.Many2one('product.product', 'Service', required=True)
|
||||
|
||||
folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade')
|
||||
|
||||
product_id = fields.Many2one('product.product', 'Service',
|
||||
required=True)
|
||||
folio_id = fields.Many2one('hotel.folio', 'Folio',
|
||||
ondelete='cascade')
|
||||
ser_room_line = fields.Many2one('hotel.reservation', 'Room',
|
||||
default=_default_ser_room_line)
|
||||
|
||||
list_price = fields.Float(
|
||||
related='product_id.list_price')
|
||||
|
||||
service_line_ids = fields.One2many('hotel.service.line',
|
||||
'service_id')
|
||||
product_qty = fields.Integer('Quantity')
|
||||
pricelist_id = fields.Many2one(
|
||||
related='folio_id.pricelist_id')
|
||||
channel_type = fields.Selection([
|
||||
('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone'),
|
||||
('call', 'Call Center'),
|
||||
('web', 'Web')], 'Sales Channel')
|
||||
currency_id = fields.Many2one('res.currency',
|
||||
related='pricelist_id.currency_id',
|
||||
string='Currency', readonly=True, required=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')
|
||||
|
||||
ser_checkin = fields.Datetime('From Date', required=True,
|
||||
default=_service_checkin)
|
||||
ser_checkout = fields.Datetime('To Date', required=True,
|
||||
default=_service_checkout)
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_calc_qty(self):
|
||||
"""
|
||||
Compute the default quantity according to the
|
||||
configuration of the selected product
|
||||
"""
|
||||
for record in self:
|
||||
product = record.product_id
|
||||
reservation = record.ser_room_line
|
||||
if product and reservation:
|
||||
qty = 1
|
||||
if product.per_day:
|
||||
qty = qty * reservation.nights
|
||||
if product.per_person:
|
||||
qty = qty * (reservation.adults + reservation.children)
|
||||
record.product_qty = qty
|
||||
|
||||
@api.onchange('product_qty')
|
||||
def onchange_product_qty_days_selection(self):
|
||||
"""
|
||||
Try to calculate the days on which the product
|
||||
should be served as long as the product is per day
|
||||
"""
|
||||
for record in self:
|
||||
reservation = record.ser_room_line
|
||||
if record.product_id.per_day:
|
||||
days_diff = (
|
||||
fields.Date.from_string(reservation.checkout) - fields.Date.from_string(reservation.checkin)
|
||||
).days
|
||||
record.update(record.prepare_service_lines(
|
||||
reservation.checkin,
|
||||
days_diff))
|
||||
else:
|
||||
record.update(rec.prepare_service_lines(
|
||||
reservation.checkin, 1))
|
||||
|
||||
|
||||
# TODO Hierarchical relationship for parent-child tree
|
||||
# parent_id = fields.Many2one ...
|
||||
@api.multi
|
||||
def prepare_service_lines(self, dfrom, days, vals=False):
|
||||
self.ensure_one()
|
||||
old_qty = 0
|
||||
cmds = [(5, 0, 0)]
|
||||
if not vals:
|
||||
vals
|
||||
product = vals.get('product_id') or self.product_id
|
||||
old_lines_days = self.mapped('service_line_ids.date')
|
||||
for day in service_line_ids:
|
||||
old_qty = old_qty + day.day_qty
|
||||
qty_day = (self.product_qty - old_qty) // (days - count(old_line_days))
|
||||
rest_day = (self.product_qty - old_qty) % (days - count(old_line_days))
|
||||
reservation = rec.ser_room_line
|
||||
for i in range(0, days):
|
||||
idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
old_line = self.service_line_ids.filtered(lambda r: r.date == idate)
|
||||
if idate not in old_lines_days:
|
||||
cmds.append((0, False, {
|
||||
'date': idate,
|
||||
'day_qty': qty
|
||||
}))
|
||||
else:
|
||||
cmds.append((4, old_line.id))
|
||||
return {'service_line_ids': cmds}
|
||||
|
||||
# service_id = fields.Many2one('product.product', 'Service_id',
|
||||
# required=True, ondelete='cascade',
|
||||
# delegate=True)
|
||||
# service_type_id = fields.Many2one('hotel.service.type',
|
||||
# 'Service Catagory')
|
||||
# service_line_id = fields.Many2one('hotel.service.line',
|
||||
# 'Service Line')
|
||||
# @api.multi
|
||||
# def unlink(self):
|
||||
# # for record in self:
|
||||
# # record.service_id.unlink()
|
||||
# return super(HotelServices, self).unlink()
|
||||
@api.depends('qty_product', 'tax_id')
|
||||
def _compute_amount_service(self):
|
||||
"""
|
||||
Compute the amounts of the service line.
|
||||
"""
|
||||
for record in self:
|
||||
product = rec.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'],
|
||||
})
|
||||
|
||||
|
||||
|
||||
32
hotel/models/hotel_service_line.py
Normal file
32
hotel/models/hotel_service_line.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# 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
|
||||
|
||||
class HotelServiceLine(models.Model):
|
||||
_name = "hotel.service.line"
|
||||
_order = "date"
|
||||
|
||||
service_id = fields.Many2one('hotel.service', string='Service',
|
||||
ondelete='cascade', required=True,
|
||||
copy=False)
|
||||
date = fields.Date('Date')
|
||||
day_qty = fields.Integer('Units')
|
||||
product_id = fields.Many2one(related='service_id.product_id')
|
||||
|
||||
@api.constrains('day_qty')
|
||||
def no_free_resources(self):
|
||||
for record in self:
|
||||
limit = record.product_id.daily_limit
|
||||
if limit > 0:
|
||||
out_qty = sum(self.env['hotel.service.line'].search([(
|
||||
'product_id','=',record.product_id,
|
||||
'date','=',record.date)]).mapped('day_qty'))
|
||||
if limit < out_qty + record.day_qty:
|
||||
raise ValidationError(
|
||||
_("Limit exceeded for %s")% record.date)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from openerp import models, api
|
||||
from openerp import models, fields, api, _
|
||||
|
||||
|
||||
class ProductPricelist(models.Model):
|
||||
@@ -21,3 +21,6 @@ class ProductPricelist(models.Model):
|
||||
else:
|
||||
names.append((name[0], name[1]))
|
||||
return names
|
||||
|
||||
|
||||
is_staff = fields.Boolean('Is Staff')
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from openerp import models, fields, api, _
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
is_room_type = fields.Boolean('Is a Room Type', default=False)
|
||||
# iscategid = fields.Boolean('Is categ id')
|
||||
# isservice = fields.Boolean('Is Service id')
|
||||
13
hotel/models/inherit_product_template.py
Normal file
13
hotel/models/inherit_product_template.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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 ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
is_hotel_service = fields.Boolean('Is a Hotel Service', default=False)
|
||||
per_day = fields.Boolean('Unit increment per day')
|
||||
per_person = fields.Boolean('Unit increment per person')
|
||||
daily_limit = fields.Integer('Daily limit')
|
||||
@@ -24,7 +24,6 @@ class ResPartner(models.Model):
|
||||
reservations_count = fields.Integer('Reservations',
|
||||
compute='_compute_reservations_count')
|
||||
folios_count = fields.Integer('Folios', compute='_compute_folios_count')
|
||||
is_staff = fields.Boolean('Is Staff')
|
||||
|
||||
""" TODO
|
||||
@api.onchange('is_staff')
|
||||
|
||||
Reference in New Issue
Block a user