diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index 40d54dee0..05a1ff765 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -48,6 +48,7 @@ 'views/hotel_reservation_views.xml', 'views/hotel_room_closure_reason_views.xml', 'views/hotel_service_views.xml', + 'views/hotel_service_line_views.xml', 'views/hotel_board_service_views.xml', 'views/hotel_checkin_partner_views.xml', 'views/hotel_room_type_availability_views.xml', diff --git a/hotel/models/hotel_checkin_partner.py b/hotel/models/hotel_checkin_partner.py index 5fe511faa..f8883ecbd 100644 --- a/hotel/models/hotel_checkin_partner.py +++ b/hotel/models/hotel_checkin_partner.py @@ -23,6 +23,11 @@ class HotelCheckinPartner(models.Model): self.env.context['folio_id'] ]) return folio + if 'reservation_id' in self.env.context: + folio = self.env['hotel.reservation'].browse([ + self.env.context['reservation_id'] + ]).folio_id + return folio return False def _default_enter_date(self): @@ -46,16 +51,20 @@ class HotelCheckinPartner(models.Model): reservation = self.env['hotel.reservation'].browse([ self.env.context['reservation_id'] ]) - return reservation.partner_id + if reservation.partner_id.id not in reservation.mapped( + 'checkin_partner_ids.partner_id.id'): + return reservation.partner_id return False partner_id = fields.Many2one('res.partner', default=_default_partner_id, required=True) + email = fields.Char('E-mail', related='partner_id.email') + mobile = fields.Char('Mobile', related='partner_id.mobile') reservation_id = fields.Many2one( 'hotel.reservation', default=_default_reservation_id) - folio_id = fields.Many2one('hotel.reservation', - default=_default_folio_id, readonly=True) + folio_id = fields.Many2one('hotel.folio', + default=_default_folio_id, readonly=True, required=True) enter_date = fields.Date(default=_default_enter_date, required=True) exit_date = fields.Date(default=_default_exit_date, required=True) state = fields.Selection([('draft', 'Pending Entry'), @@ -65,6 +74,7 @@ class HotelCheckinPartner(models.Model): 'State', readonly=True, default=lambda *a: 'draft', track_visibility='onchange') + # Validation for Departure date is after arrival date. @api.multi @@ -89,10 +99,36 @@ class HotelCheckinPartner(models.Model): _('Departure date, is prior to arrival. Check it now. %s') % date_out) + @api.multi + @api.onchange('partner_id') + def _check_partner_id(self): + for record in self: + checkins = self.env['hotel.checkin.partner'].search([ + ('id','!=', record.id), + ('reservation_id','=', record.reservation_id.id) + ]) + if record.partner_id.id in checkins.mapped('partner_id.id'): + raise models.ValidationError( + _('This guest is already registered in the room')) + + @api.multi + @api.constrains('partner_id') + def _check_partner_id(self): + for record in self: + checkins = self.env['hotel.checkin.partner'].search([ + ('id','!=', record.id), + ('reservation_id','=', record.reservation_id.id) + ]) + if record.partner_id.id in checkins.mapped('partner_id.id'): + raise models.ValidationError( + _('This guest is already registered in the room')) + @api.multi def action_on_board(self): for record in self: record.state = 'booking' + if record.reservation_id.state == 'confirm': + record.reservation_id.state = 'booking' @api.multi def action_done(self): diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 60fab830d..61a4d1a37 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -34,6 +34,20 @@ class HotelFolio(models.Model): def _compute_qty_delivered_updateable(self): pass + @api.model + def _default_diff_invoicing(self): + """ + If the guest has an invoicing address set, + this method return diff_invoicing = True, else, return False + """ + if 'folio_id' in self.env.context: + folio = self.env['hotel.folio'].browse([ + self.env.context['folio_id'] + ]) + if folio.partner_id.id == folio.partner_invoice_id.id: + return False + return True + @api.depends('state', 'room_lines.invoice_status', 'service_ids.invoice_status') def _get_invoiced(self): """ @@ -188,7 +202,7 @@ class HotelFolio(models.Model): compute='_amount_all', track_visibility='always') #Checkin Fields----------------------------------------------------- - checkin_partner_ids = fields.One2many('hotel.checkin.partner', 'reservation_id') + checkin_partner_ids = fields.One2many('hotel.checkin.partner', 'folio_id') booking_pending = fields.Integer('Booking pending', compute='_compute_checkin_partner_count') checkin_partner_count = fields.Integer('Checkin counter', @@ -211,7 +225,20 @@ class HotelFolio(models.Model): string='Invoice Address', required=True, states={'done': [('readonly', True)]}, help="Invoice address for current sales order.") + partner_invoice_vat = fields.Char(related="partner_invoice_id.vat") + partner_invoice_name = fields.Char(related="partner_invoice_id.name") + partner_invoice_street = fields.Char(related="partner_invoice_id.street") + partner_invoice_street2 = fields.Char(related="partner_invoice_id.street") + partner_invoice_zip = fields.Char(related="partner_invoice_id.zip") + partner_invoice_city = fields.Char(related="partner_invoice_id.city") + partner_invoice_state_id = fields.Many2one(related="partner_invoice_id.state_id") + partner_invoice_country_id = fields.Many2one(related="partner_invoice_id.country_id") + partner_invoice_email = fields.Char(related="partner_invoice_id.email") + partner_invoice_lang = fields.Selection(related="partner_invoice_id.lang") + partner_invoice_type = fields.Selection(related="partner_invoice_id.type") + partner_invoice_parent_id = fields.Many2one(related="partner_invoice_id.parent_id") fiscal_position_id = fields.Many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position') + partner_diff_invoicing = fields.Boolean('Bill to another Address', default='_default_diff_invoicing') #WorkFlow Mail Fields----------------------------------------------- has_confirmed_reservations_to_send = fields.Boolean( @@ -253,7 +280,7 @@ class HotelFolio(models.Model): 'amount_total': amount_untaxed + amount_tax, }) - @api.depends('amount_total', 'payment_ids', 'return_ids') + @api.depends('amount_total', 'payment_ids', 'return_ids', 'reservation_type') @api.multi def compute_amount(self): acc_pay_obj = self.env['account.payment'] @@ -446,18 +473,20 @@ class HotelFolio(models.Model): 'partner_invoice_id': False, 'payment_term_id': False, 'fiscal_position_id': False, + 'partner_diff_invoicing': False, }) return addr = self.partner_id.address_get(['invoice']) pricelist = self.partner_id.property_product_pricelist and \ self.partner_id.property_product_pricelist.id or \ - self.env['ir.default'].sudo().get('res.config.settings', 'default_pricelist_id') + self.env['ir.default'].sudo().get('res.config.settings', 'default_pricelist_id') values = { 'pricelist_id': pricelist, 'payment_term_id': self.partner_id.property_payment_term_id and self.partner_id.property_payment_term_id.id or False, 'partner_invoice_id': addr['invoice'], - 'user_id': self.partner_id.user_id.id or self.env.uid + 'user_id': self.partner_id.user_id.id or self.env.uid, + 'partner_diff_invoicing': False if self.partner_id.id == addr['invoice'] else True } if self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note') and \ @@ -477,6 +506,20 @@ class HotelFolio(models.Model): self.reservation_type)} self.update(values) + @api.onchange('partner_diff_invoicing') + def onchange_partner_diff_invoicing(self): + if self.partner_diff_invoicing == False: + self.update({'partner_invoice_id': self.partner_id.id}) + elif self.partner_id == self.partner_invoice_id: + self.update({'partner_invoice_id': self.partner_id.address_get(['invoice'])['invoice'] or None}) + + @api.onchange('partner_invoice_id') + def onchange_partner_invoice_id(self): + if self.partner_invoice_id and not self.partner_invoice_id.parent_id and \ + self.partner_invoice_id != self.partner_id: + self.update({ + 'partner_invoice_parent_id': self.partner_id.id, + 'partner_invoice_type': 'invoice'}) @api.model def calcule_reservation_type(self, is_staff, current_type): @@ -587,64 +630,73 @@ class HotelFolio(models.Model): @api.depends('room_lines') def _compute_has_confirmed_reservations_to_send(self): has_to_send = False - for rline in self.room_lines: - if rline.splitted: - master_reservation = rline.parent_reservation or rline - has_to_send = self.env['hotel.reservation'].search_count([ - ('splitted', '=', True), - ('folio_id', '=', self.id), - ('to_send', '=', True), - ('state', 'in', ('confirm', 'booking')), - '|', - ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ]) > 0 - elif rline.to_send and rline.state in ('confirm', 'booking'): - has_to_send = True - break - self.has_confirmed_reservations_to_send = has_to_send + if self.reservation_type != 'out': + for rline in self.room_lines: + if rline.splitted: + master_reservation = rline.parent_reservation or rline + has_to_send = self.env['hotel.reservation'].search_count([ + ('splitted', '=', True), + ('folio_id', '=', self.id), + ('to_send', '=', True), + ('state', 'in', ('confirm', 'booking')), + '|', + ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ]) > 0 + elif rline.to_send and rline.state in ('confirm', 'booking'): + has_to_send = True + break + self.has_confirmed_reservations_to_send = has_to_send + else: + self.has_confirmed_reservations_to_send = False @api.depends('room_lines') def _compute_has_cancelled_reservations_to_send(self): has_to_send = False - for rline in self.room_lines: - if rline.splitted: - master_reservation = rline.parent_reservation or rline - has_to_send = self.env['hotel.reservation'].search_count([ - ('splitted', '=', True), - ('folio_id', '=', self.id), - ('to_send', '=', True), - ('state', '=', 'cancelled'), - '|', - ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ]) > 0 - elif rline.to_send and rline.state == 'cancelled': - has_to_send = True - break - self.has_cancelled_reservations_to_send = has_to_send + if self.reservation_type != 'out': + for rline in self.room_lines: + if rline.splitted: + master_reservation = rline.parent_reservation or rline + has_to_send = self.env['hotel.reservation'].search_count([ + ('splitted', '=', True), + ('folio_id', '=', self.id), + ('to_send', '=', True), + ('state', '=', 'cancelled'), + '|', + ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ]) > 0 + elif rline.to_send and rline.state == 'cancelled': + has_to_send = True + break + self.has_cancelled_reservations_to_send = has_to_send + else: + self.has_cancelled_reservations_to_send = False @api.depends('room_lines') def _compute_has_checkout_to_send(self): has_to_send = True - for rline in self.room_lines: - if rline.splitted: - master_reservation = rline.parent_reservation or rline - nreservs = self.env['hotel.reservation'].search_count([ - ('splitted', '=', True), - ('folio_id', '=', self.id), - ('to_send', '=', True), - ('state', '=', 'done'), - '|', - ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ]) - if nreservs != len(self.room_lines): + if self.reservation_type != 'out': + for rline in self.room_lines: + if rline.splitted: + master_reservation = rline.parent_reservation or rline + nreservs = self.env['hotel.reservation'].search_count([ + ('splitted', '=', True), + ('folio_id', '=', self.id), + ('to_send', '=', True), + ('state', '=', 'done'), + '|', + ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ]) + if nreservs != len(self.room_lines): + has_to_send = False + elif not rline.to_send or rline.state != 'done': has_to_send = False - elif not rline.to_send or rline.state != 'done': - has_to_send = False - break - self.has_checkout_to_send = has_to_send + break + self.has_checkout_to_send = has_to_send + else: + self.has_checkout_to_send = False @api.multi def send_reservation_mail(self): diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 8eedba68f..a5e63a641 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -78,6 +78,20 @@ class HotelReservation(models.Model): else: return default_departure_hour + @api.model + def _default_diff_invoicing(self): + """ + If the guest has an invoicing address set, + this method return diff_invoicing = True, else, return False + """ + if 'reservation_id' in self.env.context: + reservation = self.env['hotel.reservation'].browse([ + self.env.context['reservation_id'] + ]) + if reservation.partner_id.id == reservation.partner_invoice_id.id: + return False + return True + @api.depends('state', 'qty_to_invoice', 'qty_invoiced') def _compute_invoice_status(self): """ @@ -172,14 +186,12 @@ class HotelReservation(models.Model): ondelete='cascade') checkin = fields.Date('Check In', required=True, - default=_get_default_checkin, - track_visibility='onchange') + default=_get_default_checkin) checkout = fields.Date('Check Out', required=True, - default=_get_default_checkout, - track_visibility='onchange') - real_checkin = fields.Date('Real Check In', required=True, + default=_get_default_checkout) + real_checkin = fields.Date('Arrival', required=True, track_visibility='onchange') - real_checkout = fields.Date('Real Check Out', required=True, + real_checkout = fields.Date('Departure', required=True, track_visibility='onchange') arrival_hour = fields.Char('Arrival Hour', default=_get_default_arrival_hour, @@ -191,7 +203,21 @@ class HotelReservation(models.Model): required=True, track_visibility='onchange') partner_id = fields.Many2one(related='folio_id.partner_id') + partner_invoice_id = fields.Many2one(related='folio_id.partner_invoice_id') + partner_invoice_vat = fields.Char(related="partner_invoice_id.vat") + partner_invoice_name = fields.Char(related="partner_invoice_id.name") + partner_invoice_street = fields.Char(related="partner_invoice_id.street") + partner_invoice_street2 = fields.Char(related="partner_invoice_id.street") + partner_invoice_zip = fields.Char(related="partner_invoice_id.zip") + partner_invoice_city = fields.Char(related="partner_invoice_id.city") + partner_invoice_state_id = fields.Many2one(related="partner_invoice_id.state_id") + partner_invoice_country_id = fields.Many2one(related="partner_invoice_id.country_id") + partner_invoice_email = fields.Char(related="partner_invoice_id.email") + partner_invoice_lang = fields.Selection(related="partner_invoice_id.lang") closure_reason_id = fields.Many2one(related='folio_id.closure_reason_id') + partner_invoice_type = fields.Selection(related="partner_invoice_id.type") + partner_invoice_parent_id = fields.Many2one(related="partner_invoice_id.parent_id") + partner_diff_invoicing = fields.Boolean('Bill to another Address', default='_default_diff_invoicing') company_id = fields.Many2one(related='folio_id.company_id', string='Company', store=True, readonly=True) reservation_line_ids = fields.One2many('hotel.reservation.line', 'reservation_id', @@ -296,8 +322,10 @@ class HotelReservation(models.Model): readonly=True, store=True, compute='_compute_amount_set') - # FIXME discount per night - discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) + discount = fields.Float(string='Discount (€)', + digits=dp.get_precision('Discount'), + compute='_compute_discount', + store=True) analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') @@ -551,11 +579,14 @@ class HotelReservation(models.Model): @api.onchange('partner_id') def onchange_partner_id(self): + addr = self.partner_id.address_get(['invoice']) pricelist = self.partner_id.property_product_pricelist and \ self.partner_id.property_product_pricelist.id or \ self.env['ir.default'].sudo().get('res.config.settings', 'default_pricelist_id') values = { 'pricelist_id': pricelist, + 'partner_invoice_id': addr['invoice'], + 'partner_diff_invoicing': False if self.partner_id.id == addr['invoice'] else True } self.update(values) @@ -655,6 +686,21 @@ class HotelReservation(models.Model): ] return {'domain': {'room_id': domain_rooms}} + @api.onchange('partner_diff_invoicing') + def onchange_partner_diff_invoicing(self): + if self.partner_diff_invoicing == False: + self.update({'partner_invoice_id': self.partner_id.id}) + elif self.partner_id == self.partner_invoice_id: + self.update({'partner_invoice_id': self.partner_id.address_get(['invoice'])['invoice'] or None}) + + @api.onchange('partner_invoice_id') + def onchange_partner_invoice_id(self): + if self.partner_invoice_id and not self.partner_invoice_id.parent_id and \ + self.partner_invoice_id != self.partner_id: + self.update({ + 'partner_invoice_parent_id': self.partner_id.id, + 'partner_invoice_type': 'invoice'}) + @api.onchange('board_service_room_id') def onchange_board_service(self): if self.board_service_room_id: @@ -731,7 +777,6 @@ class HotelReservation(models.Model): for record in self: record.write({ 'state': 'cancelled', - 'discount': 100.0, }) if record.splitted: master_reservation = record.parent_reservation or record @@ -790,7 +835,13 @@ class HotelReservation(models.Model): return True return False - @api.depends('reservation_line_ids', 'reservation_line_ids.discount', 'tax_ids') + @api.depends('reservation_line_ids.discount') + def _compute_discount(self): + for record in self: + record.discount = sum(line.price * ((line.discount or 0.0) * 0.01) \ + for line in record.reservation_line_ids) + + @api.depends('reservation_line_ids.price', 'discount', 'tax_ids') def _compute_amount_reservation(self): """ Compute the amounts of the reservation. @@ -799,7 +850,7 @@ class HotelReservation(models.Model): amount_room = sum(record.reservation_line_ids.mapped('price')) if amount_room > 0: product = record.room_type_id.product_id - price = amount_room * (1 - (record.discount or 0.0) * 0.01) + price = amount_room - record.discount taxes = record.tax_ids.compute_all(price, record.currency_id, 1, product=product) record.update({ 'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])), @@ -858,7 +909,7 @@ class HotelReservation(models.Model): def action_pay_reservation(self): self.ensure_one() partner = self.partner_id.id - amount = min(self.amount_reservation, self.folio_pending_amount) + amount = min(self.price_room_services_set, self.folio_pending_amount) note = self.folio_id.name + ' (' + self.name + ')' view_id = self.env.ref('hotel.account_payment_view_form_folio').id return{ @@ -971,9 +1022,13 @@ class HotelReservation(models.Model): def _compute_checkin_partner_count(self): _logger.info('_compute_checkin_partner_count') for record in self: - record.checkin_partner_count = len(record.checkin_partner_ids) - record.checkin_partner_pending_count = (record.adults + record.children) \ - - len(record.checkin_partner_ids) + if record.reservation_type != 'out': + record.checkin_partner_count = len(record.checkin_partner_ids) + record.checkin_partner_pending_count = (record.adults + record.children) \ + - len(record.checkin_partner_ids) + else: + record.checkin_partner_count = 0 + record.checkin_partner_pending_count = 0 # https://www.odoo.com/es_ES/forum/ayuda-1/question/calculated-fields-in-search-filter-possible-118501 @api.multi @@ -991,15 +1046,11 @@ class HotelReservation(models.Model): @api.multi def action_checks(self): self.ensure_one() - return { - 'name': _('Checkins'), - 'view_type': 'form', - 'view_mode': 'tree,form', - 'res_model': 'hotel.checkin.partner', - 'type': 'ir.actions.act_window', - 'domain': [('reservation_id', '=', self.id)], - 'target': 'new', - } + action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] + action['views'] = [(self.env.ref('hotel.hotel_reservation_checkin_view_form').id, 'form')] + action['res_id'] = self.id + action['target'] = 'new' + return action """ RESERVATION SPLITTED ----------------------------------------------- diff --git a/hotel/models/hotel_service_line.py b/hotel/models/hotel_service_line.py index 2afcf7673..34a476d97 100644 --- a/hotel/models/hotel_service_line.py +++ b/hotel/models/hotel_service_line.py @@ -8,12 +8,12 @@ class HotelServiceLine(models.Model): _name = "hotel.service.line" _order = "date" - service_id = fields.Many2one('hotel.service', string='Service', + service_id = fields.Many2one('hotel.service', string='Service Room', ondelete='cascade', required=True, copy=False) date = fields.Date('Date') day_qty = fields.Integer('Units') - product_id = fields.Many2one(related='service_id.product_id') + product_id = fields.Many2one(related='service_id.product_id', store=True) @api.constrains('day_qty') def no_free_resources(self): diff --git a/hotel/views/hotel_checkin_partner_views.xml b/hotel/views/hotel_checkin_partner_views.xml index b19c5ce48..fb6f77f64 100644 --- a/hotel/views/hotel_checkin_partner_views.xml +++ b/hotel/views/hotel_checkin_partner_views.xml @@ -4,7 +4,7 @@ hotel.checkin.partner 20 - @@ -51,10 +51,13 @@ help="Get in" /> + + - - + + + @@ -75,10 +78,13 @@ help="Get in" /> + + - + + @@ -101,23 +107,38 @@ /> - - - + + + + + + + + + + + + diff --git a/hotel/views/hotel_folio_views.xml b/hotel/views/hotel_folio_views.xml index 315445b0d..5f517a19a 100644 --- a/hotel/views/hotel_folio_views.xml +++ b/hotel/views/hotel_folio_views.xml @@ -39,33 +39,6 @@
- - - - - -
- - - - - + + + + + - - - - - - + + @@ -251,7 +234,8 @@ - + - - - - - - - - - - + +