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/wizard_payment_folio.xml",
|
||||||
"wizards/folio_make_invoice_advance_views.xml",
|
"wizards/folio_make_invoice_advance_views.xml",
|
||||||
"wizards/wizard_folio.xml",
|
"wizards/wizard_folio.xml",
|
||||||
"wizards/wizard_invoice_filter_days.xml",
|
|
||||||
"wizards/wizard_folio_changes.xml",
|
"wizards/wizard_folio_changes.xml",
|
||||||
"views/pms_amenity_views.xml",
|
"views/pms_amenity_views.xml",
|
||||||
"views/pms_amenity_type_views.xml",
|
"views/pms_amenity_type_views.xml",
|
||||||
|
|||||||
@@ -301,7 +301,6 @@
|
|||||||
'product_id': ref('pms_service_0'),
|
'product_id': ref('pms_service_0'),
|
||||||
'amount': 3})]"
|
'amount': 3})]"
|
||||||
/>
|
/>
|
||||||
<field name="price_type">fixed</field>
|
|
||||||
</record>
|
</record>
|
||||||
<record id="pms_board_service_1" model="pms.board.service">
|
<record id="pms_board_service_1" model="pms.board.service">
|
||||||
<field name="name">Half Board</field>
|
<field name="name">Half Board</field>
|
||||||
@@ -314,7 +313,6 @@
|
|||||||
'amount': 8})
|
'amount': 8})
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
<field name="price_type">fixed</field>
|
|
||||||
</record>
|
</record>
|
||||||
<record id="pms_board_service_2" model="pms.board.service">
|
<record id="pms_board_service_2" model="pms.board.service">
|
||||||
<field name="name">FullBoard</field>
|
<field name="name">FullBoard</field>
|
||||||
@@ -329,36 +327,29 @@
|
|||||||
'amount': 8})
|
'amount': 8})
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
<field name="price_type">fixed</field>
|
|
||||||
</record>
|
</record>
|
||||||
<!-- pms.board.service.room.type -->
|
<!-- pms.board.service.room.type -->
|
||||||
<!--Room 0 Economic-->
|
<!--Room 0 Economic-->
|
||||||
<record id="pms_board_service_room_0" model="pms.board.service.room.type">
|
<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_board_service_id" ref="pms_board_service_0" />
|
||||||
<field name="pms_room_type_id" ref="pms_room_type_0" />
|
<field name="pms_room_type_id" ref="pms_room_type_0" />
|
||||||
<field name="price_type">fixed</field>
|
|
||||||
</record>
|
</record>
|
||||||
<record id="pms_board_service_room_1" model="pms.board.service.room.type">
|
<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_board_service_id" ref="pms_board_service_1" />
|
||||||
<field name="pms_room_type_id" ref="pms_room_type_0" />
|
<field name="pms_room_type_id" ref="pms_room_type_0" />
|
||||||
<field name="price_type">fixed</field>
|
|
||||||
</record>
|
</record>
|
||||||
<record id="pms_board_service_room_2" model="pms.board.service.room.type">
|
<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_board_service_id" ref="pms_board_service_1" />
|
||||||
<field name="pms_room_type_id" ref="pms_room_type_0" />
|
<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>
|
</record>
|
||||||
<!--Room 3 Triple-->
|
<!--Room 3 Triple-->
|
||||||
<record id="pms_board_service_room_3" model="pms.board.service.room.type">
|
<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_board_service_id" ref="pms_board_service_0" />
|
||||||
<field name="pms_room_type_id" ref="pms_room_type_3" />
|
<field name="pms_room_type_id" ref="pms_room_type_3" />
|
||||||
<field name="price_type">fixed</field>
|
|
||||||
</record>
|
</record>
|
||||||
<record id="pms_board_service_room_4" model="pms.board.service.room.type">
|
<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_board_service_id" ref="pms_board_service_2" />
|
||||||
<field name="pms_room_type_id" ref="pms_room_type_3" />
|
<field name="pms_room_type_id" ref="pms_room_type_3" />
|
||||||
<field name="price_type">fixed</field>
|
|
||||||
</record>
|
</record>
|
||||||
<!-- room.closure.reason -->
|
<!-- room.closure.reason -->
|
||||||
<record id="pms_room_closure_reason_0" model="room.closure.reason">
|
<record id="pms_room_closure_reason_0" model="room.closure.reason">
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ class AccountBankStatementLine(models.Model):
|
|||||||
line.update(
|
line.update(
|
||||||
{
|
{
|
||||||
"folio_ids": [(6, 0, self.statement_folio_ids.ids)],
|
"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
|
return line_vals_list
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class AccountMove(models.Model):
|
|||||||
comodel_name="pms.folio", compute="_compute_folio_origin"
|
comodel_name="pms.folio", compute="_compute_folio_origin"
|
||||||
)
|
)
|
||||||
pms_property_id = fields.Many2one("pms.property")
|
pms_property_id = fields.Many2one("pms.property")
|
||||||
from_reservation = fields.Boolean(compute="_compute_from_reservation")
|
|
||||||
outstanding_folios_debits_widget = fields.Text(
|
outstanding_folios_debits_widget = fields.Text(
|
||||||
compute="_compute_get_outstanding_folios_JSON"
|
compute="_compute_get_outstanding_folios_JSON"
|
||||||
)
|
)
|
||||||
@@ -31,12 +30,6 @@ class AccountMove(models.Model):
|
|||||||
if folios:
|
if folios:
|
||||||
inv.folio_ids = [(6, 0, folios.ids)]
|
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
|
# Action methods
|
||||||
|
|
||||||
def action_folio_payments(self):
|
def action_folio_payments(self):
|
||||||
|
|||||||
@@ -1,40 +1,13 @@
|
|||||||
# Copyright 2017 Alexandre Díaz
|
# Copyright 2017 Alexandre Díaz
|
||||||
# Copyright 2017 Dario Lodeiros
|
# Copyright 2017 Dario Lodeiros
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
# 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):
|
class AccountMoveLine(models.Model):
|
||||||
_inherit = "account.move.line"
|
_inherit = "account.move.line"
|
||||||
|
|
||||||
# Fields declaration
|
# 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_line_ids = fields.Many2many(
|
||||||
"folio.sale.line",
|
"folio.sale.line",
|
||||||
"folio_sale_line_invoice_rel",
|
"folio_sale_line_invoice_rel",
|
||||||
@@ -49,68 +22,8 @@ class AccountMoveLine(models.Model):
|
|||||||
"move_id",
|
"move_id",
|
||||||
"folio_id",
|
"folio_id",
|
||||||
string="Folios",
|
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):
|
def _copy_data_extend_business_fields(self, values):
|
||||||
super(AccountMoveLine, self)._copy_data_extend_business_fields(values)
|
super(AccountMoveLine, self)._copy_data_extend_business_fields(values)
|
||||||
values["folio_line_ids"] = [(6, None, self.folio_line_ids.ids)]
|
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:
|
for line in self:
|
||||||
if line.state == "draft":
|
if line.state == "draft":
|
||||||
line.invoice_status = "no"
|
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):
|
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
|
||||||
line.invoice_status = "to invoice"
|
line.invoice_status = "to invoice"
|
||||||
elif (
|
elif (
|
||||||
@@ -71,18 +74,6 @@ class FolioSaleLine(models.Model):
|
|||||||
else:
|
else:
|
||||||
return False
|
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")
|
@api.depends("product_uom_qty", "discount", "price_unit", "tax_ids")
|
||||||
def _compute_amount(self):
|
def _compute_amount(self):
|
||||||
"""
|
"""
|
||||||
@@ -121,11 +112,23 @@ class FolioSaleLine(models.Model):
|
|||||||
else record.reservation_id.tax_ids
|
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):
|
def _compute_discount(self):
|
||||||
self.discount = 0.0
|
"""
|
||||||
for record in self.filtered("service_id"):
|
Only in services without room we compute discount,
|
||||||
record.discount = record.service_id.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")
|
@api.depends("reservation_id.room_type_id", "service_id.product_id")
|
||||||
def _compute_product_id(self):
|
def _compute_product_id(self):
|
||||||
@@ -355,6 +358,12 @@ class FolioSaleLine(models.Model):
|
|||||||
index=True,
|
index=True,
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
|
is_board_service = fields.Boolean(
|
||||||
|
string="Board Service",
|
||||||
|
related="service_id.is_board_service",
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
|
|
||||||
name = fields.Text(
|
name = fields.Text(
|
||||||
string="Description", compute="_compute_name", store=True, readonly=False
|
string="Description", compute="_compute_name", store=True, readonly=False
|
||||||
)
|
)
|
||||||
@@ -363,6 +372,10 @@ class FolioSaleLine(models.Model):
|
|||||||
"pms.reservation.line",
|
"pms.reservation.line",
|
||||||
string="Nights",
|
string="Nights",
|
||||||
)
|
)
|
||||||
|
service_line_ids = fields.Many2many(
|
||||||
|
"pms.service.line",
|
||||||
|
string="Service Lines",
|
||||||
|
)
|
||||||
sequence = fields.Integer(string="Sequence", default=10)
|
sequence = fields.Integer(string="Sequence", default=10)
|
||||||
|
|
||||||
invoice_lines = fields.Many2many(
|
invoice_lines = fields.Many2many(
|
||||||
@@ -389,8 +402,6 @@ class FolioSaleLine(models.Model):
|
|||||||
price_unit = fields.Float(
|
price_unit = fields.Float(
|
||||||
"Unit Price",
|
"Unit Price",
|
||||||
digits="Product Price",
|
digits="Product Price",
|
||||||
compute="_compute_price_unit",
|
|
||||||
store=True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
price_subtotal = fields.Monetary(
|
price_subtotal = fields.Monetary(
|
||||||
@@ -433,6 +444,7 @@ class FolioSaleLine(models.Model):
|
|||||||
string="Discount (%)",
|
string="Discount (%)",
|
||||||
digits="Discount",
|
digits="Discount",
|
||||||
compute="_compute_discount",
|
compute="_compute_discount",
|
||||||
|
readonly=False,
|
||||||
store=True,
|
store=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -550,13 +562,13 @@ class FolioSaleLine(models.Model):
|
|||||||
help="Technical field for UX purpose.",
|
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):
|
def _compute_product_uom_qty(self):
|
||||||
for line in self:
|
for line in self:
|
||||||
if line.reservation_line_ids:
|
if line.reservation_line_ids:
|
||||||
line.product_uom_qty = len(line.reservation_line_ids)
|
line.product_uom_qty = len(line.reservation_line_ids)
|
||||||
elif line.service_id:
|
elif line.service_line_ids:
|
||||||
line.product_uom_qty = line.service_id.product_qty
|
line.product_uom_qty = sum(line.service_line_ids.mapped("day_qty"))
|
||||||
elif not line.product_uom_qty:
|
elif not line.product_uom_qty:
|
||||||
line.product_uom_qty = False
|
line.product_uom_qty = False
|
||||||
|
|
||||||
@@ -716,15 +728,6 @@ class FolioSaleLine(models.Model):
|
|||||||
+ " The quantity pending to invoice is %s" % self.qty_to_invoice
|
+ " 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 = {
|
res = {
|
||||||
"display_type": self.display_type,
|
"display_type": self.display_type,
|
||||||
"sequence": self.sequence,
|
"sequence": self.sequence,
|
||||||
@@ -738,9 +741,6 @@ class FolioSaleLine(models.Model):
|
|||||||
"analytic_account_id": self.folio_id.analytic_account_id.id,
|
"analytic_account_id": self.folio_id.analytic_account_id.id,
|
||||||
"analytic_tag_ids": [(6, 0, self.analytic_tag_ids.ids)],
|
"analytic_tag_ids": [(6, 0, self.analytic_tag_ids.ids)],
|
||||||
"folio_line_ids": [(6, 0, [self.id])],
|
"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:
|
if optional_values:
|
||||||
res.update(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_ids = fields.One2many(
|
||||||
"pms.board.service.room.type", "pms_board_service_id"
|
"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 = fields.Float(
|
||||||
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
|
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Copyright 2017 Dario
|
# Copyright 2017 Dario
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
class PmsBoardServiceRoomType(models.Model):
|
class PmsBoardServiceRoomType(models.Model):
|
||||||
@@ -11,21 +11,6 @@ class PmsBoardServiceRoomType(models.Model):
|
|||||||
_log_access = False
|
_log_access = False
|
||||||
_description = "Board Service included in Room"
|
_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
|
# Fields declaration
|
||||||
pms_board_service_id = fields.Many2one(
|
pms_board_service_id = fields.Many2one(
|
||||||
"pms.board.service",
|
"pms.board.service",
|
||||||
@@ -52,27 +37,9 @@ class PmsBoardServiceRoomType(models.Model):
|
|||||||
("pms_property_ids", "in", pms_property_ids),
|
("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(
|
board_service_line_ids = fields.One2many(
|
||||||
"pms.board.service.room.type.line", "pms_board_service_room_type_id"
|
"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 = fields.Float(
|
||||||
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
|
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
|
||||||
)
|
)
|
||||||
@@ -87,41 +54,7 @@ class PmsBoardServiceRoomType(models.Model):
|
|||||||
total += service.amount
|
total += service.amount
|
||||||
record.update({"amount": total})
|
record.update({"amount": total})
|
||||||
|
|
||||||
# Constraints and onchanges
|
@api.constrains("by_default")
|
||||||
@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")
|
|
||||||
def constrains_duplicated_board_defaul(self):
|
def constrains_duplicated_board_defaul(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
default_boards = (
|
default_boards = (
|
||||||
@@ -130,34 +63,8 @@ class PmsBoardServiceRoomType(models.Model):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
# TODO Check properties (with different propertys is allowed)
|
# TODO Check properties (with different propertys is allowed)
|
||||||
if any(
|
if any(default_boards.filtered(lambda l: l.id != record.id)):
|
||||||
default_boards.mapped(
|
raise UserError(_("""Only can set one default board service"""))
|
||||||
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"))
|
|
||||||
|
|
||||||
# Action methods
|
# Action methods
|
||||||
|
|
||||||
@@ -176,13 +83,13 @@ class PmsBoardServiceRoomType(models.Model):
|
|||||||
def init(self):
|
def init(self):
|
||||||
self._cr.execute(
|
self._cr.execute(
|
||||||
"SELECT indexname FROM pg_indexes WHERE indexname = %s",
|
"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():
|
if not self._cr.fetchone():
|
||||||
self._cr.execute(
|
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 \
|
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
|
@api.model
|
||||||
|
|||||||
@@ -391,6 +391,9 @@ class PmsFolio(models.Model):
|
|||||||
"reservation_ids",
|
"reservation_ids",
|
||||||
"service_ids",
|
"service_ids",
|
||||||
"service_ids.reservation_id",
|
"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",
|
||||||
"reservation_ids.reservation_line_ids.price",
|
"reservation_ids.reservation_line_ids.price",
|
||||||
"reservation_ids.reservation_line_ids.discount",
|
"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:
|
for line in reservation.reservation_line_ids:
|
||||||
# On resevations the price, and discounts fields are used
|
# On resevations the price, and discounts fields are used
|
||||||
# by group, we need pass this in the create line
|
# 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]:
|
for discount in [line.discount, line.cancel_discount]:
|
||||||
discount_factor = discount_factor * ((100.0 - discount) / 100.0)
|
discount_factor = discount_factor * ((100.0 - discount) / 100.0)
|
||||||
final_discount = 100.0 - (discount_factor * 100.0)
|
final_discount = 100.0 - (discount_factor * 100.0)
|
||||||
if group_key not in group_lines:
|
if group_key not in group_reservation_lines:
|
||||||
group_lines[group_key] = {
|
group_reservation_lines[group_key] = {
|
||||||
"reservation_id": reservation.id,
|
"reservation_id": reservation.id,
|
||||||
"discount": final_discount,
|
"discount": final_discount,
|
||||||
"price_unit": line.price,
|
"price_unit": line.price,
|
||||||
"reservation_line_ids": [(4, line.id)],
|
"reservation_line_ids": [(4, line.id)],
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
group_lines[group_key][("reservation_line_ids")].append(
|
group_reservation_lines[group_key][
|
||||||
(4, line.id)
|
("reservation_line_ids")
|
||||||
)
|
].append((4, line.id))
|
||||||
for item in group_lines.items():
|
for item in group_reservation_lines.items():
|
||||||
sale_lines.append((0, False, item[1]))
|
sale_lines.append((0, False, item[1]))
|
||||||
for service in reservation.service_ids:
|
for service in reservation.service_ids:
|
||||||
# On service the price, and discounts fields are
|
# Service days with different prices,
|
||||||
# compute in the sale.order.line
|
# go to differente sale lines
|
||||||
sale_lines.append(
|
group_service_lines = {}
|
||||||
(
|
for service_line in service.service_line_ids:
|
||||||
0,
|
service_group_key = (
|
||||||
False,
|
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,
|
"name": service.name,
|
||||||
"service_id": service.id,
|
"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:
|
if services_without_room:
|
||||||
sale_lines.append(
|
sale_lines.append(
|
||||||
(
|
(
|
||||||
@@ -646,25 +662,20 @@ class PmsFolio(models.Model):
|
|||||||
else:
|
else:
|
||||||
order.invoice_status = "no"
|
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):
|
def _compute_amount_all(self):
|
||||||
"""
|
"""
|
||||||
Compute the total amounts of the SO.
|
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 = amount_tax = 0.0
|
||||||
amount_untaxed = sum(record.reservation_ids.mapped("price_subtotal")) + sum(
|
for line in folio.sale_line_ids:
|
||||||
record.service_ids.mapped("price_subtotal")
|
amount_untaxed += line.price_subtotal
|
||||||
)
|
amount_tax += line.price_tax
|
||||||
amount_tax = sum(record.reservation_ids.mapped("price_tax")) + sum(
|
folio.update(
|
||||||
record.service_ids.mapped("price_tax")
|
|
||||||
)
|
|
||||||
record.update(
|
|
||||||
{
|
{
|
||||||
"amount_untaxed": record.pricelist_id.currency_id.round(
|
"amount_untaxed": amount_untaxed,
|
||||||
amount_untaxed
|
"amount_tax": amount_tax,
|
||||||
),
|
|
||||||
"amount_tax": record.pricelist_id.currency_id.round(amount_tax),
|
|
||||||
"amount_total": amount_untaxed + amount_tax,
|
"amount_total": amount_untaxed + amount_tax,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -1248,9 +1259,9 @@ class PmsFolio(models.Model):
|
|||||||
def _get_tax_amount_by_group(self):
|
def _get_tax_amount_by_group(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
res = {}
|
res = {}
|
||||||
for line in self.reservation_ids:
|
for line in self.sale_line_ids:
|
||||||
price_reduce = line.price_total
|
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 = line.tax_ids.compute_all(price_reduce, quantity=1, product=product)[
|
||||||
"taxes"
|
"taxes"
|
||||||
]
|
]
|
||||||
@@ -1261,18 +1272,6 @@ class PmsFolio(models.Model):
|
|||||||
if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids:
|
if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids:
|
||||||
res[group]["amount"] += t["amount"]
|
res[group]["amount"] += t["amount"]
|
||||||
res[group]["base"] += t["base"]
|
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 = sorted(res.items(), key=lambda line: line[0].sequence)
|
||||||
res = [
|
res = [
|
||||||
(line[0].name, line[1]["amount"], line[1]["base"], len(res)) for line in 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 import _, api, fields, models
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
from odoo.tools import float_compare, float_is_zero
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -83,6 +82,12 @@ class PmsReservation(models.Model):
|
|||||||
copy=False,
|
copy=False,
|
||||||
check_company=True,
|
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(
|
board_service_room_id = fields.Many2one(
|
||||||
"pms.board.service.room.type",
|
"pms.board.service.room.type",
|
||||||
string="Board Service",
|
string="Board Service",
|
||||||
@@ -184,7 +189,8 @@ class PmsReservation(models.Model):
|
|||||||
)
|
)
|
||||||
user_id = fields.Many2one(
|
user_id = fields.Many2one(
|
||||||
related="folio_id.user_id",
|
related="folio_id.user_id",
|
||||||
depends=["folio_id"],
|
depends=["folio_id.user_id"],
|
||||||
|
default=lambda self: self.env.user.id,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
store=True,
|
store=True,
|
||||||
)
|
)
|
||||||
@@ -274,14 +280,6 @@ class PmsReservation(models.Model):
|
|||||||
ondelete="restrict",
|
ondelete="restrict",
|
||||||
domain=["|", ("active", "=", False), ("active", "=", True)],
|
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(
|
localizator = fields.Char(
|
||||||
string="Localizator",
|
string="Localizator",
|
||||||
compute="_compute_localizator",
|
compute="_compute_localizator",
|
||||||
@@ -446,32 +444,6 @@ class PmsReservation(models.Model):
|
|||||||
readonly=True,
|
readonly=True,
|
||||||
default="no",
|
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(
|
analytic_tag_ids = fields.Many2many(
|
||||||
"account.analytic.tag",
|
"account.analytic.tag",
|
||||||
string="Analytic Tags",
|
string="Analytic Tags",
|
||||||
@@ -552,20 +524,15 @@ class PmsReservation(models.Model):
|
|||||||
if reservation.pricelist_id and reservation.room_type_id:
|
if reservation.pricelist_id and reservation.room_type_id:
|
||||||
board_service_default = self.env["pms.board.service.room.type"].search(
|
board_service_default = self.env["pms.board.service.room.type"].search(
|
||||||
[
|
[
|
||||||
"&",
|
|
||||||
"&",
|
|
||||||
("pms_room_type_id", "=", reservation.room_type_id.id),
|
("pms_room_type_id", "=", reservation.room_type_id.id),
|
||||||
("by_default", "=", True),
|
("by_default", "=", True),
|
||||||
"|",
|
|
||||||
("pricelist_id", "=", reservation.pricelist_id.id),
|
|
||||||
("pricelist_id", "=", False),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if len(board_service_default) > 1:
|
if (
|
||||||
reservation.board_service_room_id = board_service_default.filtered(
|
not reservation.board_service_room_id
|
||||||
lambda b: b.pricelist_id == reservation.pricelist_id
|
or not reservation.board_service_room_id.pms_room_type_id
|
||||||
)
|
== reservation.room_type_id
|
||||||
else:
|
):
|
||||||
reservation.board_service_room_id = (
|
reservation.board_service_room_id = (
|
||||||
board_service_default.id if board_service_default else False
|
board_service_default.id if board_service_default else False
|
||||||
)
|
)
|
||||||
@@ -693,6 +660,13 @@ class PmsReservation(models.Model):
|
|||||||
("is_board_service", "=", True),
|
("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:
|
if reservation.board_service_room_id:
|
||||||
board = self.env["pms.board.service.room.type"].browse(
|
board = self.env["pms.board.service.room.type"].browse(
|
||||||
reservation.board_service_room_id.id
|
reservation.board_service_room_id.id
|
||||||
@@ -707,6 +681,8 @@ class PmsReservation(models.Model):
|
|||||||
board_services.append((0, False, res))
|
board_services.append((0, False, res))
|
||||||
reservation.service_ids -= old_board_lines
|
reservation.service_ids -= old_board_lines
|
||||||
reservation.service_ids = board_services
|
reservation.service_ids = board_services
|
||||||
|
elif old_board_lines:
|
||||||
|
reservation.service_ids -= old_board_lines
|
||||||
|
|
||||||
@api.depends("partner_id", "agency_id")
|
@api.depends("partner_id", "agency_id")
|
||||||
def _compute_pricelist_id(self):
|
def _compute_pricelist_id(self):
|
||||||
@@ -727,13 +703,19 @@ class PmsReservation(models.Model):
|
|||||||
reservation.pms_property_id.default_pricelist_id.id
|
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):
|
def _compute_show_update_pricelist(self):
|
||||||
for reservation in self:
|
for reservation in self:
|
||||||
if (
|
if (
|
||||||
sum(reservation.reservation_line_ids.mapped("price")) > 0
|
sum(reservation.reservation_line_ids.mapped("price")) > 0
|
||||||
and reservation.pricelist_id
|
and (
|
||||||
and reservation._origin.pricelist_id != reservation.pricelist_id
|
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
|
reservation.show_update_pricelist = True
|
||||||
else:
|
else:
|
||||||
@@ -1036,183 +1018,28 @@ class PmsReservation(models.Model):
|
|||||||
if room_ids:
|
if room_ids:
|
||||||
reservation.preferred_room_id = room_ids[0]
|
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):
|
def _compute_invoice_status(self):
|
||||||
"""
|
"""
|
||||||
Compute the invoice status of a Reservation. Possible statuses:
|
Compute the invoice status of a Reservation. Possible statuses:
|
||||||
- no: if the Folio is not in status 'sale' or 'done', we consider
|
Base on folio sale line invoice status
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
for line in self:
|
for line in self:
|
||||||
if line.folio_id.state not in ["draft"]:
|
states = list(set(line.sale_line_ids.mapped("invoice_status")))
|
||||||
line.qty_to_invoice = len(line.reservation_line_ids) - line.qty_invoiced
|
if len(states) == 1:
|
||||||
else:
|
line.invoice_status = states[0]
|
||||||
line.qty_to_invoice = 0
|
elif len(states) >= 1:
|
||||||
|
if "to_invoice" in states:
|
||||||
@api.depends(
|
line.invoice_status = "to_invoice"
|
||||||
"move_line_ids.move_id.state",
|
elif "invoiced" in states:
|
||||||
"move_line_ids.quantity",
|
line.invoice_status = "invoiced"
|
||||||
"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)
|
|
||||||
else:
|
else:
|
||||||
amount_to_invoice = price_subtotal - line.untaxed_amount_invoiced
|
line.invoice_status = "no"
|
||||||
|
else:
|
||||||
line.untaxed_amount_to_invoice = amount_to_invoice
|
line.invoice_status = "no"
|
||||||
|
|
||||||
@api.depends("reservation_line_ids")
|
@api.depends("reservation_line_ids")
|
||||||
def _compute_nights(self):
|
def _compute_nights(self):
|
||||||
@@ -1529,8 +1356,10 @@ class PmsReservation(models.Model):
|
|||||||
self.show_update_pricelist = False
|
self.show_update_pricelist = False
|
||||||
self.message_post(
|
self.message_post(
|
||||||
body=_(
|
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.pricelist_id.display_name,
|
||||||
|
self.room_type_id.name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ class PmsReservationLine(models.Model):
|
|||||||
store=True,
|
store=True,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
)
|
)
|
||||||
move_line_ids = fields.Many2many(
|
sale_line_ids = fields.Many2many(
|
||||||
"account.move.line",
|
"folio.sale.line",
|
||||||
"reservation_line_move_rel",
|
"reservation_line_sale_line_rel",
|
||||||
"reservation_line_id",
|
"reservation_line_id",
|
||||||
"move_line_id",
|
"sale_line_id",
|
||||||
string="Invoice Lines",
|
string="Sales Lines",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
copy=False,
|
copy=False,
|
||||||
)
|
)
|
||||||
@@ -65,11 +65,6 @@ class PmsReservationLine(models.Model):
|
|||||||
store=True,
|
store=True,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
)
|
)
|
||||||
invoiced = fields.Boolean(
|
|
||||||
string="Invoiced",
|
|
||||||
compute="_compute_invoiced",
|
|
||||||
store=True,
|
|
||||||
)
|
|
||||||
cancel_discount = fields.Float(
|
cancel_discount = fields.Float(
|
||||||
string="Cancelation Discount (%)",
|
string="Cancelation Discount (%)",
|
||||||
digits=("Discount"),
|
digits=("Discount"),
|
||||||
@@ -311,31 +306,15 @@ class PmsReservationLine(models.Model):
|
|||||||
else:
|
else:
|
||||||
line.occupies_availability = True
|
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
|
# TODO: Refact method and allowed cancelled single days
|
||||||
@api.depends("reservation_id.cancelled_reason")
|
@api.depends("reservation_id.cancelled_reason")
|
||||||
def _compute_cancel_discount(self):
|
def _compute_cancel_discount(self):
|
||||||
for line in self:
|
for line in self:
|
||||||
line.cancel_discount = 0
|
line.cancel_discount = 0
|
||||||
|
# TODO: Review cancel logic
|
||||||
# reservation = line.reservation_id
|
# reservation = line.reservation_id
|
||||||
# pricelist = reservation.pricelist_id
|
# pricelist = reservation.pricelist_id
|
||||||
# if reservation.state == "cancelled":
|
# 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 (
|
# if (
|
||||||
# reservation.cancelled_reason
|
# reservation.cancelled_reason
|
||||||
# and pricelist
|
# and pricelist
|
||||||
|
|||||||
@@ -208,6 +208,8 @@ class PmsRoomType(models.Model):
|
|||||||
if room.pms_property_id not in record.pms_property_ids:
|
if room.pms_property_id not in record.pms_property_ids:
|
||||||
raise ValidationError(_("Property not allowed in room"))
|
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")
|
@api.constrains("board_service_room_type_ids", "pms_property_ids")
|
||||||
def _check_integrity_property_board_service_room_type(self):
|
def _check_integrity_property_board_service_room_type(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import logging
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.tools import float_compare, float_is_zero
|
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -52,8 +51,17 @@ class PmsService(models.Model):
|
|||||||
readonly=False,
|
readonly=False,
|
||||||
store=True,
|
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(
|
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(
|
service_line_ids = fields.One2many(
|
||||||
"pms.service.line",
|
"pms.service.line",
|
||||||
@@ -79,14 +87,6 @@ class PmsService(models.Model):
|
|||||||
readonly=False,
|
readonly=False,
|
||||||
domain=["|", ("active", "=", False), ("active", "=", True)],
|
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")
|
analytic_tag_ids = fields.Many2many("account.analytic.tag", string="Analytic Tags")
|
||||||
currency_id = fields.Many2one(
|
currency_id = fields.Many2one(
|
||||||
related="folio_id.currency_id", store=True, string="Currency", readonly=True
|
related="folio_id.currency_id", store=True, string="Currency", readonly=True
|
||||||
@@ -128,28 +128,6 @@ class PmsService(models.Model):
|
|||||||
],
|
],
|
||||||
string="Sales Channel",
|
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(
|
price_subtotal = fields.Monetary(
|
||||||
string="Subtotal", readonly=True, store=True, compute="_compute_amount_service"
|
string="Subtotal", readonly=True, store=True, compute="_compute_amount_service"
|
||||||
)
|
)
|
||||||
@@ -214,6 +192,7 @@ class PmsService(models.Model):
|
|||||||
# cached (otherwise double the date)
|
# cached (otherwise double the date)
|
||||||
pass
|
pass
|
||||||
elif not old_line:
|
elif not old_line:
|
||||||
|
price_unit = service._get_price_unit_line(idate)
|
||||||
lines.append(
|
lines.append(
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
@@ -221,6 +200,7 @@ class PmsService(models.Model):
|
|||||||
{
|
{
|
||||||
"date": idate,
|
"date": idate,
|
||||||
"day_qty": day_qty,
|
"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
|
service.service_line_ids = lines
|
||||||
else:
|
else:
|
||||||
# TODO: Review (business logic refact) no per_day logic service
|
# TODO: Review (business logic refact) no per_day logic service
|
||||||
if not service.service_line_ids:
|
if not service.service_line_ids:
|
||||||
|
price_unit = service._get_price_unit_line()
|
||||||
service.service_line_ids = [
|
service.service_line_ids = [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
@@ -259,6 +238,7 @@ class PmsService(models.Model):
|
|||||||
{
|
{
|
||||||
"date": fields.Date.today(),
|
"date": fields.Date.today(),
|
||||||
"day_qty": day_qty,
|
"day_qty": day_qty,
|
||||||
|
"price_unit": price_unit,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@@ -266,6 +246,7 @@ class PmsService(models.Model):
|
|||||||
# TODO: Service without reservation(room) but with folio¿?
|
# TODO: Service without reservation(room) but with folio¿?
|
||||||
# example: tourist tour in group
|
# example: tourist tour in group
|
||||||
if not service.service_line_ids:
|
if not service.service_line_ids:
|
||||||
|
price_unit = service._get_price_unit_line()
|
||||||
service.service_line_ids = [
|
service.service_line_ids = [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
@@ -273,6 +254,7 @@ class PmsService(models.Model):
|
|||||||
{
|
{
|
||||||
"date": fields.Date.today(),
|
"date": fields.Date.today(),
|
||||||
"day_qty": day_qty,
|
"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"))
|
qty = sum(service.service_line_ids.mapped("day_qty"))
|
||||||
service.product_qty = 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")
|
@api.depends("reservation_id")
|
||||||
def _compute_folio_id(self):
|
def _compute_folio_id(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
@@ -381,133 +290,54 @@ class PmsService(models.Model):
|
|||||||
elif not record.folio_id:
|
elif not record.folio_id:
|
||||||
record.folio_id = False
|
record.folio_id = False
|
||||||
|
|
||||||
def _recompute_price(self):
|
@api.depends(
|
||||||
# REVIEW: Conditional to avoid overriding already calculated prices,
|
"sale_line_ids",
|
||||||
# I'm not sure it's the best way
|
"sale_line_ids.invoice_status",
|
||||||
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")
|
|
||||||
def _compute_invoice_status(self):
|
def _compute_invoice_status(self):
|
||||||
"""
|
"""
|
||||||
Compute the invoice status of a SO line. Possible statuses:
|
Compute the invoice status of a Reservation. Possible statuses:
|
||||||
- no: if the SO is not in status 'sale' or 'done',
|
Base on folio sale line invoice status
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
precision = self.env["decimal.precision"].precision_get(
|
|
||||||
"Product Unit of Measure"
|
|
||||||
)
|
|
||||||
for line in self:
|
for line in self:
|
||||||
state = line.folio_id.state or "draft"
|
states = list(set(line.sale_line_ids.mapped("invoice_status")))
|
||||||
if state == "draft":
|
if len(states) == 1:
|
||||||
line.invoice_status = "no"
|
line.invoice_status = states[0]
|
||||||
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
|
elif len(states) >= 1:
|
||||||
line.invoice_status = "to invoice"
|
if "to_invoice" in states:
|
||||||
elif (
|
line.invoice_status = "to_invoice"
|
||||||
float_compare(
|
elif "invoiced" in states:
|
||||||
line.qty_invoiced, line.product_qty, precision_digits=precision
|
line.invoice_status = "invoiced"
|
||||||
)
|
else:
|
||||||
>= 0
|
line.invoice_status = "no"
|
||||||
):
|
|
||||||
line.invoice_status = "invoiced"
|
|
||||||
else:
|
else:
|
||||||
line.invoice_status = "no"
|
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):
|
def _compute_amount_service(self):
|
||||||
for service in self:
|
for service in self:
|
||||||
folio = service.folio_id
|
if service.service_line_ids:
|
||||||
reservation = service.reservation_id
|
service.update(
|
||||||
currency = folio.currency_id if folio else reservation.currency_id
|
{
|
||||||
product = service.product_id
|
"price_tax": sum(
|
||||||
price = service.price_unit * (1 - (service.discount or 0.0) * 0.01)
|
service.service_line_ids.mapped("price_day_tax")
|
||||||
taxes = service.tax_ids.compute_all(
|
),
|
||||||
price, currency, service.product_qty, product=product
|
"price_total": sum(
|
||||||
)
|
service.service_line_ids.mapped("price_day_total")
|
||||||
service.update(
|
),
|
||||||
{
|
"price_subtotal": sum(
|
||||||
"price_tax": sum(
|
service.service_line_ids.mapped("price_day_subtotal")
|
||||||
t.get("amount", 0.0) for t in taxes.get("taxes", [])
|
),
|
||||||
),
|
}
|
||||||
"price_total": taxes["total_included"],
|
)
|
||||||
"price_subtotal": taxes["total_excluded"],
|
else:
|
||||||
}
|
service.update(
|
||||||
)
|
{
|
||||||
|
"price_tax": 0,
|
||||||
|
"price_total": 0,
|
||||||
|
"price_subtotal": 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Action methods
|
# Action methods
|
||||||
def open_service_ids(self):
|
def open_service_ids(self):
|
||||||
@@ -537,20 +367,12 @@ class PmsService(models.Model):
|
|||||||
reservation = self.reservation_id
|
reservation = self.reservation_id
|
||||||
origin = folio if folio else reservation
|
origin = folio if folio else reservation
|
||||||
if origin.pricelist_id.discount_policy == "with_discount":
|
if origin.pricelist_id.discount_policy == "with_discount":
|
||||||
return product.with_context(pricelist=origin.pricelist_id.id).price
|
return product.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,
|
|
||||||
)
|
|
||||||
final_price, rule_id = origin.pricelist_id.with_context(
|
final_price, rule_id = origin.pricelist_id.with_context(
|
||||||
product_context
|
product._context
|
||||||
).get_product_price_rule(
|
).get_product_price_rule(product, self.product_qty or 1.0, origin.partner_id)
|
||||||
self.product_id, self.product_qty or 1.0, origin.partner_id
|
|
||||||
)
|
|
||||||
base_price, currency_id = self.with_context(
|
base_price, currency_id = self.with_context(
|
||||||
product_context
|
product._context
|
||||||
)._get_real_price_currency(
|
)._get_real_price_currency(
|
||||||
product,
|
product,
|
||||||
rule_id,
|
rule_id,
|
||||||
@@ -562,12 +384,74 @@ class PmsService(models.Model):
|
|||||||
base_price = (
|
base_price = (
|
||||||
self.env["res.currency"]
|
self.env["res.currency"]
|
||||||
.browse(currency_id)
|
.browse(currency_id)
|
||||||
.with_context(product_context)
|
.with_context(product._context)
|
||||||
.compute(base_price, origin.pricelist_id.currency_id)
|
.compute(base_price, origin.pricelist_id.currency_id)
|
||||||
)
|
)
|
||||||
# negative discounts (= surcharge) are included in the display price
|
# negative discounts (= surcharge) are included in the display price
|
||||||
return max(base_price, final_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
|
# Businness Methods
|
||||||
def _service_day_qty(self):
|
def _service_day_qty(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
@@ -578,3 +462,38 @@ class PmsService(models.Model):
|
|||||||
if self.product_id.per_person:
|
if self.product_id.per_person:
|
||||||
qty = self.reservation_id.adults
|
qty = self.reservation_id.adults
|
||||||
return qty
|
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")
|
date = fields.Date("Date")
|
||||||
day_qty = fields.Integer("Units")
|
day_qty = fields.Integer("Units")
|
||||||
price_total = fields.Float(
|
|
||||||
"Price Total", compute="_compute_price_total", store=True
|
|
||||||
)
|
|
||||||
price_unit = fields.Float(
|
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(
|
room_id = fields.Many2one(
|
||||||
string="Room", related="service_id.reservation_id", readonly=True, store=True
|
string="Room", related="service_id.reservation_id", readonly=True, store=True
|
||||||
)
|
)
|
||||||
discount = fields.Float(
|
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(
|
cancel_discount = fields.Float(
|
||||||
"Cancelation Discount", compute="_compute_cancel_discount"
|
"Cancelation Discount", compute="_compute_cancel_discount"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compute and Search methods
|
@api.depends("day_qty", "discount", "price_unit", "tax_ids")
|
||||||
@api.depends("day_qty", "service_id.price_total")
|
def _compute_day_amount_service(self):
|
||||||
def _compute_price_total(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:
|
for record in self:
|
||||||
if record.service_id.product_qty != 0:
|
if record.is_board_service:
|
||||||
record.price_total = (
|
record.discount = (
|
||||||
record.service_id.price_total * record.day_qty
|
record.service_id.reservation_id.reservation_line_ids.filtered(
|
||||||
) / record.service_id.product_qty
|
lambda l: l.date == record.date
|
||||||
else:
|
).discount
|
||||||
record.price_total = 0
|
)
|
||||||
|
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
|
# Constraints and onchanges
|
||||||
@api.constrains("day_qty")
|
@api.constrains("day_qty")
|
||||||
|
|||||||
@@ -80,15 +80,16 @@ class ProductPricelist(models.Model):
|
|||||||
def _compute_price_rule_get_items(
|
def _compute_price_rule_get_items(
|
||||||
self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
|
self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
|
||||||
):
|
):
|
||||||
|
|
||||||
if (
|
if (
|
||||||
"property" in self._context
|
"property" in self._context
|
||||||
and self._context["property"]
|
and self._context["property"]
|
||||||
and "date_overnight" in self._context
|
and self._context.get("date_overnight")
|
||||||
):
|
):
|
||||||
self.env["product.pricelist.item"].flush(
|
# board_service_id = self._context.get("board_service")
|
||||||
["price", "currency_id", "company_id"]
|
# 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(
|
self.env.cr.execute(
|
||||||
"""
|
"""
|
||||||
SELECT item.id
|
SELECT item.id
|
||||||
@@ -99,6 +100,8 @@ class ProductPricelist(models.Model):
|
|||||||
ON item.pricelist_id = cab.product_pricelist_id
|
ON item.pricelist_id = cab.product_pricelist_id
|
||||||
LEFT JOIN pms_property_product_pricelist_item_rel lin
|
LEFT JOIN pms_property_product_pricelist_item_rel lin
|
||||||
ON item.id = lin.product_pricelist_item_id
|
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)
|
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 (cab.pms_property_id = %s OR cab.pms_property_id IS NULL)
|
||||||
AND (item.product_tmpl_id IS NULL
|
AND (item.product_tmpl_id IS NULL
|
||||||
@@ -132,6 +135,8 @@ class ProductPricelist(models.Model):
|
|||||||
prod_tmpl_ids,
|
prod_tmpl_ids,
|
||||||
prod_ids,
|
prod_ids,
|
||||||
categ_ids,
|
categ_ids,
|
||||||
|
# on_board_service_bool,
|
||||||
|
# board_service_id,
|
||||||
self.id,
|
self.id,
|
||||||
date,
|
date,
|
||||||
date,
|
date,
|
||||||
|
|||||||
@@ -18,6 +18,17 @@ class ProductPricelistItem(models.Model):
|
|||||||
string="End Date Overnight",
|
string="End Date Overnight",
|
||||||
help="End date to apply daily pricelist items",
|
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(
|
allowed_property_ids = fields.Many2many(
|
||||||
"pms.property",
|
"pms.property",
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
from odoo import api, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class ProductProduct(models.Model):
|
class ProductProduct(models.Model):
|
||||||
_inherit = "product.product"
|
_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(
|
@api.depends_context(
|
||||||
"pricelist",
|
"pricelist",
|
||||||
"partner",
|
"partner",
|
||||||
@@ -15,3 +22,30 @@ class ProductProduct(models.Model):
|
|||||||
)
|
)
|
||||||
def _compute_product_price(self):
|
def _compute_product_price(self):
|
||||||
super(ProductProduct, self)._compute_product_price()
|
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-if="service.is_board_service">
|
||||||
<t
|
<t
|
||||||
t-set="price"
|
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>
|
||||||
</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_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_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_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_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_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
|
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
|
from .common import TestHotel
|
||||||
|
|
||||||
|
|
||||||
@@ -39,32 +37,3 @@ class TestPmsBoardServiceRoomType(TestHotel):
|
|||||||
"class_id": self.room_type_class.id,
|
"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)]
|
r.pms_property_ids = [(4, self.p1.id)]
|
||||||
|
|
||||||
def test_check_board_service_property_integrity(self):
|
# TODO: pending multi property PR
|
||||||
self.company1 = self.env["res.company"].create(
|
# def test_check_board_service_property_integrity(self):
|
||||||
{
|
# self.company1 = self.env["res.company"].create(
|
||||||
"name": "Pms_Company_Test",
|
# {
|
||||||
}
|
# "name": "Pms_Company_Test",
|
||||||
)
|
# }
|
||||||
self.property1 = self.env["pms.property"].create(
|
# )
|
||||||
{
|
# self.property1 = self.env["pms.property"].create(
|
||||||
"name": "Pms_property_test1",
|
# {
|
||||||
"company_id": self.company1.id,
|
# "name": "Pms_property_test1",
|
||||||
"default_pricelist_id": self.env.ref("product.list0").id,
|
# "company_id": self.company1.id,
|
||||||
}
|
# "default_pricelist_id": self.env.ref("product.list0").id,
|
||||||
)
|
# }
|
||||||
self.property2 = self.env["pms.property"].create(
|
# )
|
||||||
{
|
# self.property2 = self.env["pms.property"].create(
|
||||||
"name": "Pms_property_test2",
|
# {
|
||||||
"company_id": self.company1.id,
|
# "name": "Pms_property_test2",
|
||||||
"default_pricelist_id": self.env.ref("product.list0").id,
|
# "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_class = self.env["pms.room.type.class"].create(
|
||||||
)
|
# {"name": "Room Type Class", "code_class": "SIN1"}
|
||||||
self.room_type = self.env["pms.room.type"].create(
|
# )
|
||||||
{
|
# self.room_type = self.env["pms.room.type"].create(
|
||||||
"name": "Room Type",
|
# {
|
||||||
"code_type": "Type1",
|
# "name": "Room Type",
|
||||||
"pms_property_ids": self.property2,
|
# "code_type": "Type1",
|
||||||
"class_id": self.room_type_class.id,
|
# "pms_property_ids": self.property2,
|
||||||
}
|
# "class_id": self.room_type_class.id,
|
||||||
)
|
# }
|
||||||
self.board_service = self.env["pms.board.service"].create(
|
# )
|
||||||
{
|
# self.board_service = self.env["pms.board.service"].create(
|
||||||
"name": "Board Service",
|
# {
|
||||||
}
|
# "name": "Board Service",
|
||||||
)
|
# "pms_property_ids": self.property1,
|
||||||
with self.assertRaises(ValidationError):
|
# }
|
||||||
self.env["pms.board.service.room.type"].create(
|
# )
|
||||||
{
|
# with self.assertRaises(ValidationError):
|
||||||
"pms_board_service_id": self.board_service.id,
|
# self.env["pms.board.service.room.type"].create(
|
||||||
"pms_room_type_id": self.room_type.id,
|
# {
|
||||||
"pricelist_id": self.env.ref("product.list0").id,
|
# "pms_board_service_id": self.board_service.id,
|
||||||
"pms_property_ids": self.property2,
|
# "pms_room_type_id": self.room_type.id,
|
||||||
}
|
# "pms_property_ids": self.property2,
|
||||||
)
|
# }
|
||||||
|
# )
|
||||||
|
|
||||||
def test_check_amenities_property_integrity(self):
|
def test_check_amenities_property_integrity(self):
|
||||||
self.company1 = self.env["res.company"].create(
|
self.company1 = self.env["res.company"].create(
|
||||||
|
|||||||
@@ -6,37 +6,8 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//field[@name='invoice_date']" position="after">
|
<xpath expr="//field[@name='invoice_date']" position="after">
|
||||||
<field name="folio_ids" widget="many2many_tags" />
|
<field name="folio_ids" widget="many2many_tags" />
|
||||||
<field name="from_reservation" invisible="1" />
|
|
||||||
<field name="pms_property_id" invisible="1" />
|
<field name="pms_property_id" invisible="1" />
|
||||||
</xpath>
|
</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>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|||||||
@@ -303,7 +303,7 @@
|
|||||||
<t t-if="service.is_board_service">
|
<t t-if="service.is_board_service">
|
||||||
<t
|
<t
|
||||||
t-set="price"
|
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>
|
||||||
</t>
|
</t>
|
||||||
|
|||||||
@@ -190,6 +190,7 @@
|
|||||||
attrs="{'invisible':[('state','not in',('cancel'))]}"
|
attrs="{'invisible':[('state','not in',('cancel'))]}"
|
||||||
/>
|
/>
|
||||||
<field name="internal_comment" />
|
<field name="internal_comment" />
|
||||||
|
<field name="user_id" />
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
colspan="2"
|
colspan="2"
|
||||||
@@ -286,8 +287,16 @@
|
|||||||
<field name="sequence" widget="handle" />
|
<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. -->
|
<!-- 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="display_type" invisible="1" />
|
||||||
|
<field name="is_board_service" invisible="1" />
|
||||||
|
|
||||||
<!-- <field name="product_updatable" 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
|
<field
|
||||||
name="product_id"
|
name="product_id"
|
||||||
options="{'no_open': True}"
|
options="{'no_open': True}"
|
||||||
@@ -348,7 +357,6 @@
|
|||||||
<field
|
<field
|
||||||
name="discount"
|
name="discount"
|
||||||
string="Disc.%"
|
string="Disc.%"
|
||||||
groups="product.group_discount_per_so_line"
|
|
||||||
optional="show"
|
optional="show"
|
||||||
widget="product_discount"
|
widget="product_discount"
|
||||||
/>
|
/>
|
||||||
@@ -419,7 +427,7 @@
|
|||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
<page string="Other data" invisible="1">
|
<page string="Other data">
|
||||||
<group>
|
<group>
|
||||||
<field name="user_id" />
|
<field name="user_id" />
|
||||||
<field name="client_order_ref" />
|
<field name="client_order_ref" />
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
class="alert alert-info"
|
class="alert alert-info"
|
||||||
role="alert"
|
role="alert"
|
||||||
style="margin-bottom:0px;"
|
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
|
This reservation has other reservantions and/or services in the
|
||||||
folio, you can check it in the
|
folio, you can check it in the
|
||||||
@@ -61,6 +61,24 @@
|
|||||||
/>
|
/>
|
||||||
</bold>
|
</bold>
|
||||||
</div>
|
</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
|
<div
|
||||||
class="alert alert-warning"
|
class="alert alert-warning"
|
||||||
role="alert"
|
role="alert"
|
||||||
@@ -197,7 +215,6 @@
|
|||||||
nolabel="1"
|
nolabel="1"
|
||||||
context="{'checkin': checkin, 'checkout': checkout, 'pms_property_id':pms_property_id, 'pricelist_id':pricelist_id}"
|
context="{'checkin': checkin, 'checkout': checkout, 'pms_property_id':pms_property_id, 'pricelist_id':pricelist_id}"
|
||||||
options="{'no_create': True,'no_open': True}"
|
options="{'no_create': True,'no_open': True}"
|
||||||
attrs="{'readonly':[('state','not in',('draft'))]}"
|
|
||||||
style="margin-right: 30px;"
|
style="margin-right: 30px;"
|
||||||
/>
|
/>
|
||||||
<field
|
<field
|
||||||
@@ -302,16 +319,6 @@
|
|||||||
name="pricelist_id"
|
name="pricelist_id"
|
||||||
options="{'no_open':True,'no_create': True}"
|
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
|
<!--<field
|
||||||
name="agency_id"
|
name="agency_id"
|
||||||
options="{'no_create': True,'no_open': True}"
|
options="{'no_create': True,'no_open': True}"
|
||||||
@@ -332,16 +339,12 @@
|
|||||||
name="children_occupying"
|
name="children_occupying"
|
||||||
attrs="{'invisible': [('children', '=', 0)]}"
|
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="allowed_room_ids" invisible="1" />
|
||||||
<field name="folio_payment_state" 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¿? -->
|
<!-- TODO: How to filter to avoid show False (generic) pricelist board when exist a specific pricelist board¿? -->
|
||||||
<field
|
<field
|
||||||
name="board_service_room_id"
|
name="board_service_room_id"
|
||||||
domain="[
|
domain="[('pms_room_type_id', '=', room_type_id)]"
|
||||||
('pms_room_type_id', '=', room_type_id),
|
|
||||||
('pricelist_id', 'in', [pricelist_id, False])]"
|
|
||||||
options="{'no_create': True,'no_open': True}"
|
options="{'no_create': True,'no_open': True}"
|
||||||
attrs="{'invisible': [('reservation_type','in',('out'))]}"
|
attrs="{'invisible': [('reservation_type','in',('out'))]}"
|
||||||
/>
|
/>
|
||||||
@@ -445,7 +448,6 @@
|
|||||||
<field name="date" readonly="1" force_save="1" />
|
<field name="date" readonly="1" force_save="1" />
|
||||||
<field name="price" />
|
<field name="price" />
|
||||||
<field name="discount" />
|
<field name="discount" />
|
||||||
<field name="move_line_ids" invisible="1" />
|
|
||||||
<field
|
<field
|
||||||
name="cancel_discount"
|
name="cancel_discount"
|
||||||
attrs="{'column_invisible': [('parent.state','!=','cancelled')]}"
|
attrs="{'column_invisible': [('parent.state','!=','cancelled')]}"
|
||||||
@@ -503,8 +505,6 @@
|
|||||||
name="open_service_ids"
|
name="open_service_ids"
|
||||||
attrs="{'invisible': [('per_day','=',False)]}"
|
attrs="{'invisible': [('per_day','=',False)]}"
|
||||||
/>
|
/>
|
||||||
<field name="price_unit" />
|
|
||||||
<field name="discount" />
|
|
||||||
<field name="tax_ids" widget="many2many_tags" />
|
<field name="tax_ids" widget="many2many_tags" />
|
||||||
<field name="price_subtotal" />
|
<field name="price_subtotal" />
|
||||||
<field name="price_tax" />
|
<field name="price_tax" />
|
||||||
@@ -513,6 +513,16 @@
|
|||||||
<tree string="Days">
|
<tree string="Days">
|
||||||
<field name="date" />
|
<field name="date" />
|
||||||
<field name="day_qty" />
|
<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>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</tree>
|
</tree>
|
||||||
@@ -674,6 +684,7 @@
|
|||||||
widget="badge"
|
widget="badge"
|
||||||
optional="show"
|
optional="show"
|
||||||
/>
|
/>
|
||||||
|
<field name="invoice_status" optional="show" />
|
||||||
<field
|
<field
|
||||||
name="company_id"
|
name="company_id"
|
||||||
groups="base.group_multi_company"
|
groups="base.group_multi_company"
|
||||||
|
|||||||
@@ -64,9 +64,7 @@
|
|||||||
<field name="by_default" />
|
<field name="by_default" />
|
||||||
<field name="pms_room_type_id" invisible="1" />
|
<field name="pms_room_type_id" invisible="1" />
|
||||||
<field name="pms_board_service_id" />
|
<field name="pms_board_service_id" />
|
||||||
<field name="price_type" />
|
|
||||||
<field name="amount" />
|
<field name="amount" />
|
||||||
<field name="pricelist_id" />
|
|
||||||
<button
|
<button
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
|
|||||||
@@ -10,7 +10,13 @@
|
|||||||
<field name="day_qty" />
|
<field name="day_qty" />
|
||||||
<field name="date" />
|
<field name="date" />
|
||||||
<field name="price_unit" />
|
<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="pms_property_id" invisible="1" />
|
||||||
|
<field name="is_board_service" invisible="1" />
|
||||||
</group>
|
</group>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
@@ -23,7 +29,14 @@
|
|||||||
<field name="product_id" />
|
<field name="product_id" />
|
||||||
<field name="day_qty" />
|
<field name="day_qty" />
|
||||||
<field name="date" />
|
<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="pms_property_id" invisible="1" />
|
||||||
|
<field name="is_board_service" invisible="1" />
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -56,7 +69,7 @@
|
|||||||
attrs="{'invisible':[('is_board_service','=', True)]}"
|
attrs="{'invisible':[('is_board_service','=', True)]}"
|
||||||
/>
|
/>
|
||||||
<field
|
<field
|
||||||
name="price_total"
|
name="price_day_total"
|
||||||
optional="show"
|
optional="show"
|
||||||
attrs="{'invisible':[('is_board_service','=', True)]}"
|
attrs="{'invisible':[('is_board_service','=', True)]}"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -28,8 +28,6 @@
|
|||||||
force_save="1"
|
force_save="1"
|
||||||
/>
|
/>
|
||||||
</group>
|
</group>
|
||||||
<field name="price_unit" invisible="1" />
|
|
||||||
<field name="discount" invisible="1" />
|
|
||||||
<field name="tax_ids" widget="many2many_tags" invisible="1" />
|
<field name="tax_ids" widget="many2many_tags" invisible="1" />
|
||||||
<field name="price_subtotal" invisible="1" />
|
<field name="price_subtotal" invisible="1" />
|
||||||
<field name="price_tax" invisible="1" />
|
<field name="price_tax" invisible="1" />
|
||||||
@@ -38,6 +36,13 @@
|
|||||||
<tree string="Days" editable="bottom">
|
<tree string="Days" editable="bottom">
|
||||||
<field name="date" />
|
<field name="date" />
|
||||||
<field name="day_qty" />
|
<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>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</sheet>
|
</sheet>
|
||||||
@@ -87,8 +92,6 @@
|
|||||||
name="open_service_ids"
|
name="open_service_ids"
|
||||||
attrs="{'invisible': [('per_day','=',False)]}"
|
attrs="{'invisible': [('per_day','=',False)]}"
|
||||||
/>
|
/>
|
||||||
<field name="price_unit" />
|
|
||||||
<field name="discount" />
|
|
||||||
<field name="tax_ids" widget="many2many_tags" />
|
<field name="tax_ids" widget="many2many_tags" />
|
||||||
<field name="price_subtotal" />
|
<field name="price_subtotal" />
|
||||||
<field name="price_tax" />
|
<field name="price_tax" />
|
||||||
@@ -97,6 +100,12 @@
|
|||||||
<tree string="Days">
|
<tree string="Days">
|
||||||
<field name="date" />
|
<field name="date" />
|
||||||
<field name="day_qty" />
|
<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>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</tree>
|
</tree>
|
||||||
|
|||||||
@@ -4,6 +4,16 @@
|
|||||||
<field name="model">product.pricelist.item</field>
|
<field name="model">product.pricelist.item</field>
|
||||||
<field name="inherit_id" ref="product.product_pricelist_item_form_view" />
|
<field name="inherit_id" ref="product.product_pricelist_item_form_view" />
|
||||||
<field name="arch" type="xml">
|
<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">
|
<xpath expr="//field[@name='min_quantity']" position="before">
|
||||||
<field
|
<field
|
||||||
name="pms_property_ids"
|
name="pms_property_ids"
|
||||||
|
|||||||
@@ -200,9 +200,6 @@
|
|||||||
<th name="th_qty">
|
<th name="th_qty">
|
||||||
Quantity
|
Quantity
|
||||||
</th>
|
</th>
|
||||||
<th name="th_price">
|
|
||||||
Price Unit
|
|
||||||
</th>
|
|
||||||
<th name="th_price_total">
|
<th name="th_price_total">
|
||||||
Price Total
|
Price Total
|
||||||
</th>
|
</th>
|
||||||
@@ -215,10 +212,6 @@
|
|||||||
<td><span
|
<td><span
|
||||||
t-field="service.product_qty"
|
t-field="service.product_qty"
|
||||||
/></td>
|
/></td>
|
||||||
<td><span
|
|
||||||
t-field="service.price_unit"
|
|
||||||
t-options='{"widget": "monetary", "display_currency": reservation.pricelist_id.currency_id}'
|
|
||||||
/></td>
|
|
||||||
<td><span
|
<td><span
|
||||||
t-field="service.price_total"
|
t-field="service.price_total"
|
||||||
t-options='{"widget": "monetary", "display_currency": reservation.pricelist_id.currency_id}'
|
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
|
||||||
from . import wizard_folio_availability
|
from . import wizard_folio_availability
|
||||||
from . import folio_make_invoice_advance
|
from . import folio_make_invoice_advance
|
||||||
from . import wizard_invoice_filter_days
|
|
||||||
from . import wizard_payment_folio
|
from . import wizard_payment_folio
|
||||||
from . import wizard_folio_changes
|
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