diff --git a/pms/data/pms_data.xml b/pms/data/pms_data.xml index ecae390d8..ac077f3df 100644 --- a/pms/data/pms_data.xml +++ b/pms/data/pms_data.xml @@ -2,11 +2,11 @@ - + - - + + diff --git a/pms/demo/pms_demo.xml b/pms/demo/pms_demo.xml index 97d3022a6..c51a12c83 100644 --- a/pms/demo/pms_demo.xml +++ b/pms/demo/pms_demo.xml @@ -1,6 +1,10 @@ + + + + Ground Floor @@ -377,8 +381,8 @@ eval="[(5, 0), (0, 0, { 'pricelist_id': 1, 'room_type_id': ref('pms_room_type_1'), - 'checkin': DateTime.today().strftime('%Y-%m-%d'), - 'checkout': (DateTime.today() + timedelta(days=2)).strftime('%Y-%m-%d'), + 'checkin': (DateTime.today() + timedelta(days=5)).strftime('%Y-%m-%d'), + 'checkout': (DateTime.today() + timedelta(days=7)).strftime('%Y-%m-%d'), 'adults': 1, 'state': 'confirm', 'reservation_type': 'out', @@ -400,7 +404,7 @@ - Economic + Prop. Demo Economic ECO 21.00 @@ -408,7 +412,7 @@ - Single + Prop. Demo Single SNG 20.00 diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index 179955892..c15983214 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -23,6 +23,9 @@ class PmsReservation(models.Model): _order = "last_updated_res desc, name" # Default Methods ang Gets + def _default_pricelist_id(self): + return self.env.user.pms_property_id.default_pricelist_id.id + def _get_default_checkin(self): folio = False if "folio_id" in self._context: @@ -91,11 +94,7 @@ class PmsReservation(models.Model): # Fields declaration name = fields.Text( - "Reservation Description", - required=True, - compute="_compute_name", - store=True, - readonly=False, + "Reservation Description", compute="_compute_name", store=True, readonly=False, ) room_id = fields.Many2one( "pms.room", @@ -119,7 +118,6 @@ class PmsReservation(models.Model): room_type_id = fields.Many2one( "pms.room.type", string="Room Type", - required=True, track_visibility="onchange", compute="_compute_room_type_id", store=True, @@ -137,7 +135,6 @@ class PmsReservation(models.Model): partner_invoice_id = fields.Many2one( "res.partner", string="Invoice Address", - required=True, help="Invoice address for current sales order.", compute="_compute_partner_invoice_id", store=True, @@ -162,11 +159,17 @@ class PmsReservation(models.Model): store=True, readonly=False, ) - service_ids = fields.One2many("pms.service", "reservation_id") + service_ids = fields.One2many( + "pms.service", + "reservation_id", + compute="_compute_service_ids", + store=True, + readonly=False, + ) pricelist_id = fields.Many2one( "product.pricelist", string="Pricelist", - required=True, + default=_default_pricelist_id, ondelete="restrict", compute="_compute_pricelist_id", store=True, @@ -181,7 +184,6 @@ class PmsReservation(models.Model): related="pricelist_id.currency_id", string="Currency", readonly=True, - required=True, ) tax_ids = fields.Many2many( "account.tax", @@ -245,8 +247,8 @@ class PmsReservation(models.Model): out_service_description = fields.Text("Cause of out of service") checkin = fields.Date("Check In", required=True, default=_get_default_checkin) checkout = fields.Date("Check Out", required=True, default=_get_default_checkout) - real_checkin = fields.Date("From", required=True, track_visibility="onchange") - real_checkout = fields.Date("To", required=True, track_visibility="onchange") + real_checkin = fields.Date("From", compute="_compute_real_checkin", store=True,) + real_checkout = fields.Date("To", compute="_compute_real_checkout", store=True,) arrival_hour = fields.Char( "Arrival Hour", default=_get_default_arrival_hour, @@ -306,7 +308,10 @@ class PmsReservation(models.Model): string="Sales Channel", default="door", ) - last_updated_res = fields.Datetime("Last Updated") + # TODO: Review functionality of last_update_res + last_updated_res = fields.Datetime( + "Last Updated", compute="_compute_last_updated_res", store=True, readonly=False, + ) folio_pending_amount = fields.Monetary(related="folio_id.pending_amount") shared_folio = fields.Boolean(compute="_computed_shared") # Used to notify is the reservation folio has other reservations/services @@ -320,8 +325,10 @@ class PmsReservation(models.Model): string="Internal Folio Notes", related="folio_id.internal_comment" ) preconfirm = fields.Boolean("Auto confirm to Save", default=True) - to_send = fields.Boolean("To Send", default=True) - call_center = fields.Boolean(default="set_call_center_user") + # TODO: to_send in this module?¿ + to_send = fields.Boolean( + "To Send", default=True, compute="_compute_to_send", store=True, readonly=False, + ) has_confirmed_reservations_to_send = fields.Boolean( related="folio_id.has_confirmed_reservations_to_send", readonly=True ) @@ -400,7 +407,7 @@ class PmsReservation(models.Model): ) # Compute and Search methods - @api.depends("checkin", "checkin", "room_type_id") + @api.depends("checkin", "checkout", "room_type_id") def _compute_name(self): for reservation in self: if ( @@ -417,6 +424,8 @@ class PmsReservation(models.Model): + " - " + checkout_str ) + else: + reservation.name = "/" @api.depends("adults", "room_type_id") def _compute_room_id(self): @@ -428,6 +437,7 @@ class PmsReservation(models.Model): for reservation in reservations_no_room: if reservation.room_type_id: reservation.room_id = reservation._autoassign() + # TODO: check_split reservation if not reservation.room_id: raise UserError( _("%s: No rooms available") % (self.room_type_id.name) @@ -495,7 +505,7 @@ class PmsReservation(models.Model): cmds = [] days_diff = (reservation.checkout - reservation.checkin).days for i in range(0, days_diff): - idate = fields.Date.from_string(reservation.checkin) + timedelta(days=i) + idate = reservation.checkin + timedelta(days=i) old_line = reservation.reservation_line_ids.filtered( lambda r: r.date == idate ) @@ -504,12 +514,34 @@ class PmsReservation(models.Model): reservation.reservation_line_ids -= reservation.reservation_line_ids.filtered_domain( [ "|", - ("date", ">", reservation.checkout), + ("date", ">=", reservation.checkout), ("date", "<", reservation.checkin), ] ) reservation.reservation_line_ids = cmds + @api.depends("board_service_room_id") + def _compute_service_ids(self): + for reservation in self: + board_services = [] + old_board_lines = reservation.service_ids.filtered_domain( + [("is_board_service", "=", True),] + ) + if reservation.board_service_room_id: + board = self.env["pms.board.service.room.type"].browse( + reservation.board_service_room_id.id + ) + for line in board.board_service_line_ids: + res = { + "product_id": line.product_id.id, + "is_board_service": True, + "folio_id": reservation.folio_id.id, + "reservation_id": reservation.id, + } + board_services.append((0, False, res)) + reservation.service_ids -= old_board_lines + reservation.service_ids = board_services + @api.depends("partner_id") def _compute_pricelist_id(self): for reservation in self: @@ -532,6 +564,77 @@ class PmsReservation(models.Model): else: reservation.adults = reservation.room_id.capacity + @api.depends("checkin") + def _compute_real_checkin(self): + for reservation in self: + reservation.real_checkin = reservation.checkin + if reservation.splitted: + master_reservation = reservation.parent_reservation or reservation + reservation.real_checkin = master_reservation.checkin + splitted_reservations = self.env["pms.reservation"].search( + [ + ("splitted", "=", True), + ("folio_id", "=", self.folio_id.id), + "|", + ("parent_reservation", "=", master_reservation.id), + ("id", "=", master_reservation.id), + ] + ) + for split in splitted_reservations: + if reservation.real_checkin > split.checkin: + reservation.real_checkin = split.checkin + + @api.depends("checkout") + def _compute_real_checkout(self): + for reservation in self: + reservation.real_checkout = reservation.checkout + if reservation.splitted: + master_reservation = reservation.parent_reservation or reservation + reservation.real_checkout = master_reservation.checkout + splitted_reservations = self.env["pms.reservation"].search( + [ + ("splitted", "=", True), + ("folio_id", "=", self.folio_id.id), + "|", + ("parent_reservation", "=", master_reservation.id), + ("id", "=", master_reservation.id), + ] + ) + for split in splitted_reservations: + if reservation.real_checkout < split.checkout: + reservation.real_checkout = split.checkout + + @api.depends("splitted", "checkout") + def _compute_real_checkin(self): + for reservation in self: + if reservation.splitted: + master_reservation = reservation.parent_reservation or reservation + first_checkin = master_reservation.checkin + splitted_reservations = self.env["pms.reservation"].search( + [ + ("splitted", "=", True), + ("folio_id", "=", self.folio_id.id), + "|", + ("parent_reservation", "=", master_reservation.id), + ("id", "=", master_reservation.id), + ] + ) + for split in splitted_reservations: + if first_checkin > split.checkin: + reservation.real_checkin = split.checkin + + @api.depends("checkin", "checkout", "state") + def _compute_to_send(self): + for reservation in self: + reservation.to_send = True + + @api.depends( + "checkin", "checkout", "discount", "state", "room_type_id", "to_assign" + ) + def _compute_last_updated_res(self): + for reservation in self: + reservation.last_updated_res = fields.Datetime.now() + @api.depends("state", "qty_to_invoice", "qty_invoiced") def _compute_invoice_status(self): """ @@ -729,47 +832,6 @@ class PmsReservation(models.Model): # self._compute_tax_ids() TODO: refact - @api.onchange("checkin", "checkout") - def onchange_update_service_per_day(self): - _logger.info("----------ONCHANGE4-----------") - services = self.service_ids.filtered(lambda r: r.per_day is True) - for service in services: - service.onchange_product_id() - - @api.onchange("board_service_room_id") - def onchange_board_service(self): - _logger.info("----------ONCHANGE1-----------") - if self.board_service_room_id: - board_services = [(5, 0, 0)] - for line in self.board_service_room_id.board_service_line_ids: - product = line.product_id - if product.per_day: - res = { - "product_id": product.id, - "is_board_service": True, - "folio_id": self.folio_id.id, - "reservation_id": self.id, - } - line = self.env["pms.service"].new(res) - res.update(self.env["pms.service"]._prepare_add_missing_fields(res)) - res.update( - self.env["pms.service"].prepare_service_ids( - dfrom=self.checkin, - days=self.nights, - per_person=product.per_person, - persons=self.adults, - old_line_days=False, - consumed_on=product.consumed_on, - ) - ) - board_services.append((0, False, res)) - other_services = self.service_ids.filtered(lambda r: not r.is_board_service) - self.update({"service_ids": board_services}) - self.service_ids |= other_services - for service in self.service_ids.filtered(lambda r: r.is_board_service): - service._compute_tax_ids() - service.price_unit = service._compute_price_unit() - # Action methods def open_invoices_reservation(self): @@ -864,10 +926,15 @@ class PmsReservation(models.Model): @api.model def create(self, vals): - vals.update(self._prepare_add_missing_fields(vals)) if "folio_id" in vals and "channel_type" not in vals: folio = self.env["pms.folio"].browse(vals["folio_id"]) - vals.update({"channel_type": folio.channel_type}) + channel_type = ( + vals["channel_type"] if "channel_type" in vals else folio.channel_type + ) + partner_id = ( + vals["partner_id"] if "partner_id" in vals else folio.partner_id.id + ) + vals.update({"channel_type": channel_type, "partner_id": partner_id}) elif "partner_id" in vals: folio_vals = { "partner_id": int(vals.get("partner_id")), @@ -883,98 +950,11 @@ class PmsReservation(models.Model): "channel_type": vals.get("channel_type"), } ) - if vals.get("service_ids"): - for service in vals["service_ids"]: - if service[2]: - service[2]["folio_id"] = folio.id - vals.update( - {"last_updated_res": fields.Datetime.now(),} - ) - if ( - "checkin" in vals - and "checkout" in vals - and "real_checkin" not in vals - and "real_checkout" not in vals - ): - vals["real_checkin"] = vals["checkin"] - vals["real_checkout"] = vals["checkout"] record = super(PmsReservation, self).create(vals) if record.preconfirm: record.confirm() return record - def write(self, vals): - if self.notify_update(vals): - vals.update({"last_updated_res": fields.Datetime.now()}) - for record in self: - checkin = vals["checkin"] if "checkin" in vals else record.checkin - checkout = vals["checkout"] if "checkout" in vals else record.checkout - if not record.splitted and not vals.get("splitted", False): - if "checkin" in vals: - vals["real_checkin"] = vals["checkin"] - if "checkout" in vals: - vals["real_checkout"] = vals["checkout"] - - real_checkin = ( - vals["real_checkin"] if "real_checkin" in vals else record.real_checkin - ) - real_checkout = ( - vals["real_checkout"] - if "real_checkout" in vals - else record.real_checkout - ) - - days_diff = ( - fields.Date.from_string(checkout) - fields.Date.from_string(checkin) - ).days - if self.compute_board_services(vals): - record.service_ids.filtered( - lambda r: r.is_board_service is True - ).unlink() - board_services = [] - board = self.env["pms.board.service.room.type"].browse( - vals["board_service_room_id"] - ) - for line in board.board_service_line_ids: - res = { - "product_id": line.product_id.id, - "is_board_service": True, - "folio_id": vals.get("folio_id"), - "reservation_id": self.id, - } - res.update(self.env["pms.service"]._prepare_add_missing_fields(res)) - board_services.append((0, False, res)) - # REVIEW: Why I need add manually the old IDs if - # board service is (0,0,(-)) ¿?¿?¿ - record.update( - {"service_ids": [(6, 0, record.service_ids.ids)] + board_services} - ) - if record.compute_qty_service_day(vals): - service_days_diff = ( - fields.Date.from_string(real_checkout) - - fields.Date.from_string(real_checkin) - ).days - for service in record.service_ids: - if service.product_id.per_day: - service.update( - service.prepare_service_ids( - dfrom=real_checkin, - days=service_days_diff, - per_person=service.product_id.per_person, - persons=service.reservation_id.adults, - old_line_days=service.service_line_ids, - consumed_on=service.product_id.consumed_on, - ) - ) - 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"]) - ): - record.update({"to_send": True}) - record = super(PmsReservation, self).write(vals) - return record - # Business methods def _computed_shared(self): @@ -988,59 +968,6 @@ class PmsReservation(models.Model): ) ) - def compute_board_services(self, vals): - """ - We must compute service_ids when we have a board_service_id without - service_ids associated to reservation - """ - if "board_service_room_id" in vals: - if "service_ids" in vals: - for service in vals["service_ids"]: - if ( - "is_board_service" in service[2] - and service[2]["is_board_service"] is True - ): - return False - return True - return False - - def compute_qty_service_day(self, vals): - """ - Compute if it is necesary calc price in write/create - """ - self.ensure_one() - if not vals: - vals = {} - if "service_ids" in vals: - return False - if ( - ("checkin" in vals and self.checkin != vals["checkin"]) - or ("checkout" in vals and self.checkout != vals["checkout"]) - or ("adults" in vals and self.checkout != vals["adults"]) - ): - return True - return False - - @api.model - def _prepare_add_missing_fields(self, values): - """ Deduce missing required fields from the onchange """ - res = {} - onchange_fields = ["tax_ids", "currency_id", "service_ids"] - if values.get("room_type_id"): - if not values.get("reservation_type"): - values["reservation_type"] = "normal" - line = self.new(values) - for field in onchange_fields: - if field == "service_ids": - if self.compute_board_services(values): - line.onchange_board_service() - res[field] = line._fields[field].convert_to_write( - line[field], line - ) - if field not in values: - res[field] = line._fields[field].convert_to_write(line[field], line) - return res - def _autoassign(self): self.ensure_one() room_chosen = False @@ -1069,18 +996,6 @@ class PmsReservation(models.Model): res.message_post(subject=_("No Checkins!"), subtype="mt_comment", body=msg) return True - def notify_update(self, vals): - if ( - "checkin" in vals - or "checkout" in vals - or "discount" in vals - or "state" in vals - or "room_type_id" in vals - or "to_assign" in vals - ): - return True - return False - def overbooking_button(self): self.ensure_one() self.overbooking = not self.overbooking @@ -1360,7 +1275,6 @@ class PmsReservation(models.Model): ) ) reservation_lines[0].append((2, rline.id, False)) - parent_res = record.parent_reservation or record vals.update( { @@ -1435,7 +1349,7 @@ class PmsReservation(models.Model): raise ValidationError( _( "This reservation can't be unified: They \ - all need to be in the same room" + all need to be in the same nº room and room type" ) ) @@ -1518,7 +1432,3 @@ class PmsReservation(models.Model): record.tax_ids = product.taxes_id.filtered( lambda r: not record.company_id or r.company_id == folio.company_id ) - - def set_call_center_user(self): - user = self.env["res.users"].browse(self.env.uid) - return user.has_group("pms.group_pms_call") diff --git a/pms/models/pms_reservation_line.py b/pms/models/pms_reservation_line.py index 2110f7a75..06677b48c 100644 --- a/pms/models/pms_reservation_line.py +++ b/pms/models/pms_reservation_line.py @@ -3,6 +3,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import _, api, fields, models from odoo.exceptions import ValidationError +import logging +_logger = logging.getLogger(__name__) class PmsReservationLine(models.Model): @@ -64,7 +66,7 @@ class PmsReservationLine(models.Model): # Compute and Search methods @api.depends( - "date", + "reservation_id", "reservation_id.pricelist_id", "reservation_id.room_type_id", "reservation_id.reservation_type", @@ -72,24 +74,43 @@ class PmsReservationLine(models.Model): def _compute_price(self): for line in self: reservation = line.reservation_id - room_type_id = reservation.room_type_id.id - product = self.env["pms.room.type"].browse(room_type_id).product_id - partner = self.env["res.partner"].browse(reservation.partner_id.id) - product = product.with_context( - lang=partner.lang, - partner=partner.id, - quantity=1, - date=line.date, - pricelist=reservation.pricelist_id.id, - uom=product.uom_id.id, - ) - line.price = self.env["account.tax"]._fix_tax_included_price_company( - line._get_display_price(product), - product.taxes_id, - line.reservation_id.tax_ids, - line.reservation_id.company_id, - ) - # TODO: Out of service 0 amount + if not reservation.room_type_id or not reservation.pricelist_id: + line.price = 0 + elif line._recompute_price(): + room_type_id = reservation.room_type_id.id + product = self.env["pms.room.type"].browse(room_type_id).product_id + partner = self.env["res.partner"].browse(reservation.partner_id.id) + product = product.with_context( + lang=partner.lang, + partner=partner.id, + quantity=1, + date=line.date, + pricelist=reservation.pricelist_id.id, + uom=product.uom_id.id, + ) + line.price = self.env["account.tax"]._fix_tax_included_price_company( + line._get_display_price(product), + product.taxes_id, + line.reservation_id.tax_ids, + line.reservation_id.company_id, + ) + _logger.info(line.price) + # TODO: Out of service 0 amount + else: + line.price = line._origin.price + + def _recompute_price(self): + #REVIEW: Conditional to avoid overriding already calculated prices, + # I'm not sure it's the best way + self.ensure_one() + origin = self._origin.reservation_id + new = self.reservation_id + price_fields = ["pricelist_id", "room_type_id", "reservation_type"] + if any(origin[field] != new[field] for field in price_fields) or \ + self._origin.price == 0: + return True + return False + # TODO: Refact method and allowed cancelled single days @api.depends("reservation_id.cancelled_reason") @@ -201,3 +222,4 @@ class PmsReservationLine(models.Model): ) # negative discounts (= surcharge) are included in the display price return max(base_price, final_price) + diff --git a/pms/models/pms_room_type.py b/pms/models/pms_room_type.py index f06b84f05..0a3dfeb35 100644 --- a/pms/models/pms_room_type.py +++ b/pms/models/pms_room_type.py @@ -3,6 +3,7 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). from odoo import _, api, fields, models from odoo.exceptions import ValidationError +from datetime import timedelta class PmsRoomType(models.Model): @@ -99,7 +100,7 @@ class PmsRoomType(models.Model): Check the max availability for an specific type of room in a range of dates """ - reservations = self.env["pms.reservation"].get_reservations(dfrom, dto) + reservations = self.env["pms.reservation"].get_reservations(dfrom, dto - timedelta(1)) reservations_rooms = reservations.mapped("room_id.id") free_rooms = self.env["pms.room"].search( [("id", "not in", reservations_rooms), ("id", "not in", notthis)] diff --git a/pms/models/pms_service.py b/pms/models/pms_service.py index 55553db8b..2a0b587fb 100644 --- a/pms/models/pms_service.py +++ b/pms/models/pms_service.py @@ -42,7 +42,9 @@ class PmsService(models.Model): return False # Fields declaration - name = fields.Char("Service description", required=True) + name = fields.Char( + "Service description", compute="_compute_name", store=True, readonly=False, + ) product_id = fields.Many2one( "product.product", "Service", ondelete="restrict", required=True ) @@ -52,7 +54,13 @@ class PmsService(models.Model): reservation_id = fields.Many2one( "pms.reservation", "Room", default=_default_reservation_id ) - service_line_ids = fields.One2many("pms.service.line", "service_id") + service_line_ids = fields.One2many( + "pms.service.line", + "service_id", + compute="_compute_service_line_ids", + store=True, + readonly=False, + ) company_id = fields.Many2one( related="folio_id.company_id", string="Company", store=True, readonly=True ) @@ -62,6 +70,9 @@ class PmsService(models.Model): tax_ids = fields.Many2many( "account.tax", string="Taxes", + compute="_compute_tax_ids", + store=True, + readonly=False, domain=["|", ("active", "=", False), ("active", "=", True)], ) move_line_ids = fields.Many2many( @@ -79,8 +90,12 @@ class PmsService(models.Model): sequence = fields.Integer(string="Sequence", default=10) state = fields.Selection(related="folio_id.state") per_day = fields.Boolean(related="product_id.per_day", related_sudo=True) - product_qty = fields.Integer("Quantity", default=1) - days_qty = fields.Integer(compute="_compute_days_qty", store=True) + product_qty = fields.Integer( + "Quantity", + compute="_compute_product_qty", + store=True, + readonly=False, + ) is_board_service = fields.Boolean() to_print = fields.Boolean("Print", help="Print in Folio Report") # Non-stored related field to allow portal user to @@ -111,7 +126,11 @@ class PmsService(models.Model): string="Sales Channel", ) price_unit = fields.Float( - "Unit Price", required=True, digits=("Product Price"), default=0.0 + "Unit Price", + digits=("Product Price"), + compute="_compute_price_unit", + store=True, + readonly=False, ) discount = fields.Float(string="Discount (%)", digits=("Discount"), default=0.0) qty_to_invoice = fields.Float( @@ -142,6 +161,206 @@ class PmsService(models.Model): ) # Compute and Search methods + @api.depends("product_id") + def _compute_name(self): + self.name = False + for service in self.filtered("product_id"): + product = service.product_id.with_context( + lang=service.folio_id.partner_id.lang, + partner=service.folio_id.partner_id.id, + ) + title = False + message = False + warning = {} + if product.sale_line_warn != "no-message": + title = _("Warning for %s") % product.name + message = product.sale_line_warn_msg + warning["title"] = title + warning["message"] = message + result = {"warning": warning} + if product.sale_line_warn == "block": + self.product_id = False + return result + name = product.name_get()[0][1] + if product.description_sale: + name += "\n" + product.description_sale + service.name = name + + @api.depends("reservation_id.checkin", "reservation_id.checkout", "product_id") + def _compute_service_line_ids(self): + for service in self.filtered("product_id"): + day_qty = 1 + if service.reservation_id and service.product_id: + reservation = service.reservation_id + product = service.product_id + consumed_on = product.consumed_on + if product.per_day: + lines = [] + day_qty = service._service_day_qty() + days_diff = (reservation.checkout - reservation.checkin).days + for i in range(0, days_diff): + if consumed_on == "after": + i += 1 + idate = reservation.checkin + timedelta(days=i) + old_line = service._search_old_lines(idate) + if idate in [line.date for line in service.service_line_ids]: + #REVIEW: If the date is already cached (otherwise double the date) + pass + elif not old_line: + lines.append( + (0, False, { + "date": idate, + "day_qty": day_qty, + }) + ) + else: + lines.append((4, old_line.id)) + move_day = 0 + if consumed_on == "after": + move_day = 1 + service.service_line_ids -= service.service_line_ids.filtered_domain( + [ + "|", + ("date", "<", reservation.checkin + timedelta(move_day)), + ("date", ">=", reservation.checkout + timedelta(move_day)), + ] + ) + _logger.info(service) + _logger.info(lines) + service.service_line_ids = lines + else: + # TODO: Review (business logic refact) no per_day logic service + if not service.service_line_ids: + service.service_line_ids = [( + 0, + False, + { + "date": fields.Date.today(), + "day_qty": day_qty, + }, + )] + else: + # TODO: Service without reservation(room) but with folio¿? + # example: tourist tour in group + if not service.service_line_ids: + service.service_line_ids = [( + 0, + False, + { + "date": fields.Date.today(), + "day_qty": day_qty, + }, + )] + + def _search_old_lines(self, date): + self.ensure_one() + old_lines = self.env['pms.service.line'] + if isinstance(self._origin.id, int): + old_line = self._origin.service_line_ids.filtered( + lambda r: r.date == date + ) + return old_line + return False + + + @api.depends("product_id") + def _compute_tax_ids(self): + for service in self: + service.tax_ids = service.product_id.taxes_id.filtered( + lambda r: not service.company_id or r.company_id == service.company_id + ) + + @api.depends("service_line_ids", "service_line_ids.day_qty") + def _compute_product_qty(self): + self.product_qty = 0 + for service in self.filtered("service_line_ids"): + qty = sum(service.service_line_ids.mapped("day_qty")) + service.product_qty = qty + + @api.depends("product_id", "service_line_ids", "reservation_id.pricelist_id") + def _compute_price_unit(self): + for service in self: + folio = service.folio_id + reservation = service.reservation_id + origin = reservation if reservation else folio + if origin: + if service._recompute_price(): + partner = origin.partner_id + pricelist = origin.pricelist_id + if reservation and service.is_board_service: + board_room_type = reservation.board_service_room_id + if board_room_type.price_type == "fixed": + service.price_unit = ( + self.env["pms.board.service.room.type.line"] + .search( + [ + ( + "pms_board_service_room_type_id", + "=", + board_room_type.id, + ), + ("product_id", "=", service.product_id.id), + ] + ) + .amount + ) + else: + service.price_unit = ( + reservation.price_total + * self.env["pms.board.service.room.type.line"] + .search( + [ + ( + "pms_board_service_room_type_id", + "=", + board_room_type.id, + ), + ("product_id", "=", service.product_id.id), + ] + ) + .amount + ) / 100 + else: + product = service.product_id.with_context( + lang=partner.lang, + partner=partner.id, + quantity=service.product_qty, + date=folio.date_order if folio else fields.Date.today(), + pricelist=pricelist.id, + uom=service.product_id.uom_id.id, + fiscal_position=False, + ) + service.price_unit = self.env[ + "account.tax" + ]._fix_tax_included_price_company( + service._get_display_price(product), + product.taxes_id, + service.tax_ids, + origin.company_id, + ) + else: + service.price_unit = service._origin.price_unit + else: + service.price_unit = 0 + + def _recompute_price(self): + #REVIEW: Conditional to avoid overriding already calculated prices, + # I'm not sure it's the best way + self.ensure_one() + #folio/reservation origin service + folio_origin = self._origin.folio_id + reservation_origin = self._origin.reservation_id + origin = reservation_origin if reservation_origin else folio_origin + #folio/reservation new service + folio_new = self.folio_id + reservation_new = self.reservation_id + new = reservation_new if reservation_new else folio_new + price_fields = ["pricelist_id", "reservation_type"] + if any(origin[field] != new[field] for field in price_fields) or \ + self._origin.price_unit == 0: + return True + return False + @api.depends("qty_invoiced", "product_qty", "folio_id.state") def _get_to_invoice_qty(self): """ @@ -204,7 +423,8 @@ class PmsService(models.Model): "Product Unit of Measure" ) for line in self: - if line.folio_id.state in ("draft"): + state = line.folio_id.state or "draft" + if state in ("draft"): line.invoice_status = "no" elif not float_is_zero(line.qty_to_invoice, precision_digits=precision): line.invoice_status = "to invoice" @@ -220,24 +440,16 @@ class PmsService(models.Model): @api.depends("product_qty", "discount", "price_unit", "tax_ids") def _compute_amount_service(self): - """ - Compute the amounts of the service line. - """ - for record in self: - folio = record.folio_id or self.env["pms.folio"].browse( - self.env.context.get("default_folio_id") - ) - reservation = record.reservation_id or self.env.context.get( - "reservation_id" - ) + for service in self: + folio = service.folio_id + reservation = service.reservation_id currency = folio.currency_id if folio else reservation.currency_id - product = record.product_id - price = record.price_unit * (1 - (record.discount or 0.0) * 0.01) - taxes = record.tax_ids.compute_all( - price, currency, record.product_qty, product=product + product = service.product_id + price = service.price_unit * (1 - (service.discount or 0.0) * 0.01) + taxes = service.tax_ids.compute_all( + price, currency, service.product_qty, product=product ) - - record.update( + service.update( { "price_tax": sum( t.get("amount", 0.0) for t in taxes.get("taxes", []) @@ -247,92 +459,7 @@ class PmsService(models.Model): } ) - @api.depends("service_line_ids.day_qty") - def _compute_days_qty(self): - for record in self: - if record.per_day: - qty = sum(record.service_line_ids.mapped("day_qty")) - vals = {"days_qty": qty, "product_qty": qty} - else: - vals = {"days_qty": 0} - record.update(vals) - - # Constraints and onchanges - @api.onchange("product_id") - def onchange_product_id(self): - """ - Compute the default quantity according to the - configuration of the selected product, in per_day - product configuration, the qty is autocalculated and - readonly based on service_ids qty - """ - if not self.product_id: - return - vals = {} - vals["product_qty"] = 1.0 - for record in self: - if record.per_day and record.reservation_id: - product = record.product_id - if self.env.context.get("default_reservation_id"): - reservation = self.env["pms.reservation"].browse( - self.env.context.get("default_reservation_id") - ) - else: - reservation = record.reservation_id - if reservation.splitted: - checkin = reservation.real_checkin - checkout = reservation.real_checkout - else: - checkin = reservation.checkin - checkout = reservation.checkout - checkin_dt = fields.Date.from_string(checkin) - checkout_dt = fields.Date.from_string(checkout) - nights = abs((checkout_dt - checkin_dt).days) - vals.update( - record.prepare_service_ids( - dfrom=checkin, - days=nights, - per_person=product.per_person, - persons=reservation.adults, - old_line_days=record.service_line_ids, - consumed_on=product.consumed_on, - ) - ) - if record.product_id.daily_limit > 0: - for day in record.service_line_ids: - day.no_free_resources() - """ - Description and warnings - """ - product = self.product_id.with_context( - lang=self.folio_id.partner_id.lang, partner=self.folio_id.partner_id.id - ) - title = False - message = False - warning = {} - if product.sale_line_warn != "no-message": - title = _("Warning for %s") % product.name - message = product.sale_line_warn_msg - warning["title"] = title - warning["message"] = message - result = {"warning": warning} - if product.sale_line_warn == "block": - self.product_id = False - return result - - name = product.name_get()[0][1] - if product.description_sale: - name += "\n" + product.description_sale - vals["name"] = name - """ - Compute tax and price unit - """ - self._compute_tax_ids() - vals["price_unit"] = self._compute_price_unit() - record.update(vals) - # Action methods - def open_service_ids(self): action = self.env.ref("pms.action_pms_services_form").read()[0] action["views"] = [(self.env.ref("pms.pms_service_view_form").id, "form")] @@ -355,119 +482,9 @@ class PmsService(models.Model): name="", args=args, operator="ilike", limit=limit ) - @api.model - def create(self, vals): - vals.update(self._prepare_add_missing_fields(vals)) - if self.compute_lines_out_vals(vals): - reservation = self.env["pms.reservation"].browse(vals["reservation_id"]) - product = self.env["product.product"].browse(vals["product_id"]) - if reservation.splitted: - checkin = reservation.real_checkin - checkout = reservation.real_checkout - else: - checkin = reservation.checkin - checkout = reservation.checkout - checkin_dt = fields.Date.from_string(checkin) - checkout_dt = fields.Date.from_string(checkout) - nights = abs((checkout_dt - checkin_dt).days) - vals.update( - self.prepare_service_ids( - dfrom=checkin, - days=nights, - per_person=product.per_person, - persons=reservation.adults, - old_day_lines=False, - consumed_on=product.consumed_on, - ) - ) - record = super(PmsService, self).create(vals) - return record - - def write(self, vals): - # If you write product, We must check if its necesary create or delete - # service lines - if vals.get("product_id"): - product = self.env["product.product"].browse(vals.get("product_id")) - if not product.per_day: - vals.update({"service_line_ids": [(5, 0, 0)]}) - else: - for record in self: - reservations = self.env["pms.reservation"] - reservation = ( - reservations.browse(vals["reservation_id"]) - if "reservation_id" in vals - else record.reservation_id - ) - if reservation.splitted: - checkin = reservation.real_checkin - checkout = reservation.real_checkout - else: - checkin = reservation.checkin - checkout = reservation.checkout - checkin_dt = fields.Date.from_string(checkin) - checkout_dt = fields.Date.from_string(checkout) - nights = abs((checkout_dt - checkin_dt).days) - record.update( - record.prepare_service_ids( - dfrom=checkin, - days=nights, - per_person=product.per_person, - persons=reservation.adults, - old_line_days=self.service_line_ids, - consumed_on=product.consumed_on, - ) - ) - res = super(PmsService, self).write(vals) - return res - - # Business methods - @api.model - def _prepare_add_missing_fields(self, values): - """ Deduce missing required fields from the onchange """ - res = {} - onchange_fields = ["price_unit", "tax_ids", "name"] - if values.get("product_id"): - line = self.new(values) - if any(f not in values for f in onchange_fields): - line.onchange_product_id() - for field in onchange_fields: - if field not in values: - res[field] = line._fields[field].convert_to_write(line[field], line) - return res - - def compute_lines_out_vals(self, vals): - """ - Compute if It is necesary service days in write/create - """ - if not vals: - vals = {} - if "product_id" in vals: - product = ( - self.env["product.product"].browse(vals["product_id"]) - if "product_id" in vals - else self.product_id - ) - if product.per_day and "service_line_ids" not in vals: - return True - return False - - def _compute_tax_ids(self): - for record in self: - # If company_id is set, always filter taxes by the company - folio = record.folio_id or self.env["pms.folio"].browse( - self.env.context.get("default_folio_id") - ) - reservation = record.reservation_id or self.env.context.get( - "reservation_id" - ) - origin = folio if folio else reservation - record.tax_ids = record.product_id.taxes_id.filtered( - lambda r: not record.company_id or r.company_id == origin.company_id - ) - def _get_display_price(self, product): - folio = self.folio_id or self.env.context.get("default_folio_id") - reservation = self.reservation_id or self.env.context.get("reservation_id") + folio = self.folio_id + reservation = self.reservation_id origin = folio if folio else reservation if origin.pricelist_id.discount_policy == "with_discount": return product.with_context(pricelist=origin.pricelist_id.id).price @@ -501,90 +518,13 @@ class PmsService(models.Model): # negative discounts (= surcharge) are included in the display price return max(base_price, final_price) - def _compute_price_unit(self): + # Businness Methods + def _service_day_qty(self): self.ensure_one() - folio = self.folio_id or self.env.context.get("default_folio_id") - reservation = self.reservation_id or self.env.context.get("reservation_id") - origin = reservation if reservation else folio - if origin: - partner = origin.partner_id - pricelist = origin.pricelist_id - if reservation and self.is_board_service: - board_room_type = reservation.board_service_room_id - if board_room_type.price_type == "fixed": - return ( - self.env["pms.board.service.room.type.line"] - .search( - [ - ( - "pms_board_service_room_type_id", - "=", - board_room_type.id, - ), - ("product_id", "=", self.product_id.id), - ] - ) - .amount - ) - else: - return ( - reservation.price_total - * self.env["pms.board.service.room.type.line"] - .search( - [ - ( - "pms_board_service_room_type_id", - "=", - board_room_type.id, - ), - ("product_id", "=", self.product_id.id), - ] - ) - .amount - ) / 100 - else: - product = self.product_id.with_context( - lang=partner.lang, - partner=partner.id, - quantity=self.product_qty, - date=folio.date_order if folio else fields.Date.today(), - pricelist=pricelist.id, - uom=self.product_id.uom_id.id, - fiscal_position=False, - ) - return self.env["account.tax"]._fix_tax_included_price_company( - self._get_display_price(product), - product.taxes_id, - self.tax_ids, - origin.company_id, - ) - - @api.model - def prepare_service_ids(self, **kwargs): - """ - Prepare line and respect the old manual changes on lines - """ - cmds = [(5, 0, 0)] - old_line_days = kwargs.get("old_line_days") - consumed_on = ( - kwargs.get("consumed_on") if kwargs.get("consumed_on") else "before" - ) - total_qty = 0 - day_qty = 1 - # WARNING: Change adults in reservation NOT update qty service!! - if kwargs.get("per_person"): - day_qty = kwargs.get("persons") - for i in range(0, kwargs.get("days")): - if consumed_on == "after": - i += 1 - idate = ( - fields.Date.from_string(kwargs.get("dfrom")) + timedelta(days=i) - ).strftime(DEFAULT_SERVER_DATE_FORMAT) - if not old_line_days or idate not in old_line_days.mapped("date"): - cmds.append((0, False, {"date": idate, "day_qty": day_qty})) - total_qty = total_qty + day_qty - else: - old_line = old_line_days.filtered(lambda r: r.date == idate) - cmds.append((4, old_line.id)) - total_qty = total_qty + old_line.day_qty - return {"service_line_ids": cmds, "product_qty": total_qty} + qty = self.product_qty if len(self.service_line_ids) == 1 else 0 + if not self.reservation_id: + return qty + # TODO: Pass per_person to service line from product default_per_person + if self.product_id.per_person: + qty = self.reservation_id.adults + return qty diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml index 675a771c1..d760fe6d3 100644 --- a/pms/views/pms_reservation_views.xml +++ b/pms/views/pms_reservation_views.xml @@ -260,8 +260,7 @@ name="partner_id" default_focus="1" placeholder="Lastname, Firstname" - attrs="{'readonly':[('folio_id','!=',False)], - 'invisible':[('reservation_type','in',('out'))]}" + attrs="{'invisible':[('reservation_type','in',('out'))]}" required="1" /> @@ -558,11 +557,10 @@