diff --git a/hotel/i18n/es.po b/hotel/i18n/es.po index 09ee9fe59..f32aa8511 100644 --- a/hotel/i18n/es.po +++ b/hotel/i18n/es.po @@ -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" diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 67503fcb0..c2d7a2e39 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -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): diff --git a/hotel/models/hotel_room.py b/hotel/models/hotel_room.py index 79bca659e..7fbea57af 100644 --- a/hotel/models/hotel_room.py +++ b/hotel/models/hotel_room.py @@ -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 diff --git a/hotel/models/hotel_room_type.py b/hotel/models/hotel_room_type.py index 55bd58ea0..beadc615b 100644 --- a/hotel/models/hotel_room_type.py +++ b/hotel/models/hotel_room_type.py @@ -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 diff --git a/hotel/models/hotel_room_type_class.py b/hotel/models/hotel_room_type_class.py index 6cdb7444e..8724f8896 100644 --- a/hotel/models/hotel_room_type_class.py +++ b/hotel/models/hotel_room_type_class.py @@ -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( diff --git a/hotel/models/hotel_room_type_restriction.py b/hotel/models/hotel_room_type_restriction.py index 5bae90ca4..5716a7167 100644 --- a/hotel/models/hotel_room_type_restriction.py +++ b/hotel/models/hotel_room_type_restriction.py @@ -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.') diff --git a/hotel/models/hotel_room_type_restriction_item.py b/hotel/models/hotel_room_type_restriction_item.py index ced5e70f8..16726b894 100644 --- a/hotel/models/hotel_room_type_restriction_item.py +++ b/hotel/models/hotel_room_type_restriction_item.py @@ -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 diff --git a/hotel/models/hotel_service.py b/hotel/models/hotel_service.py index 0bc8d5f63..8a4205ef1 100644 --- a/hotel/models/hotel_service.py +++ b/hotel/models/hotel_service.py @@ -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')) diff --git a/hotel/models/hotel_service_line.py b/hotel/models/hotel_service_line.py index ee4daf578..402572390 100644 --- a/hotel/models/hotel_service_line.py +++ b/hotel/models/hotel_service_line.py @@ -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 diff --git a/hotel/models/hotel_shared_room.py b/hotel/models/hotel_shared_room.py index d529f7271..0926a0f9c 100644 --- a/hotel/models/hotel_shared_room.py +++ b/hotel/models/hotel_shared_room.py @@ -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() diff --git a/hotel/models/inherited_account_invoice.py b/hotel/models/inherited_account_invoice.py index a26476160..c673cbf34 100644 --- a/hotel/models/inherited_account_invoice.py +++ b/hotel/models/inherited_account_invoice.py @@ -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 diff --git a/hotel/models/inherited_account_invoice_line.py b/hotel/models/inherited_account_invoice_line.py index e07c91c0b..d8217c111 100644 --- a/hotel/models/inherited_account_invoice_line.py +++ b/hotel/models/inherited_account_invoice_line.py @@ -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', diff --git a/hotel/models/inherited_account_payment.py b/hotel/models/inherited_account_payment.py index 5bf5c1953..9e15b18d6 100644 --- a/hotel/models/inherited_account_payment.py +++ b/hotel/models/inherited_account_payment.py @@ -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 diff --git a/hotel/models/inherited_ir_http.py b/hotel/models/inherited_ir_http.py index 6a7725db8..bd18b5b49 100644 --- a/hotel/models/inherited_ir_http.py +++ b/hotel/models/inherited_ir_http.py @@ -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 diff --git a/hotel/models/inherited_mail_compose_message.py b/hotel/models/inherited_mail_compose_message.py index 99fb34b71..faf1cffb9 100644 --- a/hotel/models/inherited_mail_compose_message.py +++ b/hotel/models/inherited_mail_compose_message.py @@ -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) diff --git a/hotel/models/inherited_payment_return.py b/hotel/models/inherited_payment_return.py index d8382e317..b7726c609 100644 --- a/hotel/models/inherited_payment_return.py +++ b/hotel/models/inherited_payment_return.py @@ -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)]}) diff --git a/hotel/models/inherited_product_pricelist.py b/hotel/models/inherited_product_pricelist.py index c0de7a3e2..6797cf9a5 100644 --- a/hotel/models/inherited_product_pricelist.py +++ b/hotel/models/inherited_product_pricelist.py @@ -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.")) diff --git a/hotel/models/inherited_product_template.py b/hotel/models/inherited_product_template.py index bcd0e7079..d0937651a 100644 --- a/hotel/models/inherited_product_template.py +++ b/hotel/models/inherited_product_template.py @@ -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') diff --git a/hotel/models/inherited_res_company.py b/hotel/models/inherited_res_company.py index c110994d4..79b3a7eb7 100644 --- a/hotel/models/inherited_res_company.py +++ b/hotel/models/inherited_res_company.py @@ -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.") diff --git a/hotel/models/inherited_res_partner.py b/hotel/models/inherited_res_partner.py index 0e41db720..a2370bf0d 100644 --- a/hotel/models/inherited_res_partner.py +++ b/hotel/models/inherited_res_partner.py @@ -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: diff --git a/hotel/models/inherited_res_users.py b/hotel/models/inherited_res_users.py index 0adb77138..1f4843632 100644 --- a/hotel/models/inherited_res_users.py +++ b/hotel/models/inherited_res_users.py @@ -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) diff --git a/hotel/views/hotel_reservation_views.xml b/hotel/views/hotel_reservation_views.xml index 72d142028..a12dfc28f 100644 --- a/hotel/views/hotel_reservation_views.xml +++ b/hotel/views/hotel_reservation_views.xml @@ -250,7 +250,7 @@ type="action" icon="fa-coffee"/> - -