# Copyright 2017 Alexandre Díaz # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from datetime import timedelta from odoo import models, fields, api, _ from odoo.exceptions import ValidationError from odoo.tools import DEFAULT_SERVER_DATE_FORMAT class MassiveChangesWizard(models.TransientModel): _name = 'hotel.wizard.massive.changes' # Default methods @api.model def _get_default_hotel(self): return self.env.user.hotel_id # Fields declaration hotel_id = fields.Many2one('hotel.property', 'Hotel', required=True, default=_get_default_hotel) pricelist_id = fields.Many2one( 'product.pricelist', 'Pricelist Plan', domain="[('pricelist_type', '=', 'daily'), ('hotel_ids.id', '=', context.get('hotel_id'))]") restriction_id = fields.Many2one( 'hotel.room.type.restriction', 'Restriction Plan', domain="[('hotel_id.id', '=', context.get('hotel_id'))]") room_type_ids = fields.Many2many( 'hotel.room.type', string="Room Types", domain="[('hotel_id.id', '=', context.get('hotel_id'))]") section = fields.Selection([ ('restrictions', 'Restrictions'), ('prices', 'Pricelist'), ], string='Section', default='prices') date_start = fields.Date('Start Date', required=True) date_end = fields.Date('End Date', required=True) dmo = fields.Boolean('Monday', default=True) dtu = fields.Boolean('Tuesday', default=True) dwe = fields.Boolean('Wednesday', default=True) dth = fields.Boolean('Thursday', default=True) dfr = fields.Boolean('Friday', default=True) dsa = fields.Boolean('Saturday', default=True) dsu = fields.Boolean('Sunday', default=True) applied_on = fields.Selection([ ('0', 'Global'), ('1', 'Room Type'), ], string='Applied On', default='0') # Restriction fields change_min_stay = fields.Boolean(default=False, help="Mark for a massive change in this field.") min_stay = fields.Integer("Min. Stay") change_min_stay_arrival = fields.Boolean(default=False, help="Mark for a massive change in this field.") min_stay_arrival = fields.Integer("Min. Stay Arrival") change_max_stay = fields.Boolean(default=False, help="Mark for a massive change in this field.") max_stay = fields.Integer("Max. Stay") change_max_stay_arrival = fields.Boolean(default=False, help="Mark for a massive change in this field.") max_stay_arrival = fields.Integer("Max. Stay Arrival") change_closed = fields.Boolean(default=False, help="Mark for a massive change in this field.") closed = fields.Boolean('Closed') change_closed_departure = fields.Boolean(default=False, help="Mark for a massive change in this field.") closed_departure = fields.Boolean('Closed Departure') change_closed_arrival = fields.Boolean(default=False, help="Mark for a massive change in this field.") closed_arrival = fields.Boolean('Closed Arrival') # Pricelist fields price = fields.Char('Price', help="Can use '+','-' \ or '%'...\nExamples:\n a) +12.3 \ \t> Increase the price in 12.3\n \ b) -1.45% \t> Substract 1.45%\n c) 45 \ \t\t> Sets the price to 45") # Constraints and onchanges @api.constrains('pricelist_id') def _check_pricelist_id(self): for record in self: if record.pricelist_id.pricelist_type != 'daily': raise ValidationError(_("A daily pricelist plan is required for massive changes.") + " " + _("Please review the hotel configuration before proceed.")) @api.constrains('hotel_id') def _check_hotel_id(self): for record in self: if record.section == 'prices' and record.pricelist_id.hotel_ids != record.hotel_id: raise ValidationError(_("Mismatch between hotel and pricelist plan.") + " " + _("The pricelist plan does not belongs to the current hotel.")) if record.section == 'restriction' and record.restriction_id.hotel_id != record.hotel_id: raise ValidationError(_("Mismatch between hotel and restriction plan.") + " " + _("The restriction plan does not belongs to the current hotel.")) @api.onchange('date_start') def onchange_date_start(self): self.ensure_one() self.date_end = self.date_start # Action methods @api.multi def _get_restrictions_values(self, record): self.ensure_one() vals = {} if record.change_min_stay: vals.update({'min_stay': record.min_stay}) if record.change_min_stay_arrival: vals.update({'min_stay_arrival': record.min_stay_arrival}) if record.change_max_stay: vals.update({'max_stay': record.max_stay}) if record.change_max_stay_arrival: vals.update({'max_stay_arrival': record.max_stay_arrival}) if record.change_closed: vals.update({'closed': record.closed}) if record.change_closed_departure: vals.update({'closed_departure': record.closed_departure}) if record.change_closed_arrival: vals.update({'closed_arrival': record.closed_arrival}) return vals @api.model def _save_restrictions(self, ndate, room_types, record): hotel_room_type_re_it_obj = self.env['hotel.room.type.restriction.item'] domain = [ ('date', '>=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)), ('date', '<=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)), ('restriction_id', '=', record.restriction_id.id), ] for room_type in room_types: vals = self._get_restrictions_values(record) if not any(vals): continue rrest_item_ids = hotel_room_type_re_it_obj.search( domain+[('room_type_id', '=', room_type.id)]) if any(rrest_item_ids): rrest_item_ids.write(vals) else: vals.update({ 'date': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT), 'restriction_id': record.restriction_id.id, 'room_type_id': room_type.id, }) hotel_room_type_re_it_obj.create(vals) @api.model def _save_prices(self, ndate, room_types, record): product_pricelist_item_obj = self.env['product.pricelist.item'] price = 0.0 operation = 'a' if record.price[0] == '+' or record.price[0] == '-': if record.price[-1] == '%': price = float(record.price[1:-1]) operation = 'ap' if (record.price[0] == '+') else 'sp' else: price = float(record.price[1:]) operation = 'a' if (record.price[0] == '+') else 's' else: if record.price[-1] == '%': price = float(record.price[:-1]) operation = 'np' else: price = float(record.price) operation = 'n' domain = [ ('pricelist_id', '=', record.pricelist_id.id), ('date_start', '>=', ndate.strftime( DEFAULT_SERVER_DATE_FORMAT)), ('date_end', '<=', ndate.strftime( DEFAULT_SERVER_DATE_FORMAT)), ('compute_price', '=', 'fixed'), ('applied_on', '=', '1_product'), ] for room_type in room_types: prod_tmpl_id = room_type.product_id.product_tmpl_id pricelist_item_ids = product_pricelist_item_obj.search( domain+[('product_tmpl_id', '=', prod_tmpl_id.id)]) if any(pricelist_item_ids): if operation != 'n': for pli in pricelist_item_ids: pli_price = pli.fixed_price if operation == 'a': pli.write({ 'fixed_price': pli_price + price}) elif operation == 'ap': pli.write({'fixed_price': pli_price + price * pli_price * 0.01}) elif operation == 's': pli.write({ 'fixed_price': pli_price - price}) elif operation == 'sp': pli.write({'fixed_price': pli_price - price * pli_price * 0.01}) elif operation == 'np': pli.write({'fixed_price': price * pli_price * 0.01}) else: pricelist_item_ids.write({'fixed_price': price}) else: product_pricelist_item_obj.create({ 'pricelist_id': record.pricelist_id.id, 'date_start': ndate.strftime( DEFAULT_SERVER_DATE_FORMAT), 'date_end': ndate.strftime( DEFAULT_SERVER_DATE_FORMAT), 'compute_price': 'fixed', 'applied_on': '1_product', 'product_tmpl_id': prod_tmpl_id.id, 'fixed_price': price, }) @api.model def _save(self, ndate, room_types, record): if record.section == 'restrictions': self._save_restrictions(ndate, room_types, record) elif record.section == 'prices': self._save_prices(ndate, room_types, record) @api.multi def _do_massive_change(self): hotel_room_type_obj = self.env['hotel.room.type'] for record in self: date_start_dt = fields.Date.from_string(record.date_start) date_end_dt = fields.Date.from_string(record.date_end) # Use min '1' for same date diff_days = abs((date_end_dt - date_start_dt).days) + 1 wedays = (record.dmo, record.dtu, record.dwe, record.dth, record.dfr, record.dsa, record.dsu) # filter room types to the pricelist or restriction hotel room_types = record.room_type_ids if record.applied_on == '1' \ else hotel_room_type_obj.search([('hotel_id', '=', record.hotel_id.id)]) for i in range(0, diff_days): ndate = date_start_dt + timedelta(days=i) if not wedays[ndate.timetuple()[6]]: continue self._save(ndate, room_types, record) return True @api.multi def massive_change(self): self._do_massive_change() return { "type": "ir.actions.do_nothing", } @api.multi def massive_change_close(self): self._do_massive_change() return True # TODO: method deprecated and not used anywhere @api.multi def is_valid_date(self, chkdate): self.ensure_one() wday = chkdate.timetuple()[6] wedays = (self.dmo, self.dtu, self.dwe, self.dth, self.dfr, self.dsa, self.dsu) return (chkdate >= self.date_start and chkdate <= self.date_end and wedays[wday])