mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
Merge branch 'multihotel' of https://github.com/hootel/hootel into multihotel
This commit is contained in:
@@ -7758,7 +7758,7 @@ msgstr "Devoluciones"
|
||||
#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_room_id
|
||||
#: model:ir.model.fields,field_description:hotel.field_hotel_reservation_wizard_room_id
|
||||
#: model:ir.model.fields,field_description:hotel.field_hotel_service_line_room_id
|
||||
#: model:ir.model.fields,field_description:hotel.field_hotel_service_ser_room_line
|
||||
#: model:ir.model.fields,field_description:hotel.field_hotel_service_reservation_id
|
||||
#: model:ir.ui.menu,name:hotel.menu_hotel_room
|
||||
#: model:ir.ui.view,arch_db:hotel.hotel_reservation_view_form
|
||||
msgid "Room"
|
||||
|
||||
@@ -139,7 +139,7 @@ class HotelReservation(models.Model):
|
||||
required=True)
|
||||
service_ids = fields.One2many(
|
||||
'hotel.service',
|
||||
'ser_room_line')
|
||||
'reservation_id')
|
||||
pricelist_id = fields.Many2one(
|
||||
'product.pricelist',
|
||||
related='folio_id.pricelist_id')
|
||||
@@ -680,7 +680,7 @@ class HotelReservation(models.Model):
|
||||
'product_id': product.id,
|
||||
'is_board_service': True,
|
||||
'folio_id': self.folio_id.id,
|
||||
'ser_room_line': self.id,
|
||||
'reservation_id': self.id,
|
||||
}
|
||||
line = self.env['hotel.service'].new(res)
|
||||
res.update(
|
||||
@@ -920,7 +920,7 @@ class HotelReservation(models.Model):
|
||||
dfrom=real_checkin,
|
||||
days=service_days_diff,
|
||||
per_person=service.product_id.per_person,
|
||||
persons=service.ser_room_line.adults,
|
||||
persons=service.reservation_id.adults,
|
||||
old_line_days=service.service_line_ids,
|
||||
consumed_on=service.product_id.consumed_on,
|
||||
))
|
||||
@@ -943,9 +943,10 @@ class HotelReservation(models.Model):
|
||||
# Yes?, then, this is share folio ;)
|
||||
for record in self:
|
||||
if record.folio_id:
|
||||
record.shared_folio = len(record.folio_id.reservation_ids) > 1 or \
|
||||
any(record.folio_id.service_ids.filtered(
|
||||
lambda x: x.ser_room_line.id != record.id))
|
||||
record.shared_folio = \
|
||||
len(record.folio_id.reservation_ids) > 1 \
|
||||
or any(record.folio_id.service_ids.filtered(
|
||||
lambda x: x.reservation_id.id != record.id))
|
||||
|
||||
@api.multi
|
||||
def compute_board_services(self, vals):
|
||||
|
||||
@@ -7,8 +7,9 @@ from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class HotelRoom(models.Model):
|
||||
""" The rooms for lodging can be for sleeping, usually called rooms, and also
|
||||
for speeches (conference rooms), parking, relax with cafe con leche, spa...
|
||||
""" The rooms for lodging can be for sleeping, usually called rooms,
|
||||
and also for speeches (conference rooms), parking,
|
||||
relax with cafe con leche, spa...
|
||||
"""
|
||||
_name = 'hotel.room'
|
||||
_description = 'Hotel Room'
|
||||
@@ -16,15 +17,24 @@ class HotelRoom(models.Model):
|
||||
|
||||
# Fields declaration
|
||||
name = fields.Char('Room Name', required=True)
|
||||
hotel_id = fields.Many2one('hotel.property', store=True, readonly=True,
|
||||
related='room_type_id.hotel_id')
|
||||
room_type_id = fields.Many2one('hotel.room.type', 'Hotel Room Type',
|
||||
required=True,
|
||||
ondelete='restrict')
|
||||
shared_room_id = fields.Many2one('hotel.shared.room', 'Shared Room',
|
||||
default=False)
|
||||
floor_id = fields.Many2one('hotel.floor', 'Ubication',
|
||||
help='At which floor the room is located.')
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property',
|
||||
store=True,
|
||||
readonly=True,
|
||||
related='room_type_id.hotel_id')
|
||||
room_type_id = fields.Many2one(
|
||||
'hotel.room.type',
|
||||
'Hotel Room Type',
|
||||
required=True,
|
||||
ondelete='restrict')
|
||||
shared_room_id = fields.Many2one(
|
||||
'hotel.shared.room',
|
||||
'Shared Room',
|
||||
default=False)
|
||||
floor_id = fields.Many2one(
|
||||
'hotel.floor',
|
||||
'Ubication',
|
||||
help='At which floor the room is located.')
|
||||
capacity = fields.Integer('Capacity')
|
||||
to_be_cleaned = fields.Boolean('To be Cleaned', default=False)
|
||||
extra_beds_allowed = fields.Integer('Extra beds allowed',
|
||||
@@ -43,29 +53,36 @@ class HotelRoom(models.Model):
|
||||
def _check_capacity(self):
|
||||
for record in self:
|
||||
if record.capacity < 1:
|
||||
raise ValidationError(_("The capacity of the room must be greater than 0."))
|
||||
raise ValidationError(_("The capacity of the \
|
||||
room must be greater than 0."))
|
||||
|
||||
# CRUD methods
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('hotel_id', self.env.user.hotel_id.id) != self.env['hotel.room.type'].browse(
|
||||
vals['room_type_id']).hotel_id.id:
|
||||
raise ValidationError(_("A room cannot be created in a room type of another hotel."))
|
||||
if vals.get('hotel_id', self.env.user.hotel_id.id) != \
|
||||
self.env['hotel.room.type'].browse(
|
||||
vals['room_type_id']).hotel_id.id:
|
||||
raise ValidationError(
|
||||
_("A room cannot be created in a room type \
|
||||
of another hotel."))
|
||||
return super().create(vals)
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
for record in self:
|
||||
if vals.get('hotel_id', record.hotel_id.id) != record.hotel_id.id:
|
||||
raise ValidationError(_("A room cannot be changed to another hotel.") + " " +
|
||||
_("%s does not belong to %s.")
|
||||
% (record, record.hotel_id))
|
||||
raise ValidationError(
|
||||
_("A room cannot be changed to another hotel.") + " " +
|
||||
_("%s does not belong to %s.")
|
||||
% (record, record.hotel_id))
|
||||
room_type_ids = self.env['hotel.room.type'].search([
|
||||
('hotel_id', '=', record.hotel_id.id)
|
||||
]).ids
|
||||
if vals.get('room_type_id', record.room_type_id.id) not in room_type_ids:
|
||||
raise ValidationError(_("A room cannot be changed to a room type of another hotel or "
|
||||
"unlinked from a room type."))
|
||||
if vals.get('room_type_id', record.room_type_id.id) \
|
||||
not in room_type_ids:
|
||||
raise ValidationError(
|
||||
_("A room cannot be changed to a room type of \
|
||||
another hotel or unlinked from a room type."))
|
||||
return super().write(vals)
|
||||
|
||||
# Business methods
|
||||
|
||||
@@ -21,30 +21,47 @@ class HotelRoomType(models.Model):
|
||||
return self.env.user.hotel_id
|
||||
|
||||
# Fields declaration
|
||||
product_id = fields.Many2one('product.product', 'Product Room Type',
|
||||
required=True, delegate=True,
|
||||
ondelete='cascade')
|
||||
hotel_id = fields.Many2one('hotel.property', 'Hotel', required=True, ondelete='restrict',
|
||||
default=_get_default_hotel,)
|
||||
room_ids = fields.One2many('hotel.room', 'room_type_id', 'Rooms')
|
||||
class_id = fields.Many2one('hotel.room.type.class', 'Hotel Type Class')
|
||||
product_id = fields.Many2one(
|
||||
'product.product',
|
||||
'Product Room Type',
|
||||
required=True,
|
||||
delegate=True,
|
||||
ondelete='cascade')
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property',
|
||||
'Hotel',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
default=_get_default_hotel,)
|
||||
room_ids = fields.One2many(
|
||||
'hotel.room',
|
||||
'room_type_id',
|
||||
'Rooms')
|
||||
class_id = fields.Many2one(
|
||||
'hotel.room.type.class',
|
||||
'Hotel Type Class')
|
||||
board_service_room_type_ids = fields.One2many(
|
||||
'hotel.board.service.room.type', 'hotel_room_type_id', string='Board Services')
|
||||
room_amenity_ids = fields.Many2many('hotel.amenity',
|
||||
'hotel_room_type_aminity_rel',
|
||||
'room_type_ids', 'amenity_ids',
|
||||
string='Room Type Amenities',
|
||||
help='List of Amenities.')
|
||||
|
||||
'hotel.board.service.room.type',
|
||||
'hotel_room_type_id',
|
||||
string='Board Services')
|
||||
room_amenity_ids = fields.Many2many(
|
||||
'hotel.amenity',
|
||||
'hotel_room_type_aminity_rel',
|
||||
'room_type_ids',
|
||||
'amenity_ids',
|
||||
string='Room Type Amenities',
|
||||
help='List of Amenities.')
|
||||
code_type = fields.Char('Code', required=True, )
|
||||
shared_room = fields.Boolean('Shared Room', default=False,
|
||||
help="This room type is reservation by beds")
|
||||
total_rooms_count = fields.Integer(compute='_compute_total_rooms', store=True)
|
||||
total_rooms_count = fields.Integer(
|
||||
compute='_compute_total_rooms', store=True)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
sequence = fields.Integer('Sequence', default=0)
|
||||
|
||||
_sql_constraints = [
|
||||
('code_type_hotel_unique', 'unique(code_type, hotel_id)', 'Room Type Code must be unique by Hotel!'),
|
||||
('code_type_hotel_unique', 'unique(code_type, hotel_id)',
|
||||
'Room Type Code must be unique by Hotel!'),
|
||||
]
|
||||
|
||||
# Constraints and onchanges
|
||||
@@ -80,7 +97,8 @@ class HotelRoomType(models.Model):
|
||||
def check_availability_room_type(self, dfrom, dto,
|
||||
room_type_id=False, notthis=[]):
|
||||
"""
|
||||
Check the max availability for an specific type of room in a range of dates
|
||||
Check the max availability for an specific
|
||||
type of room in a range of dates
|
||||
"""
|
||||
reservations = self.env['hotel.reservation'].get_reservations(dfrom,
|
||||
dto)
|
||||
@@ -118,10 +136,11 @@ class HotelRoomType(models.Model):
|
||||
raise ValidationError(_('Date From and days are mandatory'))
|
||||
partner_id = kwargs.get('partner_id', False)
|
||||
partner = self.env['res.partner'].browse(partner_id)
|
||||
pricelist_id = kwargs.get('pricelist_id',
|
||||
partner.property_product_pricelist.id and
|
||||
partner.property_product_pricelist.id or
|
||||
self.env.user.hotel_id.default_pricelist_id.id)
|
||||
pricelist_id = kwargs.get(
|
||||
'pricelist_id',
|
||||
partner.property_product_pricelist.id and
|
||||
partner.property_product_pricelist.id or
|
||||
self.env.user.hotel_id.default_pricelist_id.id)
|
||||
vals.update({
|
||||
'partner_id': partner_id if partner_id else False,
|
||||
'discount': discount,
|
||||
@@ -129,14 +148,16 @@ class HotelRoomType(models.Model):
|
||||
rate_vals = {}
|
||||
for room_type in room_types:
|
||||
vals.update({'room_type_id': room_type.id})
|
||||
room_vals = self.env['hotel.reservation'].prepare_reservation_lines(
|
||||
date_from,
|
||||
days,
|
||||
pricelist_id=pricelist_id,
|
||||
vals=vals,
|
||||
update_old_prices=False)
|
||||
room_vals = self.env['hotel.reservation'].\
|
||||
prepare_reservation_lines(
|
||||
date_from,
|
||||
days,
|
||||
pricelist_id=pricelist_id,
|
||||
vals=vals,
|
||||
update_old_prices=False)
|
||||
rate_vals.update({
|
||||
room_type.id: [item[2] for item in \
|
||||
room_vals['reservation_line_ids'] if item[2]]
|
||||
room_type.id: [item[2] for item in
|
||||
room_vals['reservation_line_ids'] if
|
||||
item[2]]
|
||||
})
|
||||
return rate_vals
|
||||
|
||||
@@ -14,6 +14,7 @@ class HotelRoomTypeClass(models.Model):
|
||||
_description = "Room Type Class"
|
||||
_order = "sequence, name, code_class"
|
||||
|
||||
# Fields declaration
|
||||
name = fields.Char('Class Name', required=True, translate=True)
|
||||
# Relationship between models
|
||||
hotel_ids = fields.Many2many(
|
||||
|
||||
@@ -15,11 +15,19 @@ class HotelRoomTypeRestriction(models.Model):
|
||||
|
||||
# Fields declaration
|
||||
name = fields.Char('Restriction Plan Name', required=True)
|
||||
hotel_id = fields.Many2one('hotel.property', 'Hotel', ondelete='restrict',
|
||||
default=_get_default_hotel)
|
||||
item_ids = fields.One2many('hotel.room.type.restriction.item',
|
||||
'restriction_id', string='Restriction Items',
|
||||
copy=True)
|
||||
active = fields.Boolean('Active', default=True,
|
||||
help='If unchecked, it will allow you to hide the '
|
||||
'restriction plan without removing it.')
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property',
|
||||
'Hotel',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
default=_get_default_hotel)
|
||||
item_ids = fields.One2many(
|
||||
'hotel.room.type.restriction.item',
|
||||
'restriction_id',
|
||||
string='Restriction Items',
|
||||
copy=True)
|
||||
active = fields.Boolean(
|
||||
'Active',
|
||||
default=True,
|
||||
help='If unchecked, it will allow you to hide the '
|
||||
'restriction plan without removing it.')
|
||||
|
||||
@@ -25,7 +25,8 @@ class HotelRoomTypeRestrictionItem(models.Model):
|
||||
|
||||
_sql_constraints = [('room_type_registry_unique',
|
||||
'unique(restriction_id, room_type_id, date)',
|
||||
'Only can exists one restriction in the same day for the same room type!')]
|
||||
'Only can exists one restriction in the same \
|
||||
day for the same room type!')]
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.multi
|
||||
|
||||
@@ -7,7 +7,6 @@ from odoo.tools import (
|
||||
float_compare,
|
||||
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__)
|
||||
@@ -17,38 +16,26 @@ 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)
|
||||
|
||||
# Default methods
|
||||
@api.multi
|
||||
def name_get(self):
|
||||
result = []
|
||||
for rec in self:
|
||||
name = []
|
||||
name.append('%(name)s' % {'name': rec.name})
|
||||
if rec.ser_room_line.name:
|
||||
name.append('%(name)s' % {'name': rec.ser_room_line.name})
|
||||
if rec.reservation_id.name:
|
||||
name.append('%(name)s' % {'name': rec.reservation_id.name})
|
||||
result.append((rec.id, ", ".join(name)))
|
||||
return result
|
||||
|
||||
@api.model
|
||||
def _default_ser_room_line(self):
|
||||
def _default_reservation_id(self):
|
||||
if self.env.context.get('reservation_ids'):
|
||||
ids = [item[1] for item in self.env.context['reservation_ids']]
|
||||
return self.env['hotel.reservation'].browse([
|
||||
(ids)], limit=1)
|
||||
elif self.env.context.get('default_ser_room_line'):
|
||||
return self.env.context.get('default_ser_room_line')
|
||||
elif self.env.context.get('default_reservation_id'):
|
||||
return self.env.context.get('default_reservation_id')
|
||||
return False
|
||||
|
||||
@api.model
|
||||
@@ -57,253 +44,247 @@ class HotelService(models.Model):
|
||||
return self._context['folio_id']
|
||||
return False
|
||||
|
||||
# Fields declaration
|
||||
name = fields.Char('Service description', required=True)
|
||||
product_id = fields.Many2one(
|
||||
'product.product',
|
||||
'Service',
|
||||
ondelete='restrict',
|
||||
required=True)
|
||||
folio_id = fields.Many2one(
|
||||
'hotel.folio',
|
||||
'Folio',
|
||||
ondelete='cascade',
|
||||
default=_default_folio_id)
|
||||
reservation_id = fields.Many2one(
|
||||
'hotel.reservation',
|
||||
'Room',
|
||||
default=_default_reservation_id)
|
||||
service_line_ids = fields.One2many(
|
||||
'hotel.service.line',
|
||||
'service_id')
|
||||
company_id = fields.Many2one(
|
||||
related='folio_id.company_id',
|
||||
string='Company',
|
||||
store=True,
|
||||
readonly=True)
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property',
|
||||
store=True,
|
||||
readonly=True,
|
||||
related='folio_id.hotel_id')
|
||||
tax_ids = fields.Many2many(
|
||||
'account.tax',
|
||||
string='Taxes',
|
||||
domain=['|', ('active', '=', False), ('active', '=', True)])
|
||||
invoice_line_ids = fields.Many2many(
|
||||
'account.invoice.line',
|
||||
'service_line_invoice_rel',
|
||||
'service_id',
|
||||
'invoice_line_id',
|
||||
string='Invoice Lines',
|
||||
copy=False)
|
||||
analytic_tag_ids = fields.Many2many(
|
||||
'account.analytic.tag',
|
||||
string='Analytic Tags')
|
||||
currency_id = fields.Many2one(
|
||||
related='folio_id.currency_id',
|
||||
store=True,
|
||||
string='Currency',
|
||||
readonly=True)
|
||||
sequence = fields.Integer(string='Sequence', default=10)
|
||||
state = fields.Selection(related='folio_id.state')
|
||||
per_day = fields.Boolean(related='product_id.per_day', related_sudo=True)
|
||||
product_qty = fields.Integer('Quantity', default=1)
|
||||
days_qty = fields.Integer(compute="_compute_days_qty", store=True)
|
||||
is_board_service = fields.Boolean()
|
||||
to_print = fields.Boolean('Print', help='Print in Folio Report')
|
||||
# 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, related_sudo=True)
|
||||
invoice_status = fields.Selection([
|
||||
('invoiced', 'Fully Invoiced'),
|
||||
('to invoice', 'To Invoice'),
|
||||
('no', 'Nothing to Invoice')],
|
||||
string='Invoice Status',
|
||||
compute='_compute_invoice_status',
|
||||
store=True,
|
||||
readonly=True,
|
||||
default='no')
|
||||
channel_type = fields.Selection([
|
||||
('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone'),
|
||||
('call', 'Call Center'),
|
||||
('web', 'Web')],
|
||||
string='Sales Channel')
|
||||
price_unit = fields.Float(
|
||||
'Unit Price',
|
||||
required=True,
|
||||
digits=dp.get_precision('Product Price'), default=0.0)
|
||||
discount = fields.Float(
|
||||
string='Discount (%)',
|
||||
digits=dp.get_precision('Discount'), default=0.0)
|
||||
qty_to_invoice = fields.Float(
|
||||
compute='_get_to_invoice_qty',
|
||||
string='To Invoice',
|
||||
store=True,
|
||||
readonly=True,
|
||||
digits=dp.get_precision('Product Unit of Measure'))
|
||||
qty_invoiced = fields.Float(
|
||||
compute='_get_invoice_qty',
|
||||
string='Invoiced',
|
||||
store=True,
|
||||
readonly=True,
|
||||
digits=dp.get_precision('Product Unit of Measure'))
|
||||
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')
|
||||
|
||||
# Compute and Search methods
|
||||
@api.depends('qty_invoiced', 'product_qty', 'folio_id.state')
|
||||
def _get_to_invoice_qty(self):
|
||||
"""
|
||||
Compute the quantity to invoice. If the invoice policy is order, the quantity to invoice is
|
||||
calculated from the ordered quantity. Otherwise, the quantity delivered is used.
|
||||
Compute the quantity to invoice. If the invoice policy is order,
|
||||
the quantity to invoice is calculated from the ordered quantity.
|
||||
Otherwise, the quantity delivered is used.
|
||||
"""
|
||||
for line in self:
|
||||
if line.folio_id.state not in ['draft']:
|
||||
line.qty_to_invoice = line.product_qty - line.qty_invoiced
|
||||
line.qty_to_invoice = line.product_qty - line.qty_invoiced
|
||||
else:
|
||||
line.qty_to_invoice = 0
|
||||
|
||||
@api.depends('invoice_line_ids.invoice_id.state', 'invoice_line_ids.quantity')
|
||||
@api.depends('invoice_line_ids.invoice_id.state',
|
||||
'invoice_line_ids.quantity')
|
||||
def _get_invoice_qty(self):
|
||||
"""
|
||||
Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. Note
|
||||
that this is the case only if the refund is generated from the SO and that is intentional: if
|
||||
a refund made would automatically decrease the invoiced quantity, then there is a risk of reinvoicing
|
||||
it automatically, which may not be wanted at all. That's why the refund has to be created from the SO
|
||||
Compute the quantity invoiced. If case of a refund,
|
||||
the quantity invoiced is decreased. Note that this is the case only
|
||||
if the refund is generated from the Folio and that is intentional: if
|
||||
a refund made would automatically decrease the invoiced quantity,
|
||||
then there is a risk of reinvoicing it automatically, which may
|
||||
not be wanted at all. That's why the refund has to be
|
||||
created from the Folio
|
||||
"""
|
||||
for line in self:
|
||||
qty_invoiced = 0.0
|
||||
for invoice_line in line.invoice_line_ids:
|
||||
if invoice_line.invoice_id.state != 'cancel':
|
||||
if invoice_line.invoice_id.type == 'out_invoice':
|
||||
qty_invoiced += invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_id.uom_id)
|
||||
qty_invoiced += invoice_line.uom_id._compute_quantity(
|
||||
invoice_line.quantity, line.product_id.uom_id)
|
||||
elif invoice_line.invoice_id.type == 'out_refund':
|
||||
qty_invoiced -= invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_id.uom_id)
|
||||
qty_invoiced -= invoice_line.uom_id._compute_quantity(
|
||||
invoice_line.quantity, line.product_id.uom_id)
|
||||
line.qty_invoiced = qty_invoiced
|
||||
|
||||
@api.depends('product_qty', 'qty_to_invoice', 'qty_invoiced')
|
||||
def _compute_invoice_status(self):
|
||||
"""
|
||||
Compute the invoice status of a SO line. Possible statuses:
|
||||
- no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
|
||||
invoice. This is also hte default value if the conditions of no other status is met.
|
||||
- to invoice: we refer to the quantity to invoice of the line. Refer to method
|
||||
`_get_to_invoice_qty()` for more information on how this quantity is calculated.
|
||||
- upselling: this is possible only for a product invoiced on ordered quantities for which
|
||||
we delivered more than expected. The could arise if, for example, a project took more
|
||||
time than expected but we decided not to invoice the extra cost to the client. This
|
||||
occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity
|
||||
is removed from the list.
|
||||
- invoiced: the quantity invoiced is larger or equal to the quantity ordered.
|
||||
- no: if the SO is not in status 'sale' or 'done',
|
||||
we consider that there is nothing to invoice.
|
||||
This is also hte default value if the conditions of no other
|
||||
status is met.
|
||||
- to invoice: we refer to the quantity to invoice of the line.
|
||||
Refer to method `_get_to_invoice_qty()` for more information on
|
||||
how this quantity is calculated.
|
||||
- upselling: this is possible only for a product invoiced on ordered
|
||||
quantities for which we delivered more than expected.
|
||||
The could arise if, for example, a project took more time than
|
||||
expected but we decided not to invoice the extra cost to the
|
||||
client. This occurs onyl in state 'sale', so that when a Folio
|
||||
is set to done, the upselling opportunity is removed from the list.
|
||||
- invoiced: the quantity invoiced is larger or equal to the
|
||||
quantity ordered.
|
||||
"""
|
||||
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
|
||||
precision = self.env['decimal.precision'].precision_get(
|
||||
'Product Unit of Measure')
|
||||
for line in self:
|
||||
if line.folio_id.state in ('draft'):
|
||||
line.invoice_status = 'no'
|
||||
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
|
||||
elif not float_is_zero(line.qty_to_invoice,
|
||||
precision_digits=precision):
|
||||
line.invoice_status = 'to invoice'
|
||||
elif float_compare(line.qty_invoiced, line.product_qty, precision_digits=precision) >= 0:
|
||||
elif float_compare(line.qty_invoiced, line.product_qty,
|
||||
precision_digits=precision) >= 0:
|
||||
line.invoice_status = 'invoiced'
|
||||
else:
|
||||
line.invoice_status = 'no'
|
||||
|
||||
name = fields.Char('Service description', required=True)
|
||||
sequence = fields.Integer(string='Sequence', default=10)
|
||||
product_id = fields.Many2one('product.product', 'Service',
|
||||
ondelete='restrict', required=True)
|
||||
folio_id = fields.Many2one('hotel.folio', 'Folio',
|
||||
ondelete='cascade',
|
||||
default=_default_folio_id)
|
||||
state = fields.Selection(related='folio_id.state')
|
||||
ser_room_line = fields.Many2one('hotel.reservation', 'Room',
|
||||
default=_default_ser_room_line)
|
||||
per_day = fields.Boolean(related='product_id.per_day', related_sudo=True)
|
||||
service_line_ids = fields.One2many('hotel.service.line', 'service_id')
|
||||
product_qty = fields.Integer('Quantity', default=1)
|
||||
days_qty = fields.Integer(compute="_compute_days_qty", store=True)
|
||||
is_board_service = fields.Boolean()
|
||||
to_print = fields.Boolean('Print', help='Print in Folio Report')
|
||||
# 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, related_sudo=True)
|
||||
company_id = fields.Many2one(related='folio_id.company_id', string='Company', store=True, readonly=True)
|
||||
hotel_id = fields.Many2one('hotel.property', store=True, readonly=True,
|
||||
related='folio_id.hotel_id')
|
||||
invoice_status = fields.Selection([
|
||||
('invoiced', 'Fully Invoiced'),
|
||||
('to invoice', 'To Invoice'),
|
||||
('no', 'Nothing to Invoice')
|
||||
], string='Invoice Status', compute='_compute_invoice_status',
|
||||
store=True, readonly=True, default='no')
|
||||
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)
|
||||
invoice_line_ids = fields.Many2many('account.invoice.line', 'service_line_invoice_rel', 'service_id', 'invoice_line_id', string='Invoice Lines', copy=False)
|
||||
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
|
||||
qty_to_invoice = fields.Float(
|
||||
compute='_get_to_invoice_qty', string='To Invoice', store=True, readonly=True,
|
||||
digits=dp.get_precision('Product Unit of Measure'))
|
||||
qty_invoiced = fields.Float(
|
||||
compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
|
||||
digits=dp.get_precision('Product Unit of Measure'))
|
||||
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'])
|
||||
if reservation.splitted:
|
||||
checkin = reservation.real_checkin
|
||||
checkout = reservation.real_checkout
|
||||
else:
|
||||
checkin = reservation.checkin
|
||||
checkout = reservation.checkout
|
||||
checkin_dt = fields.Date.from_string(checkin)
|
||||
checkout_dt = fields.Date.from_string(checkout)
|
||||
nights = abs((checkout_dt - checkin_dt).days)
|
||||
vals.update(self.prepare_service_ids(
|
||||
dfrom=checkin,
|
||||
days=nights,
|
||||
per_person=product.per_person,
|
||||
persons=reservation.adults,
|
||||
old_day_lines=False,
|
||||
consumed_on=product.consumed_on,
|
||||
))
|
||||
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
|
||||
if reservation.splitted:
|
||||
checkin = reservation.real_checkin
|
||||
checkout = reservation.real_checkout
|
||||
else:
|
||||
checkin = reservation.checkin
|
||||
checkout = reservation.checkout
|
||||
checkin_dt = fields.Date.from_string(checkin)
|
||||
checkout_dt = fields.Date.from_string(checkout)
|
||||
nights = abs((checkout_dt - checkin_dt).days)
|
||||
record.update(record.prepare_service_ids(
|
||||
dfrom=checkin,
|
||||
days=nights,
|
||||
per_person=product.per_person,
|
||||
persons=reservation.adults,
|
||||
old_line_days=self.service_line_ids,
|
||||
consumed_on=product.consumed_on,
|
||||
))
|
||||
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', 'name']
|
||||
if values.get('product_id'):
|
||||
line = self.new(values)
|
||||
if any(f not in values for f in onchange_fields):
|
||||
line.onchange_product_id()
|
||||
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):
|
||||
@api.depends('product_qty', 'discount', 'price_unit', 'tax_ids')
|
||||
def _compute_amount_service(self):
|
||||
"""
|
||||
Compute if It is necesary service days in write/create
|
||||
Compute the amounts of the service line.
|
||||
"""
|
||||
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['hotel.folio'].browse(self.env.context.get('default_folio_id'))
|
||||
reservation = record.ser_room_line or self.env.context.get('ser_room_line')
|
||||
origin = folio if folio else reservation
|
||||
record.tax_ids = record.product_id.taxes_id.filtered(lambda r: not record.company_id or r.company_id == origin.company_id)
|
||||
folio = record.folio_id or self.env['hotel.folio'].browse(
|
||||
self.env.context.get('default_folio_id'))
|
||||
reservation = record.reservation_id or self.env.context.get(
|
||||
'reservation_id')
|
||||
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)
|
||||
|
||||
@api.multi
|
||||
def _get_display_price(self, product):
|
||||
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')
|
||||
origin = folio if folio else reservation
|
||||
if origin.pricelist_id.discount_policy == 'with_discount':
|
||||
return product.with_context(pricelist=origin.pricelist_id.id).price
|
||||
product_context = dict(self.env.context, partner_id=origin.partner_id.id, date=folio.date_order if folio else fields.Date.today(), uom=self.product_id.uom_id.id)
|
||||
final_price, rule_id = origin.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_qty or 1.0, origin.partner_id)
|
||||
base_price, currency_id = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_qty, self.product_id.uom_id, origin.pricelist_id.id)
|
||||
if currency_id != origin.pricelist_id.currency_id.id:
|
||||
base_price = self.env['res.currency'].browse(currency_id).with_context(product_context).compute(base_price, origin.pricelist_id.currency_id)
|
||||
# negative discounts (= surcharge) are included in the display price
|
||||
return max(base_price, final_price)
|
||||
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)
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_id(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_ids qty
|
||||
configuration of the selected product, in per_day
|
||||
product configuration, the qty is autocalculated and
|
||||
readonly based on service_ids 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:
|
||||
if record.per_day and record.reservation_id:
|
||||
product = record.product_id
|
||||
if self.env.context.get('default_ser_room_line'):
|
||||
if self.env.context.get('default_reservation_id'):
|
||||
reservation = self.env['hotel.reservation'].browse(
|
||||
self.env.context.get('default_ser_room_line')
|
||||
self.env.context.get('default_reservation_id')
|
||||
)
|
||||
else:
|
||||
reservation = record.ser_room_line
|
||||
reservation = record.reservation_id
|
||||
if reservation.splitted:
|
||||
checkin = reservation.real_checkin
|
||||
checkout = reservation.real_checkout
|
||||
@@ -320,11 +301,8 @@ class HotelService(models.Model):
|
||||
persons=reservation.adults,
|
||||
old_line_days=record.service_line_ids,
|
||||
consumed_on=product.consumed_on,
|
||||
))
|
||||
))
|
||||
if record.product_id.daily_limit > 0:
|
||||
for i in range(0, nights):
|
||||
idate = (fields.Date.from_string(checkin) + timedelta(days=i)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
for day in record.service_line_ids:
|
||||
day.no_free_resources()
|
||||
"""
|
||||
@@ -358,11 +336,175 @@ class HotelService(models.Model):
|
||||
vals['price_unit'] = self._compute_price_unit()
|
||||
record.update(vals)
|
||||
|
||||
# Action methods
|
||||
@api.multi
|
||||
def open_service_ids(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
|
||||
|
||||
# ORM Overrides
|
||||
@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 += [
|
||||
'|',
|
||||
('reservation_id.name', operator, name),
|
||||
('name', operator, name)
|
||||
]
|
||||
return super(HotelService, self).name_search(
|
||||
name='', args=args, operator='ilike', limit=limit)
|
||||
|
||||
@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['reservation_id'])
|
||||
product = self.env['product.product'].browse(vals['product_id'])
|
||||
if reservation.splitted:
|
||||
checkin = reservation.real_checkin
|
||||
checkout = reservation.real_checkout
|
||||
else:
|
||||
checkin = reservation.checkin
|
||||
checkout = reservation.checkout
|
||||
checkin_dt = fields.Date.from_string(checkin)
|
||||
checkout_dt = fields.Date.from_string(checkout)
|
||||
nights = abs((checkout_dt - checkin_dt).days)
|
||||
vals.update(self.prepare_service_ids(
|
||||
dfrom=checkin,
|
||||
days=nights,
|
||||
per_person=product.per_person,
|
||||
persons=reservation.adults,
|
||||
old_day_lines=False,
|
||||
consumed_on=product.consumed_on,
|
||||
))
|
||||
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['reservation_id']) \
|
||||
if 'reservation_id' in vals else record.reservation_id
|
||||
if reservation.splitted:
|
||||
checkin = reservation.real_checkin
|
||||
checkout = reservation.real_checkout
|
||||
else:
|
||||
checkin = reservation.checkin
|
||||
checkout = reservation.checkout
|
||||
checkin_dt = fields.Date.from_string(checkin)
|
||||
checkout_dt = fields.Date.from_string(checkout)
|
||||
nights = abs((checkout_dt - checkin_dt).days)
|
||||
record.update(record.prepare_service_ids(
|
||||
dfrom=checkin,
|
||||
days=nights,
|
||||
per_person=product.per_person,
|
||||
persons=reservation.adults,
|
||||
old_line_days=self.service_line_ids,
|
||||
consumed_on=product.consumed_on,
|
||||
))
|
||||
res = super(HotelService, self).write(vals)
|
||||
return res
|
||||
|
||||
# Business methods
|
||||
@api.model
|
||||
def _prepare_add_missing_fields(self, values):
|
||||
""" Deduce missing required fields from the onchange """
|
||||
res = {}
|
||||
onchange_fields = ['price_unit', 'tax_ids', 'name']
|
||||
if values.get('product_id'):
|
||||
line = self.new(values)
|
||||
if any(f not in values for f in onchange_fields):
|
||||
line.onchange_product_id()
|
||||
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['hotel.folio'].browse(
|
||||
self.env.context.get('default_folio_id'))
|
||||
reservation = record.reservation_id or self.env.context.get(
|
||||
'reservation_id')
|
||||
origin = folio if folio else reservation
|
||||
record.tax_ids = record.product_id.taxes_id.filtered(
|
||||
lambda r: not record.company_id or
|
||||
r.company_id == origin.company_id)
|
||||
|
||||
@api.multi
|
||||
def _get_display_price(self, product):
|
||||
folio = self.folio_id or self.env.context.get('default_folio_id')
|
||||
reservation = self.reservation_id or self.env.context.get(
|
||||
'reservation_id')
|
||||
origin = folio if folio else reservation
|
||||
if origin.pricelist_id.discount_policy == 'with_discount':
|
||||
return product.with_context(pricelist=origin.pricelist_id.id).price
|
||||
product_context = dict(
|
||||
self.env.context,
|
||||
partner_id=origin.partner_id.id,
|
||||
date=folio.date_order if folio else fields.Date.today(),
|
||||
uom=self.product_id.uom_id.id)
|
||||
final_price, rule_id = origin.pricelist_id.with_context(
|
||||
product_context).get_product_price_rule(
|
||||
self.product_id,
|
||||
self.product_qty or 1.0,
|
||||
origin.partner_id)
|
||||
base_price, currency_id = self.with_context(
|
||||
product_context)._get_real_price_currency(
|
||||
product,
|
||||
rule_id,
|
||||
self.product_qty,
|
||||
self.product_id.uom_id,
|
||||
origin.pricelist_id.id)
|
||||
if currency_id != origin.pricelist_id.currency_id.id:
|
||||
base_price = self.env['res.currency'].browse(
|
||||
currency_id).with_context(product_context).compute(
|
||||
base_price,
|
||||
origin.pricelist_id.currency_id)
|
||||
# negative discounts (= surcharge) are included in the display price
|
||||
return max(base_price, final_price)
|
||||
|
||||
@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')
|
||||
reservation = self.reservation_id or self.env.context.get(
|
||||
'reservation_id')
|
||||
origin = reservation if reservation else folio
|
||||
if origin:
|
||||
partner = origin.partner_id
|
||||
@@ -370,23 +512,29 @@ class HotelService(models.Model):
|
||||
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
|
||||
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
|
||||
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 if folio else fields.Date.today(),
|
||||
pricelist=pricelist.id,
|
||||
uom=self.product_id.uom_id.id,
|
||||
fiscal_position=False
|
||||
)
|
||||
lang=partner.lang,
|
||||
partner=partner.id,
|
||||
quantity=self.product_qty,
|
||||
date=folio.date_order if folio else 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,
|
||||
@@ -399,16 +547,19 @@ class HotelService(models.Model):
|
||||
"""
|
||||
cmds = [(5, 0, 0)]
|
||||
old_line_days = kwargs.get('old_line_days')
|
||||
consumed_on = kwargs.get('consumed_on') if kwargs.get('consumed_on') else 'before'
|
||||
consumed_on = kwargs.get('consumed_on') if kwargs.get(
|
||||
'consumed_on') else 'before'
|
||||
total_qty = 0
|
||||
day_qty = 1
|
||||
if kwargs.get('per_person'): #WARNING: Change adults in reservation NOT update qty service!!
|
||||
# WARNING: Change adults in reservation NOT update qty service!!
|
||||
if kwargs.get('per_person'):
|
||||
day_qty = kwargs.get('persons')
|
||||
for i in range(0, kwargs.get('days')):
|
||||
if consumed_on == 'after':
|
||||
i += 1
|
||||
idate = (fields.Date.from_string(kwargs.get('dfrom')) + timedelta(days=i)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
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,
|
||||
@@ -420,52 +571,3 @@ class HotelService(models.Model):
|
||||
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['hotel.folio'].browse(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_ids(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_ids = self.env['hotel.service_line']
|
||||
#~ total_day_qty = sum(service_ids.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'))
|
||||
|
||||
@@ -9,43 +9,51 @@ class HotelServiceLine(models.Model):
|
||||
_name = "hotel.service.line"
|
||||
_order = "date"
|
||||
|
||||
service_id = fields.Many2one('hotel.service', string='Service Room',
|
||||
ondelete='cascade', required=True,
|
||||
copy=False)
|
||||
# Fields declaration
|
||||
service_id = fields.Many2one(
|
||||
'hotel.service',
|
||||
string='Service Room',
|
||||
ondelete='cascade',
|
||||
required=True,
|
||||
copy=False)
|
||||
product_id = fields.Many2one(
|
||||
related='service_id.product_id',
|
||||
store=True)
|
||||
tax_ids = fields.Many2many(
|
||||
'account.tax',
|
||||
string='Taxes',
|
||||
related="service_id.tax_ids",
|
||||
readonly="True")
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property',
|
||||
store=True,
|
||||
readonly=True,
|
||||
related='service_id.hotel_id')
|
||||
date = fields.Date('Date')
|
||||
day_qty = fields.Integer('Units')
|
||||
product_id = fields.Many2one(related='service_id.product_id', store=True)
|
||||
price_total = fields.Float('Price Total',
|
||||
compute='_compute_price_total',
|
||||
store=True)
|
||||
price_unit = fields.Float('Unit Price',
|
||||
related="service_id.price_unit",
|
||||
readonly=True,
|
||||
store=True)
|
||||
room_id = fields.Many2one(strin='Room',
|
||||
related="service_id.ser_room_line",
|
||||
readonly=True,
|
||||
store=True)
|
||||
discount = fields.Float('Discount',
|
||||
related="service_id.discount",
|
||||
readonly=True,
|
||||
store=True)
|
||||
cancel_discount = fields.Float('Discount', compute='_compute_cancel_discount')
|
||||
tax_ids = fields.Many2many('account.tax',
|
||||
string='Taxes',
|
||||
related="service_id.tax_ids",
|
||||
readonly="True")
|
||||
hotel_id = fields.Many2one('hotel.property', store=True, readonly=True,
|
||||
related='service_id.hotel_id')
|
||||
|
||||
def _cancel_discount(self):
|
||||
for record in self:
|
||||
if record.reservation_id:
|
||||
day = record.reservation_id.reservation_line_ids.filtered(
|
||||
lambda d: d.date == record.date
|
||||
)
|
||||
record.cancel_discount = day.cancel_discount
|
||||
price_total = fields.Float(
|
||||
'Price Total',
|
||||
compute='_compute_price_total',
|
||||
store=True)
|
||||
price_unit = fields.Float(
|
||||
'Unit Price',
|
||||
related="service_id.price_unit",
|
||||
readonly=True,
|
||||
store=True)
|
||||
room_id = fields.Many2one(
|
||||
string='Room',
|
||||
related="service_id.reservation_id",
|
||||
readonly=True,
|
||||
store=True)
|
||||
discount = fields.Float(
|
||||
'Discount',
|
||||
related="service_id.discount",
|
||||
readonly=True,
|
||||
store=True)
|
||||
cancel_discount = fields.Float(
|
||||
'Discount', compute='_compute_cancel_discount')
|
||||
|
||||
# Compute and Search methods
|
||||
@api.depends('day_qty', 'service_id.price_total')
|
||||
def _compute_price_total(self):
|
||||
"""
|
||||
@@ -53,10 +61,13 @@ class HotelServiceLine(models.Model):
|
||||
"""
|
||||
for record in self:
|
||||
if record.service_id.product_qty != 0:
|
||||
record.price_total = (record.service_id.price_total * record.day_qty) / record.service_id.product_qty
|
||||
record.price_total = (
|
||||
record.service_id.price_total * record.day_qty) \
|
||||
/ record.service_id.product_qty
|
||||
else:
|
||||
record.price_total = 0
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.constrains('day_qty')
|
||||
def no_free_resources(self):
|
||||
for record in self:
|
||||
@@ -69,5 +80,14 @@ class HotelServiceLine(models.Model):
|
||||
]).mapped('day_qty'))
|
||||
if limit < out_qty + record.day_qty:
|
||||
raise ValidationError(
|
||||
_("%s limit exceeded for %s")% (record.service_id.product_id.name,
|
||||
record.date))
|
||||
_("%s limit exceeded for %s") %
|
||||
(record.service_id.product_id.name, record.date))
|
||||
|
||||
# Business methods
|
||||
def _cancel_discount(self):
|
||||
for record in self:
|
||||
if record.reservation_id:
|
||||
day = record.reservation_id.reservation_line_ids.filtered(
|
||||
lambda d: d.date == record.date
|
||||
)
|
||||
record.cancel_discount = day.cancel_discount
|
||||
|
||||
@@ -11,30 +11,41 @@ class HotelSharedRoom(models.Model):
|
||||
_description = 'Hotel Shared Room'
|
||||
_order = "room_type_id, name"
|
||||
|
||||
# Fields declaration
|
||||
name = fields.Char('Room Name', required=True)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
room_type_id = fields.Many2one(
|
||||
'hotel.room.type', 'Hotel Room Type',
|
||||
required=True, ondelete='restrict',
|
||||
'hotel.room.type',
|
||||
'Hotel Room Type',
|
||||
required=True,
|
||||
ondelete='restrict',
|
||||
domain=[('shared_room', '=', True)]
|
||||
)
|
||||
hotel_id = fields.Many2one('hotel.property', store=True, readonly=True,
|
||||
related='room_type_id.hotel_id')
|
||||
floor_id = fields.Many2one('hotel.floor', 'Ubication',
|
||||
help='At which floor the room is located.',
|
||||
ondelete='restrict',)
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property',
|
||||
store=True,
|
||||
readonly=True,
|
||||
related='room_type_id.hotel_id')
|
||||
floor_id = fields.Many2one(
|
||||
'hotel.floor',
|
||||
'Ubication',
|
||||
ondelete='restrict',
|
||||
help='At which floor the room is located.')
|
||||
bed_ids = fields.One2many(
|
||||
'hotel.room',
|
||||
'shared_room_id',
|
||||
readonly=True,
|
||||
ondelete='restrict',)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
sequence = fields.Integer('Sequence', required=True)
|
||||
beds = fields.Integer('Beds')
|
||||
bed_ids = fields.One2many('hotel.room',
|
||||
'shared_room_id',
|
||||
readonly=True,
|
||||
ondelete='restrict',)
|
||||
description_sale = fields.Text(
|
||||
'Sale Description', translate=True,
|
||||
'Sale Description',
|
||||
translate=True,
|
||||
help="A description of the Product that you want to communicate to "
|
||||
" your customers. This description will be copied to every Sales "
|
||||
" Order, Delivery Order and Customer Invoice/Credit Note")
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.constrains('beds')
|
||||
def _constrain_beds(self):
|
||||
self.ensure_one()
|
||||
|
||||
@@ -10,12 +10,13 @@ class AccountInvoice(models.Model):
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
# Field Declarations
|
||||
from_folio = fields.Boolean(
|
||||
compute='_computed_folio_origin')
|
||||
folio_ids = fields.Many2many(
|
||||
comodel_name='hotel.folio',
|
||||
compute='_computed_folio_origin')
|
||||
hotel_id = fields.Many2one('hotel.property')
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property')
|
||||
from_folio = fields.Boolean(
|
||||
compute='_computed_folio_origin')
|
||||
outstanding_folios_debits_widget = fields.Text(
|
||||
compute='_get_outstanding_folios_JSON')
|
||||
has_folios_outstanding = fields.Boolean(
|
||||
@@ -93,7 +94,8 @@ class AccountInvoice(models.Model):
|
||||
else:
|
||||
amount_to_show = line.company_id.currency_id.\
|
||||
with_context(date=line.date).\
|
||||
compute(abs(line.amount_residual), self.currency_id)
|
||||
compute(abs(line.amount_residual),
|
||||
self.currency_id)
|
||||
if float_is_zero(
|
||||
amount_to_show,
|
||||
precision_rounding=self.currency_id.rounding
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AccountInvoiceLine(models.Model):
|
||||
_inherit = 'account.invoice.line'
|
||||
|
||||
# Fields declaration
|
||||
reservation_ids = fields.Many2many(
|
||||
'hotel.reservation',
|
||||
'reservation_invoice_rel',
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
from odoo.exceptions import except_orm
|
||||
from odoo import models, fields, api, _
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
_inherit = 'account.payment'
|
||||
|
||||
folio_id = fields.Many2one('hotel.folio', string='Folio')
|
||||
# Fields declaration
|
||||
folio_id = fields.Many2one(
|
||||
'hotel.folio',
|
||||
string='Folio')
|
||||
amount_total_folio = fields.Float(
|
||||
compute="_compute_folio_amount", store=True,
|
||||
string="Total amount in folio",
|
||||
@@ -15,13 +19,38 @@ class AccountPayment(models.Model):
|
||||
save_date = fields.Date()
|
||||
save_journal_id = fields.Integer()
|
||||
|
||||
@api.onchange('amount','payment_date','journal_id')
|
||||
# Compute and Search methods
|
||||
@api.multi
|
||||
@api.depends('state')
|
||||
def _compute_folio_amount(self):
|
||||
# FIXME: Finalize method
|
||||
res = []
|
||||
fol = ()
|
||||
for payment in self:
|
||||
if payment.folio_id:
|
||||
fol = payment.env['hotel.folio'].search([
|
||||
('id', '=', payment.folio_id.id)
|
||||
])
|
||||
else:
|
||||
return
|
||||
if not any(fol):
|
||||
return
|
||||
if len(fol) > 1:
|
||||
raise except_orm(_('Warning'), _('This pay is related with \
|
||||
more than one Reservation.'))
|
||||
else:
|
||||
fol.compute_amount()
|
||||
return res
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.onchange('amount', 'payment_date', 'journal_id')
|
||||
def onchange_amount(self):
|
||||
if self._origin:
|
||||
self.save_amount = self._origin.amount
|
||||
self.save_journal_id = self._origin.journal_id.id
|
||||
self.save_date = self._origin.payment_date
|
||||
|
||||
# Action methods
|
||||
"""WIP"""
|
||||
@api.multi
|
||||
def return_payment_folio(self):
|
||||
@@ -47,9 +76,11 @@ class AccountPayment(models.Model):
|
||||
if self.save_date:
|
||||
self.payment_date = self.save_date
|
||||
if self.save_journal_id:
|
||||
self.journal_id = self.env['account.journal'].browse(self.save_journal_id)
|
||||
self.journal_id = self.env['account.journal'].browse(
|
||||
self.save_journal_id)
|
||||
return_pay.action_confirm()
|
||||
|
||||
# Business methods
|
||||
@api.multi
|
||||
def modify(self):
|
||||
self.cancel()
|
||||
@@ -66,53 +97,40 @@ class AccountPayment(models.Model):
|
||||
if self.folio_id:
|
||||
msg = _("Payment %s modified: \n") % (self.communication)
|
||||
if self.save_amount and self.save_amount != self.amount:
|
||||
msg += _("Amount from %s to %s %s \n") % (self.save_amount, self.amount, self.currency_id.symbol)
|
||||
msg += _("Amount from %s to %s %s \n") % (
|
||||
self.save_amount, self.amount, self.currency_id.symbol)
|
||||
if self.save_date and self.save_date != self.payment_date:
|
||||
msg += _("Date from %s to %s \n") % (self.save_date, self.payment_date)
|
||||
if self.save_journal_id and self.save_journal_id != self.journal_id.id:
|
||||
msg += _("Journal from %s to %s") % (self.env['account.journal'].browse(self.save_journal_id).name, self.journal_id.name)
|
||||
msg += _("Date from %s to %s \n") % (
|
||||
self.save_date, self.payment_date)
|
||||
if self.save_journal_id and \
|
||||
self.save_journal_id != self.journal_id.id:
|
||||
msg += _("Journal from %s to %s") % (
|
||||
self.env['account.journal'].browse(
|
||||
self.save_journal_id).name, self.journal_id.name)
|
||||
self.folio_id.message_post(subject=_('Payment'), body=msg)
|
||||
|
||||
@api.multi
|
||||
def delete(self):
|
||||
msg = False
|
||||
if self.folio_id:
|
||||
msg = _("Deleted payment: %s %s ") % (self.amount, self.currency_id.symbol)
|
||||
msg = _("Deleted payment: %s %s ") % (
|
||||
self.amount, self.currency_id.symbol)
|
||||
self.cancel()
|
||||
self.move_name = ''
|
||||
self.unlink()
|
||||
if msg:
|
||||
self.folio_id.message_post(subject=_('Payment Deleted'), body=msg)
|
||||
|
||||
@api.multi
|
||||
@api.depends('state')
|
||||
def _compute_folio_amount(self):
|
||||
# FIXME: Finalize method
|
||||
res = []
|
||||
fol = ()
|
||||
for payment in self:
|
||||
if payment.folio_id:
|
||||
fol = payment.env['hotel.folio'].search([
|
||||
('id', '=', payment.folio_id.id)
|
||||
])
|
||||
else:
|
||||
return
|
||||
if not any(fol):
|
||||
return
|
||||
if len(fol) > 1:
|
||||
raise except_orm(_('Warning'), _('This pay is related with \
|
||||
more than one Reservation.'))
|
||||
else:
|
||||
fol.compute_amount()
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def post(self):
|
||||
rec = super(AccountPayment, self).post()
|
||||
if rec and not self._context.get("ignore_notification_post", False):
|
||||
for pay in self:
|
||||
if pay.folio_id:
|
||||
msg = _("Payment of %s %s registered from %s using %s payment method") % (pay.amount, pay.currency_id.symbol, pay.communication, pay.journal_id.name)
|
||||
msg = _("Payment of %s %s registered from %s \
|
||||
using %s payment method") % \
|
||||
(pay.amount, pay.currency_id.symbol,
|
||||
pay.communication, pay.journal_id.name)
|
||||
pay.folio_id.message_post(subject=_('Payment'), body=msg)
|
||||
|
||||
@api.multi
|
||||
|
||||
@@ -14,11 +14,16 @@ class IrHttp(models.AbstractModel):
|
||||
res = super().session_info()
|
||||
user = request.env.user
|
||||
display_switch_hotel_menu = len(user.hotel_ids) > 1
|
||||
# TODO: limit hotels to the current company? or switch company automatically
|
||||
res['hotel_id'] = request.env.user.hotel_id.id if request.session.uid else None
|
||||
res['user_hotels'] = {'current_hotel': (user.hotel_id.id, user.hotel_id.name),
|
||||
'allowed_hotels': [(hotel.id, hotel.name) for hotel in
|
||||
user.hotel_ids]} if display_switch_hotel_menu else False
|
||||
# TODO: limit hotels to the current company?
|
||||
# or switch company automatically
|
||||
res['hotel_id'] = request.env.user.hotel_id.id if \
|
||||
request.session.uid else None
|
||||
res['user_hotels'] = {
|
||||
'current_hotel': (user.hotel_id.id, user.hotel_id.name),
|
||||
'allowed_hotels': [
|
||||
(hotel.id, hotel.name) for hotel in user.hotel_ids
|
||||
]
|
||||
} if display_switch_hotel_menu else False
|
||||
if user.hotel_id.company_id in user.company_ids:
|
||||
user.company_id = user.hotel_id.company_id
|
||||
res['company_id'] = user.hotel_id.company_id.id
|
||||
|
||||
@@ -10,12 +10,15 @@ 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'):
|
||||
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 = [(1, lid, {'to_send': False}) for lid in folio.reservation_ids.ids]
|
||||
cmds = [(1, lid, {'to_send': False}) for lid in
|
||||
folio.reservation_ids.ids]
|
||||
if any(cmds):
|
||||
folio.reservation_ids = cmds
|
||||
return super(MailComposeMessage, self).send_mail(auto_commit=auto_commit)
|
||||
return super(MailComposeMessage, self).send_mail(
|
||||
auto_commit=auto_commit)
|
||||
|
||||
@@ -6,10 +6,17 @@ from openerp import models, fields, api, _
|
||||
class PaymentReturn(models.Model):
|
||||
_inherit = 'payment.return'
|
||||
|
||||
folio_id = fields.Many2one('hotel.folio', string='Folio')
|
||||
hotel_id = fields.Many2one('hotel.property', store=True, readonly=True,
|
||||
related='folio_id.hotel_id')
|
||||
# Fields declaration
|
||||
folio_id = fields.Many2one(
|
||||
'hotel.folio',
|
||||
string='Folio')
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property',
|
||||
store=True,
|
||||
readonly=True,
|
||||
related='folio_id.hotel_id')
|
||||
|
||||
# Business methods
|
||||
@api.multi
|
||||
def action_confirm(self):
|
||||
pay = super(PaymentReturn, self).action_confirm()
|
||||
@@ -20,7 +27,8 @@ class PaymentReturn(models.Model):
|
||||
payments = self.env['account.payment'].search([
|
||||
('move_line_ids', 'in', line.move_line_ids.ids)
|
||||
])
|
||||
folios_line = self.env['hotel.folio'].browse(payments.mapped('folio_id.id'))
|
||||
folios_line = self.env['hotel.folio'].browse(
|
||||
payments.mapped('folio_id.id'))
|
||||
for folio in folios_line:
|
||||
if self.id not in folio.return_ids.ids:
|
||||
folio.update({'return_ids': [(4, self.id)]})
|
||||
|
||||
@@ -12,13 +12,18 @@ class ProductPricelist(models.Model):
|
||||
_inherit = 'product.pricelist'
|
||||
|
||||
# Fields declaration
|
||||
hotel_ids = fields.Many2many('hotel.property', string='Hotels', required=False,
|
||||
ondelete='restrict')
|
||||
cancelation_rule_id = fields.Many2one('hotel.cancelation.rule',string="Cancelation Policy")
|
||||
|
||||
hotel_ids = fields.Many2many(
|
||||
'hotel.property',
|
||||
string='Hotels',
|
||||
required=False,
|
||||
ondelete='restrict')
|
||||
cancelation_rule_id = fields.Many2one(
|
||||
'hotel.cancelation.rule',
|
||||
string="Cancelation Policy")
|
||||
pricelist_type = fields.Selection([
|
||||
('daily', 'Daily Plan'),
|
||||
], string='Pricelist Type', default='daily')
|
||||
('daily', 'Daily Plan')],
|
||||
string='Pricelist Type',
|
||||
default='daily')
|
||||
is_staff = fields.Boolean('Is Staff')
|
||||
|
||||
# Constraints and onchanges
|
||||
@@ -26,13 +31,17 @@ class ProductPricelist(models.Model):
|
||||
def _check_pricelist_type_hotel_ids(self):
|
||||
for record in self:
|
||||
if record.pricelist_type == 'daily' and len(record.hotel_ids) != 1:
|
||||
raise ValidationError(_("A daily pricelist is used as a daily Rate Plan for room types "
|
||||
"and therefore must be related with one and only one hotel."))
|
||||
raise ValidationError(
|
||||
_("A daily pricelist is used as a daily Rate Plan "
|
||||
"for room types and therefore must be related with "
|
||||
"one and only one hotel."))
|
||||
|
||||
if record.pricelist_type == 'daily' and len(record.hotel_ids) == 1:
|
||||
hotel_id = self.env['hotel.property'].search([
|
||||
('default_pricelist_id', '=', record.id)
|
||||
]) or None
|
||||
if hotel_id and hotel_id != record.hotel_ids:
|
||||
raise ValidationError(_("Relationship mismatch.") + " " +
|
||||
_("This pricelist is used as default in a different hotel."))
|
||||
raise ValidationError(
|
||||
_("Relationship mismatch.") + " " +
|
||||
_("This pricelist is used as default in a "
|
||||
"different hotel."))
|
||||
|
||||
@@ -7,6 +7,11 @@ from odoo import models, fields
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
hotel_ids = fields.Many2many(
|
||||
'hotel.property',
|
||||
string='Hotels',
|
||||
required=False,
|
||||
ondelete='restrict')
|
||||
per_day = fields.Boolean('Unit increment per day')
|
||||
per_person = fields.Boolean('Unit increment per person')
|
||||
consumed_on = fields.Selection([
|
||||
@@ -14,7 +19,7 @@ class ProductTemplate(models.Model):
|
||||
('after', 'After night')], 'Consumed', default='before')
|
||||
daily_limit = fields.Integer('Daily limit')
|
||||
is_extra_bed = fields.Boolean('Is extra bed', default=False)
|
||||
show_in_calendar = fields.Boolean('Show in Calendar', default=False,
|
||||
show_in_calendar = fields.Boolean(
|
||||
'Show in Calendar',
|
||||
default=False,
|
||||
help='Specifies if the product is shown in the calendar information.')
|
||||
hotel_ids = fields.Many2many('hotel.property', string='Hotels', required=False,
|
||||
ondelete='restrict')
|
||||
|
||||
@@ -7,20 +7,20 @@ from odoo import models, fields
|
||||
class ResCompany(models.Model):
|
||||
_inherit = 'res.company'
|
||||
|
||||
# Fields declaration
|
||||
hotel_ids = fields.One2many('hotel.property', 'company_id', 'Hotels')
|
||||
|
||||
# TODO: need extra explanation or remove otherwise
|
||||
# additional_hours = fields.Integer('Additional Hours',
|
||||
# help="Provide the min hours value for \
|
||||
# check in, checkout days, whatever \
|
||||
# the hours will be provided here based \
|
||||
# on that extra days will be \
|
||||
# calculated.")
|
||||
# check in, checkout days, whatever \
|
||||
# the hours will be provided here based \
|
||||
# on that extra days will be \
|
||||
# calculated.")
|
||||
# TODO: move the text to the default template for confirmed reservations
|
||||
# cardex_warning = fields.Text(
|
||||
# 'Warning in Cardex',
|
||||
# default="Time to access rooms: 14: 00h. Departure time: \
|
||||
# 12: 00h. If the accommodation is not left at that time, \
|
||||
# the establishment will charge a day's stay according to \
|
||||
# current rate that day",
|
||||
# 12: 00h. If the accommodation is not left at that time, \
|
||||
# the establishment will charge a day's stay according to \
|
||||
# current rate that day",
|
||||
# help="Notice under the signature on the traveler's ticket.")
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from odoo import api, fields, models
|
||||
from odoo.osv.expression import get_unaccent_wrapper
|
||||
from odoo.exceptions import ValidationError
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -11,6 +9,18 @@ _logger = logging.getLogger(__name__)
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
# Fields declaration
|
||||
main_partner_id = fields.Many2one(
|
||||
'res.partner',
|
||||
string='Destination Partner fusion')
|
||||
reservations_count = fields.Integer(
|
||||
'Reservations', compute='_compute_reservations_count')
|
||||
folios_count = fields.Integer(
|
||||
'Folios', compute='_compute_folios_count')
|
||||
unconfirmed = fields.Boolean('Unconfirmed', default=True)
|
||||
is_tour_operator = fields.Boolean('Is Tour Operator')
|
||||
|
||||
# Compute and Search methods
|
||||
def _compute_reservations_count(self):
|
||||
hotel_reservation_obj = self.env['hotel.reservation']
|
||||
for record in self:
|
||||
@@ -25,13 +35,7 @@ class ResPartner(models.Model):
|
||||
('partner_id.id', '=', record.id)
|
||||
])
|
||||
|
||||
reservations_count = fields.Integer('Reservations',
|
||||
compute='_compute_reservations_count')
|
||||
folios_count = fields.Integer('Folios', compute='_compute_folios_count')
|
||||
unconfirmed = fields.Boolean('Unconfirmed', default=True)
|
||||
main_partner_id = fields.Many2one('res.partner', string='Destination Partner fusion')
|
||||
is_tour_operator = fields.Boolean('Is Tour Operator')
|
||||
|
||||
# ORM Overrides
|
||||
@api.model
|
||||
def name_search(self, name, args=None, operator='ilike', limit=100):
|
||||
if not args:
|
||||
|
||||
@@ -6,12 +6,22 @@ from odoo import models, api, fields
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
# Default Methods ang Gets
|
||||
@api.model
|
||||
def _get_default_hotel(self):
|
||||
return self.env.user.hotel_id
|
||||
|
||||
hotel_id = fields.Many2one('hotel.property', string='Hotel', default=_get_default_hotel,
|
||||
help='The hotel this user is currently working for.',
|
||||
context={'user_preference': True})
|
||||
hotel_ids = fields.Many2many('hotel.property', 'hotel_property_users_rel', 'user_id', 'hotel_id',
|
||||
string='Hotels', default=_get_default_hotel)
|
||||
# Fields declaration
|
||||
hotel_id = fields.Many2one(
|
||||
'hotel.property',
|
||||
string='Hotel',
|
||||
default=_get_default_hotel,
|
||||
help='The hotel this user is currently working for.',
|
||||
context={'user_preference': True})
|
||||
hotel_ids = fields.Many2many(
|
||||
'hotel.property',
|
||||
'hotel_property_users_rel',
|
||||
'user_id',
|
||||
'hotel_id',
|
||||
string='Hotels',
|
||||
default=_get_default_hotel)
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
type="action" icon="fa-coffee"/>
|
||||
<group string="Reservation Services" name="reservation_services">
|
||||
<field name="service_ids"
|
||||
context="{'default_ser_room_line': active_id, 'default_folio_id': folio_id, 'form_view_ref':'hotel.hotel_service_view_form'}"
|
||||
context="{'default_reservation_id': active_id, 'default_folio_id': folio_id, 'form_view_ref':'hotel.hotel_service_view_form'}"
|
||||
nolabel="1">
|
||||
<!-- If charge this view with tree_view_ref, its !crash! (field not found) when open reservation form from folio form... ¿?-->
|
||||
<tree string="Services" editable="bottom"
|
||||
@@ -264,7 +264,7 @@
|
||||
attrs="{'invisible':[('is_board_service','=', False)]}" />
|
||||
<field name="per_day" invisible="1" readonly="1"/>
|
||||
<field name="folio_id" invisible="1"/>
|
||||
<field name="ser_room_line" invisible="1"
|
||||
<field name="reservation_id" invisible="1"
|
||||
attrs = "{'required': [('per_day','=',True)]}" />
|
||||
<field name="product_id"
|
||||
domain="[('sale_ok', '=', True)]"
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<field name="folio_id" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="hotel_id" invisible="1"/>
|
||||
<field name="ser_room_line"
|
||||
<field name="reservation_id"
|
||||
attrs = "{'required': [('per_day','=',True)]}" />
|
||||
<field name="product_id"
|
||||
domain="[('sale_ok', '=', True)]"
|
||||
|
||||
@@ -294,8 +294,8 @@ class FolioAdvancePaymentInv(models.TransientModel):
|
||||
for service in folio.service_ids.filtered(
|
||||
lambda x: x.is_board_service == False and \
|
||||
x.qty_to_invoice != 0 and \
|
||||
(x.ser_room_line.id in self.reservation_ids.ids or \
|
||||
not x.ser_room_line.id)):
|
||||
(x.reservation_id.id in self.reservation_ids.ids or \
|
||||
not x.reservation_id.id)):
|
||||
invoice_lines[service.id] = {
|
||||
'description': service.name,
|
||||
'product_id': service.product_id.id,
|
||||
|
||||
@@ -237,7 +237,7 @@ class HotelReservation(models.Model):
|
||||
ON hf.closure_reason_id = rcr.id
|
||||
LEFT JOIN hotel_board_service_room_type_rel AS hbsrt ON hr.board_service_room_id = hbsrt.id
|
||||
LEFT JOIN hotel_board_service AS hbs ON hbsrt.hotel_board_service_id = hbs.id
|
||||
LEFT JOIN hotel_service AS hs ON hr.id = hs.ser_room_line
|
||||
LEFT JOIN hotel_service AS hs ON hr.id = hs.reservation_id
|
||||
LEFT JOIN product_product AS pp2 ON hs.product_id = pp2.id
|
||||
LEFT JOIN product_template AS pt2 ON pp2.product_tmpl_id = pt2.id
|
||||
WHERE room_id IN %s AND (
|
||||
|
||||
Reference in New Issue
Block a user