diff --git a/hotel/i18n/es.po b/hotel/i18n/es.po index a10548771..9e4b9e51c 100644 --- a/hotel/i18n/es.po +++ b/hotel/i18n/es.po @@ -4842,7 +4842,7 @@ msgid "Invoices" msgstr "Facturas" #. module: hotel -#: model:ir.model.fields,field_description:hotel.field_hotel_folio_invoices_amount +#: model:ir.model.fields,field_description:hotel.field_hotel_folio_pending_amount #: model:ir.model.fields,field_description:hotel.field_hotel_reservation_folio_pending_amount msgid "Invoices amount" msgstr "Cantidad Facturada" diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 54dd4fbe8..2169bc68e 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -23,50 +23,6 @@ from odoo.addons import decimal_precision as dp class HotelFolio(models.Model): - @api.model - def name_search(self, name='', args=None, operator='ilike', limit=100): - if args is None: - args = [] - args += ([('name', operator, name)]) - mids = self.search(args, limit=100) - return mids.name_get() - - @api.model - def _needaction_count(self, domain=None): - """ - Show a count of draft state folio on the menu badge. - @param self: object pointer - """ - return self.search_count([('state', '=', 'draft')]) - - @api.multi - def copy(self, default=None): - ''' - @param self: object pointer - @param default: dict of default values to be set - ''' - return super(HotelFolio, self).copy(default=default) - - @api.multi - def _invoiced(self, name, arg): - ''' - @param self: object pointer - @param name: Names of fields. - @param arg: User defined arguments - ''' - pass - # return self.env['sale.order']._invoiced(name, arg) - - @api.multi - def _invoiced_search(self, obj, name, args): - ''' - @param self: object pointer - @param name: Names of fields. - @param arg: User defined arguments - ''' - pass - # return self.env['sale.order']._invoiced_search(obj, name, args) - # @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity') def _get_invoice_qty(self): pass @@ -93,7 +49,7 @@ class HotelFolio(models.Model): _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin'] name = fields.Char('Folio Number', readonly=True, index=True, - default='New') + default=lambda self: _('New')) partner_id = fields.Many2one('res.partner', track_visibility='onchange') # partner_invoice_id = fields.Many2one('res.partner', @@ -108,6 +64,7 @@ class HotelFolio(models.Model): mobile = fields.Char('Mobile', related='partner_id.mobile') phone = fields.Char('Phone', related='partner_id.phone') + #Review: How to use state in folio? state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'), ('booking', 'On Board'), ('done', 'Out'), ('cancelled', 'Cancelled')], @@ -126,37 +83,27 @@ class HotelFolio(models.Model): help="Hotel services detail provide to " "customer and it will include in " "main Invoice.") - # service_line_ids = fields.One2many('hotel.service.line', 'folio_id', - # readonly=False, - # states={'done': [('readonly', True)]}, - # help="Hotel services detail provide to" - # "customer and it will include in " - # "main Invoice.") - # has no sense used as this way hotel_invoice_id = fields.Many2one('account.invoice', 'Invoice') company_id = fields.Many2one('res.company', 'Company') - # currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', - # string='Currency', readonly=True, required=True) + currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id', + string='Currency', readonly=True, required=True) - # pricelist_id = fields.Many2one('product.pricelist', - # string='Pricelist', - # required=True, - # readonly=True, - # states={'draft': [('readonly', False)], - # 'sent': [('readonly', False)]}, - # help="Pricelist for current sales order.") - # Monetary to Float - invoices_amount = fields.Float(compute='compute_invoices_amount', + pricelist_id = fields.Many2one('product.pricelist', + string='Pricelist', + required=True, + readonly=True, + states={'draft': [('readonly', False)], + 'sent': [('readonly', False)]}, + help="Pricelist for current folio.") + pending_amount = fields.Monetary(compute='compute_amount', store=True, string="Pending in Folio") - # Monetary to Float - refund_amount = fields.Float(compute='compute_invoices_amount', + refund_amount = fields.Monetary(compute='compute_amount', store=True, string="Payment Returns") - # Monetary to Float - invoices_paid = fields.Float(compute='compute_invoices_amount', + invoices_paid = fields.Monetary(compute='compute_amount', store=True, track_visibility='onchange', string="Payments") @@ -228,75 +175,27 @@ class HotelFolio(models.Model): amount_total = fields.Float(string='Total', store=True, readonly=True, track_visibility='always') - - def _compute_fix_price(self): - for record in self: - for res in record.room_lines: - if res.fix_total == True: - record.fix_price = True - break - else: - record.fix_price = False - - def action_recalcule_payment(self): - for record in self: - for res in record.room_lines: - res.on_change_checkin_checkout_product_id() - def _computed_rooms_char(self): for record in self: rooms = ', '.join(record.mapped('room_lines.room_id.name')) record.rooms_char = rooms - @api.model - def recompute_amount(self): - folios = self.env['hotel.folio'] - if folios: - folios = folios.filtered(lambda x: ( - x.name == folio_name)) - folios.compute_invoices_amount() - @api.multi def _compute_num_invoices(self): pass # for fol in self: # fol.num_invoices = len(self.mapped('invoice_ids.id')) - @api.model - def daily_plan(self): - _logger.info('daily_plan') - self._cr.execute("update hotel_folio set checkins_reservations = 0, \ - checkouts_reservations = 0 where checkins_reservations > 0 \ - or checkouts_reservations > 0") - folios_in = self.env['hotel.folio'].search([ - ('room_lines.is_checkin', '=', True) - ]) - folios_out = self.env['hotel.folio'].search([ - ('room_lines.is_checkout', '=', True) - ]) - for fol in folios_in: - count_checkin = fol.room_lines.search_count([ - ('is_checkin', '=', True), ('folio_id.id', '=', fol.id) - ]) - fol.write({'checkins_reservations': count_checkin}) - for fol in folios_out: - count_checkout = fol.room_lines.search_count([ - ('is_checkout', '=', True), - ('folio_id.id', '=', fol.id) - ]) - fol.write({'checkouts_reservations': count_checkout}) - return True - # @api.depends('order_line.price_total', 'payment_ids', 'return_ids') @api.multi - def compute_invoices_amount(self): - _logger.info('compute_invoices_amount') + def compute_amount(self): + _logger.info('compute_amount') @api.multi def action_pay(self): self.ensure_one() partner = self.partner_id.id - amount = self.invoices_amount + amount = self.pending_amount view_id = self.env.ref('hotel.view_account_payment_folio_form').id return{ 'name': _('Register Payment'), @@ -373,6 +272,202 @@ class HotelFolio(models.Model): 'domain': [('id', 'in', return_move_ids)], } + @api.multi + def action_folios_amount(self): + now_utc_dt = date_utils.now() + now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + reservations = self.env['hotel.reservation'].search([ + ('checkout', '<=', now_utc_str) + ]) + folio_ids = reservations.mapped('folio_id.id') + folios = self.env['hotel.folio'].search([('id', 'in', folio_ids)]) + folios = folios.filtered(lambda r: r.pending_amount > 0) + return { + 'name': _('Pending'), + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'hotel.folio', + 'type': 'ir.actions.act_window', + 'domain': [('id', 'in', folios.ids)] + } + + @api.multi + def go_to_currency_exchange(self): + ''' + when Money Exchange button is clicked then this method is called. + ------------------------------------------------------------------- + @param self: object pointer + ''' + _logger.info('go_to_currency_exchange') + pass + # cr, uid, context = self.env.args + # context = dict(context) + # for rec in self: + # if rec.partner_id.id and len(rec.room_lines) != 0: + # context.update({'folioid': rec.id, 'guest': rec.partner_id.id, + # 'room_no': rec.room_lines[0].product_id.name}) + # self.env.args = cr, uid, misc.frozendict(context) + # else: + # raise except_orm(_('Warning'), _('Please Reserve Any Room.')) + # return {'name': _('Currency Exchange'), + # 'res_model': 'currency.exchange', + # 'type': 'ir.actions.act_window', + # 'view_id': False, + # 'view_mode': 'form,tree', + # 'view_type': 'form', + # 'context': {'default_folio_no': context.get('folioid'), + # 'default_hotel_id': context.get('hotel'), + # 'default_guest_name': context.get('guest'), + # 'default_room_number': context.get('room_no') + # }, + # } + + @api.model + def create(self, vals, check=True): + if vals.get('name', _('New')) == _('New'): + if 'company_id' in vals: + vals['name'] = self.env['ir.sequence'].with_context(force_company=vals['company_id']).next_by_code('sale.order') or _('New') + else: + vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') or _('New') + + # Makes sure partner_invoice_id' and 'pricelist_id' are defined + if any(f not in vals for f in ['partner_invoice_id', 'partner_shipping_id', 'pricelist_id']): + partner = self.env['res.partner'].browse(vals.get('partner_id')) + addr = partner.address_get(['delivery', 'invoice']) + vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice']) + vals['pricelist_id'] = vals.setdefault('pricelist_id', partner.property_product_pricelist and partner.property_product_pricelist.id) + result = super(HotelFolio, self).create(vals) + return result + + @api.multi + @api.onchange('partner_id') + def onchange_partner_id(self): + ''' + When you change partner_id it will update the partner_invoice_id, + partner_shipping_id and pricelist_id of the hotel folio as well + --------------------------------------------------------------- + @param self: object pointer + ''' + _logger.info('onchange_partner_id') + pass + # self.update({ + # 'currency_id': self.env.ref('base.main_company').currency_id, + # 'partner_invoice_id': self.partner_id and self.partner_id.id or False, + # 'partner_shipping_id': self.partner_id and self.partner_id.id or False, + # 'pricelist_id': self.partner_id and self.partner_id.property_product_pricelist.id or False, + # }) + # """ + # Warning messajes saved in partner form to folios + # """ + # if not self.partner_id: + # return + # warning = {} + # title = False + # message = False + # partner = self.partner_id + # + # # If partner has no warning, check its company + # if partner.sale_warn == 'no-message' and partner.parent_id: + # partner = partner.parent_id + # + # if partner.sale_warn != 'no-message': + # # Block if partner only has warning but parent company is blocked + # if partner.sale_warn != 'block' and partner.parent_id \ + # and partner.parent_id.sale_warn == 'block': + # partner = partner.parent_id + # title = _("Warning for %s") % partner.name + # message = partner.sale_warn_msg + # warning = { + # 'title': title, + # 'message': message, + # } + # if self.partner_id.sale_warn == 'block': + # self.update({ + # 'partner_id': False, + # 'partner_invoice_id': False, + # 'partner_shipping_id': False, + # 'pricelist_id': False + # }) + # return {'warning': warning} + # + # if warning: + # return {'warning': warning} + + @api.multi + def action_invoice_create(self, grouped=False, states=None): + ''' + @param self: object pointer + ''' + pass + # if states is None: + # states = ['confirmed', 'done'] + # order_ids = [folio.order_id.id for folio in self] + # sale_obj = self.env['sale.order'].browse(order_ids) + # invoice_id = (sale_obj.action_invoice_create(grouped=False, + # states=['confirmed', + # 'done'])) + # for line in self: + # values = {'invoiced': True, + # 'state': 'progress' if grouped else 'progress', + # 'hotel_invoice_id': invoice_id + # } + # line.write(values) + # return invoice_id + + @api.multi + def advance_invoice(self): + pass + + ''' + WORKFLOW STATE + ''' + + @api.multi + def button_dummy(self): + ''' + @param self: object pointer + ''' + # for folio in self: + # folio.order_id.button_dummy() + return True + + @api.multi + def action_done(self): + for line in self.room_lines: + if line.state == "booking": + line.action_reservation_checkout() + + @api.multi + def action_cancel(self): + ''' + @param self: object pointer + ''' + pass + # for sale in self: + # if not sale.order_id: + # raise ValidationError(_('Order id is not available')) + # for invoice in sale.invoice_ids: + # invoice.state = 'cancel' + # sale.room_lines.action_cancel() + # sale.order_id.action_cancel() + + @api.multi + def print_quotation(self): + pass + # TODO- New report to reservation order + # self.order_id.filtered(lambda s: s.state == 'draft').write({ + # 'state': 'sent', + # }) + # return self.env.ref('sale.report_saleorder').report_action(self, data=data) + + @api.multi + def action_confirm(self): + _logger.info('action_confirm') + + + """ + CHECKIN/OUT PROCESS + """ @api.multi def action_checks(self): self.ensure_one() @@ -386,25 +481,59 @@ class HotelFolio(models.Model): 'domain': [('reservation_id', 'in', rooms)], 'target': 'new', } + + @api.model + def daily_plan(self): + _logger.info('daily_plan') + self._cr.execute("update hotel_folio set checkins_reservations = 0, \ + checkouts_reservations = 0 where checkins_reservations > 0 \ + or checkouts_reservations > 0") + folios_in = self.env['hotel.folio'].search([ + ('room_lines.is_checkin', '=', True) + ]) + folios_out = self.env['hotel.folio'].search([ + ('room_lines.is_checkout', '=', True) + ]) + for fol in folios_in: + count_checkin = fol.room_lines.search_count([ + ('is_checkin', '=', True), ('folio_id.id', '=', fol.id) + ]) + fol.write({'checkins_reservations': count_checkin}) + for fol in folios_out: + count_checkout = fol.room_lines.search_count([ + ('is_checkout', '=', True), + ('folio_id.id', '=', fol.id) + ]) + fol.write({'checkouts_reservations': count_checkout}) + return True @api.multi - def action_folios_amount(self): - now_utc_dt = date_utils.now() - now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) - reservations = self.env['hotel.reservation'].search([ - ('checkout', '<=', now_utc_str) - ]) - folio_ids = reservations.mapped('folio_id.id') - folios = self.env['hotel.folio'].search([('id', 'in', folio_ids)]) - folios = folios.filtered(lambda r: r.invoices_amount > 0) - return { - 'name': _('Pending'), - 'view_type': 'form', - 'view_mode': 'tree,form', - 'res_model': 'hotel.folio', - 'type': 'ir.actions.act_window', - 'domain': [('id', 'in', folios.ids)] - } + def _compute_cardex_count(self): + _logger.info('_compute_cardex_amount') + for fol in self: + num_cardex = 0 + pending = False + if fol.reservation_type == 'normal': + for reser in fol.room_lines: + if reser.state != 'cancelled' and \ + not reser.parent_reservation: + num_cardex += len(reser.cardex_ids) + fol.cardex_count = num_cardex + pending = 0 + for reser in fol.room_lines: + if reser.state != 'cancelled' and \ + not reser.parent_reservation: + pending += (reser.adults + reser.children) \ + - len(reser.cardex_ids) + if pending <= 0: + fol.cardex_pending = False + else: + fol.cardex_pending = True + fol.cardex_pending_num = pending + + """ + MAILING PROCESS + """ @api.depends('room_lines') def _compute_has_confirmed_reservations_to_send(self): @@ -468,221 +597,6 @@ class HotelFolio(models.Model): break self.has_checkout_to_send = has_to_send - @api.multi - def _compute_cardex_count(self): - _logger.info('_compute_cardex_amount') - for fol in self: - num_cardex = 0 - pending = False - if fol.reservation_type == 'normal': - for reser in fol.room_lines: - if reser.state != 'cancelled' and \ - not reser.parent_reservation: - num_cardex += len(reser.cardex_ids) - fol.cardex_count = num_cardex - pending = 0 - for reser in fol.room_lines: - if reser.state != 'cancelled' and \ - not reser.parent_reservation: - pending += (reser.adults + reser.children) \ - - len(reser.cardex_ids) - if pending <= 0: - fol.cardex_pending = False - else: - fol.cardex_pending = True - fol.cardex_pending_num = pending - - @api.multi - def go_to_currency_exchange(self): - ''' - when Money Exchange button is clicked then this method is called. - ------------------------------------------------------------------- - @param self: object pointer - ''' - _logger.info('go_to_currency_exchange') - pass - # cr, uid, context = self.env.args - # context = dict(context) - # for rec in self: - # if rec.partner_id.id and len(rec.room_lines) != 0: - # context.update({'folioid': rec.id, 'guest': rec.partner_id.id, - # 'room_no': rec.room_lines[0].product_id.name}) - # self.env.args = cr, uid, misc.frozendict(context) - # else: - # raise except_orm(_('Warning'), _('Please Reserve Any Room.')) - # return {'name': _('Currency Exchange'), - # 'res_model': 'currency.exchange', - # 'type': 'ir.actions.act_window', - # 'view_id': False, - # 'view_mode': 'form,tree', - # 'view_type': 'form', - # 'context': {'default_folio_no': context.get('folioid'), - # 'default_hotel_id': context.get('hotel'), - # 'default_guest_name': context.get('guest'), - # 'default_room_number': context.get('room_no') - # }, - # } - - @api.model - def create(self, vals, check=True): - """ - Overrides orm create method. - @param self: The object pointer - @param vals: dictionary of fields value. - @return: new record set for hotel folio. - """ - _logger.info('create') - if not 'service_line_ids' and 'folio_id' in vals: - tmp_room_lines = vals.get('room_lines', []) - vals['order_policy'] = vals.get('hotel_policy', 'manual') - vals.update({'room_lines': []}) - for line in (tmp_room_lines): - line[2].update({'folio_id': folio_id}) - vals.update({'room_lines': tmp_room_lines}) - folio_id = super(HotelFolio, self).create(vals) - else: - if not vals: - vals = {} - vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') - folio_id = super(HotelFolio, self).create(vals) - - return folio_id - - @api.multi - def write(self, vals): - if 'room_lines' in vals and vals['room_lines'][0][2] and 'reservation_line_ids' in vals['room_lines'][0][2] and vals['room_lines'][0][2]['reservation_line_ids'][0][0] == 5: - del vals['room_lines'] - return super(HotelFolio, self).write(vals) - - @api.multi - @api.onchange('partner_id') - def onchange_partner_id(self): - ''' - When you change partner_id it will update the partner_invoice_id, - partner_shipping_id and pricelist_id of the hotel folio as well - --------------------------------------------------------------- - @param self: object pointer - ''' - _logger.info('onchange_partner_id') - pass - # self.update({ - # 'currency_id': self.env.ref('base.main_company').currency_id, - # 'partner_invoice_id': self.partner_id and self.partner_id.id or False, - # 'partner_shipping_id': self.partner_id and self.partner_id.id or False, - # 'pricelist_id': self.partner_id and self.partner_id.property_product_pricelist.id or False, - # }) - # """ - # Warning messajes saved in partner form to folios - # """ - # if not self.partner_id: - # return - # warning = {} - # title = False - # message = False - # partner = self.partner_id - # - # # If partner has no warning, check its company - # if partner.sale_warn == 'no-message' and partner.parent_id: - # partner = partner.parent_id - # - # if partner.sale_warn != 'no-message': - # # Block if partner only has warning but parent company is blocked - # if partner.sale_warn != 'block' and partner.parent_id \ - # and partner.parent_id.sale_warn == 'block': - # partner = partner.parent_id - # title = _("Warning for %s") % partner.name - # message = partner.sale_warn_msg - # warning = { - # 'title': title, - # 'message': message, - # } - # if self.partner_id.sale_warn == 'block': - # self.update({ - # 'partner_id': False, - # 'partner_invoice_id': False, - # 'partner_shipping_id': False, - # 'pricelist_id': False - # }) - # return {'warning': warning} - # - # if warning: - # return {'warning': warning} - - @api.multi - def button_dummy(self): - ''' - @param self: object pointer - ''' - # for folio in self: - # folio.order_id.button_dummy() - return True - - @api.multi - def action_done(self): - for line in self.room_lines: - if line.state == "booking": - line.action_reservation_checkout() - - @api.multi - def action_invoice_create(self, grouped=False, states=None): - ''' - @param self: object pointer - ''' - pass - # if states is None: - # states = ['confirmed', 'done'] - # order_ids = [folio.order_id.id for folio in self] - # sale_obj = self.env['sale.order'].browse(order_ids) - # invoice_id = (sale_obj.action_invoice_create(grouped=False, - # states=['confirmed', - # 'done'])) - # for line in self: - # values = {'invoiced': True, - # 'state': 'progress' if grouped else 'progress', - # 'hotel_invoice_id': invoice_id - # } - # line.write(values) - # return invoice_id - - @api.multi - def advance_invoice(self): - pass - # order_ids = [folio.order_id.id for folio in self] - # sale_obj = self.env['sale.order'].browse(order_ids) - # invoices = action_invoice_create(self, grouped=True) - # return invoices - - @api.multi - def action_cancel(self): - ''' - @param self: object pointer - ''' - pass - # for sale in self: - # if not sale.order_id: - # raise ValidationError(_('Order id is not available')) - # for invoice in sale.invoice_ids: - # invoice.state = 'cancel' - # sale.room_lines.action_cancel() - # sale.order_id.action_cancel() - - - @api.multi - def action_confirm(self): - _logger.info('action_confirm') - - @api.multi - def print_quotation(self): - pass - # self.order_id.filtered(lambda s: s.state == 'draft').write({ - # 'state': 'sent', - # }) - # return self.env.ref('sale.report_saleorder').report_action(self, data=data) - - @api.multi - def action_cancel_draft(self): - _logger.info('action_confirm') - @api.multi def send_reservation_mail(self): ''' @@ -847,12 +761,6 @@ class HotelFolio(models.Model): template_rec.send_mail(reserv_rec.id, force_send=True) return True - @api.multi - def unlink(self): - # for record in self: - # record.order_id.unlink() - return super(HotelFolio, self).unlink() - @api.multi def get_grouped_reservations_json(self, state, import_all=False): self.ensure_one() @@ -882,3 +790,4 @@ class HotelFolio(models.Model): if not founded: info_grouped.append(vals) return sorted(sorted(info_grouped, key=lambda k: k['num'], reverse=True), key=lambda k: k['room_type']['id']) + diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 2330898ba..bd17a56f4 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -22,138 +22,6 @@ from odoo.addons import decimal_precision as dp class HotelReservation(models.Model): - @api.multi - def _generate_color(self): - self.ensure_one() - now_utc_dt = date_utils.now() - # unused variables - # diff_checkin_now = date_utils.date_diff(now_utc_dt, self.checkin, - # hours=False) - # diff_checkout_now = date_utils.date_diff(now_utc_dt, self.checkout, - # hours=False) - - ir_values_obj = self.env['ir.default'] - reserv_color = '#FFFFFF' - reserv_color_text = '#000000' - # FIXME added for migration - return ('#4E9DC4', '#000000') - - if self.reservation_type == 'staff': - reserv_color = ir_values_obj.get('res.config.settings', - 'color_staff') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', - 'color_letter_staff') - elif self.reservation_type == 'out': - reserv_color = ir_values_obj.get('res.config.settings', - 'color_dontsell') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', - 'color_letter_dontsell') - elif self.to_assign: - reserv_color = ir_values_obj.get('res.config.settings', - 'color_to_assign') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', - 'color_letter_to_assign') - elif self.state == 'draft': - reserv_color = ir_values_obj.get('res.config.settings', - 'color_pre_reservation') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', - 'color_letter_pre_reservation') - elif self.state == 'confirm': - if self.folio_id.invoices_amount == 0: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_reservation_pay') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_reservation_pay') - else: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_reservation') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_reservation') - elif self.state == 'booking': - if self.folio_id.invoices_amount == 0: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_stay_pay') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_stay_pay') - else: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_stay') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_stay') - else: - if self.folio_id.invoices_amount == 0: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_checkout') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_checkout') - else: - reserv_color = ir_values_obj.get( - 'res.config.settings', 'color_payment_pending') - reserv_color_text = ir_values_obj.get( - 'res.config.settings', 'color_letter_payment_pending') - return (reserv_color, reserv_color_text) - - @api.depends('state', 'reservation_type', 'folio_id.invoices_amount', 'to_assign') - def _compute_color(self): - _logger.info('_compute_color') - for rec in self: - colors = rec._generate_color() - rec.update({ - 'reserve_color': colors[0], - 'reserve_color_text': colors[1], - }) - rec.folio_id.color = colors[0] - - # hotel_reserv_obj = self.env['hotel.reservation'] - # if rec.splitted: - # master_reservation = rec.parent_reservation or rec - # splitted_reservs = hotel_reserv_obj.search([ - # ('splitted', '=', True), - # '|', ('parent_reservation', '=', master_reservation.id), - # ('id', '=', master_reservation.id), - # ('folio_id', '=', rec.folio_id.id), - # ('id', '!=', rec.id), - # ]) - # splitted_reservs.write({'reserve_color': rec.reserve_color}) - - @api.multi - def copy(self, default=None): - ''' - @param self: object pointer - @param default: dict of default values to be set - ''' - - return super(HotelReservation, self).copy(default=default) - - @api.multi - def _amount_line(self, field_name, arg): - ''' - @param self: object pointer - @param field_name: Names of fields. - @param arg: User defined arguments - ''' - return False - # return self.env['sale.order.line']._amount_line(field_name, arg) - - @api.multi - def _number_packages(self, field_name, arg): - ''' - @param self: object pointer - @param field_name: Names of fields. - @param arg: User defined arguments - ''' - return False - # return self.env['sale.order.line']._number_packages(field_name, arg) - - @api.multi - def set_call_center_user(self): - user = self.env['res.users'].browse(self.env.uid) - self.call_center = user.has_group('hotel.group_hotel_call') - def _get_default_checkin(self): folio = False if 'folio_id' in self._context: @@ -215,35 +83,43 @@ class HotelReservation(models.Model): if not(name == '' and operator == 'ilike'): args += [ '|', - ('folio_id.name', operator, name) - # FIXME Remove product inheritance - # ('product_id.name', operator, name) + ('folio_id.name', operator, name), + ('room_id.name', operator, name) ] return super(HotelReservation, self).name_search( name='', args=args, operator='ilike', limit=limit) @api.multi def name_get(self): - # FIXME Remove product inheritance result = [] for res in self: name = u'%s (%s)' % (res.folio_id.name, res.room_id.name) result.append((res.id, name)) return result - # # FIXME added for migration - # def _compute_qty_delivered_updateable(self): - # pass - # # FIXME added for migration - # def _compute_invoice_status(self): - # pass + @api.multi + def _computed_shared(self): + # Has this reservation more charges associates in folio?, Yes?, then, this is share folio ;) + for record in self: + if record.folio_id: + if len(record.folio_id.room_lines) > 1 or \ + record.folio_id.service_line_ids.filtered(lambda x: ( + x.ser_room_line != record.id)): + record.shared_folio = True + else: + record.shared_folio = False + + @api.depends('checkin', 'checkout') + def _computed_nights(self): + for res in self: + if res.checkin and res.checkout: + res.nights = (fields.Date.from_string(res.checkout) - fields.Date.from_string(res.checkin)).days _name = 'hotel.reservation' _description = 'Hotel Reservation' _inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin'] _order = "last_updated_res desc, name" - # The record's name should now be used for description of the reservation ? name = fields.Text('Reservation Description', required=True) room_id = fields.Many2one('hotel.room', string='Room') @@ -305,9 +181,9 @@ class HotelReservation(models.Model): store=True) service_line_ids = fields.One2many('hotel.service', 'ser_room_line') - # pricelist_id = fields.Many2one('product.pricelist', - # related='folio_id.pricelist_id', - # readonly="1") + pricelist_id = fields.Many2one('product.pricelist', + related='folio_id.pricelist_id', + readonly="1") cardex_ids = fields.One2many('cardex', 'reservation_id') # TODO: As cardex_count is a computed field, it can't not be used in a domain filer # Non-stored field hotel.reservation.cardex_count cannot be searched @@ -327,22 +203,7 @@ class HotelReservation(models.Model): parent_reservation = fields.Many2one('hotel.reservation', 'Parent Reservation') overbooking = fields.Boolean('Is Overbooking', default=False) - # To show de total amount line in read_only mode - amount_reservation = fields.Float('Total', - compute='_computed_amount_reservation', - store=True) - amount_reservation_services = fields.Float('Services Amount', - compute='_computed_amount_reservation', - store=True) - amount_room = fields.Float('Amount Room', compute="_computed_amount_reservation", - store=True) - amount_discount = fields.Float('Room with Discount', compute="_computed_amount_reservation", - store=True) - discount_type = fields.Selection([ - ('percent', 'Percent'), - ('fixed', 'Fixed')], 'Discount Type', default=lambda *a: 'percent') - discount_fixed = fields.Float('Fixed Discount') - + nights = fields.Integer('Nights', compute='_computed_nights', store=True) channel_type = fields.Selection([ ('door', 'Door'), @@ -351,8 +212,7 @@ class HotelReservation(models.Model): ('call', 'Call Center'), ('web', 'Web')], 'Sales Channel', default='door') last_updated_res = fields.Datetime('Last Updated') - # Monetary to Float - folio_pending_amount = fields.Float(related='folio_id.invoices_amount') + folio_pending_amount = fields.Monetary(related='folio_id.pending_amount') segmentation_ids = fields.Many2many(related='folio_id.segmentation_ids') shared_folio = fields.Boolean(compute='_computed_shared') #Used to notify is the reservation folio has other reservations or services @@ -364,7 +224,6 @@ class HotelReservation(models.Model): folio_internal_comment = fields.Text(string='Internal Folio Notes', related='folio_id.internal_comment') preconfirm = fields.Boolean('Auto confirm to Save', default=True) - call_center = fields.Boolean(compute='set_call_center_user') to_send = fields.Boolean('To Send', default=True) has_confirmed_reservations_to_send = fields.Boolean( related='folio_id.has_confirmed_reservations_to_send', @@ -375,17 +234,14 @@ class HotelReservation(models.Model): has_checkout_to_send = fields.Boolean( related='folio_id.has_checkout_to_send', readonly=True) - # fix_total = fields.Boolean(compute='_compute_fix_total') - # fix_folio_pending = fields.Boolean(related='folio_id.fix_price') - # order_line = fields.One2many('sale.order.line', 'order_id', string='Order Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True, auto_join=True) # product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product') # product_uom = fields.Many2one('product.uom', string='Unit of Measure', required=True) # product_uom_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0) - # currency_id = fields.Many2one('res.currency', - # related='pricelist_id.currency_id', - # string='Currency', readonly=True, required=True) + currency_id = fields.Many2one('res.currency', + related='pricelist_id.currency_id', + string='Currency', readonly=True, required=True) # invoice_status = fields.Selection([ # ('upselling', 'Upselling Opportunity'), # ('invoiced', 'Fully Invoiced'), @@ -401,113 +257,503 @@ class HotelReservation(models.Model): # digits=dp.get_precision('Product Unit of Measure')) # qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0) # qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True) - price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0) - # Monetary to Float - price_subtotal = fields.Float(compute='_compute_amount', string='Subtotal', readonly=True, store=True) - # Monetary to Float - price_total = fields.Float(compute='_compute_amount', string='Total', readonly=True, store=True) - + price_subtotal = fields.Monetary(compute='_compute_amount_reservation', string='Subtotal', readonly=True, store=True) + price_total = fields.Monetary(compute='_compute_amount_reservation', string='Total', readonly=True, store=True) + price_tax = fields.Float(compute='_compute_amount_reservation', string='Taxes', readonly=True, store=True) + currency_id = fields.Many2one(related='folio_id.currency_id', store=True, string='Currency', readonly=True) # 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'), default=0.0) # analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags') + @api.model + def create(self, vals): + if not 'reservation_type' in vals or not vals.get('reservation_type'): + vals.update({'reservation_type': 'normal'}) + if 'folio_id' in vals: + folio = self.env["hotel.folio"].browse(vals['folio_id']) + vals.update({'channel_type': folio.channel_type}) + elif 'partner_id' in vals: + folio_vals = {'partner_id':int(vals.get('partner_id')), + 'channel_type': vals.get('channel_type')} + # Create the folio in case of need (To Allow create direct reservations) + folio = self.env["hotel.folio"].create(folio_vals) + vals.update({'folio_id': folio.id, + 'reservation_type': vals.get('reservation_type'), + 'channel_type': vals.get('channel_type')}) + vals.update({ + 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + }) + if folio: + record = super(HotelReservation, self).create(vals) + if (record.state == 'draft' and record.folio_id.state == 'sale') or \ + record.preconfirm: + record.confirm() + record._compute_color() + return record - def action_recalcule_payment(self): + @api.multi + def write(self, vals): for record in self: - for res in record.folio_id.room_lines: - res.on_change_checkin_checkout_product_id() + if ('checkin' in vals and record.checkin != vals['checkin']) or \ + ('checkout' in vals and record.checkout != vals['checkout']) or \ + ('state' in vals and record.state != vals['state']) : + vals.update({'to_send': True}) - def _computed_folio_name(self): - for res in self: - res.folio_name = res.folio_id.name + '-' + \ - res.folio_id.date_order + pricesChanged = ('checkin' in vals or \ + 'checkout' in vals or \ + 'discount' in vals) + import wdb; wdb.set_trace() + if pricesChanged or 'state' in vals or 'room_type_id' in vals or 'to_assign' in vals: + vals.update({ + 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) + }) + res = super(HotelReservation, self).write(vals) + return res @api.multi - def send_reservation_mail(self): - return self.folio_id.send_reservation_mail() + def overbooking_button(self): + self.ensure_one() + return self.write({'overbooking': not self.overbooking}) @api.multi - def send_exit_mail(self): - return self.folio_id.send_exit_mail() + def open_folio(self): + action = self.env.ref('hotel.open_hotel_folio1_form_tree_all').read()[0] + if self.folio_id: + action['views'] = [(self.env.ref('hotel.view_hotel_folio1_form').id, 'form')] + action['res_id'] = self.folio_id.id + else: + action = {'type': 'ir.actions.act_window_close'} + return action @api.multi - def send_cancel_mail(self): - return self.folio_id.send_cancel_mail() + def open_reservation_form(self): + action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] + action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')] + action['res_id'] = self.id + return action @api.multi - def action_checks(self): + def generate_copy_values(self, checkin=False, checkout=False): self.ensure_one() return { - 'name': _('Cardexs'), - 'view_type': 'form', - 'view_mode': 'tree,form', - 'res_model': 'cardex', - 'type': 'ir.actions.act_window', - 'domain': [('reservation_id', '=', self.id)], - 'target': 'new', + 'name': self.name, + 'adults': self.adults, + 'children': self.children, + 'checkin': checkin or self.checkin, + 'checkout': checkout or self.checkout, + 'folio_id': self.folio_id.id, + # 'product_id': self.product_id.id, + 'parent_reservation': self.parent_reservation.id, + 'state': self.state, + 'overbooking': self.overbooking, + 'price_unit': self.price_unit, + 'splitted': self.splitted, + # 'virtual_room_id': self.virtual_room_id.id, + 'room_type_id': self.room_type_id.id, } - @api.multi - def _computed_shared(self): + @api.onchange('adults', 'room_id') + def check_capacity(self): + if self.room_id: + if self.room_id.capacity < self.adults: + self.adults = self.room_id.capacity + raise UserError( + _('%s people do not fit in this room! ;)') % (persons)) + + @api.constrains('adults') + def _check_adults(self): for record in self: - if record.folio_id: - if len(record.folio_id.room_lines) > 1 or \ - record.folio_id.service_line_ids.filtered(lambda x: ( - x.ser_room_line != record.id)): - record.shared_folio = True - else: - record.shared_folio = False + if record.adults > record.room_id.capacity: + raise ValidationError( + _("Reservation persons can't be higher than room capacity")) + if record.adults == 0: - @api.depends('checkin', 'checkout') - def _computed_nights(self): - for res in self: - if res.checkin and res.checkout: - res.nights = (fields.Date.from_string(res.checkout) - fields.Date.from_string(res.checkin)).days + raise ValidationError(_("Reservation has no adults")) - @api.depends('reservation_line_ids.price') - def _computed_amount_reservation(self): - _logger.info('_computed_amount_reservation') - # FIXME commented during migration - # import wdb; wdb.set_trace() - # for res in self: - # amount_service = amount_room = 0 - # for line in res.reservation_line_ids: - # amount_room += line.price - # for service in res.service_line_ids: - # # We must calc the line to can show the price in edit mode - # # on smartbutton whithout having to wait to save. - # total_line = service.price_unit * service.product_uom_qty - # discount = (service.discount * total_line) / 100 - # amount_service += total_line - discount - # res.amount_room = amount_room #To view price_unit with read_only - # if res.discount_type == 'fixed' and amount_room > 0: - # res.discount = (res.discount_fixed * 100) / amount_room # WARNING Posible division by zero - # else: - # res.discount_fixed = (res.discount * amount_room) / 100 - # res.amount_discount = amount_room - res.discount_fixed - # res.price_unit = amount_room - # res.amount_reservation_services = amount_service - # res.amount_reservation = res.amount_discount + amount_service #To the smartbutton + @api.onchange('room_type_id') + def on_change_room_type_id(self): + if self.room_type_id: + # TODO: Remove this check once added as contrain + if not self.checkin: + self.checkin = self._get_default_checkin() + if not self.checkout: + self.checkout = self._get_default_checkout() + # days_diff = date_utils.date_diff( + # self.checkin, self.checkout, hours=False) + days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days + rlines = self.prepare_reservation_lines( + self.checkin, + days_diff, + update_old_prices=True) + self.reservation_line_ids = rlines['commands'] - @api.multi - def _compute_cardex_count(self): - _logger.info('_compute_cardex_count') - for res in self: - res.cardex_count = len(res.cardex_ids) - res.cardex_pending_num = (res.adults + res.children) \ - - len(res.cardex_ids) - if (res.adults + res.children - len(res.cardex_ids)) <= 0: - res.cardex_pending = False + if self.reservation_type in ['staff', 'out']: + self.price_unit = 0.0 + self.cardex_pending = 0 else: - res.cardex_pending = True + self.price_unit = rlines['total_price'] + + @api.onchange('checkin', 'checkout', 'room_id', + 'reservation_type', 'room_type_id') + def on_change_checkin_checkout_product_id(self): + _logger.info('on_change_checkin_checkout_product_id') + # import wdb; wdb.set_trace() + if not self.checkin: + self.checkin = self._get_default_checkin() + if not self.checkout: + self.checkout = self._get_default_checkout() + # WARNING Need a review + # if self.product_id: + # self.tax_id = [(6, False, self.virtual_room_id.product_id.taxes_id.ids)] + # room = self.env['hotel.room'].search([ + # ('product_id', '=', self.product_id.id) + # ]) + # if self.adults == 0: + # self.adults = room.capacity + # if not self.virtual_room_id and room.price_virtual_room: + # self.virtual_room_id = room.price_virtual_room.id + if self.room_id: + # self.tax_id = [(6, False, self.room_type_id.product_id.taxes_id.ids)] + if self.adults == 0: + self.adults = self.room_id.capacity + if not self.room_type_id: + self.room_type_id = self.room_id.room_type_id + self.tax_id = [(6, False, self.room_id.room_type_id.taxes_id.ids)] + + checkin_dt = fields.Date.from_string(self.checkin) + checkout_dt = fields.Date.from_string(self.checkout) + + if checkin_dt >= checkout_dt: + self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) + + if self.state == 'confirm' and self.checkin_is_today(): + self.is_checkin = True + folio = self.env['hotel.folio'].browse(self.folio_id.id) + if folio: + folio.checkins_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), ('is_checkin', '=', True) + ]) + + if self.state == 'booking' and self.checkout_is_today(): + self.is_checkout = False + folio = self.env['hotel.folio'].browse(self.folio_id.id) + if folio: + folio.checkouts_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), ('is_checkout', '=', True) + ]) + + # ensure checkin and checkout are correct before changing the name + if self.room_type_id: + checkin_str = checkin_dt.strftime('%d/%m/%Y') + checkout_str = checkout_dt.strftime('%d/%m/%Y') + self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\ + + checkout_str + days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days + rlines = self.prepare_reservation_lines( + self.checkin, + days_diff, + update_old_prices=False) + self.reservation_line_ids = rlines['commands'] + + if self.reservation_type in ['staff', 'out']: + self.price_unit = 0.0 + self.cardex_pending = 0 + else: + self.price_unit = rlines['total_price'] - # https://www.odoo.com/es_ES/forum/ayuda-1/question/calculated-fields-in-search-filter-possible-118501 @api.multi - def _search_cardex_pending(self, operator, value): - recs = self.search([]).filtered(lambda x: x.cardex_pending is True) - if recs: - return [('id', 'in', [x.id for x in recs])] + # TODO: This onchange has almost the same values than on_change_checkin_checkout_product_id... join ? + @api.onchange('checkin', 'checkout', 'room_type_id', 'room_id') + def on_change_checkout(self): + ''' + When you change checkin or checkout it will checked it + and update the qty of hotel folio line + ----------------------------------------------------------------- + @param self: object pointer + ''' + _logger.info('on_change_checkout') + self.ensure_one() + if self.overbooking: + return + + occupied = self.env['hotel.reservation'].get_reservations( + self.checkin, + fields.Date.from_string(self.checkout).strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( + lambda r: r.id != self._origin.id) + rooms_occupied = occupied.mapped('room_id.id') + if self.room_id and self.room_id.id in rooms_occupied: + warning_msg = _('You tried to change \ + reservation with room those already reserved in this \ + reservation period') + raise ValidationError(warning_msg) + domain_rooms = [ + # ('isroom', '=', True), + ('id', 'not in', rooms_occupied) + ] + # if self.check_rooms: + # if self.room_type_id: + # domain_rooms.append( + # ('categ_id.id', '=', self.room_type_id.cat_id.id) + # ) + # if self.virtual_room_id: + # room_categories = self.virtual_room_id.room_type_ids.mapped( + # 'cat_id.id') + # link_virtual_rooms = self.virtual_room_id.room_ids\ + # | self.env['hotel.room'].search([ + # ('categ_id.id', 'in', room_categories)]) + # room_ids = link_virtual_rooms.mapped('room_id.id') + # domain_rooms.append(('id', 'in', room_ids)) + return {'domain': {'room_id': domain_rooms}} + + """ + COMPUTE RESERVE COLOR + """ + + @api.multi + def _generate_color(self): + self.ensure_one() + now_utc_dt = date_utils.now() + # unused variables + # diff_checkin_now = date_utils.date_diff(now_utc_dt, self.checkin, + # hours=False) + # diff_checkout_now = date_utils.date_diff(now_utc_dt, self.checkout, + # hours=False) + + ir_values_obj = self.env['ir.default'] + reserv_color = '#FFFFFF' + reserv_color_text = '#000000' + # FIXME added for migration + return ('#4E9DC4', '#000000') + + if self.reservation_type == 'staff': + reserv_color = ir_values_obj.get('res.config.settings', + 'color_staff') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', + 'color_letter_staff') + elif self.reservation_type == 'out': + reserv_color = ir_values_obj.get('res.config.settings', + 'color_dontsell') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', + 'color_letter_dontsell') + elif self.to_assign: + reserv_color = ir_values_obj.get('res.config.settings', + 'color_to_assign') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', + 'color_letter_to_assign') + elif self.state == 'draft': + reserv_color = ir_values_obj.get('res.config.settings', + 'color_pre_reservation') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', + 'color_letter_pre_reservation') + elif self.state == 'confirm': + if self.folio_id.pending_amount == 0: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_reservation_pay') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_reservation_pay') + else: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_reservation') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_reservation') + elif self.state == 'booking': + if self.folio_id.pending_amount == 0: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_stay_pay') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_stay_pay') + else: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_stay') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_stay') + else: + if self.folio_id.pending_amount == 0: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_checkout') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_checkout') + else: + reserv_color = ir_values_obj.get( + 'res.config.settings', 'color_payment_pending') + reserv_color_text = ir_values_obj.get( + 'res.config.settings', 'color_letter_payment_pending') + return (reserv_color, reserv_color_text) + + @api.depends('state', 'reservation_type', 'folio_id.pending_amount', 'to_assign') + def _compute_color(self): + _logger.info('_compute_color') + for rec in self: + colors = rec._generate_color() + rec.update({ + 'reserve_color': colors[0], + 'reserve_color_text': colors[1], + }) + rec.folio_id.color = colors[0] + + # hotel_reserv_obj = self.env['hotel.reservation'] + # if rec.splitted: + # master_reservation = rec.parent_reservation or rec + # splitted_reservs = hotel_reserv_obj.search([ + # ('splitted', '=', True), + # '|', ('parent_reservation', '=', master_reservation.id), + # ('id', '=', master_reservation.id), + # ('folio_id', '=', rec.folio_id.id), + # ('id', '!=', rec.id), + # ]) + # splitted_reservs.write({'reserve_color': rec.reserve_color}) + + """ + STATE WORKFLOW + """ + + @api.multi + def confirm(self): + ''' + @param self: object pointer + ''' + _logger.info('confirm') + hotel_folio_obj = self.env['hotel.folio'] + hotel_reserv_obj = self.env['hotel.reservation'] + for r in self: + vals = {} + if r.cardex_ids: + vals.update({'state': 'booking'}) + else: + vals.update({'state': 'confirm'}) + if r.checkin_is_today(): + vals.update({'is_checkin': True}) + folio = hotel_folio_obj.browse(r.folio_id.id) + folio.checkins_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), ('is_checkin', '=', True)]) + r.write(vals) + + if r.splitted: + master_reservation = r.parent_reservation or r + splitted_reservs = hotel_reserv_obj.search([ + ('splitted', '=', True), + '|', ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ('folio_id', '=', r.folio_id.id), + ('id', '!=', r.id), + ('state', '!=', 'confirm') + ]) + splitted_reservs.confirm() + return True + + @api.multi + def button_done(self): + ''' + @param self: object pointer + ''' + for res in self: + res.action_reservation_checkout() + return True + + @api.multi + def action_cancel(self): + for record in self: + record.write({ + 'state': 'cancelled', + 'discount': 100.0, + }) + if record.checkin_is_today: + record.is_checkin = False + folio = self.env['hotel.folio'].browse(record.folio_id.id) + folio.checkins_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), + ('is_checkin', '=', True) + ]) + + if record.splitted: + master_reservation = record.parent_reservation or record + splitted_reservs = self.env['hotel.reservation'].search([ + ('splitted', '=', True), + '|', ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ('folio_id', '=', record.folio_id.id), + ('id', '!=', record.id), + ('state', '!=', 'cancelled') + ]) + splitted_reservs.action_cancel() + record.folio_id.compute_amount() + + @api.multi + def draft(self): + for record in self: + record.write({'state': 'draft'}) + + if record.splitted: + master_reservation = record.parent_reservation or record + splitted_reservs = self.env['hotel.reservation'].search([ + ('splitted', '=', True), + '|', ('parent_reservation', '=', master_reservation.id), + ('id', '=', master_reservation.id), + ('folio_id', '=', record.folio_id.id), + ('id', '!=', record.id), + ('state', '!=', 'draft') + ]) + splitted_reservs.draft() + + """ + PRICE PROCESS + """ + + @api.depends('reservation_line_ids.price', 'reservation_line_ids.discount', 'tax_id') + def _compute_amount_reservation(self): + """ + Compute the amounts of the reservation. + """ + for line in self: + amount_room = 0 + for day in line.reservation_line_ids: + amount_room += day.price + if amount_room > 0: + product = line.room_type_id.product_id + price = amount_room * (1 - (line.discount or 0.0) / 100.0) + taxes = line.tax_id.compute_all(price, line.currency_id, 1, product=product) + line.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.multi + def prepare_reservation_lines(self, dfrom, days, + update_old_prices=False): + self.ensure_one() + total_price = 0.0 + cmds = [(5, False, False)] + #~ pricelist_id = self.env['ir.default'].sudo().get( + #~ 'res.config.settings', 'parity_pricelist_id') + pricelist_id = int(self.pricelist_id) + product_id = self.room_type_id.product_id + old_lines_days = self.mapped('reservation_line_ids.date') + for i in range(0, days): + idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(DEFAULT_SERVER_DATE_FORMAT) + if update_old_prices or (idate not in old_lines_days): + product = product_id.with_context( + lang=self.partner_id.lang, + partner=self.partner_id.id, + quantity=1, + date=idate, + pricelist=pricelist_id, + uom=self.room_type_id.product_id.uom_id.id) + line_price = self.env['account.tax']._fix_tax_included_price_company(product.price, product.taxes_id, self.tax_id, self.company_id) + else: + line = self.reservation_line_ids.filtered(lambda r: r.date == idate) + line_price = line.price + cmds.append((0, False, { + 'date': idate, + 'price': line_price + })) + total_price += line_price + return {'total_price': total_price, 'commands': cmds} @api.multi def action_pay_folio(self): @@ -540,6 +786,112 @@ class HotelReservation(models.Model): 'target': 'new', } + """ + AVAILABILTY PROCESS + """ + + @api.model + def get_reservations(self, dfrom, dto): + """ + @param self: The object pointer + @param dfrom: range date from + @param dto: range date to + @return: array with the reservations _confirmed_ between dfrom and dto + """ + domain = [('reservation_line_ids.date', '>=', dfrom), + ('reservation_line_ids.date', '<', dto), + ('state', '!=', 'cancelled'), + ('overbooking', '=', False)] + reservations = self.env['hotel.reservation'].search(domain) + return self.env['hotel.reservation'].search(domain) + + @api.model + def get_reservations_dates(self, dfrom, dto, room_type=False): + """ + @param self: The object pointer + @param dfrom: range date from + @param dto: range date to + @return: dictionary of lists with reservations (a hash of arrays!) + with the reservations dates between dfrom and dto + reservations_dates + {'2018-07-30': [hotel.reservation(29,), hotel.reservation(30,), + hotel.reservation(31,)], + '2018-07-31': [hotel.reservation(22,), hotel.reservation(35,), + hotel.reservation(36,)], + } + """ + domain = [('date', '>=', dfrom), + ('date', '<', dto)] + lines = self.env['hotel.reservation.line'].search(domain) + reservations_dates = {} + for record in lines: + # kumari.net/index.php/programming/programmingcat/22-python-making-a-dictionary-of-lists-a-hash-of-arrays + # reservations_dates.setdefault(record.date,[]).append(record.reservation_id.room_type_id) + reservations_dates.setdefault(record.date, []).append( + [record.reservation_id, record.reservation_id.room_type_id]) + return reservations_dates + + # TODO: Use default values on checkin /checkout is empty + @api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking') + def check_dates(self): + """ + 1.-When date_order is less then checkin date or + Checkout date should be greater than the checkin date. + 3.-Check the reservation dates are not occuped + """ + _logger.info('check_dates') + if fields.Date.from_string(self.checkin) >= fields.Date.from_string(self.checkout): + raise ValidationError(_('Room line Check In Date Should be \ + less than the Check Out Date!')) + if not self.overbooking and not self._context.get("ignore_avail_restrictions", False): + occupied = self.env['hotel.reservation'].get_reservations( + self.checkin, + self.checkout) + occupied = occupied.filtered( + lambda r: r.room_id.id == self.room_id.id + and r.id != self.id) + occupied_name = ','.join(str(x.room_id.name) for x in occupied) + if occupied: + warning_msg = _('You tried to change/confirm \ + reservation with room those already reserved in this \ + reservation period: %s ') % occupied_name + raise ValidationError(warning_msg) + + """ + CHECKIN/OUT PROCESS + """ + + @api.multi + def _compute_cardex_count(self): + _logger.info('_compute_cardex_count') + for res in self: + res.cardex_count = len(res.cardex_ids) + res.cardex_pending_num = (res.adults + res.children) \ + - len(res.cardex_ids) + if (res.adults + res.children - len(res.cardex_ids)) <= 0: + res.cardex_pending = False + else: + res.cardex_pending = True + + # https://www.odoo.com/es_ES/forum/ayuda-1/question/calculated-fields-in-search-filter-possible-118501 + @api.multi + def _search_cardex_pending(self, operator, value): + recs = self.search([]).filtered(lambda x: x.cardex_pending is True) + if recs: + return [('id', 'in', [x.id for x in recs])] + + @api.multi + def action_reservation_checkout(self): + for record in self: + record.state = 'done' + if record.checkout_is_today(): + record.is_checkout = False + folio = self.env['hotel.folio'].browse(self.folio_id.id) + folio.checkouts_reservations = folio.room_lines.search_count([ + ('folio_id', '=', folio.id), + ('is_checkout', '=', True) + ]) + @api.model def daily_plan(self): _logger.info('daily_plan') @@ -598,93 +950,21 @@ class HotelReservation(models.Model): return self.checkout == today @api.multi - def action_cancel(self): - for record in self: - record.write({ - 'state': 'cancelled', - 'discount': 100.0, - }) - if record.checkin_is_today: - record.is_checkin = False - folio = self.env['hotel.folio'].browse(record.folio_id.id) - folio.checkins_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), - ('is_checkin', '=', True) - ]) - - if record.splitted: - master_reservation = record.parent_reservation or record - splitted_reservs = self.env['hotel.reservation'].search([ - ('splitted', '=', True), - '|', ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ('folio_id', '=', record.folio_id.id), - ('id', '!=', record.id), - ('state', '!=', 'cancelled') - ]) - splitted_reservs.action_cancel() - record.folio_id.compute_invoices_amount() - - @api.multi - def draft(self): - for record in self: - record.write({'state': 'draft'}) - - if record.splitted: - master_reservation = record.parent_reservation or record - splitted_reservs = self.env['hotel.reservation'].search([ - ('splitted', '=', True), - '|', ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ('folio_id', '=', record.folio_id.id), - ('id', '!=', record.id), - ('state', '!=', 'draft') - ]) - splitted_reservs.draft() - - @api.multi - def action_reservation_checkout(self): - for record in self: - record.state = 'done' - if record.checkout_is_today(): - record.is_checkout = False - folio = self.env['hotel.folio'].browse(self.folio_id.id) - folio.checkouts_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), - ('is_checkout', '=', True) - ]) - - @api.multi - def overbooking_button(self): + def action_checks(self): self.ensure_one() - return self.write({'overbooking': not self.overbooking}) + return { + 'name': _('Cardexs'), + 'view_type': 'form', + 'view_mode': 'tree,form', + 'res_model': 'cardex', + 'type': 'ir.actions.act_window', + 'domain': [('reservation_id', '=', self.id)], + 'target': 'new', + } - @api.multi - def open_master(self): - self.ensure_one() - if not self.parent_reservation: - raise ValidationError(_("This is the parent reservation")) - action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] - action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')] - action['res_id'] = self.parent_reservation.id - return action - - @api.multi - def open_folio(self): - action = self.env.ref('hotel.open_hotel_folio1_form_tree_all').read()[0] - if self.folio_id: - action['views'] = [(self.env.ref('hotel.view_hotel_folio1_form').id, 'form')] - action['res_id'] = self.folio_id.id - else: - action = {'type': 'ir.actions.act_window_close'} - return action - - @api.multi - def open_reservation_form(self): - action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] - action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')] - action['res_id'] = self.id - return action + """ + RESERVATION SPLITTED + """ @api.multi def get_real_checkin_checkout(self): @@ -780,483 +1060,30 @@ class HotelReservation(models.Model): # This function generate a usable dictionary with reservation values # for copy purposes. # ''' + @api.multi - def generate_copy_values(self, checkin=False, checkout=False): + def open_master(self): self.ensure_one() - return { - 'name': self.name, - 'adults': self.adults, - 'children': self.children, - 'checkin': checkin or self.checkin, - 'checkout': checkout or self.checkout, - 'folio_id': self.folio_id.id, - # 'product_id': self.product_id.id, - 'parent_reservation': self.parent_reservation.id, - 'state': self.state, - 'overbooking': self.overbooking, - 'price_unit': self.price_unit, - 'splitted': self.splitted, - # 'virtual_room_id': self.virtual_room_id.id, - 'room_type_id': self.room_type_id.id, - } + if not self.parent_reservation: + raise ValidationError(_("This is the parent reservation")) + action = self.env.ref('hotel.open_hotel_reservation_form_tree_all').read()[0] + action['views'] = [(self.env.ref('hotel.view_hotel_reservation_form').id, 'form')] + action['res_id'] = self.parent_reservation.id + return action - @api.model - def create(self, vals): - """ - Overrides orm create method. - @param self: The object pointer - @param vals: dictionary of fields value. - @return: new record set for hotel folio line. - """ - # import wdb; wdb.set_trace() - if not 'reservation_type' in vals or not vals.get('reservation_type'): - vals.update({'reservation_type': 'normal'}) - if 'folio_id' in vals: - folio = self.env["hotel.folio"].browse(vals['folio_id']) - vals.update({'channel_type': folio.channel_type}) - elif 'partner_id' in vals: - folio_vals = {'partner_id':int(vals.get('partner_id')), - 'channel_type': vals.get('channel_type')} - # Create the folio in case of need - folio = self.env["hotel.folio"].create(folio_vals) - vals.update({'folio_id': folio.id, - 'reservation_type': vals.get('reservation_type'), - 'channel_type': vals.get('channel_type')}) - user = self.env['res.users'].browse(self.env.uid) - if user.has_group('hotel.group_hotel_call'): - vals.update({'to_assign': True, - 'channel_type': 'call'}) - vals.update({ - 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - }) - if folio: - record = super(HotelReservation, self).create(vals) - # TODO: Check Capacity should be done before creating the Folio - if record.adults > record.room_id.capacity: - raise ValidationError( - _("Reservation persons can't be higher than room capacity")) - if record.adults == 0: - raise ValidationError(_("Reservation has no adults")) - if (record.state == 'draft' and record.folio_id.state == 'sale') or \ - record.preconfirm: - record.confirm() - record._compute_color() - return record + """ + MAILING PROCESS + """ @api.multi - def write(self, vals): - for record in self: - if ('checkin' in vals and record.checkin != vals['checkin']) or \ - ('checkout' in vals and record.checkout != vals['checkout']) or \ - ('state' in vals and record.state != vals['state']) or \ - ('amount_discount' in vals and record.amount_discount != vals['amount_discount']): - vals.update({'to_send': True}) - - pricesChanged = ('checkin' in vals or \ - 'checkout' in vals or \ - 'discount' in vals) - # vals.update({ - # 'edit_room': False, - # }) - # if pricesChanged or 'state' in vals or 'virtual_room_id' in vals or 'to_assign' in vals: - if pricesChanged or 'state' in vals or 'room_type_id' in vals or 'to_assign' in vals: - vals.update({ - 'last_updated_res': date_utils.now(hours=True).strftime(DEFAULT_SERVER_DATETIME_FORMAT) - }) - user = self.env['res.users'].browse(self.env.uid) - if user.has_group('hotel.group_hotel_call'): - vals.update({ - 'to_read': True, - 'to_assign': True, - }) - res = super(HotelReservation, self).write(vals) - if pricesChanged: - for record in self: - if record.reservation_type in ('staff', 'out'): - record.update({'price_unit': 0}) - record.folio_id.compute_invoices_amount() - checkin = vals.get('checkin', record.checkin) - checkout = vals.get('checkout', record.checkout) - # days_diff = date_utils.date_diff(checkin, - # checkout, hours=False) - days_diff = (fields.Date.from_string(checkout) - fields.Date.from_string(checkin)).days - rlines = record.prepare_reservation_lines(checkin, days_diff) - record.update({ - 'reservation_line_ids': rlines['commands'], - 'price_unit': rlines['total_price'], - }) - return res - - # FIXME add room.id to on change after removing inheritance - @api.onchange('adults', 'children') - def check_capacity(self): - if self.room_id: - persons = self.adults + self.children - if self.room_id.capacity < persons: - self.adults = self.room_id.capacity - self.children = 0 - raise UserError( - _('%s people do not fit in this room! ;)') % (persons)) - - @api.onchange('room_type_id') - def on_change_room_type_id(self): - # import wdb; wdb.set_trace() - # TODO: Remove this check once added as contrain - if not self.checkin: - self.checkin = self._get_default_checkin() - if not self.checkout: - self.checkout = self._get_default_checkout() - # days_diff = date_utils.date_diff( - # self.checkin, self.checkout, hours=False) - days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days - rlines = self.prepare_reservation_lines( - self.checkin, - days_diff, - update_old_prices=True) - self.reservation_line_ids = rlines['commands'] - - if self.reservation_type in ['staff', 'out']: - self.price_unit = 0.0 - self.cardex_pending = 0 - else: - self.price_unit = rlines['total_price'] - - @api.onchange('checkin', 'checkout', 'room_id', - 'reservation_type', 'room_type_id') - def on_change_checkin_checkout_product_id(self): - _logger.info('on_change_checkin_checkout_product_id') - # import wdb; wdb.set_trace() - if not self.checkin: - self.checkin = self._get_default_checkin() - if not self.checkout: - self.checkout = self._get_default_checkout() - # WARNING Need a review - # if self.product_id: - # self.tax_id = [(6, False, self.virtual_room_id.product_id.taxes_id.ids)] - # room = self.env['hotel.room'].search([ - # ('product_id', '=', self.product_id.id) - # ]) - # if self.adults == 0: - # self.adults = room.capacity - # if not self.virtual_room_id and room.price_virtual_room: - # self.virtual_room_id = room.price_virtual_room.id - if self.room_id: - # self.tax_id = [(6, False, self.room_type_id.product_id.taxes_id.ids)] - if self.adults == 0: - self.adults = self.room_id.capacity - if not self.room_type_id: - self.room_type_id = self.room_id.room_type_id - self.tax_id = [(6, False, self.room_id.room_type_id.taxes_id.ids)] - - checkin_dt = fields.Date.from_string(self.checkin) - checkout_dt = fields.Date.from_string(self.checkout) - - if checkin_dt >= checkout_dt: - self.checkout = (fields.Date.from_string(self.checkin) + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) - # ensure checkin and checkout are correct before changing the name - if self.room_type_id: - checkin_str = checkin_dt.strftime('%d/%m/%Y') - checkout_str = checkout_dt.strftime('%d/%m/%Y') - self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\ - + checkout_str - - if self.state == 'confirm' and self.checkin_is_today(): - self.is_checkin = True - folio = self.env['hotel.folio'].browse(self.folio_id.id) - if folio: - folio.checkins_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), ('is_checkin', '=', True) - ]) - - if self.state == 'booking' and self.checkout_is_today(): - self.is_checkout = False - folio = self.env['hotel.folio'].browse(self.folio_id.id) - if folio: - folio.checkouts_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), ('is_checkout', '=', True) - ]) - - days_diff = (fields.Date.from_string(self.checkout) - fields.Date.from_string(self.checkin)).days - rlines = self.prepare_reservation_lines( - self.checkin, - days_diff, - update_old_prices=False) - self.reservation_line_ids = rlines['commands'] - - if self.reservation_type in ['staff', 'out']: - self.price_unit = 0.0 - self.cardex_pending = 0 - else: - self.price_unit = rlines['total_price'] - - # FIXME add room.id to on change after removing inheritance - @api.model - def get_availability(self, checkin, checkout, dbchanged=True, - dtformat=DEFAULT_SERVER_DATE_FORMAT): - _logger.info('get_availability') - date_start = date_utils.get_datetime(checkin) - date_end = date_utils.get_datetime(checkout) - # Not count end day of the reservation - date_diff = date_utils.date_diff(date_start, date_end, hours=False) - - hotel_vroom_obj = self.env['hotel.room.type'] - # virtual_room_avail_obj = self.env['hotel.room.type.availability'] - - rooms_avail = [] - # FIXME con una relacion Many2one, cada habitacion está en un solo tipo - # por lo que la disponibilidad para la habitación se tiene que buscar - # directamente en ese tipo - # vrooms = hotel_vroom_obj.search([ - # ('room_ids.product_id', '=', self.room_id) - # ]) - # FIXME Si lo de arriba es cierto, este bucle sobra. Sólo hay un room_type_id - for vroom in self.room_type_id: - rdays = [] - for i in range(0, date_diff): - ndate_dt = date_start + timedelta(days=i) - ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT) - avail = len(hotel_vroom_obj.check_availability_virtual_room( - ndate_str, - ndate_str, - room_type_id=vroom.id)) - if not dbchanged: - avail = avail - 1 - # Can be less than zero because 'avail' can not equal - # with the real 'avail' (ex. Online Limits) - avail = max(min(avail, vroom.total_rooms_count), 0) - rdays.append({ - 'date': ndate_dt.strftime(dtformat), - 'avail': avail, - }) - ravail = {'id': vroom.id, 'days': rdays} - rooms_avail.append(ravail) - - return rooms_avail + def send_reservation_mail(self): + return self.folio_id.send_reservation_mail() @api.multi - def prepare_reservation_lines(self, dfrom, days, - update_old_prices=False): - self.ensure_one() - total_price = 0.0 - cmds = [(5, False, False)] - # import wdb; - # wdb.set_trace() - # TO-DO: Redesign relation between hotel.reservation - # and sale.order.line to allow manage days by units in order - #~ if self.invoice_status == 'invoiced' and not self.splitted: - #~ raise ValidationError(_("This reservation is already invoiced. \ - #~ To expand it you must create a new reservation.")) - # hotel_tz = self.env['ir.default'].sudo().get( - # 'res.config.settings', 'hotel_tz') - # start_date_utc_dt = date_utils.get_datetime(str_start_date_utc) - # start_date_dt = date_utils.dt_as_timezone(start_date_utc_dt, hotel_tz) - - # room = self.env['hotel.room'].search([ - # ('product_id', '=', self.product_id.id) - # ]) - # product_id = self.room_id.sale_price_type == 'vroom' and self.room_id.price_virtual_room.product_id - product_id = self.room_type_id.product_id - pricelist_id = self.env['ir.default'].sudo().get( - 'res.config.settings', 'parity_pricelist_id') - if pricelist_id: - pricelist_id = int(pricelist_id) - old_lines_days = self.mapped('reservation_line_ids.date') - for i in range(0, days): - idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(DEFAULT_SERVER_DATE_FORMAT) - if update_old_prices or idate not in old_lines_days: - # prod = product_id.with_context( - # lang=self.partner_id.lang, - # partner=self.partner_id.id, - # quantity=1, - # date=ndate_str, - # pricelist=pricelist_id, - # uom=self.product_uom.id) - # prod = product_id.with_context( - # lang=self.partner_id.lang, - # partner=self.partner_id.id, - # quantity=1, - # date=ndate_str, # AttributeError("'product.product' object has no attribute 'date'",) - # pricelist=pricelist_id) - # line_price = prod.price - line_price = product_id.list_price - else: - line = self.reservation_line_ids.filtered(lambda r: r.date == idate) - line_price = line.price - cmds.append((0, False, { - 'date': idate, - 'price': line_price - })) - total_price += line_price - return {'total_price': total_price, 'commands': cmds} - - @api.constrains('adults') - def _check_adults(self): - if self.adults == 0 and self.room_id and self.room_id.capacity > 0: - self.adults = self.room_id.capacity + def send_exit_mail(self): + return self.folio_id.send_exit_mail() @api.multi - # TODO: This onchange has almost the same values than on_change_checkin_checkout_product_id... join ? - @api.onchange('checkin', 'checkout', 'room_type_id', 'room_id') - def on_change_checkout(self): - ''' - When you change checkin or checkout it will checked it - and update the qty of hotel folio line - ----------------------------------------------------------------- - @param self: object pointer - ''' - _logger.info('on_change_checkout') - self.ensure_one() - if self.overbooking: - return + def send_cancel_mail(self): + return self.folio_id.send_cancel_mail() - occupied = self.env['hotel.reservation'].get_reservations( - self.checkin, - fields.Date.from_string(self.checkout).strftime(DEFAULT_SERVER_DATE_FORMAT)).filtered( - lambda r: r.id != self._origin.id) - rooms_occupied = occupied.mapped('room_id.id') - if self.room_id and self.room_id.id in rooms_occupied: - warning_msg = _('You tried to change \ - reservation with room those already reserved in this \ - reservation period') - raise ValidationError(warning_msg) - domain_rooms = [ - # ('isroom', '=', True), - ('id', 'not in', rooms_occupied) - ] - # if self.check_rooms: - # if self.room_type_id: - # domain_rooms.append( - # ('categ_id.id', '=', self.room_type_id.cat_id.id) - # ) - # if self.virtual_room_id: - # room_categories = self.virtual_room_id.room_type_ids.mapped( - # 'cat_id.id') - # link_virtual_rooms = self.virtual_room_id.room_ids\ - # | self.env['hotel.room'].search([ - # ('categ_id.id', 'in', room_categories)]) - # room_ids = link_virtual_rooms.mapped('room_id.id') - # domain_rooms.append(('id', 'in', room_ids)) - return {'domain': {'room_id': domain_rooms}} - - @api.multi - def confirm(self): - ''' - @param self: object pointer - ''' - _logger.info('confirm') - hotel_folio_obj = self.env['hotel.folio'] - hotel_reserv_obj = self.env['hotel.reservation'] - for r in self: - vals = {} - if r.cardex_ids: - vals.update({'state': 'booking'}) - else: - vals.update({'state': 'confirm'}) - if r.checkin_is_today(): - vals.update({'is_checkin': True}) - folio = hotel_folio_obj.browse(r.folio_id.id) - folio.checkins_reservations = folio.room_lines.search_count([ - ('folio_id', '=', folio.id), ('is_checkin', '=', True)]) - r.write(vals) - - if r.splitted: - master_reservation = r.parent_reservation or r - splitted_reservs = hotel_reserv_obj.search([ - ('splitted', '=', True), - '|', ('parent_reservation', '=', master_reservation.id), - ('id', '=', master_reservation.id), - ('folio_id', '=', r.folio_id.id), - ('id', '!=', r.id), - ('state', '!=', 'confirm') - ]) - splitted_reservs.confirm() - return True - - @api.multi - def button_done(self): - ''' - @param self: object pointer - ''' - for res in self: - res.action_reservation_checkout() - return True - - @api.one - def copy_data(self, default=None): - ''' - @param self: object pointer - @param default: dict of default values to be set - ''' - return False - # FIXME added for migration - # line_id = self.order_line_id.id - # sale_line_obj = self.env['sale.order.line'].browse(line_id) - # return sale_line_obj.copy_data(default=default) - - # TODO: Use default values on checkin /checkout is empty - @api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking') - def check_dates(self): - """ - 1.-When date_order is less then checkin date or - Checkout date should be greater than the checkin date. - 3.-Check the reservation dates are not occuped - """ - _logger.info('check_dates') - if fields.Date.from_string(self.checkin) >= fields.Date.from_string(self.checkout): - raise ValidationError(_('Room line Check In Date Should be \ - less than the Check Out Date!')) - if not self.overbooking and not self._context.get("ignore_avail_restrictions", False): - occupied = self.env['hotel.reservation'].get_reservations( - self.checkin, - self.checkout) - occupied = occupied.filtered( - lambda r: r.room_id.id == self.room_id.id - and r.id != self.id) - occupied_name = ','.join(str(x.room_id.name) for x in occupied) - if occupied: - warning_msg = _('You tried to change/confirm \ - reservation with room those already reserved in this \ - reservation period: %s ') % occupied_name - raise ValidationError(warning_msg) - - @api.model - def get_reservations(self, dfrom, dto): - """ - @param self: The object pointer - @param dfrom: range date from - @param dto: range date to - @return: array with the reservations _confirmed_ between dfrom and dto - """ - # QUESTION dto must be strictly < - domain = [('reservation_line_ids.date', '>=', dfrom), - ('reservation_line_ids.date', '<', dto), - ('state', '!=', 'cancelled'), - ('overbooking', '=', False)] - reservations = self.env['hotel.reservation'].search(domain) - return self.env['hotel.reservation'].search(domain) - - @api.model - def get_reservations_dates(self, dfrom, dto): - """ - @param self: The object pointer - @param dfrom: range date from - @param dto: range date to - @return: dictionary of lists with reservations (a hash of arrays!) - with the reservations dates between dfrom and dto - reservations_dates - {'2018-07-30': [[hotel.reservation(29,), hotel.room.type(1,)], - [hotel.reservation(30,), hotel.room.type(1,)], - [hotel.reservation(31,), hotel.room.type(3,)]], - '2018-07-31': [[hotel.reservation(30,), hotel.room.type(1,)], - [hotel.reservation(31,), hotel.room.type(3,)]]} - """ - domain = [('date', '>=', dfrom), - ('date', '<', dto)] - lines = self.env['hotel.reservation.line'].search(domain) - reservations_dates = {} - for record in lines: - # kumari.net/index.php/programming/programmingcat/22-python-making-a-dictionary-of-lists-a-hash-of-arrays - # reservations_dates.setdefault(record.date,[]).append(record.reservation_id.room_type_id) - reservations_dates.setdefault(record.date, []).append( - [record.reservation_id, record.reservation_id.room_type_id]) - return reservations_dates diff --git a/hotel/models/hotel_room.py b/hotel/models/hotel_room.py index 27d452841..a0d799cfa 100644 --- a/hotel/models/hotel_room.py +++ b/hotel/models/hotel_room.py @@ -11,18 +11,12 @@ class HotelRoom(models.Model): """ _name = 'hotel.room' _description = 'Hotel Room' - # The record's name - name = fields.Char('Room Name', required=True) - # Used for activate records - active = fields.Boolean('Active', default=True) - # Used for ordering - sequence = fields.Integer('Sequence', default=0) - _order = "sequence, room_type_id, name" - - # each room has only one type (Many2one) + + name = fields.Char('Room Name', required=True) + active = fields.Boolean('Active', default=True) + sequence = fields.Integer('Sequence', default=0) room_type_id = fields.Many2one('hotel.room.type', 'Hotel Room Type') - floor_id = fields.Many2one('hotel.floor', 'Ubication', help='At which floor the room is located.') # TODO Q. Should the amenities be on the Room Type ? - @@ -30,16 +24,12 @@ class HotelRoom(models.Model): 'room_amenities', 'rcateg_id', string='Room Amenities', help='List of room amenities.') - # max number of adults and children per room max_adult = fields.Integer('Max Adult') max_child = fields.Integer('Max Child') - # maximum capacity of the room capacity = fields.Integer('Capacity') # FIXME not used to_be_cleaned = fields.Boolean('To be Cleaned', default=False) - shared_room = fields.Boolean('Shared Room', default=False) - description_sale = fields.Text( 'Sale Description', translate=True, help="A description of the Product that you want to communicate to " diff --git a/hotel/models/inherit_account_payment.py b/hotel/models/inherit_account_payment.py index d111cdc6d..687ba0db7 100644 --- a/hotel/models/inherit_account_payment.py +++ b/hotel/models/inherit_account_payment.py @@ -90,5 +90,5 @@ class AccountPayment(models.Model): raise except_orm(_('Warning'), _('This pay is related with \ more than one Reservation.')) else: - fol.compute_invoices_amount() + fol.compute_amount() return res diff --git a/hotel/models/inherit_payment_return.py b/hotel/models/inherit_payment_return.py index b19d47260..d4188b7c7 100644 --- a/hotel/models/inherit_payment_return.py +++ b/hotel/models/inherit_payment_return.py @@ -36,4 +36,4 @@ class PaymentReturn(models.Model): payments = self.env['account.payment'].search([('move_line_ids','in',line.move_line_ids.ids)]) folio_ids += payments.mapped('folio_id.id') folios = self.env['hotel.folio'].browse(folio_ids) - folios.compute_invoices_amount() + folios.compute_amount() diff --git a/hotel/views/hotel_folio.xml b/hotel/views/hotel_folio.xml index 3ac843ffb..52f05db24 100644 --- a/hotel/views/hotel_folio.xml +++ b/hotel/views/hotel_folio.xml @@ -84,10 +84,10 @@ id="payment_smart_button" icon="fa-money" name="action_pay" - attrs="{'invisible': ['|',('invoices_amount','<=',0)]}"> + attrs="{'invisible': ['|',('pending_amount','<=',0)]}">