[IMP] Models py guide lines

This commit is contained in:
Dario Lodeiros
2019-09-24 20:09:26 +02:00
parent 22b7ec6626
commit 857a8a6486
20 changed files with 707 additions and 459 deletions

View File

@@ -943,8 +943,9 @@ class HotelReservation(models.Model):
# Yes?, then, this is share folio ;) # Yes?, then, this is share folio ;)
for record in self: for record in self:
if record.folio_id: if record.folio_id:
record.shared_folio = len(record.folio_id.reservation_ids) > 1 or \ record.shared_folio = \
any(record.folio_id.service_ids.filtered( len(record.folio_id.reservation_ids) > 1 \
or any(record.folio_id.service_ids.filtered(
lambda x: x.ser_room_line.id != record.id)) lambda x: x.ser_room_line.id != record.id))
@api.multi @api.multi

View File

@@ -7,8 +7,9 @@ from odoo.exceptions import ValidationError
class HotelRoom(models.Model): class HotelRoom(models.Model):
""" The rooms for lodging can be for sleeping, usually called rooms, and also """ The rooms for lodging can be for sleeping, usually called rooms,
for speeches (conference rooms), parking, relax with cafe con leche, spa... and also for speeches (conference rooms), parking,
relax with cafe con leche, spa...
""" """
_name = 'hotel.room' _name = 'hotel.room'
_description = 'Hotel Room' _description = 'Hotel Room'
@@ -16,15 +17,24 @@ class HotelRoom(models.Model):
# Fields declaration # Fields declaration
name = fields.Char('Room Name', required=True) name = fields.Char('Room Name', required=True)
hotel_id = fields.Many2one('hotel.property', store=True, readonly=True, hotel_id = fields.Many2one(
related='room_type_id.hotel_id') 'hotel.property',
room_type_id = fields.Many2one('hotel.room.type', 'Hotel Room Type', store=True,
required=True, readonly=True,
ondelete='restrict') related='room_type_id.hotel_id')
shared_room_id = fields.Many2one('hotel.shared.room', 'Shared Room', room_type_id = fields.Many2one(
default=False) 'hotel.room.type',
floor_id = fields.Many2one('hotel.floor', 'Ubication', 'Hotel Room Type',
help='At which floor the room is located.') 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') capacity = fields.Integer('Capacity')
to_be_cleaned = fields.Boolean('To be Cleaned', default=False) to_be_cleaned = fields.Boolean('To be Cleaned', default=False)
extra_beds_allowed = fields.Integer('Extra beds allowed', extra_beds_allowed = fields.Integer('Extra beds allowed',
@@ -43,29 +53,36 @@ class HotelRoom(models.Model):
def _check_capacity(self): def _check_capacity(self):
for record in self: for record in self:
if record.capacity < 1: 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 # CRUD methods
@api.model @api.model
def create(self, vals): def create(self, vals):
if vals.get('hotel_id', self.env.user.hotel_id.id) != self.env['hotel.room.type'].browse( if vals.get('hotel_id', self.env.user.hotel_id.id) != \
vals['room_type_id']).hotel_id.id: self.env['hotel.room.type'].browse(
raise ValidationError(_("A room cannot be created in a room type of another hotel.")) 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) return super().create(vals)
@api.multi @api.multi
def write(self, vals): def write(self, vals):
for record in self: for record in self:
if vals.get('hotel_id', record.hotel_id.id) != record.hotel_id.id: if vals.get('hotel_id', record.hotel_id.id) != record.hotel_id.id:
raise ValidationError(_("A room cannot be changed to another hotel.") + " " + raise ValidationError(
_("%s does not belong to %s.") _("A room cannot be changed to another hotel.") + " " +
% (record, record.hotel_id)) _("%s does not belong to %s.")
% (record, record.hotel_id))
room_type_ids = self.env['hotel.room.type'].search([ room_type_ids = self.env['hotel.room.type'].search([
('hotel_id', '=', record.hotel_id.id) ('hotel_id', '=', record.hotel_id.id)
]).ids ]).ids
if vals.get('room_type_id', record.room_type_id.id) not in room_type_ids: if vals.get('room_type_id', record.room_type_id.id) \
raise ValidationError(_("A room cannot be changed to a room type of another hotel or " not in room_type_ids:
"unlinked from a room type.")) raise ValidationError(
_("A room cannot be changed to a room type of \
another hotel or unlinked from a room type."))
return super().write(vals) return super().write(vals)
# Business methods # Business methods

View File

@@ -21,30 +21,47 @@ class HotelRoomType(models.Model):
return self.env.user.hotel_id return self.env.user.hotel_id
# Fields declaration # Fields declaration
product_id = fields.Many2one('product.product', 'Product Room Type', product_id = fields.Many2one(
required=True, delegate=True, 'product.product',
ondelete='cascade') 'Product Room Type',
hotel_id = fields.Many2one('hotel.property', 'Hotel', required=True, ondelete='restrict', required=True,
default=_get_default_hotel,) delegate=True,
room_ids = fields.One2many('hotel.room', 'room_type_id', 'Rooms') ondelete='cascade')
class_id = fields.Many2one('hotel.room.type.class', 'Hotel Type Class') 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( board_service_room_type_ids = fields.One2many(
'hotel.board.service.room.type', 'hotel_room_type_id', string='Board Services') 'hotel.board.service.room.type',
room_amenity_ids = fields.Many2many('hotel.amenity', 'hotel_room_type_id',
'hotel_room_type_aminity_rel', string='Board Services')
'room_type_ids', 'amenity_ids', room_amenity_ids = fields.Many2many(
string='Room Type Amenities', 'hotel.amenity',
help='List of Amenities.') '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, ) code_type = fields.Char('Code', required=True, )
shared_room = fields.Boolean('Shared Room', default=False, shared_room = fields.Boolean('Shared Room', default=False,
help="This room type is reservation by beds") 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) active = fields.Boolean('Active', default=True)
sequence = fields.Integer('Sequence', default=0) sequence = fields.Integer('Sequence', default=0)
_sql_constraints = [ _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 # Constraints and onchanges
@@ -80,7 +97,8 @@ class HotelRoomType(models.Model):
def check_availability_room_type(self, dfrom, dto, def check_availability_room_type(self, dfrom, dto,
room_type_id=False, notthis=[]): 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, reservations = self.env['hotel.reservation'].get_reservations(dfrom,
dto) dto)
@@ -118,10 +136,11 @@ class HotelRoomType(models.Model):
raise ValidationError(_('Date From and days are mandatory')) raise ValidationError(_('Date From and days are mandatory'))
partner_id = kwargs.get('partner_id', False) partner_id = kwargs.get('partner_id', False)
partner = self.env['res.partner'].browse(partner_id) partner = self.env['res.partner'].browse(partner_id)
pricelist_id = kwargs.get('pricelist_id', pricelist_id = kwargs.get(
partner.property_product_pricelist.id and 'pricelist_id',
partner.property_product_pricelist.id or partner.property_product_pricelist.id and
self.env.user.hotel_id.default_pricelist_id.id) partner.property_product_pricelist.id or
self.env.user.hotel_id.default_pricelist_id.id)
vals.update({ vals.update({
'partner_id': partner_id if partner_id else False, 'partner_id': partner_id if partner_id else False,
'discount': discount, 'discount': discount,
@@ -129,14 +148,16 @@ class HotelRoomType(models.Model):
rate_vals = {} rate_vals = {}
for room_type in room_types: for room_type in room_types:
vals.update({'room_type_id': room_type.id}) vals.update({'room_type_id': room_type.id})
room_vals = self.env['hotel.reservation'].prepare_reservation_lines( room_vals = self.env['hotel.reservation'].\
date_from, prepare_reservation_lines(
days, date_from,
pricelist_id=pricelist_id, days,
vals=vals, pricelist_id=pricelist_id,
update_old_prices=False) vals=vals,
update_old_prices=False)
rate_vals.update({ rate_vals.update({
room_type.id: [item[2] for item in \ room_type.id: [item[2] for item in
room_vals['reservation_line_ids'] if item[2]] room_vals['reservation_line_ids'] if
item[2]]
}) })
return rate_vals return rate_vals

View File

@@ -14,6 +14,7 @@ class HotelRoomTypeClass(models.Model):
_description = "Room Type Class" _description = "Room Type Class"
_order = "sequence, name, code_class" _order = "sequence, name, code_class"
# Fields declaration
name = fields.Char('Class Name', required=True, translate=True) name = fields.Char('Class Name', required=True, translate=True)
# Relationship between models # Relationship between models
hotel_ids = fields.Many2many( hotel_ids = fields.Many2many(

View File

@@ -15,11 +15,19 @@ class HotelRoomTypeRestriction(models.Model):
# Fields declaration # Fields declaration
name = fields.Char('Restriction Plan Name', required=True) name = fields.Char('Restriction Plan Name', required=True)
hotel_id = fields.Many2one('hotel.property', 'Hotel', required=True, ondelete='restrict', hotel_id = fields.Many2one(
default=_get_default_hotel) 'hotel.property',
item_ids = fields.One2many('hotel.room.type.restriction.item', 'Hotel',
'restriction_id', string='Restriction Items', required=True,
copy=True) ondelete='restrict',
active = fields.Boolean('Active', default=True, default=_get_default_hotel)
help='If unchecked, it will allow you to hide the ' item_ids = fields.One2many(
'restriction plan without removing it.') '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.')

View File

@@ -25,7 +25,8 @@ class HotelRoomTypeRestrictionItem(models.Model):
_sql_constraints = [('room_type_registry_unique', _sql_constraints = [('room_type_registry_unique',
'unique(restriction_id, room_type_id, date)', '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 # Constraints and onchanges
@api.multi @api.multi

View File

@@ -7,7 +7,6 @@ from odoo.tools import (
float_compare, float_compare,
DEFAULT_SERVER_DATE_FORMAT) DEFAULT_SERVER_DATE_FORMAT)
from datetime import timedelta from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons import decimal_precision as dp from odoo.addons import decimal_precision as dp
import logging import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -17,19 +16,7 @@ class HotelService(models.Model):
_name = 'hotel.service' _name = 'hotel.service'
_description = 'Hotel Services and its charges' _description = 'Hotel Services and its charges'
@api.model # Default methods
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 @api.multi
def name_get(self): def name_get(self):
result = [] result = []
@@ -57,239 +44,233 @@ class HotelService(models.Model):
return self._context['folio_id'] return self._context['folio_id']
return False 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)
ser_room_line = fields.Many2one(
'hotel.reservation',
'Room',
default=_default_ser_room_line)
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') @api.depends('qty_invoiced', 'product_qty', 'folio_id.state')
def _get_to_invoice_qty(self): def _get_to_invoice_qty(self):
""" """
Compute the quantity to invoice. If the invoice policy is order, the quantity to invoice is Compute the quantity to invoice. If the invoice policy is order,
calculated from the ordered quantity. Otherwise, the quantity delivered is used. the quantity to invoice is calculated from the ordered quantity.
Otherwise, the quantity delivered is used.
""" """
for line in self: for line in self:
if line.folio_id.state not in ['draft']: 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: else:
line.qty_to_invoice = 0 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): def _get_invoice_qty(self):
""" """
Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. Note Compute the quantity invoiced. If case of a refund,
that this is the case only if the refund is generated from the SO and that is intentional: if the quantity invoiced is decreased. Note that this is the case only
a refund made would automatically decrease the invoiced quantity, then there is a risk of reinvoicing if the refund is generated from the Folio and that is intentional: if
it automatically, which may not be wanted at all. That's why the refund has to be created from the SO 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: for line in self:
qty_invoiced = 0.0 qty_invoiced = 0.0
for invoice_line in line.invoice_line_ids: for invoice_line in line.invoice_line_ids:
if invoice_line.invoice_id.state != 'cancel': if invoice_line.invoice_id.state != 'cancel':
if invoice_line.invoice_id.type == 'out_invoice': 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': 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 line.qty_invoiced = qty_invoiced
@api.depends('product_qty', 'qty_to_invoice', 'qty_invoiced') @api.depends('product_qty', 'qty_to_invoice', 'qty_invoiced')
def _compute_invoice_status(self): def _compute_invoice_status(self):
""" """
Compute the invoice status of a SO line. Possible statuses: 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 - no: if the SO is not in status 'sale' or 'done',
invoice. This is also hte default value if the conditions of no other status is met. we consider that there is nothing to invoice.
- to invoice: we refer to the quantity to invoice of the line. Refer to method This is also hte default value if the conditions of no other
`_get_to_invoice_qty()` for more information on how this quantity is calculated. status is met.
- upselling: this is possible only for a product invoiced on ordered quantities for which - to invoice: we refer to the quantity to invoice of the line.
we delivered more than expected. The could arise if, for example, a project took more Refer to method `_get_to_invoice_qty()` for more information on
time than expected but we decided not to invoice the extra cost to the client. This how this quantity is calculated.
occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity - upselling: this is possible only for a product invoiced on ordered
is removed from the list. quantities for which we delivered more than expected.
- invoiced: the quantity invoiced is larger or equal to the quantity ordered. 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: for line in self:
if line.folio_id.state in ('draft'): if line.folio_id.state in ('draft'):
line.invoice_status = 'no' 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' 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' line.invoice_status = 'invoiced'
else: else:
line.invoice_status = 'no' line.invoice_status = 'no'
name = fields.Char('Service description', required=True) @api.depends('product_qty', 'discount', 'price_unit', 'tax_ids')
sequence = fields.Integer(string='Sequence', default=10) def _compute_amount_service(self):
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):
""" """
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: 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(
folio = record.folio_id or self.env['hotel.folio'].browse(self.env.context.get('default_folio_id')) self.env.context.get('default_folio_id'))
reservation = record.ser_room_line or self.env.context.get('ser_room_line') reservation = record.ser_room_line or self.env.context.get(
origin = folio if folio else reservation 'ser_room_line')
record.tax_ids = record.product_id.taxes_id.filtered(lambda r: not record.company_id or r.company_id == origin.company_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 record.update({
def _get_display_price(self, product): 'price_tax': sum(t.get('amount', 0.0) for t in
folio = self.folio_id or self.env.context.get('default_folio_id') taxes.get('taxes', [])),
reservation = self.ser_room_line or self.env.context.get('ser_room_line') 'price_total': taxes['total_included'],
origin = folio if folio else reservation 'price_subtotal': taxes['total_excluded'],
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.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') @api.onchange('product_id')
def onchange_product_id(self): def onchange_product_id(self):
""" """
Compute the default quantity according to the Compute the default quantity according to the
configuration of the selected product, in per_day product configuration, configuration of the selected product, in per_day
the qty is autocalculated and readonly based on service_ids qty product configuration, the qty is autocalculated and
readonly based on service_ids qty
""" """
if not self.product_id: if not self.product_id:
return return
@@ -320,11 +301,8 @@ class HotelService(models.Model):
persons=reservation.adults, persons=reservation.adults,
old_line_days=record.service_line_ids, old_line_days=record.service_line_ids,
consumed_on=product.consumed_on, consumed_on=product.consumed_on,
)) ))
if record.product_id.daily_limit > 0: 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: for day in record.service_line_ids:
day.no_free_resources() day.no_free_resources()
""" """
@@ -358,11 +336,175 @@ class HotelService(models.Model):
vals['price_unit'] = self._compute_price_unit() vals['price_unit'] = self._compute_price_unit()
record.update(vals) 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 += [
'|',
('ser_room_line.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['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
# 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.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)
@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)
@api.multi @api.multi
def _compute_price_unit(self): def _compute_price_unit(self):
self.ensure_one() self.ensure_one()
folio = self.folio_id or self.env.context.get('default_folio_id') 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.ser_room_line or self.env.context.get(
'ser_room_line')
origin = reservation if reservation else folio origin = reservation if reservation else folio
if origin: if origin:
partner = origin.partner_id partner = origin.partner_id
@@ -371,22 +513,27 @@ class HotelService(models.Model):
board_room_type = reservation.board_service_room_id board_room_type = reservation.board_service_room_id
if board_room_type.price_type == 'fixed': if board_room_type.price_type == 'fixed':
return self.env['hotel.board.service.room.type.line'].search([ return self.env['hotel.board.service.room.type.line'].search([
('hotel_board_service_room_type_id', '=', board_room_type.id), ('hotel_board_service_room_type_id',
'=', board_room_type.id),
('product_id', '=', self.product_id.id)]).amount ('product_id', '=', self.product_id.id)]).amount
else: else:
return (reservation.price_total * self.env['hotel.board.service.room.type.line'].search([ return (reservation.price_total *
('hotel_board_service_room_type_id', '=', board_room_type.id), self.env['hotel.board.service.room.type.line'].
('product_id', '=', self.product_id.id)]).amount) / 100 search([
('hotel_board_service_room_type_id',
'=', board_room_type.id),
('product_id', '=', self.product_id.id)])
.amount) / 100
else: else:
product = self.product_id.with_context( product = self.product_id.with_context(
lang=partner.lang, lang=partner.lang,
partner=partner.id, partner=partner.id,
quantity=self.product_qty, quantity=self.product_qty,
date=folio.date_order if folio else fields.Date.today(), date=folio.date_order if folio else fields.Date.today(),
pricelist=pricelist.id, pricelist=pricelist.id,
uom=self.product_id.uom_id.id, uom=self.product_id.uom_id.id,
fiscal_position=False fiscal_position=False
) )
return self.env['account.tax']._fix_tax_included_price_company( return self.env['account.tax']._fix_tax_included_price_company(
self._get_display_price(product), self._get_display_price(product),
product.taxes_id, self.tax_ids, product.taxes_id, self.tax_ids,
@@ -399,16 +546,19 @@ class HotelService(models.Model):
""" """
cmds = [(5, 0, 0)] cmds = [(5, 0, 0)]
old_line_days = kwargs.get('old_line_days') 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 total_qty = 0
day_qty = 1 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') day_qty = kwargs.get('persons')
for i in range(0, kwargs.get('days')): for i in range(0, kwargs.get('days')):
if consumed_on == 'after': if consumed_on == 'after':
i += 1 i += 1
idate = (fields.Date.from_string(kwargs.get('dfrom')) + timedelta(days=i)).strftime( idate = (fields.Date.from_string(kwargs.get('dfrom')) +
DEFAULT_SERVER_DATE_FORMAT) timedelta(days=i)).strftime(
DEFAULT_SERVER_DATE_FORMAT)
if not old_line_days or idate not in old_line_days.mapped('date'): if not old_line_days or idate not in old_line_days.mapped('date'):
cmds.append((0, False, { cmds.append((0, False, {
'date': idate, 'date': idate,
@@ -420,52 +570,3 @@ class HotelService(models.Model):
cmds.append((4, old_line.id)) cmds.append((4, old_line.id))
total_qty = total_qty + old_line.day_qty total_qty = total_qty + old_line.day_qty
return {'service_line_ids': cmds, 'product_qty': total_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'))

View File

@@ -9,43 +9,51 @@ class HotelServiceLine(models.Model):
_name = "hotel.service.line" _name = "hotel.service.line"
_order = "date" _order = "date"
service_id = fields.Many2one('hotel.service', string='Service Room', # Fields declaration
ondelete='cascade', required=True, service_id = fields.Many2one(
copy=False) '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') date = fields.Date('Date')
day_qty = fields.Integer('Units') day_qty = fields.Integer('Units')
product_id = fields.Many2one(related='service_id.product_id', store=True) price_total = fields.Float(
price_total = fields.Float('Price Total', 'Price Total',
compute='_compute_price_total', compute='_compute_price_total',
store=True) store=True)
price_unit = fields.Float('Unit Price', price_unit = fields.Float(
related="service_id.price_unit", 'Unit Price',
readonly=True, related="service_id.price_unit",
store=True) readonly=True,
room_id = fields.Many2one(strin='Room', store=True)
related="service_id.ser_room_line", room_id = fields.Many2one(
readonly=True, string='Room',
store=True) related="service_id.ser_room_line",
discount = fields.Float('Discount', readonly=True,
related="service_id.discount", store=True)
readonly=True, discount = fields.Float(
store=True) 'Discount',
cancel_discount = fields.Float('Discount', compute='_compute_cancel_discount') related="service_id.discount",
tax_ids = fields.Many2many('account.tax', readonly=True,
string='Taxes', store=True)
related="service_id.tax_ids", cancel_discount = fields.Float(
readonly="True") 'Discount', compute='_compute_cancel_discount')
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
# Compute and Search methods
@api.depends('day_qty', 'service_id.price_total') @api.depends('day_qty', 'service_id.price_total')
def _compute_price_total(self): def _compute_price_total(self):
""" """
@@ -53,10 +61,13 @@ class HotelServiceLine(models.Model):
""" """
for record in self: for record in self:
if record.service_id.product_qty != 0: 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: else:
record.price_total = 0 record.price_total = 0
# Constraints and onchanges
@api.constrains('day_qty') @api.constrains('day_qty')
def no_free_resources(self): def no_free_resources(self):
for record in self: for record in self:
@@ -69,5 +80,14 @@ class HotelServiceLine(models.Model):
]).mapped('day_qty')) ]).mapped('day_qty'))
if limit < out_qty + record.day_qty: if limit < out_qty + record.day_qty:
raise ValidationError( raise ValidationError(
_("%s limit exceeded for %s")% (record.service_id.product_id.name, _("%s limit exceeded for %s") %
record.date)) (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

View File

@@ -11,30 +11,41 @@ class HotelSharedRoom(models.Model):
_description = 'Hotel Shared Room' _description = 'Hotel Shared Room'
_order = "room_type_id, name" _order = "room_type_id, name"
# Fields declaration
name = fields.Char('Room Name', required=True) name = fields.Char('Room Name', required=True)
active = fields.Boolean('Active', default=True)
room_type_id = fields.Many2one( room_type_id = fields.Many2one(
'hotel.room.type', 'Hotel Room Type', 'hotel.room.type',
required=True, ondelete='restrict', 'Hotel Room Type',
required=True,
ondelete='restrict',
domain=[('shared_room', '=', True)] domain=[('shared_room', '=', True)]
) )
hotel_id = fields.Many2one('hotel.property', store=True, readonly=True, hotel_id = fields.Many2one(
related='room_type_id.hotel_id') 'hotel.property',
floor_id = fields.Many2one('hotel.floor', 'Ubication', store=True,
help='At which floor the room is located.', readonly=True,
ondelete='restrict',) 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) sequence = fields.Integer('Sequence', required=True)
beds = fields.Integer('Beds') beds = fields.Integer('Beds')
bed_ids = fields.One2many('hotel.room',
'shared_room_id',
readonly=True,
ondelete='restrict',)
description_sale = fields.Text( description_sale = fields.Text(
'Sale Description', translate=True, 'Sale Description',
translate=True,
help="A description of the Product that you want to communicate to " help="A description of the Product that you want to communicate to "
" your customers. This description will be copied to every Sales " " your customers. This description will be copied to every Sales "
" Order, Delivery Order and Customer Invoice/Credit Note") " Order, Delivery Order and Customer Invoice/Credit Note")
# Constraints and onchanges
@api.constrains('beds') @api.constrains('beds')
def _constrain_beds(self): def _constrain_beds(self):
self.ensure_one() self.ensure_one()

View File

@@ -10,12 +10,13 @@ class AccountInvoice(models.Model):
_inherit = 'account.invoice' _inherit = 'account.invoice'
# Field Declarations # Field Declarations
from_folio = fields.Boolean(
compute='_computed_folio_origin')
folio_ids = fields.Many2many( folio_ids = fields.Many2many(
comodel_name='hotel.folio', comodel_name='hotel.folio',
compute='_computed_folio_origin') 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( outstanding_folios_debits_widget = fields.Text(
compute='_get_outstanding_folios_JSON') compute='_get_outstanding_folios_JSON')
has_folios_outstanding = fields.Boolean( has_folios_outstanding = fields.Boolean(
@@ -93,7 +94,8 @@ class AccountInvoice(models.Model):
else: else:
amount_to_show = line.company_id.currency_id.\ amount_to_show = line.company_id.currency_id.\
with_context(date=line.date).\ 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( if float_is_zero(
amount_to_show, amount_to_show,
precision_rounding=self.currency_id.rounding precision_rounding=self.currency_id.rounding

View File

@@ -1,11 +1,13 @@
# Copyright 2017 Alexandre Díaz # Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros # Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import api, fields, models, _ from odoo import fields, models
class AccountInvoiceLine(models.Model): class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line' _inherit = 'account.invoice.line'
# Fields declaration
reservation_ids = fields.Many2many( reservation_ids = fields.Many2many(
'hotel.reservation', 'hotel.reservation',
'reservation_invoice_rel', 'reservation_invoice_rel',

View File

@@ -3,10 +3,14 @@
from odoo.exceptions import except_orm from odoo.exceptions import except_orm
from odoo import models, fields, api, _ from odoo import models, fields, api, _
class AccountPayment(models.Model): class AccountPayment(models.Model):
_inherit = 'account.payment' _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( amount_total_folio = fields.Float(
compute="_compute_folio_amount", store=True, compute="_compute_folio_amount", store=True,
string="Total amount in folio", string="Total amount in folio",
@@ -15,13 +19,38 @@ class AccountPayment(models.Model):
save_date = fields.Date() save_date = fields.Date()
save_journal_id = fields.Integer() 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): def onchange_amount(self):
if self._origin: if self._origin:
self.save_amount = self._origin.amount self.save_amount = self._origin.amount
self.save_journal_id = self._origin.journal_id.id self.save_journal_id = self._origin.journal_id.id
self.save_date = self._origin.payment_date self.save_date = self._origin.payment_date
# Action methods
"""WIP""" """WIP"""
@api.multi @api.multi
def return_payment_folio(self): def return_payment_folio(self):
@@ -47,9 +76,11 @@ class AccountPayment(models.Model):
if self.save_date: if self.save_date:
self.payment_date = self.save_date self.payment_date = self.save_date
if self.save_journal_id: 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() return_pay.action_confirm()
# Business methods
@api.multi @api.multi
def modify(self): def modify(self):
self.cancel() self.cancel()
@@ -66,53 +97,40 @@ class AccountPayment(models.Model):
if self.folio_id: if self.folio_id:
msg = _("Payment %s modified: \n") % (self.communication) msg = _("Payment %s modified: \n") % (self.communication)
if self.save_amount and self.save_amount != self.amount: 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: if self.save_date and self.save_date != self.payment_date:
msg += _("Date from %s to %s \n") % (self.save_date, self.payment_date) msg += _("Date from %s to %s \n") % (
if self.save_journal_id and self.save_journal_id != self.journal_id.id: self.save_date, self.payment_date)
msg += _("Journal from %s to %s") % (self.env['account.journal'].browse(self.save_journal_id).name, self.journal_id.name) 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) self.folio_id.message_post(subject=_('Payment'), body=msg)
@api.multi @api.multi
def delete(self): def delete(self):
msg = False msg = False
if self.folio_id: 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.cancel()
self.move_name = '' self.move_name = ''
self.unlink() self.unlink()
if msg: if msg:
self.folio_id.message_post(subject=_('Payment Deleted'), body=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 @api.multi
def post(self): def post(self):
rec = super(AccountPayment, self).post() rec = super(AccountPayment, self).post()
if rec and not self._context.get("ignore_notification_post", False): if rec and not self._context.get("ignore_notification_post", False):
for pay in self: for pay in self:
if pay.folio_id: 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) pay.folio_id.message_post(subject=_('Payment'), body=msg)
@api.multi @api.multi

View File

@@ -14,11 +14,16 @@ class IrHttp(models.AbstractModel):
res = super().session_info() res = super().session_info()
user = request.env.user user = request.env.user
display_switch_hotel_menu = len(user.hotel_ids) > 1 display_switch_hotel_menu = len(user.hotel_ids) > 1
# TODO: limit hotels to the current company? or switch company automatically # TODO: limit hotels to the current company?
res['hotel_id'] = request.env.user.hotel_id.id if request.session.uid else None # or switch company automatically
res['user_hotels'] = {'current_hotel': (user.hotel_id.id, user.hotel_id.name), res['hotel_id'] = request.env.user.hotel_id.id if \
'allowed_hotels': [(hotel.id, hotel.name) for hotel in request.session.uid else None
user.hotel_ids]} if display_switch_hotel_menu else False 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: if user.hotel_id.company_id in user.company_ids:
user.company_id = user.hotel_id.company_id user.company_id = user.hotel_id.company_id
res['company_id'] = user.hotel_id.company_id.id res['company_id'] = user.hotel_id.company_id.id

View File

@@ -10,12 +10,15 @@ class MailComposeMessage(models.TransientModel):
@api.multi @api.multi
def send_mail(self, auto_commit=False): def send_mail(self, auto_commit=False):
if self._context.get('default_model') == 'hotel.folio' and \ 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([ folio = self.env['hotel.folio'].browse([
self._context['default_res_id'] self._context['default_res_id']
]) ])
if folio: 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): if any(cmds):
folio.reservation_ids = 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)

View File

@@ -6,10 +6,17 @@ from openerp import models, fields, api, _
class PaymentReturn(models.Model): class PaymentReturn(models.Model):
_inherit = 'payment.return' _inherit = 'payment.return'
folio_id = fields.Many2one('hotel.folio', string='Folio') # Fields declaration
hotel_id = fields.Many2one('hotel.property', store=True, readonly=True, folio_id = fields.Many2one(
related='folio_id.hotel_id') 'hotel.folio',
string='Folio')
hotel_id = fields.Many2one(
'hotel.property',
store=True,
readonly=True,
related='folio_id.hotel_id')
# Business methods
@api.multi @api.multi
def action_confirm(self): def action_confirm(self):
pay = super(PaymentReturn, self).action_confirm() pay = super(PaymentReturn, self).action_confirm()

View File

@@ -12,13 +12,18 @@ class ProductPricelist(models.Model):
_inherit = 'product.pricelist' _inherit = 'product.pricelist'
# Fields declaration # Fields declaration
hotel_ids = fields.Many2many('hotel.property', string='Hotels', required=False, hotel_ids = fields.Many2many(
ondelete='restrict') 'hotel.property',
cancelation_rule_id = fields.Many2one('hotel.cancelation.rule',string="Cancelation Policy") string='Hotels',
required=False,
ondelete='restrict')
cancelation_rule_id = fields.Many2one(
'hotel.cancelation.rule',
string="Cancelation Policy")
pricelist_type = fields.Selection([ pricelist_type = fields.Selection([
('daily', 'Daily Plan'), ('daily', 'Daily Plan')],
], string='Pricelist Type', default='daily') string='Pricelist Type',
default='daily')
is_staff = fields.Boolean('Is Staff') is_staff = fields.Boolean('Is Staff')
# Constraints and onchanges # Constraints and onchanges
@@ -26,13 +31,17 @@ class ProductPricelist(models.Model):
def _check_pricelist_type_hotel_ids(self): def _check_pricelist_type_hotel_ids(self):
for record in self: for record in self:
if record.pricelist_type == 'daily' and len(record.hotel_ids) != 1: 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 " raise ValidationError(
"and therefore must be related with one and only one hotel.")) _("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: if record.pricelist_type == 'daily' and len(record.hotel_ids) == 1:
hotel_id = self.env['hotel.property'].search([ hotel_id = self.env['hotel.property'].search([
('default_pricelist_id', '=', record.id) ('default_pricelist_id', '=', record.id)
]) or None ]) or None
if hotel_id and hotel_id != record.hotel_ids: if hotel_id and hotel_id != record.hotel_ids:
raise ValidationError(_("Relationship mismatch.") + " " + raise ValidationError(
_("This pricelist is used as default in a different hotel.")) _("Relationship mismatch.") + " " +
_("This pricelist is used as default in a "
"different hotel."))

View File

@@ -7,6 +7,11 @@ from odoo import models, fields
class ProductTemplate(models.Model): class ProductTemplate(models.Model):
_inherit = "product.template" _inherit = "product.template"
hotel_ids = fields.Many2many(
'hotel.property',
string='Hotels',
required=False,
ondelete='restrict')
per_day = fields.Boolean('Unit increment per day') per_day = fields.Boolean('Unit increment per day')
per_person = fields.Boolean('Unit increment per person') per_person = fields.Boolean('Unit increment per person')
consumed_on = fields.Selection([ consumed_on = fields.Selection([
@@ -14,7 +19,7 @@ class ProductTemplate(models.Model):
('after', 'After night')], 'Consumed', default='before') ('after', 'After night')], 'Consumed', default='before')
daily_limit = fields.Integer('Daily limit') daily_limit = fields.Integer('Daily limit')
is_extra_bed = fields.Boolean('Is extra bed', default=False) 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.') help='Specifies if the product is shown in the calendar information.')
hotel_ids = fields.Many2many('hotel.property', string='Hotels', required=False,
ondelete='restrict')

View File

@@ -7,8 +7,8 @@ from odoo import models, fields
class ResCompany(models.Model): class ResCompany(models.Model):
_inherit = 'res.company' _inherit = 'res.company'
# Fields declaration
hotel_ids = fields.One2many('hotel.property', 'company_id', 'Hotels') hotel_ids = fields.One2many('hotel.property', 'company_id', 'Hotels')
# TODO: need extra explanation or remove otherwise # TODO: need extra explanation or remove otherwise
# additional_hours = fields.Integer('Additional Hours', # additional_hours = fields.Integer('Additional Hours',
# help="Provide the min hours value for \ # help="Provide the min hours value for \

View File

@@ -11,6 +11,18 @@ _logger = logging.getLogger(__name__)
class ResPartner(models.Model): class ResPartner(models.Model):
_inherit = 'res.partner' _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): def _compute_reservations_count(self):
hotel_reservation_obj = self.env['hotel.reservation'] hotel_reservation_obj = self.env['hotel.reservation']
for record in self: for record in self:
@@ -25,13 +37,7 @@ class ResPartner(models.Model):
('partner_id.id', '=', record.id) ('partner_id.id', '=', record.id)
]) ])
reservations_count = fields.Integer('Reservations', # ORM Overrides
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')
@api.model @api.model
def name_search(self, name, args=None, operator='ilike', limit=100): def name_search(self, name, args=None, operator='ilike', limit=100):
if not args: if not args:

View File

@@ -6,12 +6,22 @@ from odoo import models, api, fields
class ResUsers(models.Model): class ResUsers(models.Model):
_inherit = 'res.users' _inherit = 'res.users'
# Default Methods ang Gets
@api.model @api.model
def _get_default_hotel(self): def _get_default_hotel(self):
return self.env.user.hotel_id return self.env.user.hotel_id
hotel_id = fields.Many2one('hotel.property', string='Hotel', default=_get_default_hotel, # Fields declaration
help='The hotel this user is currently working for.', hotel_id = fields.Many2one(
context={'user_preference': True}) 'hotel.property',
hotel_ids = fields.Many2many('hotel.property', 'hotel_property_users_rel', 'user_id', 'hotel_id', string='Hotel',
string='Hotels', default=_get_default_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)