Files
pms/hotel/models/hotel_service.py
2019-01-15 12:08:22 +01:00

307 lines
14 KiB
Python

# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons import decimal_precision as dp
import logging
_logger = logging.getLogger(__name__)
class HotelService(models.Model):
_name = 'hotel.service'
_description = 'Hotel Services and its charges'
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
if args is None:
args = []
if not(name == '' and operator == 'ilike'):
args += [
'|',
('ser_room_line.name', operator, name),
('name', operator, name)
]
return super(HotelService, self).name_search(
name='', args=args, operator='ilike', limit=limit)
@api.multi
def name_get(self):
result = []
for res in self:
name = u'%s (%s)' % (res.name, res.ser_room_line.name)
result.append((res.id, name))
return result
@api.model
def _default_ser_room_line(self):
if self.env.context.get('room_lines'):
ids = [item[1] for item in self.env.context['room_lines']]
return self.env['hotel.reservation'].browse([
(ids)], limit=1)
return False
name = fields.Char('Service description')
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)
per_day = fields.Boolean(related='product_id.per_day')
service_line_ids = fields.One2many('hotel.service.line', 'service_id')
product_qty = fields.Integer('Quantity')
days_qty = fields.Integer(compute="_compute_days_qty", store=True)
is_board_service = fields.Boolean()
# Non-stored related field to allow portal user to see the image of the product he has ordered
product_image = fields.Binary('Product Image', related="product_id.image", store=False)
channel_type = fields.Selection([
('door', 'Door'),
('mail', 'Mail'),
('phone', 'Phone'),
('call', 'Call Center'),
('web', 'Web')], 'Sales Channel')
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0)
tax_ids = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
currency_id = fields.Many2one(related='folio_id.currency_id', store=True, string='Currency', readonly=True)
price_subtotal = fields.Monetary(string='Subtotal',
readonly=True,
store=True,
compute='_compute_amount_service')
price_total = fields.Monetary(string='Total',
readonly=True,
store=True,
compute='_compute_amount_service')
price_tax = fields.Float(string='Taxes',
readonly=True,
store=True,
compute='_compute_amount_service')
@api.model
def create(self, vals):
vals.update(self._prepare_add_missing_fields(vals))
if self.compute_lines_out_vals(vals):
reservation = self.env['hotel.reservation'].browse(vals['ser_room_line'])
product = self.env['product.product'].browse(vals['product_id'])
vals.update(self.prepare_service_lines(
dfrom=reservation.checkin,
days=reservation.nights,
per_person=product.per_person,
persons=reservation.adults,
old_day_lines=False,
))
record = super(HotelService, self).create(vals)
return record
@api.multi
def write(self, vals):
#If you write product, We must check if its necesary create or delete
#service lines
if vals.get('product_id'):
product = self.env['product.product'].browse(vals.get('product_id'))
if not product.per_day:
vals.update({
'service_line_ids' : [(5, 0, 0)]
})
else:
for record in self:
reservations = self.env['hotel.reservation']
reservation = reservations.browse(vals['ser_room_line']) \
if 'ser_room_line' in vals else record.ser_room_line
record.update(record.prepare_service_lines(
dfrom=reservation.checkin,
days=reservation.nights,
per_person=product.per_person,
persons=reservation.adults,
old_line_days=self.service_line_ids
))
res = super(HotelService, self).write(vals)
return res
@api.model
def _prepare_add_missing_fields(self, values):
""" Deduce missing required fields from the onchange """
res = {}
onchange_fields = ['price_unit','tax_ids']
if values.get('product_id'):
line = self.new(values)
if any(f not in values for f in onchange_fields):
line.onchange_product_calc_qty()
for field in onchange_fields:
if field not in values:
res[field] = line._fields[field].convert_to_write(line[field], line)
return res
@api.multi
def compute_lines_out_vals(self, vals):
"""
Compute if It is necesary service days in write/create
"""
if not vals:
vals = {}
if 'product_id' in vals:
product = self.env['product.product'].browse(vals['product_id']) \
if 'product_id' in vals else self.product_id
if (product.per_day and 'service_line_ids' not in vals):
return True
return False
@api.multi
def _compute_tax_ids(self):
for record in self:
# If company_id is set, always filter taxes by the company
folio = record.folio_id or self.env.context.get('default_folio_id')
record.tax_ids = record.product_id.taxes_id.filtered(lambda r: not record.company_id or r.company_id == folio.company_id)
@api.multi
def _get_display_price(self, product):
folio = self.folio_id or self.env.context.get('default_folio_id')
if folio.pricelist_id.discount_policy == 'with_discount':
return product.with_context(pricelist=folio.pricelist_id.id).price
product_context = dict(self.env.context, partner_id=folio.partner_id.id, date=folio.date_order, uom=self.product_id.uom_id.id)
final_price, rule_id = folio.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_qty or 1.0, folio.partner_id)
base_price, currency_id = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_qty, product_id.uom_id, folio.pricelist_id.id)
if currency_id != folio.pricelist_id.currency_id.id:
base_price = self.env['res.currency'].browse(currency_id).with_context(product_context).compute(base_price, folio.pricelist_id.currency_id)
# negative discounts (= surcharge) are included in the display price
return max(base_price, final_price)
@api.onchange('product_id')
def onchange_product_calc_qty(self):
"""
Compute the default quantity according to the
configuration of the selected product, in per_day product configuration,
the qty is autocalculated and readonly based on service_lines qty
"""
if not self.product_id:
return
vals = {}
vals['product_qty'] = 1.0
for record in self:
if record.per_day and record.ser_room_line:
product = record.product_id
reservation = record.ser_room_line
vals.update(record.prepare_service_lines(
dfrom=reservation.checkin,
days=reservation.nights,
per_person=product.per_person,
persons=reservation.adults,
old_line_days=record.service_line_ids))
if record.product_id.daily_limit > 0:
for day in record.service_line_ids:
day.no_free_resources()
"""
Compute tax and price unit
"""
self._compute_tax_ids()
vals['price_unit'] = self._compute_price_unit()
record.update(vals)
@api.multi
def _compute_price_unit(self):
self.ensure_one()
folio = self.folio_id or self.env.context.get('default_folio_id')
reservation = self.ser_room_line or self.env.context.get('ser_room_line')
if folio or reservation:
partner = folio.partner_id if folio else reservation.partner_id
pricelist = folio.pricelist_id if folio else reservation.pricelist_id
if reservation and self.is_board_service:
board_room_type = reservation.board_service_room_id
if board_room_type.price_type == 'fixed':
return self.env['hotel.board.service.room.type.line'].search([
('hotel_board_service_room_type_id', '=', board_room_type.id),
('product_id','=',self.product_id.id)]).amount
else:
return (reservation.price_total * self.env['hotel.board.service.room.type.line'].search([
('hotel_board_service_room_type_id', '=', board_room_type.id),
('product_id','=',self.product_id.id)]).amount) / 100
else:
product = self.product_id.with_context(
lang=partner.lang,
partner=partner.id,
quantity=self.product_qty,
date=folio.date_order or fields.Date.today(),
pricelist=pricelist.id,
uom=self.product_id.uom_id.id,
fiscal_position=False
)
return self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_ids, folio.company_id)
@api.model
def prepare_service_lines(self, **kwargs):
"""
Prepare line and respect the old manual changes on lines
"""
cmds = [(5, 0, 0)]
old_line_days = kwargs.get('old_line_days')
total_qty = 0
day_qty = 1
if kwargs.get('per_person'): #WARNING: Change adults in reservation NOT update qty service!!
day_qty = kwargs.get('persons')
for i in range(0, kwargs.get('days')):
idate = (fields.Date.from_string(kwargs.get('dfrom')) + timedelta(days=i)).strftime(
DEFAULT_SERVER_DATE_FORMAT)
if not old_line_days or idate not in old_line_days.mapped('date'):
cmds.append((0, False, {
'date': idate,
'day_qty': day_qty
}))
total_qty = total_qty + day_qty
else:
old_line = old_line_days.filtered(lambda r: r.date == idate)
cmds.append((4, old_line.id))
total_qty = total_qty + old_line.day_qty
return {'service_line_ids': cmds, 'product_qty': total_qty}
@api.depends('product_qty', 'discount', 'price_unit', 'tax_ids')
def _compute_amount_service(self):
"""
Compute the amounts of the service line.
"""
for record in self:
folio = record.folio_id or self.env.context.get('default_folio_id')
reservation = record.ser_room_line or self.env.context.get('ser_room_line')
currency = folio.currency_id if folio else reservation.currency_id
product = record.product_id
price = record.price_unit * (1 - (record.discount or 0.0) * 0.01)
taxes = record.tax_ids.compute_all(price, currency, record.product_qty, 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.depends('service_line_ids.day_qty')
def _compute_days_qty(self):
for record in self:
if record.per_day:
qty = sum(record.service_line_ids.mapped('day_qty'))
vals = {
'days_qty': qty,
'product_qty': qty
}
else:
vals = {'days_qty': 0}
record.update(vals)
@api.multi
def open_service_lines(self):
action = self.env.ref('hotel.action_hotel_services_form').read()[0]
action['views'] = [(self.env.ref('hotel.hotel_service_view_form').id, 'form')]
action['res_id'] = self.id
action['target'] = 'new'
return action
#~ @api.constrains('product_qty')
#~ def constrains_qty_per_day(self):
#~ for record in self:
#~ if record.per_day:
#~ service_lines = self.env['hotel.service_line']
#~ total_day_qty = sum(service_lines.with_context({'service_id': record.id}).mapped('day_qty'))
#~ if record.product_qty != total_day_qty:
#~ raise ValidationError (_('The quantity per line and per day does not correspond'))