[WIP] Check compute qty and price day reservation&services

This commit is contained in:
Darío Lodeiros
2020-09-15 16:52:29 +02:00
parent 3df6a3a198
commit 2439da79e5
10 changed files with 440 additions and 577 deletions

View File

@@ -2,11 +2,11 @@
<odoo>
<data noupdate="1">
<!-- pms.users -->
<record id="base.user_admin" model="res.users">
<record id="base.user_root" model="res.users">
<field name="groups_id" eval="[(4,ref('pms.group_pms_manager'))]" />
</record>
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4,ref('pms.group_pms_user'))]" />
<record id="base.user_admin" model="res.users">
<field name="groups_id" eval="[(4,ref('pms.group_pms_manager'))]" />
</record>
<!-- Basic pms -->
<record id="main_pms_room_type_restriction" model="pms.room.type.restriction">

View File

@@ -1,6 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data noupdate="1">
<!-- users -->
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4,ref('pms.group_pms_user'))]" />
</record>
<!-- pms.floor -->
<record id="pms_floor_0" model="pms.floor">
<field name="name">Ground Floor</field>
@@ -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 @@
<!-- pms.room.type -->
<record id="demo_pms_room_type_0" model="pms.room.type">
<field name="pms_property_id" ref="pms.demo_pms_property" />
<field name="name">Economic</field>
<field name="name">Prop. Demo Economic</field>
<field name="code_type">ECO</field>
<field name="list_price">21.00</field>
<field name="class_id" ref="pms_room_type_class_0" />
@@ -408,7 +412,7 @@
</record>
<record id="demo_pms_room_type_1" model="pms.room.type">
<field name="pms_property_id" ref="pms.demo_pms_property" />
<field name="name">Single</field>
<field name="name">Prop. Demo Single</field>
<field name="code_type">SNG</field>
<field name="list_price">20.00</field>
<field name="class_id" ref="pms_room_type_class_0" />

View File

@@ -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 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")

View File

@@ -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)

View File

@@ -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)]

View File

@@ -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

View File

@@ -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"
/>
<field
@@ -529,7 +528,7 @@
<button
type="object"
class="oe_stat_button"
icon="fa fa-1x fa-bed"
icon="fa-1x fa-bed"
name="open_service_ids"
attrs="{'invisible':[('is_board_service','=', False)]}"
/>
@@ -558,11 +557,10 @@
<button
type="object"
class="oe_stat_button"
icon="fa fa-2x fa-bars"
icon="fa-2x fa-bars"
name="open_service_ids"
attrs="{'invisible': [('per_day','=',False)]}"
/>
<field name="days_qty" invisible="1" />
<field name="price_unit" />
<field name="discount" />
<field name="tax_ids" widget="many2many_tags" />
@@ -592,6 +590,7 @@
/>
<field name="reservation_line_ids" nolabel="1">
<tree create="false" delete="false" editable="bottom">
<field name="id" />
<field name="date" readonly="1" force_save="1" />
<field name="price" />
<field name="discount" />
@@ -768,24 +767,24 @@
options="{'no_open': True}"
/>
<span
class="fa fa-user"
class="fa-user"
style="margin-left:20px;"
attrs="{'invisible': [('reservation_type','not in',('normal'))]}"
/>
<span
class="fa fa-black-tie"
class="fa-black-tie"
style="margin-left:20px; color: #C67;"
attrs="{'invisible': [('reservation_type','not in',('staff'))]}"
/>
<h3>
From <span class="fa fa-sign-in" style="margin: 5px;" />
From <span class="fa-sign-in" style="margin: 5px;" />
<field
name="checkin"
style="margin-right: 10px;"
readonly="1"
/>
to
<span class="fa fa-sign-out" style="margin-right: 5px;" />
<span class="fa-sign-out" style="margin-right: 5px;" />
<field name="checkout" readonly="1" />
</h3>
</h2>
@@ -910,7 +909,7 @@
<button
type="object"
class="oe_stat_button"
icon="fa fa-2x fa-suitcase"
icon="fa-2x fa-suitcase"
name="open_reservation_form"
help="Open Reservation Room Detail"
/>
@@ -1008,11 +1007,6 @@
domain="[('to_assign','=',True)]"
/>
<separator />
<filter
string="Call Center"
name="call_center"
domain="[('channel_type', '=', 'call')]"
/>
<filter
string="Web"
name="web"

View File

@@ -28,7 +28,6 @@
force_save="1"
/>
</group>
<field name="days_qty" invisible="1" />
<field name="price_unit" invisible="1" />
<field name="discount" invisible="1" />
<field name="tax_ids" widget="many2many_tags" invisible="1" />
@@ -89,7 +88,6 @@
name="open_service_ids"
attrs="{'invisible': [('per_day','=',False)]}"
/>
<field name="days_qty" invisible="1" />
<field name="price_unit" />
<field name="discount" />
<field name="tax_ids" widget="many2many_tags" />

View File

@@ -89,7 +89,6 @@ class FolioWizard(models.TransientModel):
room_type_wizard_ids = fields.One2many(
"pms.room.type.wizard", "folio_wizard_id", string="Room Types"
)
call_center = fields.Boolean(default=_get_default_center_user)
def assign_rooms(self):
self.assign = True
@@ -270,7 +269,8 @@ class FolioWizard(models.TransientModel):
"board_service_room_id": line.board_service_room_id.id,
"to_assign": line.to_assign,
"service_ids": services_room,
"pricelist_id": self.pricelist_id.id, # REVIEW: Create folio with reservations dont respect the pricelist_id on folio dict
# REVIEW: Create folio with reservations dont respect the pricelist_id on folio dict
"pricelist_id": self.pricelist_id.id,
},
)
)

View File

@@ -16,13 +16,7 @@
<field name="company_id" invisible="1" />
</group>
<group>
<field name="call_center" invisible="1" />
<field
name="channel_type"
required="1"
force_save="1"
attrs="{'readonly':[('call_center','=',True)]}"
/>
<field name="channel_type" required="1" force_save="1" />
<field name="pricelist_id" />
<field name="internal_comment" />
<field name="credit_card_details" />