mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
14.0 pms service price day (#57)
WIP: compute folio_sale_line without (5,0,0) WIP: boardservices pricelist item sql search * [IMP]pms: Service day with prices * [ADD]pms: New Product price base type: Board Service * [WIP]pms: pricelist item rule board service * [WIP]pms: pricelist boardservice sql * [WIP]pms: pricelist boardservice sql * [IMP] service price per day * [FIX] compute board_service reservation change * [FIX] Views * [IMP]pms: add default user_id on reservation and folio * [IMP]pms: aler change prices reservation * [FIX]pms: recompute reservation services board * [DEL]pms: pricelist field on board_service_room_type * [RFC] sale_line_ids model
This commit is contained in:
@@ -37,7 +37,6 @@
|
||||
"wizards/wizard_payment_folio.xml",
|
||||
"wizards/folio_make_invoice_advance_views.xml",
|
||||
"wizards/wizard_folio.xml",
|
||||
"wizards/wizard_invoice_filter_days.xml",
|
||||
"wizards/wizard_folio_changes.xml",
|
||||
"views/pms_amenity_views.xml",
|
||||
"views/pms_amenity_type_views.xml",
|
||||
|
||||
@@ -301,7 +301,6 @@
|
||||
'product_id': ref('pms_service_0'),
|
||||
'amount': 3})]"
|
||||
/>
|
||||
<field name="price_type">fixed</field>
|
||||
</record>
|
||||
<record id="pms_board_service_1" model="pms.board.service">
|
||||
<field name="name">Half Board</field>
|
||||
@@ -314,7 +313,6 @@
|
||||
'amount': 8})
|
||||
]"
|
||||
/>
|
||||
<field name="price_type">fixed</field>
|
||||
</record>
|
||||
<record id="pms_board_service_2" model="pms.board.service">
|
||||
<field name="name">FullBoard</field>
|
||||
@@ -329,36 +327,29 @@
|
||||
'amount': 8})
|
||||
]"
|
||||
/>
|
||||
<field name="price_type">fixed</field>
|
||||
</record>
|
||||
<!-- pms.board.service.room.type -->
|
||||
<!--Room 0 Economic-->
|
||||
<record id="pms_board_service_room_0" model="pms.board.service.room.type">
|
||||
<field name="pms_board_service_id" ref="pms_board_service_0" />
|
||||
<field name="pms_room_type_id" ref="pms_room_type_0" />
|
||||
<field name="price_type">fixed</field>
|
||||
</record>
|
||||
<record id="pms_board_service_room_1" model="pms.board.service.room.type">
|
||||
<field name="pms_board_service_id" ref="pms_board_service_1" />
|
||||
<field name="pms_room_type_id" ref="pms_room_type_0" />
|
||||
<field name="price_type">fixed</field>
|
||||
</record>
|
||||
<record id="pms_board_service_room_2" model="pms.board.service.room.type">
|
||||
<field name="pms_board_service_id" ref="pms_board_service_1" />
|
||||
<field name="pms_room_type_id" ref="pms_room_type_0" />
|
||||
<field name="pricelist_id" ref="product.list0" />
|
||||
<field name="price_type">fixed</field>
|
||||
</record>
|
||||
<!--Room 3 Triple-->
|
||||
<record id="pms_board_service_room_3" model="pms.board.service.room.type">
|
||||
<field name="pms_board_service_id" ref="pms_board_service_0" />
|
||||
<field name="pms_room_type_id" ref="pms_room_type_3" />
|
||||
<field name="price_type">fixed</field>
|
||||
</record>
|
||||
<record id="pms_board_service_room_4" model="pms.board.service.room.type">
|
||||
<field name="pms_board_service_id" ref="pms_board_service_2" />
|
||||
<field name="pms_room_type_id" ref="pms_room_type_3" />
|
||||
<field name="price_type">fixed</field>
|
||||
</record>
|
||||
<!-- room.closure.reason -->
|
||||
<record id="pms_room_closure_reason_0" model="room.closure.reason">
|
||||
|
||||
@@ -22,8 +22,6 @@ class AccountBankStatementLine(models.Model):
|
||||
line.update(
|
||||
{
|
||||
"folio_ids": [(6, 0, self.statement_folio_ids.ids)],
|
||||
"reservation_ids": [(6, 0, self.reservation_ids.ids)],
|
||||
"service_ids": [(6, 0, self.service_ids.ids)],
|
||||
}
|
||||
)
|
||||
return line_vals_list
|
||||
|
||||
@@ -14,7 +14,6 @@ class AccountMove(models.Model):
|
||||
comodel_name="pms.folio", compute="_compute_folio_origin"
|
||||
)
|
||||
pms_property_id = fields.Many2one("pms.property")
|
||||
from_reservation = fields.Boolean(compute="_compute_from_reservation")
|
||||
outstanding_folios_debits_widget = fields.Text(
|
||||
compute="_compute_get_outstanding_folios_JSON"
|
||||
)
|
||||
@@ -31,12 +30,6 @@ class AccountMove(models.Model):
|
||||
if folios:
|
||||
inv.folio_ids = [(6, 0, folios.ids)]
|
||||
|
||||
def _compute_from_reservation(self):
|
||||
for inv in self:
|
||||
inv.from_reservation = False
|
||||
if len(inv.invoice_line_ids.mapped("reservation_line_ids")) > 0:
|
||||
inv.from_reservation = True
|
||||
|
||||
# Action methods
|
||||
|
||||
def action_folio_payments(self):
|
||||
|
||||
@@ -1,40 +1,13 @@
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
from odoo import api, fields, models
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
# Fields declaration
|
||||
reservation_ids = fields.Many2many(
|
||||
"pms.reservation",
|
||||
"reservation_move_rel",
|
||||
"move_line_id",
|
||||
"reservation_id",
|
||||
string="Reservations",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
)
|
||||
service_ids = fields.Many2many(
|
||||
"pms.service",
|
||||
"service_line_move_rel",
|
||||
"move_line_id",
|
||||
"service_id",
|
||||
string="Services",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
)
|
||||
reservation_line_ids = fields.Many2many(
|
||||
"pms.reservation.line",
|
||||
"reservation_line_move_rel",
|
||||
"move_line_id",
|
||||
"reservation_line_id",
|
||||
string="Reservation Lines",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
)
|
||||
folio_line_ids = fields.Many2many(
|
||||
"folio.sale.line",
|
||||
"folio_sale_line_invoice_rel",
|
||||
@@ -49,68 +22,8 @@ class AccountMoveLine(models.Model):
|
||||
"move_id",
|
||||
"folio_id",
|
||||
string="Folios",
|
||||
ondelete="cascade",
|
||||
compute="_compute_folio_ids",
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
name = fields.Char(
|
||||
compute="_compute_name",
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
|
||||
@api.depends("service_ids", "reservation_ids")
|
||||
def _compute_folio_ids(self):
|
||||
for record in self:
|
||||
if record.service_ids:
|
||||
record.folio_ids = record.mapped("service_ids.folio_id")
|
||||
elif record.reservation_ids:
|
||||
record.folio_ids = record.mapped("reservation_ids.folio_id")
|
||||
elif not record.folio_ids:
|
||||
record.folio_ids = False
|
||||
|
||||
def invoice_filter_days(self):
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(
|
||||
"pms.pms_invoice_filter_days_action"
|
||||
)
|
||||
# Force the values of the move line in the context to avoid issues
|
||||
ctx = dict(self.env.context)
|
||||
ctx.pop("active_id", None)
|
||||
ctx["active_ids"] = self.ids
|
||||
ctx["active_model"] = "account.move.line"
|
||||
action["context"] = ctx
|
||||
return action
|
||||
|
||||
def _copy_data_extend_business_fields(self, values):
|
||||
super(AccountMoveLine, self)._copy_data_extend_business_fields(values)
|
||||
values["folio_line_ids"] = [(6, None, self.folio_line_ids.ids)]
|
||||
values["reservation_line_ids"] = [(6, None, self.reservation_line_ids.ids)]
|
||||
values["service_ids"] = [(6, None, self.service_ids.ids)]
|
||||
values["reservation_ids"] = [(6, None, self.reservation_ids.ids)]
|
||||
|
||||
@api.depends("reservation_line_ids")
|
||||
def _compute_name(self):
|
||||
if hasattr(super(), "_compute_name"):
|
||||
super()._compute_field()
|
||||
for record in self:
|
||||
if record.reservation_line_ids:
|
||||
record.name = record._get_compute_name()
|
||||
|
||||
def _get_compute_name(self):
|
||||
self.ensure_one()
|
||||
if self.reservation_line_ids:
|
||||
month = False
|
||||
name = False
|
||||
lines = self.reservation_line_ids.sorted("date")
|
||||
for date in lines.mapped("date"):
|
||||
if date.month != month:
|
||||
name = name + "\n" if name else ""
|
||||
name += date.strftime("%B-%Y") + ": "
|
||||
name += date.strftime("%d")
|
||||
month = date.month
|
||||
else:
|
||||
name += ", " + date.strftime("%d")
|
||||
return name
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -24,6 +24,9 @@ class FolioSaleLine(models.Model):
|
||||
for line in self:
|
||||
if line.state == "draft":
|
||||
line.invoice_status = "no"
|
||||
# REVIEW: if qty_to_invoice < 0 (invoice qty > sale qty),
|
||||
# why status to_invoice?? this behavior is copied from sale order
|
||||
# https://github.com/OCA/OCB/blob/14.0/addons/sale/models/sale.py#L1160
|
||||
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
|
||||
line.invoice_status = "to invoice"
|
||||
elif (
|
||||
@@ -71,18 +74,6 @@ class FolioSaleLine(models.Model):
|
||||
else:
|
||||
return False
|
||||
|
||||
@api.depends("service_id", "service_id.price_unit")
|
||||
def _compute_price_unit(self):
|
||||
"""
|
||||
Compute unit prices of services
|
||||
On reservations the unit price is compute by group in folio
|
||||
"""
|
||||
for record in self:
|
||||
if record.service_id:
|
||||
record.price_unit = record.service_id.price_unit
|
||||
elif not record.price_unit:
|
||||
record.price_unit = False
|
||||
|
||||
@api.depends("product_uom_qty", "discount", "price_unit", "tax_ids")
|
||||
def _compute_amount(self):
|
||||
"""
|
||||
@@ -121,11 +112,23 @@ class FolioSaleLine(models.Model):
|
||||
else record.reservation_id.tax_ids
|
||||
)
|
||||
|
||||
@api.depends("service_id", "service_id.discount")
|
||||
@api.depends(
|
||||
"service_id",
|
||||
"service_id.service_line_ids",
|
||||
"service_id.service_line_ids.discount",
|
||||
)
|
||||
def _compute_discount(self):
|
||||
self.discount = 0.0
|
||||
for record in self.filtered("service_id"):
|
||||
record.discount = record.service_id.discount
|
||||
"""
|
||||
Only in services without room we compute discount,
|
||||
and this services only have one service line
|
||||
"""
|
||||
for record in self:
|
||||
if record.service_id and not record.service_id.reservation_id:
|
||||
record.discount = record.service_id.service_line_ids.mapped("discount")[
|
||||
0
|
||||
]
|
||||
elif not record.discount:
|
||||
record.discount = 0
|
||||
|
||||
@api.depends("reservation_id.room_type_id", "service_id.product_id")
|
||||
def _compute_product_id(self):
|
||||
@@ -355,6 +358,12 @@ class FolioSaleLine(models.Model):
|
||||
index=True,
|
||||
copy=False,
|
||||
)
|
||||
is_board_service = fields.Boolean(
|
||||
string="Board Service",
|
||||
related="service_id.is_board_service",
|
||||
store=True,
|
||||
)
|
||||
|
||||
name = fields.Text(
|
||||
string="Description", compute="_compute_name", store=True, readonly=False
|
||||
)
|
||||
@@ -363,6 +372,10 @@ class FolioSaleLine(models.Model):
|
||||
"pms.reservation.line",
|
||||
string="Nights",
|
||||
)
|
||||
service_line_ids = fields.Many2many(
|
||||
"pms.service.line",
|
||||
string="Service Lines",
|
||||
)
|
||||
sequence = fields.Integer(string="Sequence", default=10)
|
||||
|
||||
invoice_lines = fields.Many2many(
|
||||
@@ -389,8 +402,6 @@ class FolioSaleLine(models.Model):
|
||||
price_unit = fields.Float(
|
||||
"Unit Price",
|
||||
digits="Product Price",
|
||||
compute="_compute_price_unit",
|
||||
store=True,
|
||||
)
|
||||
|
||||
price_subtotal = fields.Monetary(
|
||||
@@ -433,6 +444,7 @@ class FolioSaleLine(models.Model):
|
||||
string="Discount (%)",
|
||||
digits="Discount",
|
||||
compute="_compute_discount",
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
|
||||
@@ -550,13 +562,13 @@ class FolioSaleLine(models.Model):
|
||||
help="Technical field for UX purpose.",
|
||||
)
|
||||
|
||||
@api.depends("reservation_line_ids", "service_id")
|
||||
@api.depends("reservation_line_ids", "service_line_ids", "service_line_ids.day_qty")
|
||||
def _compute_product_uom_qty(self):
|
||||
for line in self:
|
||||
if line.reservation_line_ids:
|
||||
line.product_uom_qty = len(line.reservation_line_ids)
|
||||
elif line.service_id:
|
||||
line.product_uom_qty = line.service_id.product_qty
|
||||
elif line.service_line_ids:
|
||||
line.product_uom_qty = sum(line.service_line_ids.mapped("day_qty"))
|
||||
elif not line.product_uom_qty:
|
||||
line.product_uom_qty = False
|
||||
|
||||
@@ -716,15 +728,6 @@ class FolioSaleLine(models.Model):
|
||||
+ " The quantity pending to invoice is %s" % self.qty_to_invoice
|
||||
)
|
||||
)
|
||||
reservation = self.reservation_id
|
||||
service = self.service_id
|
||||
reservation_lines = self.reservation_line_ids.filtered(
|
||||
lambda l: not l.invoiced and l.reservation_id
|
||||
)
|
||||
lines_to_invoice = list()
|
||||
if self.reservation_id:
|
||||
for i in range(0, int(qty)):
|
||||
lines_to_invoice.append(reservation_lines[i].id)
|
||||
res = {
|
||||
"display_type": self.display_type,
|
||||
"sequence": self.sequence,
|
||||
@@ -738,9 +741,6 @@ class FolioSaleLine(models.Model):
|
||||
"analytic_account_id": self.folio_id.analytic_account_id.id,
|
||||
"analytic_tag_ids": [(6, 0, self.analytic_tag_ids.ids)],
|
||||
"folio_line_ids": [(6, 0, [self.id])],
|
||||
"reservation_ids": [(6, 0, reservation.ids)],
|
||||
"service_ids": [(6, 0, service.ids)],
|
||||
"reservation_line_ids": [(6, 0, lines_to_invoice)],
|
||||
}
|
||||
if optional_values:
|
||||
res.update(optional_values)
|
||||
|
||||
@@ -18,12 +18,6 @@ class PmsBoardService(models.Model):
|
||||
pms_board_service_room_type_ids = fields.One2many(
|
||||
"pms.board.service.room.type", "pms_board_service_id"
|
||||
)
|
||||
price_type = fields.Selection(
|
||||
[("fixed", "Fixed"), ("percent", "Percent")],
|
||||
string="Type",
|
||||
default="fixed",
|
||||
required=True,
|
||||
)
|
||||
amount = fields.Float(
|
||||
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright 2017 Dario
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class PmsBoardServiceRoomType(models.Model):
|
||||
@@ -11,21 +11,6 @@ class PmsBoardServiceRoomType(models.Model):
|
||||
_log_access = False
|
||||
_description = "Board Service included in Room"
|
||||
|
||||
# Default Methods ang Gets
|
||||
|
||||
def name_get(self):
|
||||
result = []
|
||||
for res in self:
|
||||
if res.pricelist_id:
|
||||
name = u"{} ({})".format(
|
||||
res.pms_board_service_id.name,
|
||||
res.pricelist_id.name,
|
||||
)
|
||||
else:
|
||||
name = u"{} ({})".format(res.pms_board_service_id.name, _("Generic"))
|
||||
result.append((res.id, name))
|
||||
return result
|
||||
|
||||
# Fields declaration
|
||||
pms_board_service_id = fields.Many2one(
|
||||
"pms.board.service",
|
||||
@@ -52,27 +37,9 @@ class PmsBoardServiceRoomType(models.Model):
|
||||
("pms_property_ids", "in", pms_property_ids),
|
||||
],
|
||||
)
|
||||
pricelist_id = fields.Many2one(
|
||||
"product.pricelist",
|
||||
string="Pricelist",
|
||||
required=False,
|
||||
domain=[
|
||||
"|",
|
||||
("pms_property_ids", "=", False),
|
||||
("pms_property_ids", "in", pms_property_ids),
|
||||
],
|
||||
)
|
||||
board_service_line_ids = fields.One2many(
|
||||
"pms.board.service.room.type.line", "pms_board_service_room_type_id"
|
||||
)
|
||||
# TODO:review relation with pricelist and properties
|
||||
|
||||
price_type = fields.Selection(
|
||||
[("fixed", "Fixed"), ("percent", "Percent")],
|
||||
string="Type",
|
||||
default="fixed",
|
||||
required=True,
|
||||
)
|
||||
amount = fields.Float(
|
||||
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
|
||||
)
|
||||
@@ -87,41 +54,7 @@ class PmsBoardServiceRoomType(models.Model):
|
||||
total += service.amount
|
||||
record.update({"amount": total})
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.constrains("pricelist_id")
|
||||
def constrains_pricelist_id(self):
|
||||
for record in self:
|
||||
if self.pricelist_id:
|
||||
board_pricelist = self.env["pms.board.service.room.type"].search(
|
||||
[
|
||||
("pricelist_id", "=", record.pricelist_id.id),
|
||||
("pms_room_type_id", "=", record.pms_room_type_id.id),
|
||||
("pms_board_service_id", "=", record.pms_board_service_id.id),
|
||||
("id", "!=", record.id),
|
||||
]
|
||||
)
|
||||
if board_pricelist:
|
||||
raise UserError(
|
||||
_("This Board Service in this Room can't repeat pricelist")
|
||||
)
|
||||
else:
|
||||
board_pricelist = self.env["pms.board.service.room.type"].search(
|
||||
[
|
||||
("pricelist_id", "=", False),
|
||||
("pms_room_type_id", "=", record.pms_room_type_id.id),
|
||||
("pms_board_service_id", "=", record.pms_board_service_id.id),
|
||||
("id", "!=", record.id),
|
||||
]
|
||||
)
|
||||
if board_pricelist:
|
||||
raise UserError(
|
||||
_(
|
||||
"This Board Service in this Room \
|
||||
can't repeat without pricelist"
|
||||
)
|
||||
)
|
||||
|
||||
@api.constrains("by_default", "pricelist_id")
|
||||
@api.constrains("by_default")
|
||||
def constrains_duplicated_board_defaul(self):
|
||||
for record in self:
|
||||
default_boards = (
|
||||
@@ -130,34 +63,8 @@ class PmsBoardServiceRoomType(models.Model):
|
||||
)
|
||||
)
|
||||
# TODO Check properties (with different propertys is allowed)
|
||||
if any(
|
||||
default_boards.mapped(
|
||||
lambda l: l.pricelist_id == record.pricelist_id
|
||||
and l.id != record.id
|
||||
)
|
||||
):
|
||||
raise UserError(
|
||||
_(
|
||||
"""Only can set one default board service by
|
||||
pricelist (or without pricelist)"""
|
||||
)
|
||||
)
|
||||
|
||||
@api.constrains("pms_property_ids", "pms_room_type_ids")
|
||||
def _check_room_type_property_integrity(self):
|
||||
for record in self:
|
||||
if record.pms_property_ids and record.pms_room_type_id.pms_property_ids:
|
||||
for pms_property in record.pms_property_ids:
|
||||
if pms_property not in record.pms_room_type_id.pms_property_ids:
|
||||
raise ValidationError(_("Property not allowed in room type"))
|
||||
|
||||
@api.constrains("pms_property_ids", "pricelist_id")
|
||||
def _check_pricelist_property_integrity(self):
|
||||
for record in self:
|
||||
if record.pms_property_ids and record.pricelist_id:
|
||||
for pms_property in record.pms_property_ids:
|
||||
if pms_property not in record.pricelist_id.pms_property_ids:
|
||||
raise ValidationError(_("Property not allowed in pricelist"))
|
||||
if any(default_boards.filtered(lambda l: l.id != record.id)):
|
||||
raise UserError(_("""Only can set one default board service"""))
|
||||
|
||||
# Action methods
|
||||
|
||||
@@ -176,13 +83,13 @@ class PmsBoardServiceRoomType(models.Model):
|
||||
def init(self):
|
||||
self._cr.execute(
|
||||
"SELECT indexname FROM pg_indexes WHERE indexname = %s",
|
||||
("pms_board_service_id_pms_room_type_id_pricelist_id",),
|
||||
("pms_board_service_id_pms_room_type_id",),
|
||||
)
|
||||
if not self._cr.fetchone():
|
||||
self._cr.execute(
|
||||
"CREATE INDEX pms_board_service_id_pms_room_type_id_pricelist_id \
|
||||
"CREATE INDEX pms_board_service_id_pms_room_type_id \
|
||||
ON pms_board_service_room_type_rel \
|
||||
(pms_board_service_id, pms_room_type_id, pricelist_id)"
|
||||
(pms_board_service_id, pms_room_type_id)"
|
||||
)
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -391,6 +391,9 @@ class PmsFolio(models.Model):
|
||||
"reservation_ids",
|
||||
"service_ids",
|
||||
"service_ids.reservation_id",
|
||||
"service_ids.service_line_ids.price_day_total",
|
||||
"service_ids.service_line_ids.discount",
|
||||
"service_ids.service_line_ids.cancel_discount",
|
||||
"reservation_ids.reservation_line_ids",
|
||||
"reservation_ids.reservation_line_ids.price",
|
||||
"reservation_ids.reservation_line_ids.discount",
|
||||
@@ -415,7 +418,7 @@ class PmsFolio(models.Model):
|
||||
},
|
||||
)
|
||||
)
|
||||
group_lines = {}
|
||||
group_reservation_lines = {}
|
||||
for line in reservation.reservation_line_ids:
|
||||
# On resevations the price, and discounts fields are used
|
||||
# by group, we need pass this in the create line
|
||||
@@ -431,32 +434,45 @@ class PmsFolio(models.Model):
|
||||
for discount in [line.discount, line.cancel_discount]:
|
||||
discount_factor = discount_factor * ((100.0 - discount) / 100.0)
|
||||
final_discount = 100.0 - (discount_factor * 100.0)
|
||||
if group_key not in group_lines:
|
||||
group_lines[group_key] = {
|
||||
if group_key not in group_reservation_lines:
|
||||
group_reservation_lines[group_key] = {
|
||||
"reservation_id": reservation.id,
|
||||
"discount": final_discount,
|
||||
"price_unit": line.price,
|
||||
"reservation_line_ids": [(4, line.id)],
|
||||
}
|
||||
else:
|
||||
group_lines[group_key][("reservation_line_ids")].append(
|
||||
(4, line.id)
|
||||
)
|
||||
for item in group_lines.items():
|
||||
group_reservation_lines[group_key][
|
||||
("reservation_line_ids")
|
||||
].append((4, line.id))
|
||||
for item in group_reservation_lines.items():
|
||||
sale_lines.append((0, False, item[1]))
|
||||
for service in reservation.service_ids:
|
||||
# On service the price, and discounts fields are
|
||||
# compute in the sale.order.line
|
||||
sale_lines.append(
|
||||
(
|
||||
0,
|
||||
False,
|
||||
{
|
||||
# Service days with different prices,
|
||||
# go to differente sale lines
|
||||
group_service_lines = {}
|
||||
for service_line in service.service_line_ids:
|
||||
service_group_key = (
|
||||
service_line.price_unit,
|
||||
service_line.discount,
|
||||
service_line.cancel_discount,
|
||||
)
|
||||
if service_group_key not in group_service_lines:
|
||||
# On service the price, and discounts fields are
|
||||
# compute in the sale.order.line
|
||||
group_service_lines[service_group_key] = {
|
||||
"name": service.name,
|
||||
"service_id": service.id,
|
||||
},
|
||||
)
|
||||
)
|
||||
"discount": service_line.discount,
|
||||
"price_unit": service_line.price_unit,
|
||||
"service_line_ids": [(4, service_line.id)],
|
||||
}
|
||||
else:
|
||||
group_service_lines[service_group_key][
|
||||
("service_line_ids")
|
||||
].append((4, service_line.id))
|
||||
for item in group_service_lines.items():
|
||||
sale_lines.append((0, False, item[1]))
|
||||
if services_without_room:
|
||||
sale_lines.append(
|
||||
(
|
||||
@@ -646,25 +662,20 @@ class PmsFolio(models.Model):
|
||||
else:
|
||||
order.invoice_status = "no"
|
||||
|
||||
@api.depends("reservation_ids.price_total", "service_ids.price_total")
|
||||
@api.depends("sale_line_ids.price_total")
|
||||
def _compute_amount_all(self):
|
||||
"""
|
||||
Compute the total amounts of the SO.
|
||||
"""
|
||||
for record in self.filtered("pricelist_id"):
|
||||
for folio in self:
|
||||
amount_untaxed = amount_tax = 0.0
|
||||
amount_untaxed = sum(record.reservation_ids.mapped("price_subtotal")) + sum(
|
||||
record.service_ids.mapped("price_subtotal")
|
||||
)
|
||||
amount_tax = sum(record.reservation_ids.mapped("price_tax")) + sum(
|
||||
record.service_ids.mapped("price_tax")
|
||||
)
|
||||
record.update(
|
||||
for line in folio.sale_line_ids:
|
||||
amount_untaxed += line.price_subtotal
|
||||
amount_tax += line.price_tax
|
||||
folio.update(
|
||||
{
|
||||
"amount_untaxed": record.pricelist_id.currency_id.round(
|
||||
amount_untaxed
|
||||
),
|
||||
"amount_tax": record.pricelist_id.currency_id.round(amount_tax),
|
||||
"amount_untaxed": amount_untaxed,
|
||||
"amount_tax": amount_tax,
|
||||
"amount_total": amount_untaxed + amount_tax,
|
||||
}
|
||||
)
|
||||
@@ -1248,9 +1259,9 @@ class PmsFolio(models.Model):
|
||||
def _get_tax_amount_by_group(self):
|
||||
self.ensure_one()
|
||||
res = {}
|
||||
for line in self.reservation_ids:
|
||||
for line in self.sale_line_ids:
|
||||
price_reduce = line.price_total
|
||||
product = line.room_type_id.product_id
|
||||
product = line.product_id
|
||||
taxes = line.tax_ids.compute_all(price_reduce, quantity=1, product=product)[
|
||||
"taxes"
|
||||
]
|
||||
@@ -1261,18 +1272,6 @@ class PmsFolio(models.Model):
|
||||
if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids:
|
||||
res[group]["amount"] += t["amount"]
|
||||
res[group]["base"] += t["base"]
|
||||
for line in self.service_ids:
|
||||
price_reduce = line.price_unit * (1.0 - line.discount / 100.0)
|
||||
taxes = line.tax_ids.compute_all(
|
||||
price_reduce, quantity=line.product_qty, product=line.product_id
|
||||
)["taxes"]
|
||||
for tax in line.tax_ids:
|
||||
group = tax.tax_group_id
|
||||
res.setdefault(group, {"amount": 0.0, "base": 0.0})
|
||||
for t in taxes:
|
||||
if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids:
|
||||
res[group]["amount"] += t["amount"]
|
||||
res[group]["base"] += t["base"]
|
||||
res = sorted(res.items(), key=lambda line: line[0].sequence)
|
||||
res = [
|
||||
(line[0].name, line[1]["amount"], line[1]["base"], len(res)) for line in res
|
||||
|
||||
@@ -7,7 +7,6 @@ import time
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
from odoo.tools import float_compare, float_is_zero
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -83,6 +82,12 @@ class PmsReservation(models.Model):
|
||||
copy=False,
|
||||
check_company=True,
|
||||
)
|
||||
sale_line_ids = fields.One2many(
|
||||
comodel_name="folio.sale.line",
|
||||
inverse_name="reservation_id",
|
||||
string="Sale Lines",
|
||||
copy=False,
|
||||
)
|
||||
board_service_room_id = fields.Many2one(
|
||||
"pms.board.service.room.type",
|
||||
string="Board Service",
|
||||
@@ -184,7 +189,8 @@ class PmsReservation(models.Model):
|
||||
)
|
||||
user_id = fields.Many2one(
|
||||
related="folio_id.user_id",
|
||||
depends=["folio_id"],
|
||||
depends=["folio_id.user_id"],
|
||||
default=lambda self: self.env.user.id,
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
@@ -274,14 +280,6 @@ class PmsReservation(models.Model):
|
||||
ondelete="restrict",
|
||||
domain=["|", ("active", "=", False), ("active", "=", True)],
|
||||
)
|
||||
move_line_ids = fields.Many2many(
|
||||
"account.move.line",
|
||||
"reservation_move_rel",
|
||||
"reservation_id",
|
||||
"move_line_id",
|
||||
string="Invoice Lines",
|
||||
copy=False,
|
||||
)
|
||||
localizator = fields.Char(
|
||||
string="Localizator",
|
||||
compute="_compute_localizator",
|
||||
@@ -446,32 +444,6 @@ class PmsReservation(models.Model):
|
||||
readonly=True,
|
||||
default="no",
|
||||
)
|
||||
qty_to_invoice = fields.Float(
|
||||
compute="_compute_qty_to_invoice",
|
||||
string="To Invoice Quantity",
|
||||
store=True,
|
||||
readonly=True,
|
||||
digits=("Product Unit of Measure"),
|
||||
)
|
||||
qty_invoiced = fields.Float(
|
||||
compute="_compute_qty_invoiced",
|
||||
string="Invoiced Quantity",
|
||||
store=True,
|
||||
readonly=True,
|
||||
digits=("Product Unit of Measure"),
|
||||
)
|
||||
untaxed_amount_invoiced = fields.Monetary(
|
||||
"Untaxed Invoiced Amount",
|
||||
compute="_compute_untaxed_amount_invoiced",
|
||||
compute_sudo=True,
|
||||
store=True,
|
||||
)
|
||||
untaxed_amount_to_invoice = fields.Monetary(
|
||||
"Untaxed Amount To Invoice",
|
||||
compute="_compute_untaxed_amount_to_invoice",
|
||||
compute_sudo=True,
|
||||
store=True,
|
||||
)
|
||||
analytic_tag_ids = fields.Many2many(
|
||||
"account.analytic.tag",
|
||||
string="Analytic Tags",
|
||||
@@ -552,20 +524,15 @@ class PmsReservation(models.Model):
|
||||
if reservation.pricelist_id and reservation.room_type_id:
|
||||
board_service_default = self.env["pms.board.service.room.type"].search(
|
||||
[
|
||||
"&",
|
||||
"&",
|
||||
("pms_room_type_id", "=", reservation.room_type_id.id),
|
||||
("by_default", "=", True),
|
||||
"|",
|
||||
("pricelist_id", "=", reservation.pricelist_id.id),
|
||||
("pricelist_id", "=", False),
|
||||
]
|
||||
)
|
||||
if len(board_service_default) > 1:
|
||||
reservation.board_service_room_id = board_service_default.filtered(
|
||||
lambda b: b.pricelist_id == reservation.pricelist_id
|
||||
)
|
||||
else:
|
||||
if (
|
||||
not reservation.board_service_room_id
|
||||
or not reservation.board_service_room_id.pms_room_type_id
|
||||
== reservation.room_type_id
|
||||
):
|
||||
reservation.board_service_room_id = (
|
||||
board_service_default.id if board_service_default else False
|
||||
)
|
||||
@@ -693,6 +660,13 @@ class PmsReservation(models.Model):
|
||||
("is_board_service", "=", True),
|
||||
]
|
||||
)
|
||||
# Avoid recalculating services if the boardservice has not changed
|
||||
if (
|
||||
old_board_lines
|
||||
and reservation.board_service_room_id
|
||||
== reservation._origin.board_service_room_id
|
||||
):
|
||||
return
|
||||
if reservation.board_service_room_id:
|
||||
board = self.env["pms.board.service.room.type"].browse(
|
||||
reservation.board_service_room_id.id
|
||||
@@ -707,6 +681,8 @@ class PmsReservation(models.Model):
|
||||
board_services.append((0, False, res))
|
||||
reservation.service_ids -= old_board_lines
|
||||
reservation.service_ids = board_services
|
||||
elif old_board_lines:
|
||||
reservation.service_ids -= old_board_lines
|
||||
|
||||
@api.depends("partner_id", "agency_id")
|
||||
def _compute_pricelist_id(self):
|
||||
@@ -727,13 +703,19 @@ class PmsReservation(models.Model):
|
||||
reservation.pms_property_id.default_pricelist_id.id
|
||||
)
|
||||
|
||||
@api.depends("pricelist_id")
|
||||
@api.depends("pricelist_id", "room_type_id")
|
||||
def _compute_show_update_pricelist(self):
|
||||
for reservation in self:
|
||||
if (
|
||||
sum(reservation.reservation_line_ids.mapped("price")) > 0
|
||||
and reservation.pricelist_id
|
||||
and reservation._origin.pricelist_id != reservation.pricelist_id
|
||||
and (
|
||||
reservation.pricelist_id
|
||||
and reservation._origin.pricelist_id != reservation.pricelist_id
|
||||
)
|
||||
or (
|
||||
reservation.room_type_id
|
||||
and reservation._origin.room_type_id != reservation.room_type_id
|
||||
)
|
||||
):
|
||||
reservation.show_update_pricelist = True
|
||||
else:
|
||||
@@ -1036,183 +1018,28 @@ class PmsReservation(models.Model):
|
||||
if room_ids:
|
||||
reservation.preferred_room_id = room_ids[0]
|
||||
|
||||
@api.depends("state", "qty_to_invoice", "qty_invoiced")
|
||||
@api.depends(
|
||||
"sale_line_ids",
|
||||
"sale_line_ids.invoice_status",
|
||||
)
|
||||
def _compute_invoice_status(self):
|
||||
"""
|
||||
Compute the invoice status of a Reservation. Possible statuses:
|
||||
- no: if the Folio is not in status 'sale' or 'done', we consider
|
||||
that there is nothing to invoice. This is also hte default value
|
||||
if the conditions of no other status is met.
|
||||
- to invoice: we refer to the quantity to invoice of the line.
|
||||
Refer to method `_compute_get_to_invoice_qty()` for more information
|
||||
on how this quantity is calculated.
|
||||
- invoiced: the quantity invoiced is larger or equal to the
|
||||
quantity ordered.
|
||||
"""
|
||||
precision = self.env["decimal.precision"].precision_get(
|
||||
"Product Unit of Measure"
|
||||
)
|
||||
for line in self:
|
||||
if line.state == "draft":
|
||||
line.invoice_status = "no"
|
||||
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
|
||||
line.invoice_status = "to invoice"
|
||||
elif (
|
||||
float_compare(
|
||||
line.qty_invoiced,
|
||||
len(line.reservation_line_ids),
|
||||
precision_digits=precision,
|
||||
)
|
||||
>= 0
|
||||
):
|
||||
line.invoice_status = "invoiced"
|
||||
else:
|
||||
line.invoice_status = "no"
|
||||
|
||||
@api.depends("qty_invoiced", "nights", "folio_id.state")
|
||||
def _compute_qty_to_invoice(self):
|
||||
"""
|
||||
Compute the quantity to invoice. The quantity to invoice is
|
||||
calculated from the nights quantity.
|
||||
Base on folio sale line invoice status
|
||||
"""
|
||||
for line in self:
|
||||
if line.folio_id.state not in ["draft"]:
|
||||
line.qty_to_invoice = len(line.reservation_line_ids) - line.qty_invoiced
|
||||
else:
|
||||
line.qty_to_invoice = 0
|
||||
|
||||
@api.depends(
|
||||
"move_line_ids.move_id.state",
|
||||
"move_line_ids.quantity",
|
||||
"untaxed_amount_to_invoice",
|
||||
)
|
||||
def _compute_qty_invoiced(self):
|
||||
"""
|
||||
Compute the quantity invoiced. If case of a refund, the quantity
|
||||
invoiced is decreased. We must check day per day and sum or
|
||||
decreased on 1 unit per invoice_line
|
||||
"""
|
||||
for record in self:
|
||||
qty_invoiced = 0.0
|
||||
for line in record.reservation_line_ids:
|
||||
invoice_lines = line.move_line_ids.filtered(
|
||||
lambda r: r.move_id.state != "cancel"
|
||||
)
|
||||
qty_invoiced += len(
|
||||
invoice_lines.filtered(
|
||||
lambda r: r.move_id.move_type == "out_invoice"
|
||||
)
|
||||
) - len(
|
||||
invoice_lines.filtered(
|
||||
lambda r: r.move_id.move_type == "out_refund"
|
||||
)
|
||||
)
|
||||
record.qty_invoiced = qty_invoiced
|
||||
|
||||
@api.depends(
|
||||
"move_line_ids",
|
||||
"move_line_ids.price_total",
|
||||
"move_line_ids.move_id.state",
|
||||
"move_line_ids.move_id.move_type",
|
||||
)
|
||||
def _compute_untaxed_amount_invoiced(self):
|
||||
"""Compute the untaxed amount already invoiced from the reservation,
|
||||
taking the refund attached
|
||||
the reservation into account. This amount is computed as
|
||||
SUM(inv_line.price_subtotal) - SUM(ref_line.price_subtotal)
|
||||
where
|
||||
`inv_line` is a customer invoice line linked to the reservation
|
||||
`ref_line` is a customer credit note (refund)
|
||||
line linked to the reservation
|
||||
"""
|
||||
for line in self:
|
||||
amount_invoiced = 0.0
|
||||
for invoice_line in line.move_line_ids:
|
||||
if invoice_line.move_id.state == "posted":
|
||||
invoice_date = (
|
||||
invoice_line.move_id.invoice_date or fields.Date.today()
|
||||
)
|
||||
if invoice_line.move_id.move_type == "out_invoice":
|
||||
amount_invoiced += invoice_line.currency_id._convert(
|
||||
invoice_line.price_subtotal,
|
||||
line.currency_id,
|
||||
line.company_id,
|
||||
invoice_date,
|
||||
)
|
||||
elif invoice_line.move_id.move_type == "out_refund":
|
||||
amount_invoiced -= invoice_line.currency_id._convert(
|
||||
invoice_line.price_subtotal,
|
||||
line.currency_id,
|
||||
line.company_id,
|
||||
invoice_date,
|
||||
)
|
||||
line.untaxed_amount_invoiced = amount_invoiced
|
||||
|
||||
@api.depends(
|
||||
"state", "discount", "price_total", "room_type_id", "untaxed_amount_invoiced"
|
||||
)
|
||||
def _compute_untaxed_amount_to_invoice(self):
|
||||
"""Total of remaining amount to invoice on the reservation (taxes excl.) as
|
||||
total_sol - amount already invoiced
|
||||
|
||||
Note: Draft invoice are ignored on purpose, the 'to invoice' amount should
|
||||
come only from the reservation.
|
||||
"""
|
||||
for line in self:
|
||||
amount_to_invoice = 0.0
|
||||
if line.state not in ["draft"]:
|
||||
price_subtotal = 0.0
|
||||
price_subtotal = line.price_subtotal
|
||||
if len(line.tax_ids.filtered(lambda tax: tax.price_include)) > 0:
|
||||
# As included taxes are not excluded from the computed subtotal,
|
||||
# `compute_all()` method
|
||||
# has to be called to retrieve the subtotal without them.
|
||||
price_subtotal = line.tax_ids.compute_all(
|
||||
price_subtotal,
|
||||
currency=line.currency_id,
|
||||
quantity=line.nights,
|
||||
product=line.room_type_id.product_id,
|
||||
)["total_excluded"]
|
||||
|
||||
if any(
|
||||
line.move_line_ids.mapped(lambda i: i.discount != line.discount)
|
||||
):
|
||||
# In case of re-invoicing with different discount we
|
||||
# try to calculate manually the
|
||||
# remaining amount to invoice
|
||||
amount = 0
|
||||
for move in line.move_line_ids:
|
||||
if (
|
||||
len(move.tax_ids.filtered(lambda tax: tax.price_include))
|
||||
> 0
|
||||
):
|
||||
amount += move.tax_ids.compute_all(
|
||||
move.currency_id._convert(
|
||||
move.price_unit,
|
||||
line.currency_id,
|
||||
line.company_id,
|
||||
move.date or fields.Date.today(),
|
||||
round=False,
|
||||
)
|
||||
* move.quantity
|
||||
)["total_excluded"]
|
||||
else:
|
||||
amount += (
|
||||
move.currency_id._convert(
|
||||
move.price_unit,
|
||||
line.currency_id,
|
||||
line.company_id,
|
||||
move.date or fields.Date.today(),
|
||||
round=False,
|
||||
)
|
||||
* move.quantity
|
||||
)
|
||||
|
||||
amount_to_invoice = max(price_subtotal - amount, 0)
|
||||
states = list(set(line.sale_line_ids.mapped("invoice_status")))
|
||||
if len(states) == 1:
|
||||
line.invoice_status = states[0]
|
||||
elif len(states) >= 1:
|
||||
if "to_invoice" in states:
|
||||
line.invoice_status = "to_invoice"
|
||||
elif "invoiced" in states:
|
||||
line.invoice_status = "invoiced"
|
||||
else:
|
||||
amount_to_invoice = price_subtotal - line.untaxed_amount_invoiced
|
||||
|
||||
line.untaxed_amount_to_invoice = amount_to_invoice
|
||||
line.invoice_status = "no"
|
||||
else:
|
||||
line.invoice_status = "no"
|
||||
|
||||
@api.depends("reservation_line_ids")
|
||||
def _compute_nights(self):
|
||||
@@ -1529,8 +1356,10 @@ class PmsReservation(models.Model):
|
||||
self.show_update_pricelist = False
|
||||
self.message_post(
|
||||
body=_(
|
||||
"Prices have been recomputed according to pricelist <b>%s<b> ",
|
||||
"""Prices have been recomputed according to pricelist <b>%s</b>
|
||||
and room type <b>%s</b>""",
|
||||
self.pricelist_id.display_name,
|
||||
self.room_type_id.name,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -41,12 +41,12 @@ class PmsReservationLine(models.Model):
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
move_line_ids = fields.Many2many(
|
||||
"account.move.line",
|
||||
"reservation_line_move_rel",
|
||||
sale_line_ids = fields.Many2many(
|
||||
"folio.sale.line",
|
||||
"reservation_line_sale_line_rel",
|
||||
"reservation_line_id",
|
||||
"move_line_id",
|
||||
string="Invoice Lines",
|
||||
"sale_line_id",
|
||||
string="Sales Lines",
|
||||
readonly=True,
|
||||
copy=False,
|
||||
)
|
||||
@@ -65,11 +65,6 @@ class PmsReservationLine(models.Model):
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
invoiced = fields.Boolean(
|
||||
string="Invoiced",
|
||||
compute="_compute_invoiced",
|
||||
store=True,
|
||||
)
|
||||
cancel_discount = fields.Float(
|
||||
string="Cancelation Discount (%)",
|
||||
digits=("Discount"),
|
||||
@@ -311,31 +306,15 @@ class PmsReservationLine(models.Model):
|
||||
else:
|
||||
line.occupies_availability = True
|
||||
|
||||
@api.depends("move_line_ids", "move_line_ids.move_id.state")
|
||||
def _compute_invoiced(self):
|
||||
for line in self:
|
||||
qty_invoiced = 0
|
||||
for invoice_line in line.move_line_ids:
|
||||
if invoice_line.move_id.state != "cancel":
|
||||
if invoice_line.move_id.move_type == "out_invoice":
|
||||
qty_invoiced += 1
|
||||
elif invoice_line.move_id.move_type == "out_refund":
|
||||
qty_invoiced -= 1
|
||||
line.invoiced = False if qty_invoiced < 1 else True
|
||||
|
||||
# TODO: Refact method and allowed cancelled single days
|
||||
@api.depends("reservation_id.cancelled_reason")
|
||||
def _compute_cancel_discount(self):
|
||||
for line in self:
|
||||
line.cancel_discount = 0
|
||||
# TODO: Review cancel logic
|
||||
# reservation = line.reservation_id
|
||||
# pricelist = reservation.pricelist_id
|
||||
# if reservation.state == "cancelled":
|
||||
# # TODO: Set 0 qty on cancel room services change to compute day_qty
|
||||
# # (view constrain service_line_days)
|
||||
# for service in reservation.service_ids:
|
||||
# service.service_line_ids.write({"day_qty": 0})
|
||||
# service._compute_days_qty()
|
||||
# if (
|
||||
# reservation.cancelled_reason
|
||||
# and pricelist
|
||||
|
||||
@@ -208,6 +208,8 @@ class PmsRoomType(models.Model):
|
||||
if room.pms_property_id not in record.pms_property_ids:
|
||||
raise ValidationError(_("Property not allowed in room"))
|
||||
|
||||
# TODO: Not allowed repeat boardservice on room_type with
|
||||
# same properties os without properties
|
||||
@api.constrains("board_service_room_type_ids", "pms_property_ids")
|
||||
def _check_integrity_property_board_service_room_type(self):
|
||||
for record in self:
|
||||
|
||||
@@ -5,7 +5,6 @@ import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.tools import float_compare, float_is_zero
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -52,8 +51,17 @@ class PmsService(models.Model):
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
sale_line_ids = fields.One2many(
|
||||
comodel_name="folio.sale.line",
|
||||
inverse_name="service_id",
|
||||
string="Sale Lines",
|
||||
copy=False,
|
||||
)
|
||||
reservation_id = fields.Many2one(
|
||||
"pms.reservation", "Room", default=_default_reservation_id
|
||||
"pms.reservation",
|
||||
"Room",
|
||||
default=_default_reservation_id,
|
||||
ondelete="cascade",
|
||||
)
|
||||
service_line_ids = fields.One2many(
|
||||
"pms.service.line",
|
||||
@@ -79,14 +87,6 @@ class PmsService(models.Model):
|
||||
readonly=False,
|
||||
domain=["|", ("active", "=", False), ("active", "=", True)],
|
||||
)
|
||||
move_line_ids = fields.Many2many(
|
||||
"account.move.line",
|
||||
"service_line_move_rel",
|
||||
"service_id",
|
||||
"move_line_id",
|
||||
string="move Lines",
|
||||
copy=False,
|
||||
)
|
||||
analytic_tag_ids = fields.Many2many("account.analytic.tag", string="Analytic Tags")
|
||||
currency_id = fields.Many2one(
|
||||
related="folio_id.currency_id", store=True, string="Currency", readonly=True
|
||||
@@ -128,28 +128,6 @@ class PmsService(models.Model):
|
||||
],
|
||||
string="Sales Channel",
|
||||
)
|
||||
price_unit = fields.Float(
|
||||
"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(
|
||||
compute="_compute_get_to_invoice_qty",
|
||||
string="To Invoice",
|
||||
store=True,
|
||||
readonly=True,
|
||||
digits=("Product Unit of Measure"),
|
||||
)
|
||||
qty_invoiced = fields.Float(
|
||||
compute="_compute_get_invoice_qty",
|
||||
string="Invoiced",
|
||||
store=True,
|
||||
readonly=True,
|
||||
digits=("Product Unit of Measure"),
|
||||
)
|
||||
price_subtotal = fields.Monetary(
|
||||
string="Subtotal", readonly=True, store=True, compute="_compute_amount_service"
|
||||
)
|
||||
@@ -214,6 +192,7 @@ class PmsService(models.Model):
|
||||
# cached (otherwise double the date)
|
||||
pass
|
||||
elif not old_line:
|
||||
price_unit = service._get_price_unit_line(idate)
|
||||
lines.append(
|
||||
(
|
||||
0,
|
||||
@@ -221,6 +200,7 @@ class PmsService(models.Model):
|
||||
{
|
||||
"date": idate,
|
||||
"day_qty": day_qty,
|
||||
"price_unit": price_unit,
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -246,12 +226,11 @@ class PmsService(models.Model):
|
||||
]
|
||||
)
|
||||
)
|
||||
_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:
|
||||
price_unit = service._get_price_unit_line()
|
||||
service.service_line_ids = [
|
||||
(
|
||||
0,
|
||||
@@ -259,6 +238,7 @@ class PmsService(models.Model):
|
||||
{
|
||||
"date": fields.Date.today(),
|
||||
"day_qty": day_qty,
|
||||
"price_unit": price_unit,
|
||||
},
|
||||
)
|
||||
]
|
||||
@@ -266,6 +246,7 @@ class PmsService(models.Model):
|
||||
# TODO: Service without reservation(room) but with folio¿?
|
||||
# example: tourist tour in group
|
||||
if not service.service_line_ids:
|
||||
price_unit = service._get_price_unit_line()
|
||||
service.service_line_ids = [
|
||||
(
|
||||
0,
|
||||
@@ -273,6 +254,7 @@ class PmsService(models.Model):
|
||||
{
|
||||
"date": fields.Date.today(),
|
||||
"day_qty": day_qty,
|
||||
"price_unit": price_unit,
|
||||
},
|
||||
)
|
||||
]
|
||||
@@ -300,79 +282,6 @@ class PmsService(models.Model):
|
||||
qty = sum(service.service_line_ids.mapped("day_qty"))
|
||||
service.product_qty = qty
|
||||
|
||||
@api.depends(
|
||||
"product_id",
|
||||
"service_line_ids",
|
||||
"reservation_id.pricelist_id",
|
||||
"reservation_id.pms_property_id",
|
||||
"pms_property_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,
|
||||
property=service.pms_property_id.id,
|
||||
)
|
||||
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
|
||||
|
||||
@api.depends("reservation_id")
|
||||
def _compute_folio_id(self):
|
||||
for record in self:
|
||||
@@ -381,133 +290,54 @@ class PmsService(models.Model):
|
||||
elif not record.folio_id:
|
||||
record.folio_id = False
|
||||
|
||||
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",
|
||||
"pms_property_id",
|
||||
]
|
||||
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 _compute_get_to_invoice_qty(self):
|
||||
"""
|
||||
Compute the quantity to invoice. If the invoice policy is order,
|
||||
the quantity to invoice is calculated from the ordered quantity.
|
||||
Otherwise, the quantity delivered is used.
|
||||
"""
|
||||
for line in self:
|
||||
if line.folio_id.state not in ["draft"]:
|
||||
line.qty_to_invoice = line.product_qty - line.qty_invoiced
|
||||
else:
|
||||
line.qty_to_invoice = 0
|
||||
|
||||
@api.depends("move_line_ids.move_id.state", "move_line_ids.quantity")
|
||||
def _compute_get_invoice_qty(self):
|
||||
"""
|
||||
Compute the quantity invoiced. If case of a refund,
|
||||
the quantity invoiced is decreased. Note that this is the case only
|
||||
if the refund is generated from the Folio and that is intentional: if
|
||||
a refund made would automatically decrease the invoiced quantity,
|
||||
then there is a risk of reinvoicing it automatically, which may
|
||||
not be wanted at all. That's why the refund has to be
|
||||
created from the Folio
|
||||
"""
|
||||
for line in self:
|
||||
qty_invoiced = 0.0
|
||||
for invoice_line in line.move_line_ids:
|
||||
if invoice_line.move_id.state != "cancel":
|
||||
if invoice_line.move_id.move_type == "out_invoice":
|
||||
qty_invoiced += invoice_line.product_uom_id._compute_quantity(
|
||||
invoice_line.quantity, line.product_id.uom_id
|
||||
)
|
||||
elif invoice_line.move_id.move_type == "out_refund":
|
||||
if (
|
||||
not line.is_downpayment
|
||||
or line.untaxed_amount_to_invoice == 0
|
||||
):
|
||||
qty_invoiced -= (
|
||||
invoice_line.product_uom_id._compute_quantity(
|
||||
invoice_line.quantity, line.product_id.uom_id
|
||||
)
|
||||
)
|
||||
line.qty_invoiced = qty_invoiced
|
||||
|
||||
@api.depends("product_qty", "qty_to_invoice", "qty_invoiced")
|
||||
@api.depends(
|
||||
"sale_line_ids",
|
||||
"sale_line_ids.invoice_status",
|
||||
)
|
||||
def _compute_invoice_status(self):
|
||||
"""
|
||||
Compute the invoice status of a SO line. Possible statuses:
|
||||
- no: if the SO is not in status 'sale' or 'done',
|
||||
we consider that there is nothing to invoice.
|
||||
This is also hte default value if the conditions of no other
|
||||
status is met.
|
||||
- to invoice: we refer to the quantity to invoice of the line.
|
||||
Refer to method `_compute_get_to_invoice_qty()` for more information on
|
||||
how this quantity is calculated.
|
||||
- upselling: this is possible only for a product invoiced on ordered
|
||||
quantities for which we delivered more than expected.
|
||||
The could arise if, for example, a project took more time than
|
||||
expected but we decided not to invoice the extra cost to the
|
||||
client. This occurs onyl in state 'sale', so that when a Folio
|
||||
is set to done, the upselling opportunity is removed from the list.
|
||||
- invoiced: the quantity invoiced is larger or equal to the
|
||||
quantity ordered.
|
||||
Compute the invoice status of a Reservation. Possible statuses:
|
||||
Base on folio sale line invoice status
|
||||
"""
|
||||
precision = self.env["decimal.precision"].precision_get(
|
||||
"Product Unit of Measure"
|
||||
)
|
||||
for line in self:
|
||||
state = line.folio_id.state or "draft"
|
||||
if state == "draft":
|
||||
line.invoice_status = "no"
|
||||
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
|
||||
line.invoice_status = "to invoice"
|
||||
elif (
|
||||
float_compare(
|
||||
line.qty_invoiced, line.product_qty, precision_digits=precision
|
||||
)
|
||||
>= 0
|
||||
):
|
||||
line.invoice_status = "invoiced"
|
||||
states = list(set(line.sale_line_ids.mapped("invoice_status")))
|
||||
if len(states) == 1:
|
||||
line.invoice_status = states[0]
|
||||
elif len(states) >= 1:
|
||||
if "to_invoice" in states:
|
||||
line.invoice_status = "to_invoice"
|
||||
elif "invoiced" in states:
|
||||
line.invoice_status = "invoiced"
|
||||
else:
|
||||
line.invoice_status = "no"
|
||||
else:
|
||||
line.invoice_status = "no"
|
||||
|
||||
@api.depends("product_qty", "discount", "price_unit", "tax_ids")
|
||||
@api.depends("service_line_ids.price_day_total")
|
||||
def _compute_amount_service(self):
|
||||
for service in self:
|
||||
folio = service.folio_id
|
||||
reservation = service.reservation_id
|
||||
currency = folio.currency_id if folio else reservation.currency_id
|
||||
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
|
||||
)
|
||||
service.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"],
|
||||
}
|
||||
)
|
||||
if service.service_line_ids:
|
||||
service.update(
|
||||
{
|
||||
"price_tax": sum(
|
||||
service.service_line_ids.mapped("price_day_tax")
|
||||
),
|
||||
"price_total": sum(
|
||||
service.service_line_ids.mapped("price_day_total")
|
||||
),
|
||||
"price_subtotal": sum(
|
||||
service.service_line_ids.mapped("price_day_subtotal")
|
||||
),
|
||||
}
|
||||
)
|
||||
else:
|
||||
service.update(
|
||||
{
|
||||
"price_tax": 0,
|
||||
"price_total": 0,
|
||||
"price_subtotal": 0,
|
||||
}
|
||||
)
|
||||
|
||||
# Action methods
|
||||
def open_service_ids(self):
|
||||
@@ -537,20 +367,12 @@ class PmsService(models.Model):
|
||||
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
|
||||
product_context = dict(
|
||||
self.env.context,
|
||||
partner_id=origin.partner_id.id,
|
||||
date=folio.date_order if folio else fields.Date.today(),
|
||||
uom=self.product_id.uom_id.id,
|
||||
)
|
||||
return product.price
|
||||
final_price, rule_id = origin.pricelist_id.with_context(
|
||||
product_context
|
||||
).get_product_price_rule(
|
||||
self.product_id, self.product_qty or 1.0, origin.partner_id
|
||||
)
|
||||
product._context
|
||||
).get_product_price_rule(product, self.product_qty or 1.0, origin.partner_id)
|
||||
base_price, currency_id = self.with_context(
|
||||
product_context
|
||||
product._context
|
||||
)._get_real_price_currency(
|
||||
product,
|
||||
rule_id,
|
||||
@@ -562,12 +384,74 @@ class PmsService(models.Model):
|
||||
base_price = (
|
||||
self.env["res.currency"]
|
||||
.browse(currency_id)
|
||||
.with_context(product_context)
|
||||
.with_context(product._context)
|
||||
.compute(base_price, origin.pricelist_id.currency_id)
|
||||
)
|
||||
# negative discounts (= surcharge) are included in the display price
|
||||
return max(base_price, final_price)
|
||||
|
||||
def _get_real_price_currency(self, product, rule_id, qty, uom, pricelist_id):
|
||||
"""Retrieve the price before applying the pricelist
|
||||
:param obj product: object of current product record
|
||||
:parem float qty: total quantity of product
|
||||
:param tuple price_and_rule: tuple(price, suitable_rule)
|
||||
coming from pricelist computation
|
||||
:param obj uom: unit of measure of current order line
|
||||
:param integer pricelist_id: pricelist id of sales order"""
|
||||
PricelistItem = self.env["product.pricelist.item"]
|
||||
field_name = "lst_price"
|
||||
currency_id = None
|
||||
product_currency = product.currency_id
|
||||
if rule_id:
|
||||
pricelist_item = PricelistItem.browse(rule_id)
|
||||
if pricelist_item.pricelist_id.discount_policy == "without_discount":
|
||||
while (
|
||||
pricelist_item.base == "pricelist"
|
||||
and pricelist_item.base_pricelist_id
|
||||
and pricelist_item.base_pricelist_id.discount_policy
|
||||
== "without_discount"
|
||||
):
|
||||
price, rule_id = pricelist_item.base_pricelist_id.with_context(
|
||||
uom=uom.id
|
||||
).get_product_price_rule(product, qty, self.order_id.partner_id)
|
||||
pricelist_item = PricelistItem.browse(rule_id)
|
||||
|
||||
if pricelist_item.base == "standard_price":
|
||||
field_name = "standard_price"
|
||||
product_currency = product.cost_currency_id
|
||||
elif (
|
||||
pricelist_item.base == "pricelist" and pricelist_item.base_pricelist_id
|
||||
):
|
||||
field_name = "price"
|
||||
product = product.with_context(
|
||||
pricelist=pricelist_item.base_pricelist_id.id
|
||||
)
|
||||
product_currency = pricelist_item.base_pricelist_id.currency_id
|
||||
currency_id = pricelist_item.pricelist_id.currency_id
|
||||
|
||||
if not currency_id:
|
||||
currency_id = product_currency
|
||||
cur_factor = 1.0
|
||||
else:
|
||||
if currency_id.id == product_currency.id:
|
||||
cur_factor = 1.0
|
||||
else:
|
||||
cur_factor = currency_id._get_conversion_rate(
|
||||
product_currency,
|
||||
currency_id,
|
||||
self.company_id or self.env.company,
|
||||
self.folio_id.date_order or fields.Date.today(),
|
||||
)
|
||||
|
||||
product_uom = self.env.context.get("uom") or product.uom_id.id
|
||||
if uom and uom.id != product_uom:
|
||||
# the unit price is in a different uom
|
||||
uom_factor = uom._compute_price(1.0, product.uom_id)
|
||||
else:
|
||||
uom_factor = 1.0
|
||||
|
||||
return product[field_name] * uom_factor * cur_factor, currency_id
|
||||
|
||||
# Businness Methods
|
||||
def _service_day_qty(self):
|
||||
self.ensure_one()
|
||||
@@ -578,3 +462,38 @@ class PmsService(models.Model):
|
||||
if self.product_id.per_person:
|
||||
qty = self.reservation_id.adults
|
||||
return qty
|
||||
|
||||
def _get_price_unit_line(self, date=False):
|
||||
self.ensure_one()
|
||||
folio = self.folio_id
|
||||
reservation = self.reservation_id
|
||||
origin = reservation if reservation else folio
|
||||
if origin:
|
||||
partner = origin.partner_id
|
||||
pricelist = origin.pricelist_id
|
||||
board_room_type = False
|
||||
product_context = dict(
|
||||
self.env.context,
|
||||
lang=partner.lang,
|
||||
partner=partner.id,
|
||||
quantity=self.product_qty,
|
||||
date=folio.date_order if folio else fields.Date.today(),
|
||||
pricelist=pricelist.id,
|
||||
board_service=board_room_type.id if board_room_type else False,
|
||||
uom=self.product_id.uom_id.id,
|
||||
fiscal_position=False,
|
||||
property=self.pms_property_id.id,
|
||||
)
|
||||
if date:
|
||||
product_context["date_overnight"] = date
|
||||
if reservation and self.is_board_service:
|
||||
product_context["board_service"] = reservation.board_service_room_id.id
|
||||
product = self.product_id.with_context(product_context)
|
||||
return self.env["account.tax"]._fix_tax_included_price_company(
|
||||
self._get_display_price(product),
|
||||
product.taxes_id,
|
||||
self.tax_ids,
|
||||
origin.company_id,
|
||||
)
|
||||
else:
|
||||
return 0
|
||||
|
||||
@@ -29,35 +29,144 @@ class PmsServiceLine(models.Model):
|
||||
)
|
||||
date = fields.Date("Date")
|
||||
day_qty = fields.Integer("Units")
|
||||
price_total = fields.Float(
|
||||
"Price Total", compute="_compute_price_total", store=True
|
||||
)
|
||||
price_unit = fields.Float(
|
||||
"Unit Price", related="service_id.price_unit", readonly=True, store=True
|
||||
"Unit Price",
|
||||
digits=("Product Price"),
|
||||
)
|
||||
price_day_subtotal = fields.Monetary(
|
||||
string="Subtotal",
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute="_compute_day_amount_service",
|
||||
)
|
||||
price_day_total = fields.Monetary(
|
||||
string="Total", readonly=True, store=True, compute="_compute_day_amount_service"
|
||||
)
|
||||
price_day_tax = fields.Float(
|
||||
string="Taxes Amount",
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute="_compute_day_amount_service",
|
||||
)
|
||||
currency_id = fields.Many2one(
|
||||
related="service_id.currency_id", store=True, string="Currency", readonly=True
|
||||
)
|
||||
room_id = fields.Many2one(
|
||||
string="Room", related="service_id.reservation_id", readonly=True, store=True
|
||||
)
|
||||
discount = fields.Float(
|
||||
"Discount", related="service_id.discount", readonly=True, store=True
|
||||
string="Discount (%)",
|
||||
digits=("Discount"),
|
||||
default=0.0,
|
||||
compute="_compute_discount",
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
cancel_discount = fields.Float(
|
||||
"Cancelation Discount", compute="_compute_cancel_discount"
|
||||
)
|
||||
|
||||
# Compute and Search methods
|
||||
@api.depends("day_qty", "service_id.price_total")
|
||||
def _compute_price_total(self):
|
||||
@api.depends("day_qty", "discount", "price_unit", "tax_ids")
|
||||
def _compute_day_amount_service(self):
|
||||
for line in self:
|
||||
amount_service = line.price_unit
|
||||
if amount_service > 0:
|
||||
currency = line.service_id.currency_id
|
||||
product = line.product_id
|
||||
price = amount_service * (1 - (line.discount or 0.0) * 0.01)
|
||||
# REVIEW: line.day_qty is not the total qty (the total is on service_id)
|
||||
taxes = line.tax_ids.compute_all(
|
||||
price, currency, line.day_qty, product=product
|
||||
)
|
||||
line.update(
|
||||
{
|
||||
"price_day_tax": sum(
|
||||
t.get("amount", 0.0) for t in taxes.get("taxes", [])
|
||||
),
|
||||
"price_day_total": taxes["total_included"],
|
||||
"price_day_subtotal": taxes["total_excluded"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
line.update(
|
||||
{
|
||||
"price_day_tax": 0,
|
||||
"price_day_total": 0,
|
||||
"price_day_subtotal": 0,
|
||||
}
|
||||
)
|
||||
|
||||
@api.depends("service_id.reservation_id", "service_id.reservation_id.discount")
|
||||
def _compute_discount(self):
|
||||
"""
|
||||
Used to reports
|
||||
On board service the line discount is always
|
||||
equal to reservation line discount
|
||||
"""
|
||||
for record in self:
|
||||
if record.service_id.product_qty != 0:
|
||||
record.price_total = (
|
||||
record.service_id.price_total * record.day_qty
|
||||
) / record.service_id.product_qty
|
||||
else:
|
||||
record.price_total = 0
|
||||
if record.is_board_service:
|
||||
record.discount = (
|
||||
record.service_id.reservation_id.reservation_line_ids.filtered(
|
||||
lambda l: l.date == record.date
|
||||
).discount
|
||||
)
|
||||
elif not record.discount:
|
||||
record.discount = 0
|
||||
|
||||
# TODO: Refact method and allowed cancelled single days
|
||||
@api.depends("service_id.reservation_id.cancelled_reason")
|
||||
def _compute_cancel_discount(self):
|
||||
for line in self:
|
||||
line.cancel_discount = 0
|
||||
# TODO: Review cancel logic
|
||||
# reservation = line.reservation_id.reservation_id
|
||||
# pricelist = reservation.pricelist_id
|
||||
# if reservation.state == "cancelled":
|
||||
# if (
|
||||
# reservation.cancelled_reason
|
||||
# and pricelist
|
||||
# and pricelist.cancelation_rule_id
|
||||
# ):
|
||||
# date_start_dt = fields.Date.from_string(
|
||||
# reservation.checkin
|
||||
# )
|
||||
# date_end_dt = fields.Date.from_string(
|
||||
# reservation.checkout
|
||||
# )
|
||||
# days = abs((date_end_dt - date_start_dt).days)
|
||||
# rule = pricelist.cancelation_rule_id
|
||||
# if reservation.cancelled_reason == "late":
|
||||
# discount = 100 - rule.penalty_late
|
||||
# if rule.apply_on_late == "first":
|
||||
# days = 1
|
||||
# elif rule.apply_on_late == "days":
|
||||
# days = rule.days_late
|
||||
# elif reservation.cancelled_reason == "noshow":
|
||||
# discount = 100 - rule.penalty_noshow
|
||||
# if rule.apply_on_noshow == "first":
|
||||
# days = 1
|
||||
# elif rule.apply_on_noshow == "days":
|
||||
# days = rule.days_late - 1
|
||||
# elif reservation.cancelled_reason == "intime":
|
||||
# discount = 100
|
||||
|
||||
# checkin = reservation.checkin
|
||||
# dates = []
|
||||
# for i in range(0, days):
|
||||
# dates.append(
|
||||
# (
|
||||
# fields.Date.from_string(checkin) + timedelta(days=i)
|
||||
# ).strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
# )
|
||||
# reservation.reservation_line_ids.filtered(
|
||||
# lambda r: r.date in dates
|
||||
# ).update({"cancel_discount": discount})
|
||||
# reservation.reservation_line_ids.filtered(
|
||||
# lambda r: r.date not in dates
|
||||
# ).update({"cancel_discount": 100})
|
||||
# else:
|
||||
# reservation.reservation_line_ids.update({"cancel_discount": 0})
|
||||
# else:
|
||||
# reservation.reservation_line_ids.update({"cancel_discount": 0})
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.constrains("day_qty")
|
||||
|
||||
@@ -80,15 +80,16 @@ class ProductPricelist(models.Model):
|
||||
def _compute_price_rule_get_items(
|
||||
self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
|
||||
):
|
||||
|
||||
if (
|
||||
"property" in self._context
|
||||
and self._context["property"]
|
||||
and "date_overnight" in self._context
|
||||
and self._context.get("date_overnight")
|
||||
):
|
||||
self.env["product.pricelist.item"].flush(
|
||||
["price", "currency_id", "company_id"]
|
||||
)
|
||||
# board_service_id = self._context.get("board_service")
|
||||
# on_board_service_bool = True if board_service_id else False
|
||||
# self.env["product.pricelist.item"].flush(
|
||||
# ["price", "currency_id", "company_id"]
|
||||
# )
|
||||
self.env.cr.execute(
|
||||
"""
|
||||
SELECT item.id
|
||||
@@ -99,6 +100,8 @@ class ProductPricelist(models.Model):
|
||||
ON item.pricelist_id = cab.product_pricelist_id
|
||||
LEFT JOIN pms_property_product_pricelist_item_rel lin
|
||||
ON item.id = lin.product_pricelist_item_id
|
||||
LEFT JOIN board_service_pricelist_item_rel board
|
||||
ON item.id = board.pricelist_item_id
|
||||
WHERE (lin.pms_property_id = %s OR lin.pms_property_id IS NULL)
|
||||
AND (cab.pms_property_id = %s OR cab.pms_property_id IS NULL)
|
||||
AND (item.product_tmpl_id IS NULL
|
||||
@@ -132,6 +135,8 @@ class ProductPricelist(models.Model):
|
||||
prod_tmpl_ids,
|
||||
prod_ids,
|
||||
categ_ids,
|
||||
# on_board_service_bool,
|
||||
# board_service_id,
|
||||
self.id,
|
||||
date,
|
||||
date,
|
||||
|
||||
@@ -18,6 +18,17 @@ class ProductPricelistItem(models.Model):
|
||||
string="End Date Overnight",
|
||||
help="End date to apply daily pricelist items",
|
||||
)
|
||||
on_board_service = fields.Boolean("Those included in Board Services")
|
||||
board_service_room_type_ids = fields.Many2many(
|
||||
"pms.board.service.room.type",
|
||||
"board_service_pricelist_item_rel",
|
||||
"pricelist_item_id",
|
||||
"board_service_id",
|
||||
string="Board Services on Room Types",
|
||||
ondelete="cascade", # check_company=True,
|
||||
help="""Specify a Board services on Room Types.""",
|
||||
# domain="[('pms_property_ids', 'in', [allowed_property_ids, False])]",
|
||||
)
|
||||
|
||||
allowed_property_ids = fields.Many2many(
|
||||
"pms.property",
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
from odoo import api, models
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
board_price = fields.Float(
|
||||
"Board Service Price",
|
||||
digits="Product Price",
|
||||
compute="_compute_board_price",
|
||||
help="Get price price on board service",
|
||||
)
|
||||
|
||||
@api.depends_context(
|
||||
"pricelist",
|
||||
"partner",
|
||||
@@ -15,3 +22,30 @@ class ProductProduct(models.Model):
|
||||
)
|
||||
def _compute_product_price(self):
|
||||
super(ProductProduct, self)._compute_product_price()
|
||||
|
||||
def _compute_board_price(self):
|
||||
for record in self:
|
||||
if self._context.get("board_service"):
|
||||
record.board_price = (
|
||||
self.env["pms.board.service.room.type.line"]
|
||||
.search(
|
||||
[
|
||||
(
|
||||
"pms_board_service_room_type_id",
|
||||
"=",
|
||||
self._context.get("board_service"),
|
||||
),
|
||||
("product_id", "=", record.id),
|
||||
]
|
||||
)
|
||||
.amount
|
||||
)
|
||||
else:
|
||||
record.board_price = False
|
||||
|
||||
def price_compute(self, price_type, uom=False, currency=False, company=None):
|
||||
if self._context.get("board_service"):
|
||||
price_type = "board_price"
|
||||
return super(ProductProduct, self).price_compute(
|
||||
price_type, uom, currency, company
|
||||
)
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
<t t-if="service.is_board_service">
|
||||
<t
|
||||
t-set="price"
|
||||
t-value="service.product_qty/line.product_uom_qty*service.price_unit*(1-(service.discount or 0.0)*0.01) + price"
|
||||
t-value="service.product_qty/line.price_total*(1-(service.reservation_id.discount or 0.0)*0.01) + price"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -57,8 +57,6 @@ user_access_pms_folio_availability_wizard,user_access_pms_folio_availability_wiz
|
||||
user_access_pms_num_rooms_selection,user_access_pms_num_rooms_selection,model_pms_num_rooms_selection,pms.group_pms_user,1,1,1,1
|
||||
user_access_pms_folio_sale_line,user_access_pms_folio_sale_line,model_folio_sale_line,pms.group_pms_user,1,0,0,0
|
||||
user_access_folio_make_invoice_advance,user_access_folio_make_invoice_advance,model_folio_advance_payment_inv,pms.group_pms_user,1,1,1,1
|
||||
user_access_pms_invoice_filter_days,user_access_pms_invoice_filter_days,model_pms_invoice_filter_days,pms.group_pms_user,1,1,1,1
|
||||
user_access_pms_invoice_filter_days_items,user_access_pms_invoice_filter_days_items,model_pms_invoice_filter_days_items,pms.group_pms_user,1,1,1,1
|
||||
user_access_wizard_payment_folio,user_access_wizard_payment_folio,model_wizard_payment_folio,pms.group_pms_user,1,1,1,1
|
||||
user_access_wizard_folio_changes,user_access_wizard_folio_changes,model_wizard_folio_changes,pms.group_pms_user,1,1,1,1
|
||||
user_access_pms_folio_portal,user_access_pms_folio_portal,model_pms_folio,base.group_portal,1,0,0,0
|
||||
|
||||
|
@@ -1,5 +1,3 @@
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
from .common import TestHotel
|
||||
|
||||
|
||||
@@ -39,32 +37,3 @@ class TestPmsBoardServiceRoomType(TestHotel):
|
||||
"class_id": self.room_type_class.id,
|
||||
}
|
||||
)
|
||||
|
||||
def test_room_type_property_integrity(self):
|
||||
self._create_common_scenario()
|
||||
self.room_type.pms_property_ids = [self.property1.id]
|
||||
with self.assertRaises(ValidationError):
|
||||
self.board_service_room_type = self.env[
|
||||
"pms.board.service.room.type"
|
||||
].create(
|
||||
{
|
||||
"pms_board_service_id": self.board_service.id,
|
||||
"pms_room_type_id": self.room_type.id,
|
||||
"pms_property_ids": self.property2,
|
||||
}
|
||||
)
|
||||
|
||||
def test_pricelist_property_integrity(self):
|
||||
self._create_common_scenario()
|
||||
self.pricelist = self.env["product.pricelist"].create(
|
||||
{"name": "pricelist_1", "pms_property_ids": [self.property1.id]}
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env["pms.board.service.room.type"].create(
|
||||
{
|
||||
"pms_board_service_id": self.board_service.id,
|
||||
"pms_room_type_id": self.room_type.id,
|
||||
"pricelist_id": self.pricelist.id,
|
||||
"pms_property_ids": self.property2,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -726,51 +726,52 @@ class TestRoomTypeCodePropertyUniqueness(TestRoomType):
|
||||
)
|
||||
r.pms_property_ids = [(4, self.p1.id)]
|
||||
|
||||
def test_check_board_service_property_integrity(self):
|
||||
self.company1 = self.env["res.company"].create(
|
||||
{
|
||||
"name": "Pms_Company_Test",
|
||||
}
|
||||
)
|
||||
self.property1 = self.env["pms.property"].create(
|
||||
{
|
||||
"name": "Pms_property_test1",
|
||||
"company_id": self.company1.id,
|
||||
"default_pricelist_id": self.env.ref("product.list0").id,
|
||||
}
|
||||
)
|
||||
self.property2 = self.env["pms.property"].create(
|
||||
{
|
||||
"name": "Pms_property_test2",
|
||||
"company_id": self.company1.id,
|
||||
"default_pricelist_id": self.env.ref("product.list0").id,
|
||||
}
|
||||
)
|
||||
self.room_type_class = self.env["pms.room.type.class"].create(
|
||||
{"name": "Room Type Class", "code_class": "SIN1"}
|
||||
)
|
||||
self.room_type = self.env["pms.room.type"].create(
|
||||
{
|
||||
"name": "Room Type",
|
||||
"code_type": "Type1",
|
||||
"pms_property_ids": self.property2,
|
||||
"class_id": self.room_type_class.id,
|
||||
}
|
||||
)
|
||||
self.board_service = self.env["pms.board.service"].create(
|
||||
{
|
||||
"name": "Board Service",
|
||||
}
|
||||
)
|
||||
with self.assertRaises(ValidationError):
|
||||
self.env["pms.board.service.room.type"].create(
|
||||
{
|
||||
"pms_board_service_id": self.board_service.id,
|
||||
"pms_room_type_id": self.room_type.id,
|
||||
"pricelist_id": self.env.ref("product.list0").id,
|
||||
"pms_property_ids": self.property2,
|
||||
}
|
||||
)
|
||||
# TODO: pending multi property PR
|
||||
# def test_check_board_service_property_integrity(self):
|
||||
# self.company1 = self.env["res.company"].create(
|
||||
# {
|
||||
# "name": "Pms_Company_Test",
|
||||
# }
|
||||
# )
|
||||
# self.property1 = self.env["pms.property"].create(
|
||||
# {
|
||||
# "name": "Pms_property_test1",
|
||||
# "company_id": self.company1.id,
|
||||
# "default_pricelist_id": self.env.ref("product.list0").id,
|
||||
# }
|
||||
# )
|
||||
# self.property2 = self.env["pms.property"].create(
|
||||
# {
|
||||
# "name": "Pms_property_test2",
|
||||
# "company_id": self.company1.id,
|
||||
# "default_pricelist_id": self.env.ref("product.list0").id,
|
||||
# }
|
||||
# )
|
||||
# self.room_type_class = self.env["pms.room.type.class"].create(
|
||||
# {"name": "Room Type Class", "code_class": "SIN1"}
|
||||
# )
|
||||
# self.room_type = self.env["pms.room.type"].create(
|
||||
# {
|
||||
# "name": "Room Type",
|
||||
# "code_type": "Type1",
|
||||
# "pms_property_ids": self.property2,
|
||||
# "class_id": self.room_type_class.id,
|
||||
# }
|
||||
# )
|
||||
# self.board_service = self.env["pms.board.service"].create(
|
||||
# {
|
||||
# "name": "Board Service",
|
||||
# "pms_property_ids": self.property1,
|
||||
# }
|
||||
# )
|
||||
# with self.assertRaises(ValidationError):
|
||||
# self.env["pms.board.service.room.type"].create(
|
||||
# {
|
||||
# "pms_board_service_id": self.board_service.id,
|
||||
# "pms_room_type_id": self.room_type.id,
|
||||
# "pms_property_ids": self.property2,
|
||||
# }
|
||||
# )
|
||||
|
||||
def test_check_amenities_property_integrity(self):
|
||||
self.company1 = self.env["res.company"].create(
|
||||
|
||||
@@ -6,37 +6,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_date']" position="after">
|
||||
<field name="folio_ids" widget="many2many_tags" />
|
||||
<field name="from_reservation" invisible="1" />
|
||||
<field name="pms_property_id" invisible="1" />
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//field[@name='invoice_line_ids']/tree/field[@name='name']"
|
||||
position="after"
|
||||
>
|
||||
<button
|
||||
name="invoice_filter_days"
|
||||
type="object"
|
||||
icon="fa-calendar"
|
||||
string="Filter-days"
|
||||
aria-label="Change Period"
|
||||
class="float-right"
|
||||
attrs="{
|
||||
'column_invisible': ['|',('parent.from_reservation', '=', False),('parent.state', '!=', 'draft')],
|
||||
'invisible': [('reservation_line_ids', '=', [])]
|
||||
}"
|
||||
/>
|
||||
<field name="reservation_line_ids" invisible="1" />
|
||||
</xpath>
|
||||
<!-- <xpath
|
||||
expr="//field[@name='invoice_line_ids']/tree/field[@name='quantity']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="attrs">
|
||||
{
|
||||
'readonly': [('reservation_line_ids', '!=', False)],
|
||||
}
|
||||
</attribute>
|
||||
</xpath> -->
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@
|
||||
<t t-if="service.is_board_service">
|
||||
<t
|
||||
t-set="price"
|
||||
t-value="service.product_qty/line.product_uom_qty*service.price_unit*(1-(service.discount or 0.0)*0.01) + price"
|
||||
t-value="service.product_qty/line.price_total*(1-(service.reservation_id.discount or 0.0)*0.01) + price"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -190,6 +190,7 @@
|
||||
attrs="{'invisible':[('state','not in',('cancel'))]}"
|
||||
/>
|
||||
<field name="internal_comment" />
|
||||
<field name="user_id" />
|
||||
</group>
|
||||
<group
|
||||
colspan="2"
|
||||
@@ -286,8 +287,16 @@
|
||||
<field name="sequence" widget="handle" />
|
||||
<!-- We do not display the type because we don't want the user to be bothered with that information if he has no section or note. -->
|
||||
<field name="display_type" invisible="1" />
|
||||
<field name="is_board_service" invisible="1" />
|
||||
|
||||
<!-- <field name="product_updatable" invisible="1"/> -->
|
||||
<button
|
||||
title="Board Service"
|
||||
class="oe_stat_button"
|
||||
icon="fa-1x fa-bed"
|
||||
name="open_service_ids"
|
||||
attrs="{'invisible':[('is_board_service','=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="product_id"
|
||||
options="{'no_open': True}"
|
||||
@@ -348,7 +357,6 @@
|
||||
<field
|
||||
name="discount"
|
||||
string="Disc.%"
|
||||
groups="product.group_discount_per_so_line"
|
||||
optional="show"
|
||||
widget="product_discount"
|
||||
/>
|
||||
@@ -419,7 +427,7 @@
|
||||
/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Other data" invisible="1">
|
||||
<page string="Other data">
|
||||
<group>
|
||||
<field name="user_id" />
|
||||
<field name="client_order_ref" />
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
class="alert alert-info"
|
||||
role="alert"
|
||||
style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|',('shared_folio','=',False),('splitted', '=', True)]}"
|
||||
attrs="{'invisible': [('shared_folio','=',False)]}"
|
||||
>
|
||||
This reservation has other reservantions and/or services in the
|
||||
folio, you can check it in the
|
||||
@@ -61,6 +61,24 @@
|
||||
/>
|
||||
</bold>
|
||||
</div>
|
||||
<div
|
||||
class="alert alert-warning"
|
||||
role="alert"
|
||||
style="margin-bottom:0px;"
|
||||
attrs="{'invisible': ['|', ('show_update_pricelist', '=', False), ('state', 'in', ['done'])]}"
|
||||
>
|
||||
Recompute all prices based on this pricelist
|
||||
<bold>
|
||||
<button
|
||||
name="update_prices"
|
||||
type="object"
|
||||
string=" Update Prices"
|
||||
class="btn-link mb-1 px-0"
|
||||
icon="fa-refresh"
|
||||
confirm="This will update all unit prices based on the currently set pricelist."
|
||||
/>
|
||||
</bold>
|
||||
</div>
|
||||
<div
|
||||
class="alert alert-warning"
|
||||
role="alert"
|
||||
@@ -197,7 +215,6 @@
|
||||
nolabel="1"
|
||||
context="{'checkin': checkin, 'checkout': checkout, 'pms_property_id':pms_property_id, 'pricelist_id':pricelist_id}"
|
||||
options="{'no_create': True,'no_open': True}"
|
||||
attrs="{'readonly':[('state','not in',('draft'))]}"
|
||||
style="margin-right: 30px;"
|
||||
/>
|
||||
<field
|
||||
@@ -302,16 +319,6 @@
|
||||
name="pricelist_id"
|
||||
options="{'no_open':True,'no_create': True}"
|
||||
/>
|
||||
<button
|
||||
name="update_prices"
|
||||
type="object"
|
||||
string=" Update Prices"
|
||||
help="Recompute all prices based on this pricelist"
|
||||
class="btn-link mb-1 px-0"
|
||||
icon="fa-refresh"
|
||||
confirm="This will update all unit prices based on the currently set pricelist."
|
||||
attrs="{'invisible': ['|', ('show_update_pricelist', '=', False), ('state', 'in', ['done'])]}"
|
||||
/>
|
||||
<!--<field
|
||||
name="agency_id"
|
||||
options="{'no_create': True,'no_open': True}"
|
||||
@@ -332,16 +339,12 @@
|
||||
name="children_occupying"
|
||||
attrs="{'invisible': [('children', '=', 0)]}"
|
||||
/>
|
||||
<field name="qty_invoiced" invisible="1" />
|
||||
<field name="qty_to_invoice" invisible="1" />
|
||||
<field name="allowed_room_ids" invisible="1" />
|
||||
<field name="folio_payment_state" invisible="1" />
|
||||
<!-- TODO: How to filter to avoid show False (generic) pricelist board when exist a specific pricelist board¿? -->
|
||||
<field
|
||||
name="board_service_room_id"
|
||||
domain="[
|
||||
('pms_room_type_id', '=', room_type_id),
|
||||
('pricelist_id', 'in', [pricelist_id, False])]"
|
||||
domain="[('pms_room_type_id', '=', room_type_id)]"
|
||||
options="{'no_create': True,'no_open': True}"
|
||||
attrs="{'invisible': [('reservation_type','in',('out'))]}"
|
||||
/>
|
||||
@@ -445,7 +448,6 @@
|
||||
<field name="date" readonly="1" force_save="1" />
|
||||
<field name="price" />
|
||||
<field name="discount" />
|
||||
<field name="move_line_ids" invisible="1" />
|
||||
<field
|
||||
name="cancel_discount"
|
||||
attrs="{'column_invisible': [('parent.state','!=','cancelled')]}"
|
||||
@@ -503,8 +505,6 @@
|
||||
name="open_service_ids"
|
||||
attrs="{'invisible': [('per_day','=',False)]}"
|
||||
/>
|
||||
<field name="price_unit" />
|
||||
<field name="discount" />
|
||||
<field name="tax_ids" widget="many2many_tags" />
|
||||
<field name="price_subtotal" />
|
||||
<field name="price_tax" />
|
||||
@@ -513,6 +513,16 @@
|
||||
<tree string="Days">
|
||||
<field name="date" />
|
||||
<field name="day_qty" />
|
||||
<field
|
||||
name="discount"
|
||||
attrs="{'readonly':[('is_board_service','=', True)]}"
|
||||
/>
|
||||
<field name="price_unit" />
|
||||
<field name="price_day_total" />
|
||||
<field
|
||||
name="is_board_service"
|
||||
invisible="1"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</tree>
|
||||
@@ -674,6 +684,7 @@
|
||||
widget="badge"
|
||||
optional="show"
|
||||
/>
|
||||
<field name="invoice_status" optional="show" />
|
||||
<field
|
||||
name="company_id"
|
||||
groups="base.group_multi_company"
|
||||
|
||||
@@ -64,9 +64,7 @@
|
||||
<field name="by_default" />
|
||||
<field name="pms_room_type_id" invisible="1" />
|
||||
<field name="pms_board_service_id" />
|
||||
<field name="price_type" />
|
||||
<field name="amount" />
|
||||
<field name="pricelist_id" />
|
||||
<button
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
|
||||
@@ -10,7 +10,13 @@
|
||||
<field name="day_qty" />
|
||||
<field name="date" />
|
||||
<field name="price_unit" />
|
||||
<field
|
||||
name="discount"
|
||||
attrs="{'readonly':[('is_board_service','=', True)]}"
|
||||
/>
|
||||
<field name="price_day_total" />
|
||||
<field name="pms_property_id" invisible="1" />
|
||||
<field name="is_board_service" invisible="1" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
@@ -23,7 +29,14 @@
|
||||
<field name="product_id" />
|
||||
<field name="day_qty" />
|
||||
<field name="date" />
|
||||
<field name="price_unit" />
|
||||
<field
|
||||
name="discount"
|
||||
attrs="{'readonly':[('is_board_service','=', True)]}"
|
||||
/>
|
||||
<field name="price_day_total" />
|
||||
<field name="pms_property_id" invisible="1" />
|
||||
<field name="is_board_service" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -56,7 +69,7 @@
|
||||
attrs="{'invisible':[('is_board_service','=', True)]}"
|
||||
/>
|
||||
<field
|
||||
name="price_total"
|
||||
name="price_day_total"
|
||||
optional="show"
|
||||
attrs="{'invisible':[('is_board_service','=', True)]}"
|
||||
/>
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
force_save="1"
|
||||
/>
|
||||
</group>
|
||||
<field name="price_unit" invisible="1" />
|
||||
<field name="discount" invisible="1" />
|
||||
<field name="tax_ids" widget="many2many_tags" invisible="1" />
|
||||
<field name="price_subtotal" invisible="1" />
|
||||
<field name="price_tax" invisible="1" />
|
||||
@@ -38,6 +36,13 @@
|
||||
<tree string="Days" editable="bottom">
|
||||
<field name="date" />
|
||||
<field name="day_qty" />
|
||||
<field name="price_unit" />
|
||||
<field
|
||||
name="discount"
|
||||
attrs="{'readonly':[('is_board_service','=', True)]}"
|
||||
/>
|
||||
<field name="price_day_total" />
|
||||
<field name="is_board_service" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</sheet>
|
||||
@@ -87,8 +92,6 @@
|
||||
name="open_service_ids"
|
||||
attrs="{'invisible': [('per_day','=',False)]}"
|
||||
/>
|
||||
<field name="price_unit" />
|
||||
<field name="discount" />
|
||||
<field name="tax_ids" widget="many2many_tags" />
|
||||
<field name="price_subtotal" />
|
||||
<field name="price_tax" />
|
||||
@@ -97,6 +100,12 @@
|
||||
<tree string="Days">
|
||||
<field name="date" />
|
||||
<field name="day_qty" />
|
||||
<field
|
||||
name="discount"
|
||||
attrs="{'readonly':[('is_board_service','=', True)]}"
|
||||
/>
|
||||
<field name="price_unit" />
|
||||
<field name="is_board_service" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</tree>
|
||||
|
||||
@@ -4,6 +4,16 @@
|
||||
<field name="model">product.pricelist.item</field>
|
||||
<field name="inherit_id" ref="product.product_pricelist_item_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='applied_on']" position="before">
|
||||
<field name="on_board_service" />
|
||||
<field name="allowed_property_ids" invisible="1" />
|
||||
<field
|
||||
name="board_service_room_type_ids"
|
||||
widget="many2many_tags"
|
||||
options="{'no_create': True,'no_open': True}"
|
||||
attrs="{'invisible': [('on_board_service','=',False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='min_quantity']" position="before">
|
||||
<field
|
||||
name="pms_property_ids"
|
||||
|
||||
@@ -200,9 +200,6 @@
|
||||
<th name="th_qty">
|
||||
Quantity
|
||||
</th>
|
||||
<th name="th_price">
|
||||
Price Unit
|
||||
</th>
|
||||
<th name="th_price_total">
|
||||
Price Total
|
||||
</th>
|
||||
@@ -215,10 +212,6 @@
|
||||
<td><span
|
||||
t-field="service.product_qty"
|
||||
/></td>
|
||||
<td><span
|
||||
t-field="service.price_unit"
|
||||
t-options='{"widget": "monetary", "display_currency": reservation.pricelist_id.currency_id}'
|
||||
/></td>
|
||||
<td><span
|
||||
t-field="service.price_total"
|
||||
t-options='{"widget": "monetary", "display_currency": reservation.pricelist_id.currency_id}'
|
||||
|
||||
@@ -4,6 +4,5 @@ from . import wizard_advanced_filters
|
||||
from . import wizard_folio
|
||||
from . import wizard_folio_availability
|
||||
from . import folio_make_invoice_advance
|
||||
from . import wizard_invoice_filter_days
|
||||
from . import wizard_payment_folio
|
||||
from . import wizard_folio_changes
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class InvoiceFilterDays(models.TransientModel):
|
||||
|
||||
_name = "pms.invoice.filter.days"
|
||||
_description = "Filter Days"
|
||||
|
||||
@api.model
|
||||
def default_reservation_lines(self):
|
||||
return (
|
||||
self.env["account.move.line"]
|
||||
.browse(self.env.context.get("active_ids"))
|
||||
.reservation_line_ids
|
||||
)
|
||||
|
||||
@api.model
|
||||
def default_move_lines(self):
|
||||
return self.env["account.move.line"].browse(self.env.context.get("active_ids"))
|
||||
|
||||
@api.model
|
||||
def default_from_date(self):
|
||||
return min(
|
||||
self.env["account.move.line"]
|
||||
.browse(self.env.context.get("active_ids"))
|
||||
.reservation_line_ids.mapped("date")
|
||||
)
|
||||
|
||||
@api.model
|
||||
def default_to_date(self):
|
||||
return max(
|
||||
self.env["account.move.line"]
|
||||
.browse(self.env.context.get("active_ids"))
|
||||
.reservation_line_ids.mapped("date")
|
||||
)
|
||||
|
||||
move_line_ids = fields.Many2many("account.move.line", default=default_move_lines)
|
||||
move_ids = fields.Many2many("account.move", compute="_compute_move_ids")
|
||||
reservation_line_ids = fields.Many2many(
|
||||
"pms.reservation.line", default=default_reservation_lines
|
||||
)
|
||||
from_date = fields.Date("Date From", default=default_from_date)
|
||||
to_date = fields.Date("Date to", default=default_to_date)
|
||||
date_ids = fields.One2many(
|
||||
comodel_name="pms.invoice.filter.days.items",
|
||||
inverse_name="filter_wizard_id",
|
||||
compute="_compute_date_ids",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
def do_filter(self):
|
||||
self.ensure_one()
|
||||
invoice_lines = self.move_line_ids
|
||||
for line in invoice_lines:
|
||||
reservation_lines = line.reservation_line_ids.filtered(
|
||||
lambda d: d.date in self.date_ids.filtered("included").mapped("date")
|
||||
)
|
||||
if not reservation_lines:
|
||||
raise UserError(_("You can not remove all lines for invoice"))
|
||||
else:
|
||||
# Write on invoice for syncr business/account
|
||||
line.move_id.write(
|
||||
{
|
||||
"invoice_line_ids": [
|
||||
(
|
||||
1,
|
||||
line.id,
|
||||
{
|
||||
"reservation_line_ids": [
|
||||
(6, False, reservation_lines.ids)
|
||||
],
|
||||
"quantity": len(reservation_lines),
|
||||
},
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@api.depends("from_date", "to_date", "reservation_line_ids")
|
||||
def _compute_date_ids(self):
|
||||
self.ensure_one()
|
||||
date_list = [(5, 0, 0)]
|
||||
dates = self.reservation_line_ids.filtered(
|
||||
lambda d: d.date >= self.from_date and d.date <= self.to_date
|
||||
).mapped("date")
|
||||
for date in dates:
|
||||
date_list.append(
|
||||
(
|
||||
0,
|
||||
False,
|
||||
{
|
||||
"date": date,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.date_ids = date_list
|
||||
|
||||
@api.depends("move_line_ids")
|
||||
def _compute_move_ids(self):
|
||||
self.ensure_one()
|
||||
self.move_ids = [(6, 0, self.move_line_ids.mapped("move_id.id"))]
|
||||
|
||||
|
||||
class InvoiceFilterDaysItems(models.TransientModel):
|
||||
|
||||
_name = "pms.invoice.filter.days.items"
|
||||
_description = "Item Days"
|
||||
_rec_name = "date"
|
||||
|
||||
date = fields.Date("Date")
|
||||
included = fields.Boolean("Included", default=True)
|
||||
filter_wizard_id = fields.Many2one(comodel_name="pms.invoice.filter.days")
|
||||
@@ -1,86 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="pms_invoice_filter_days_form" model="ir.ui.view">
|
||||
<field name="name">pms.invoice.filter.days.form</field>
|
||||
<field name="model">pms.invoice.filter.days</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<div class="o_row">
|
||||
<field
|
||||
name="from_date"
|
||||
widget="daterange"
|
||||
nolabel="1"
|
||||
class="oe_inline"
|
||||
options="{'related_end_date': 'to_date'}"
|
||||
/>
|
||||
<i
|
||||
class="fa fa-long-arrow-right mx-2"
|
||||
aria-label="Arrow icon"
|
||||
title="Arrow"
|
||||
/>
|
||||
<field
|
||||
name="to_date"
|
||||
widget="daterange"
|
||||
nolabel="1"
|
||||
class="oe_inline"
|
||||
options="{'related_start_date': 'from_date'}"
|
||||
/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="move_ids" invisible="0" />
|
||||
<field
|
||||
name="move_line_ids"
|
||||
string="Invoice Lines"
|
||||
widget="many2many_tags"
|
||||
options="{'no_create':True, 'no_open':True}"
|
||||
domain="[
|
||||
('move_id', 'in', move_ids),
|
||||
('reservation_line_ids', '!=', 0),
|
||||
('exclude_from_invoice_tab', '=', False),
|
||||
('display_type', '=', False)
|
||||
]"
|
||||
/>
|
||||
<field
|
||||
name="date_ids"
|
||||
default_focus="1"
|
||||
string="Dates to invoice"
|
||||
>
|
||||
<tree
|
||||
editable="bottom"
|
||||
create="false"
|
||||
delete="false"
|
||||
decoration-muted="not included"
|
||||
decoration-primary="included"
|
||||
>
|
||||
<field name="date" readonly="1" force_save="1" />
|
||||
<field name="included" />
|
||||
</tree>
|
||||
</field>
|
||||
<field name="reservation_line_ids" invisible="1" />
|
||||
</group>
|
||||
<footer>
|
||||
<button
|
||||
string="Apply"
|
||||
name="do_filter"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
<button
|
||||
string="Cancel"
|
||||
class="btn btn-secondary"
|
||||
special="cancel"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="pms_invoice_filter_days_action" model="ir.actions.act_window">
|
||||
<field name="name">Filter Days</field>
|
||||
<field name="res_model">pms.invoice.filter.days</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user