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:
Darío Lodeiros
2021-03-30 19:34:53 +02:00
committed by GitHub
parent 5d29d69fa9
commit ec841374cf
33 changed files with 605 additions and 1143 deletions

View File

@@ -37,7 +37,6 @@
"wizards/wizard_payment_folio.xml",
"wizards/folio_make_invoice_advance_views.xml",
"wizards/wizard_folio.xml",
"wizards/wizard_invoice_filter_days.xml",
"wizards/wizard_folio_changes.xml",
"views/pms_amenity_views.xml",
"views/pms_amenity_type_views.xml",

View File

@@ -301,7 +301,6 @@
'product_id': ref('pms_service_0'),
'amount': 3})]"
/>
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_1" model="pms.board.service">
<field name="name">Half Board</field>
@@ -314,7 +313,6 @@
'amount': 8})
]"
/>
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_2" model="pms.board.service">
<field name="name">FullBoard</field>
@@ -329,36 +327,29 @@
'amount': 8})
]"
/>
<field name="price_type">fixed</field>
</record>
<!-- pms.board.service.room.type -->
<!--Room 0 Economic-->
<record id="pms_board_service_room_0" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_0" />
<field name="pms_room_type_id" ref="pms_room_type_0" />
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_room_1" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_1" />
<field name="pms_room_type_id" ref="pms_room_type_0" />
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_room_2" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_1" />
<field name="pms_room_type_id" ref="pms_room_type_0" />
<field name="pricelist_id" ref="product.list0" />
<field name="price_type">fixed</field>
</record>
<!--Room 3 Triple-->
<record id="pms_board_service_room_3" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_0" />
<field name="pms_room_type_id" ref="pms_room_type_3" />
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_room_4" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_2" />
<field name="pms_room_type_id" ref="pms_room_type_3" />
<field name="price_type">fixed</field>
</record>
<!-- room.closure.reason -->
<record id="pms_room_closure_reason_0" model="room.closure.reason">

View File

@@ -22,8 +22,6 @@ class AccountBankStatementLine(models.Model):
line.update(
{
"folio_ids": [(6, 0, self.statement_folio_ids.ids)],
"reservation_ids": [(6, 0, self.reservation_ids.ids)],
"service_ids": [(6, 0, self.service_ids.ids)],
}
)
return line_vals_list

View File

@@ -14,7 +14,6 @@ class AccountMove(models.Model):
comodel_name="pms.folio", compute="_compute_folio_origin"
)
pms_property_id = fields.Many2one("pms.property")
from_reservation = fields.Boolean(compute="_compute_from_reservation")
outstanding_folios_debits_widget = fields.Text(
compute="_compute_get_outstanding_folios_JSON"
)
@@ -31,12 +30,6 @@ class AccountMove(models.Model):
if folios:
inv.folio_ids = [(6, 0, folios.ids)]
def _compute_from_reservation(self):
for inv in self:
inv.from_reservation = False
if len(inv.invoice_line_ids.mapped("reservation_line_ids")) > 0:
inv.from_reservation = True
# Action methods
def action_folio_payments(self):

View File

@@ -1,40 +1,13 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import api, fields, models
from odoo import fields, models
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
# Fields declaration
reservation_ids = fields.Many2many(
"pms.reservation",
"reservation_move_rel",
"move_line_id",
"reservation_id",
string="Reservations",
readonly=True,
copy=False,
)
service_ids = fields.Many2many(
"pms.service",
"service_line_move_rel",
"move_line_id",
"service_id",
string="Services",
readonly=True,
copy=False,
)
reservation_line_ids = fields.Many2many(
"pms.reservation.line",
"reservation_line_move_rel",
"move_line_id",
"reservation_line_id",
string="Reservation Lines",
readonly=True,
copy=False,
)
folio_line_ids = fields.Many2many(
"folio.sale.line",
"folio_sale_line_invoice_rel",
@@ -49,68 +22,8 @@ class AccountMoveLine(models.Model):
"move_id",
"folio_id",
string="Folios",
ondelete="cascade",
compute="_compute_folio_ids",
readonly=False,
store=True,
)
name = fields.Char(
compute="_compute_name",
readonly=False,
store=True,
)
@api.depends("service_ids", "reservation_ids")
def _compute_folio_ids(self):
for record in self:
if record.service_ids:
record.folio_ids = record.mapped("service_ids.folio_id")
elif record.reservation_ids:
record.folio_ids = record.mapped("reservation_ids.folio_id")
elif not record.folio_ids:
record.folio_ids = False
def invoice_filter_days(self):
action = self.env["ir.actions.act_window"]._for_xml_id(
"pms.pms_invoice_filter_days_action"
)
# Force the values of the move line in the context to avoid issues
ctx = dict(self.env.context)
ctx.pop("active_id", None)
ctx["active_ids"] = self.ids
ctx["active_model"] = "account.move.line"
action["context"] = ctx
return action
def _copy_data_extend_business_fields(self, values):
super(AccountMoveLine, self)._copy_data_extend_business_fields(values)
values["folio_line_ids"] = [(6, None, self.folio_line_ids.ids)]
values["reservation_line_ids"] = [(6, None, self.reservation_line_ids.ids)]
values["service_ids"] = [(6, None, self.service_ids.ids)]
values["reservation_ids"] = [(6, None, self.reservation_ids.ids)]
@api.depends("reservation_line_ids")
def _compute_name(self):
if hasattr(super(), "_compute_name"):
super()._compute_field()
for record in self:
if record.reservation_line_ids:
record.name = record._get_compute_name()
def _get_compute_name(self):
self.ensure_one()
if self.reservation_line_ids:
month = False
name = False
lines = self.reservation_line_ids.sorted("date")
for date in lines.mapped("date"):
if date.month != month:
name = name + "\n" if name else ""
name += date.strftime("%B-%Y") + ": "
name += date.strftime("%d")
month = date.month
else:
name += ", " + date.strftime("%d")
return name
else:
return False

View File

@@ -24,6 +24,9 @@ class FolioSaleLine(models.Model):
for line in self:
if line.state == "draft":
line.invoice_status = "no"
# REVIEW: if qty_to_invoice < 0 (invoice qty > sale qty),
# why status to_invoice?? this behavior is copied from sale order
# https://github.com/OCA/OCB/blob/14.0/addons/sale/models/sale.py#L1160
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
line.invoice_status = "to invoice"
elif (
@@ -71,18 +74,6 @@ class FolioSaleLine(models.Model):
else:
return False
@api.depends("service_id", "service_id.price_unit")
def _compute_price_unit(self):
"""
Compute unit prices of services
On reservations the unit price is compute by group in folio
"""
for record in self:
if record.service_id:
record.price_unit = record.service_id.price_unit
elif not record.price_unit:
record.price_unit = False
@api.depends("product_uom_qty", "discount", "price_unit", "tax_ids")
def _compute_amount(self):
"""
@@ -121,11 +112,23 @@ class FolioSaleLine(models.Model):
else record.reservation_id.tax_ids
)
@api.depends("service_id", "service_id.discount")
@api.depends(
"service_id",
"service_id.service_line_ids",
"service_id.service_line_ids.discount",
)
def _compute_discount(self):
self.discount = 0.0
for record in self.filtered("service_id"):
record.discount = record.service_id.discount
"""
Only in services without room we compute discount,
and this services only have one service line
"""
for record in self:
if record.service_id and not record.service_id.reservation_id:
record.discount = record.service_id.service_line_ids.mapped("discount")[
0
]
elif not record.discount:
record.discount = 0
@api.depends("reservation_id.room_type_id", "service_id.product_id")
def _compute_product_id(self):
@@ -355,6 +358,12 @@ class FolioSaleLine(models.Model):
index=True,
copy=False,
)
is_board_service = fields.Boolean(
string="Board Service",
related="service_id.is_board_service",
store=True,
)
name = fields.Text(
string="Description", compute="_compute_name", store=True, readonly=False
)
@@ -363,6 +372,10 @@ class FolioSaleLine(models.Model):
"pms.reservation.line",
string="Nights",
)
service_line_ids = fields.Many2many(
"pms.service.line",
string="Service Lines",
)
sequence = fields.Integer(string="Sequence", default=10)
invoice_lines = fields.Many2many(
@@ -389,8 +402,6 @@ class FolioSaleLine(models.Model):
price_unit = fields.Float(
"Unit Price",
digits="Product Price",
compute="_compute_price_unit",
store=True,
)
price_subtotal = fields.Monetary(
@@ -433,6 +444,7 @@ class FolioSaleLine(models.Model):
string="Discount (%)",
digits="Discount",
compute="_compute_discount",
readonly=False,
store=True,
)
@@ -550,13 +562,13 @@ class FolioSaleLine(models.Model):
help="Technical field for UX purpose.",
)
@api.depends("reservation_line_ids", "service_id")
@api.depends("reservation_line_ids", "service_line_ids", "service_line_ids.day_qty")
def _compute_product_uom_qty(self):
for line in self:
if line.reservation_line_ids:
line.product_uom_qty = len(line.reservation_line_ids)
elif line.service_id:
line.product_uom_qty = line.service_id.product_qty
elif line.service_line_ids:
line.product_uom_qty = sum(line.service_line_ids.mapped("day_qty"))
elif not line.product_uom_qty:
line.product_uom_qty = False
@@ -716,15 +728,6 @@ class FolioSaleLine(models.Model):
+ " The quantity pending to invoice is %s" % self.qty_to_invoice
)
)
reservation = self.reservation_id
service = self.service_id
reservation_lines = self.reservation_line_ids.filtered(
lambda l: not l.invoiced and l.reservation_id
)
lines_to_invoice = list()
if self.reservation_id:
for i in range(0, int(qty)):
lines_to_invoice.append(reservation_lines[i].id)
res = {
"display_type": self.display_type,
"sequence": self.sequence,
@@ -738,9 +741,6 @@ class FolioSaleLine(models.Model):
"analytic_account_id": self.folio_id.analytic_account_id.id,
"analytic_tag_ids": [(6, 0, self.analytic_tag_ids.ids)],
"folio_line_ids": [(6, 0, [self.id])],
"reservation_ids": [(6, 0, reservation.ids)],
"service_ids": [(6, 0, service.ids)],
"reservation_line_ids": [(6, 0, lines_to_invoice)],
}
if optional_values:
res.update(optional_values)

View File

@@ -18,12 +18,6 @@ class PmsBoardService(models.Model):
pms_board_service_room_type_ids = fields.One2many(
"pms.board.service.room.type", "pms_board_service_id"
)
price_type = fields.Selection(
[("fixed", "Fixed"), ("percent", "Percent")],
string="Type",
default="fixed",
required=True,
)
amount = fields.Float(
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
)

View File

@@ -1,7 +1,7 @@
# Copyright 2017 Dario
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.exceptions import UserError
class PmsBoardServiceRoomType(models.Model):
@@ -11,21 +11,6 @@ class PmsBoardServiceRoomType(models.Model):
_log_access = False
_description = "Board Service included in Room"
# Default Methods ang Gets
def name_get(self):
result = []
for res in self:
if res.pricelist_id:
name = u"{} ({})".format(
res.pms_board_service_id.name,
res.pricelist_id.name,
)
else:
name = u"{} ({})".format(res.pms_board_service_id.name, _("Generic"))
result.append((res.id, name))
return result
# Fields declaration
pms_board_service_id = fields.Many2one(
"pms.board.service",
@@ -52,27 +37,9 @@ class PmsBoardServiceRoomType(models.Model):
("pms_property_ids", "in", pms_property_ids),
],
)
pricelist_id = fields.Many2one(
"product.pricelist",
string="Pricelist",
required=False,
domain=[
"|",
("pms_property_ids", "=", False),
("pms_property_ids", "in", pms_property_ids),
],
)
board_service_line_ids = fields.One2many(
"pms.board.service.room.type.line", "pms_board_service_room_type_id"
)
# TODO:review relation with pricelist and properties
price_type = fields.Selection(
[("fixed", "Fixed"), ("percent", "Percent")],
string="Type",
default="fixed",
required=True,
)
amount = fields.Float(
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
)
@@ -87,41 +54,7 @@ class PmsBoardServiceRoomType(models.Model):
total += service.amount
record.update({"amount": total})
# Constraints and onchanges
@api.constrains("pricelist_id")
def constrains_pricelist_id(self):
for record in self:
if self.pricelist_id:
board_pricelist = self.env["pms.board.service.room.type"].search(
[
("pricelist_id", "=", record.pricelist_id.id),
("pms_room_type_id", "=", record.pms_room_type_id.id),
("pms_board_service_id", "=", record.pms_board_service_id.id),
("id", "!=", record.id),
]
)
if board_pricelist:
raise UserError(
_("This Board Service in this Room can't repeat pricelist")
)
else:
board_pricelist = self.env["pms.board.service.room.type"].search(
[
("pricelist_id", "=", False),
("pms_room_type_id", "=", record.pms_room_type_id.id),
("pms_board_service_id", "=", record.pms_board_service_id.id),
("id", "!=", record.id),
]
)
if board_pricelist:
raise UserError(
_(
"This Board Service in this Room \
can't repeat without pricelist"
)
)
@api.constrains("by_default", "pricelist_id")
@api.constrains("by_default")
def constrains_duplicated_board_defaul(self):
for record in self:
default_boards = (
@@ -130,34 +63,8 @@ class PmsBoardServiceRoomType(models.Model):
)
)
# TODO Check properties (with different propertys is allowed)
if any(
default_boards.mapped(
lambda l: l.pricelist_id == record.pricelist_id
and l.id != record.id
)
):
raise UserError(
_(
"""Only can set one default board service by
pricelist (or without pricelist)"""
)
)
@api.constrains("pms_property_ids", "pms_room_type_ids")
def _check_room_type_property_integrity(self):
for record in self:
if record.pms_property_ids and record.pms_room_type_id.pms_property_ids:
for pms_property in record.pms_property_ids:
if pms_property not in record.pms_room_type_id.pms_property_ids:
raise ValidationError(_("Property not allowed in room type"))
@api.constrains("pms_property_ids", "pricelist_id")
def _check_pricelist_property_integrity(self):
for record in self:
if record.pms_property_ids and record.pricelist_id:
for pms_property in record.pms_property_ids:
if pms_property not in record.pricelist_id.pms_property_ids:
raise ValidationError(_("Property not allowed in pricelist"))
if any(default_boards.filtered(lambda l: l.id != record.id)):
raise UserError(_("""Only can set one default board service"""))
# Action methods
@@ -176,13 +83,13 @@ class PmsBoardServiceRoomType(models.Model):
def init(self):
self._cr.execute(
"SELECT indexname FROM pg_indexes WHERE indexname = %s",
("pms_board_service_id_pms_room_type_id_pricelist_id",),
("pms_board_service_id_pms_room_type_id",),
)
if not self._cr.fetchone():
self._cr.execute(
"CREATE INDEX pms_board_service_id_pms_room_type_id_pricelist_id \
"CREATE INDEX pms_board_service_id_pms_room_type_id \
ON pms_board_service_room_type_rel \
(pms_board_service_id, pms_room_type_id, pricelist_id)"
(pms_board_service_id, pms_room_type_id)"
)
@api.model

View File

@@ -391,6 +391,9 @@ class PmsFolio(models.Model):
"reservation_ids",
"service_ids",
"service_ids.reservation_id",
"service_ids.service_line_ids.price_day_total",
"service_ids.service_line_ids.discount",
"service_ids.service_line_ids.cancel_discount",
"reservation_ids.reservation_line_ids",
"reservation_ids.reservation_line_ids.price",
"reservation_ids.reservation_line_ids.discount",
@@ -415,7 +418,7 @@ class PmsFolio(models.Model):
},
)
)
group_lines = {}
group_reservation_lines = {}
for line in reservation.reservation_line_ids:
# On resevations the price, and discounts fields are used
# by group, we need pass this in the create line
@@ -431,32 +434,45 @@ class PmsFolio(models.Model):
for discount in [line.discount, line.cancel_discount]:
discount_factor = discount_factor * ((100.0 - discount) / 100.0)
final_discount = 100.0 - (discount_factor * 100.0)
if group_key not in group_lines:
group_lines[group_key] = {
if group_key not in group_reservation_lines:
group_reservation_lines[group_key] = {
"reservation_id": reservation.id,
"discount": final_discount,
"price_unit": line.price,
"reservation_line_ids": [(4, line.id)],
}
else:
group_lines[group_key][("reservation_line_ids")].append(
(4, line.id)
)
for item in group_lines.items():
group_reservation_lines[group_key][
("reservation_line_ids")
].append((4, line.id))
for item in group_reservation_lines.items():
sale_lines.append((0, False, item[1]))
for service in reservation.service_ids:
# On service the price, and discounts fields are
# compute in the sale.order.line
sale_lines.append(
(
0,
False,
{
# Service days with different prices,
# go to differente sale lines
group_service_lines = {}
for service_line in service.service_line_ids:
service_group_key = (
service_line.price_unit,
service_line.discount,
service_line.cancel_discount,
)
if service_group_key not in group_service_lines:
# On service the price, and discounts fields are
# compute in the sale.order.line
group_service_lines[service_group_key] = {
"name": service.name,
"service_id": service.id,
},
)
)
"discount": service_line.discount,
"price_unit": service_line.price_unit,
"service_line_ids": [(4, service_line.id)],
}
else:
group_service_lines[service_group_key][
("service_line_ids")
].append((4, service_line.id))
for item in group_service_lines.items():
sale_lines.append((0, False, item[1]))
if services_without_room:
sale_lines.append(
(
@@ -646,25 +662,20 @@ class PmsFolio(models.Model):
else:
order.invoice_status = "no"
@api.depends("reservation_ids.price_total", "service_ids.price_total")
@api.depends("sale_line_ids.price_total")
def _compute_amount_all(self):
"""
Compute the total amounts of the SO.
"""
for record in self.filtered("pricelist_id"):
for folio in self:
amount_untaxed = amount_tax = 0.0
amount_untaxed = sum(record.reservation_ids.mapped("price_subtotal")) + sum(
record.service_ids.mapped("price_subtotal")
)
amount_tax = sum(record.reservation_ids.mapped("price_tax")) + sum(
record.service_ids.mapped("price_tax")
)
record.update(
for line in folio.sale_line_ids:
amount_untaxed += line.price_subtotal
amount_tax += line.price_tax
folio.update(
{
"amount_untaxed": record.pricelist_id.currency_id.round(
amount_untaxed
),
"amount_tax": record.pricelist_id.currency_id.round(amount_tax),
"amount_untaxed": amount_untaxed,
"amount_tax": amount_tax,
"amount_total": amount_untaxed + amount_tax,
}
)
@@ -1248,9 +1259,9 @@ class PmsFolio(models.Model):
def _get_tax_amount_by_group(self):
self.ensure_one()
res = {}
for line in self.reservation_ids:
for line in self.sale_line_ids:
price_reduce = line.price_total
product = line.room_type_id.product_id
product = line.product_id
taxes = line.tax_ids.compute_all(price_reduce, quantity=1, product=product)[
"taxes"
]
@@ -1261,18 +1272,6 @@ class PmsFolio(models.Model):
if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids:
res[group]["amount"] += t["amount"]
res[group]["base"] += t["base"]
for line in self.service_ids:
price_reduce = line.price_unit * (1.0 - line.discount / 100.0)
taxes = line.tax_ids.compute_all(
price_reduce, quantity=line.product_qty, product=line.product_id
)["taxes"]
for tax in line.tax_ids:
group = tax.tax_group_id
res.setdefault(group, {"amount": 0.0, "base": 0.0})
for t in taxes:
if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids:
res[group]["amount"] += t["amount"]
res[group]["base"] += t["base"]
res = sorted(res.items(), key=lambda line: line[0].sequence)
res = [
(line[0].name, line[1]["amount"], line[1]["base"], len(res)) for line in res

View File

@@ -7,7 +7,6 @@ import time
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_is_zero
_logger = logging.getLogger(__name__)
@@ -83,6 +82,12 @@ class PmsReservation(models.Model):
copy=False,
check_company=True,
)
sale_line_ids = fields.One2many(
comodel_name="folio.sale.line",
inverse_name="reservation_id",
string="Sale Lines",
copy=False,
)
board_service_room_id = fields.Many2one(
"pms.board.service.room.type",
string="Board Service",
@@ -184,7 +189,8 @@ class PmsReservation(models.Model):
)
user_id = fields.Many2one(
related="folio_id.user_id",
depends=["folio_id"],
depends=["folio_id.user_id"],
default=lambda self: self.env.user.id,
readonly=False,
store=True,
)
@@ -274,14 +280,6 @@ class PmsReservation(models.Model):
ondelete="restrict",
domain=["|", ("active", "=", False), ("active", "=", True)],
)
move_line_ids = fields.Many2many(
"account.move.line",
"reservation_move_rel",
"reservation_id",
"move_line_id",
string="Invoice Lines",
copy=False,
)
localizator = fields.Char(
string="Localizator",
compute="_compute_localizator",
@@ -446,32 +444,6 @@ class PmsReservation(models.Model):
readonly=True,
default="no",
)
qty_to_invoice = fields.Float(
compute="_compute_qty_to_invoice",
string="To Invoice Quantity",
store=True,
readonly=True,
digits=("Product Unit of Measure"),
)
qty_invoiced = fields.Float(
compute="_compute_qty_invoiced",
string="Invoiced Quantity",
store=True,
readonly=True,
digits=("Product Unit of Measure"),
)
untaxed_amount_invoiced = fields.Monetary(
"Untaxed Invoiced Amount",
compute="_compute_untaxed_amount_invoiced",
compute_sudo=True,
store=True,
)
untaxed_amount_to_invoice = fields.Monetary(
"Untaxed Amount To Invoice",
compute="_compute_untaxed_amount_to_invoice",
compute_sudo=True,
store=True,
)
analytic_tag_ids = fields.Many2many(
"account.analytic.tag",
string="Analytic Tags",
@@ -552,20 +524,15 @@ class PmsReservation(models.Model):
if reservation.pricelist_id and reservation.room_type_id:
board_service_default = self.env["pms.board.service.room.type"].search(
[
"&",
"&",
("pms_room_type_id", "=", reservation.room_type_id.id),
("by_default", "=", True),
"|",
("pricelist_id", "=", reservation.pricelist_id.id),
("pricelist_id", "=", False),
]
)
if len(board_service_default) > 1:
reservation.board_service_room_id = board_service_default.filtered(
lambda b: b.pricelist_id == reservation.pricelist_id
)
else:
if (
not reservation.board_service_room_id
or not reservation.board_service_room_id.pms_room_type_id
== reservation.room_type_id
):
reservation.board_service_room_id = (
board_service_default.id if board_service_default else False
)
@@ -693,6 +660,13 @@ class PmsReservation(models.Model):
("is_board_service", "=", True),
]
)
# Avoid recalculating services if the boardservice has not changed
if (
old_board_lines
and reservation.board_service_room_id
== reservation._origin.board_service_room_id
):
return
if reservation.board_service_room_id:
board = self.env["pms.board.service.room.type"].browse(
reservation.board_service_room_id.id
@@ -707,6 +681,8 @@ class PmsReservation(models.Model):
board_services.append((0, False, res))
reservation.service_ids -= old_board_lines
reservation.service_ids = board_services
elif old_board_lines:
reservation.service_ids -= old_board_lines
@api.depends("partner_id", "agency_id")
def _compute_pricelist_id(self):
@@ -727,13 +703,19 @@ class PmsReservation(models.Model):
reservation.pms_property_id.default_pricelist_id.id
)
@api.depends("pricelist_id")
@api.depends("pricelist_id", "room_type_id")
def _compute_show_update_pricelist(self):
for reservation in self:
if (
sum(reservation.reservation_line_ids.mapped("price")) > 0
and reservation.pricelist_id
and reservation._origin.pricelist_id != reservation.pricelist_id
and (
reservation.pricelist_id
and reservation._origin.pricelist_id != reservation.pricelist_id
)
or (
reservation.room_type_id
and reservation._origin.room_type_id != reservation.room_type_id
)
):
reservation.show_update_pricelist = True
else:
@@ -1036,183 +1018,28 @@ class PmsReservation(models.Model):
if room_ids:
reservation.preferred_room_id = room_ids[0]
@api.depends("state", "qty_to_invoice", "qty_invoiced")
@api.depends(
"sale_line_ids",
"sale_line_ids.invoice_status",
)
def _compute_invoice_status(self):
"""
Compute the invoice status of a Reservation. Possible statuses:
- no: if the Folio is not in status 'sale' or 'done', we consider
that there is nothing to invoice. This is also hte default value
if the conditions of no other status is met.
- to invoice: we refer to the quantity to invoice of the line.
Refer to method `_compute_get_to_invoice_qty()` for more information
on how this quantity is calculated.
- invoiced: the quantity invoiced is larger or equal to the
quantity ordered.
"""
precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
for line in self:
if line.state == "draft":
line.invoice_status = "no"
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
line.invoice_status = "to invoice"
elif (
float_compare(
line.qty_invoiced,
len(line.reservation_line_ids),
precision_digits=precision,
)
>= 0
):
line.invoice_status = "invoiced"
else:
line.invoice_status = "no"
@api.depends("qty_invoiced", "nights", "folio_id.state")
def _compute_qty_to_invoice(self):
"""
Compute the quantity to invoice. The quantity to invoice is
calculated from the nights quantity.
Base on folio sale line invoice status
"""
for line in self:
if line.folio_id.state not in ["draft"]:
line.qty_to_invoice = len(line.reservation_line_ids) - line.qty_invoiced
else:
line.qty_to_invoice = 0
@api.depends(
"move_line_ids.move_id.state",
"move_line_ids.quantity",
"untaxed_amount_to_invoice",
)
def _compute_qty_invoiced(self):
"""
Compute the quantity invoiced. If case of a refund, the quantity
invoiced is decreased. We must check day per day and sum or
decreased on 1 unit per invoice_line
"""
for record in self:
qty_invoiced = 0.0
for line in record.reservation_line_ids:
invoice_lines = line.move_line_ids.filtered(
lambda r: r.move_id.state != "cancel"
)
qty_invoiced += len(
invoice_lines.filtered(
lambda r: r.move_id.move_type == "out_invoice"
)
) - len(
invoice_lines.filtered(
lambda r: r.move_id.move_type == "out_refund"
)
)
record.qty_invoiced = qty_invoiced
@api.depends(
"move_line_ids",
"move_line_ids.price_total",
"move_line_ids.move_id.state",
"move_line_ids.move_id.move_type",
)
def _compute_untaxed_amount_invoiced(self):
"""Compute the untaxed amount already invoiced from the reservation,
taking the refund attached
the reservation into account. This amount is computed as
SUM(inv_line.price_subtotal) - SUM(ref_line.price_subtotal)
where
`inv_line` is a customer invoice line linked to the reservation
`ref_line` is a customer credit note (refund)
line linked to the reservation
"""
for line in self:
amount_invoiced = 0.0
for invoice_line in line.move_line_ids:
if invoice_line.move_id.state == "posted":
invoice_date = (
invoice_line.move_id.invoice_date or fields.Date.today()
)
if invoice_line.move_id.move_type == "out_invoice":
amount_invoiced += invoice_line.currency_id._convert(
invoice_line.price_subtotal,
line.currency_id,
line.company_id,
invoice_date,
)
elif invoice_line.move_id.move_type == "out_refund":
amount_invoiced -= invoice_line.currency_id._convert(
invoice_line.price_subtotal,
line.currency_id,
line.company_id,
invoice_date,
)
line.untaxed_amount_invoiced = amount_invoiced
@api.depends(
"state", "discount", "price_total", "room_type_id", "untaxed_amount_invoiced"
)
def _compute_untaxed_amount_to_invoice(self):
"""Total of remaining amount to invoice on the reservation (taxes excl.) as
total_sol - amount already invoiced
Note: Draft invoice are ignored on purpose, the 'to invoice' amount should
come only from the reservation.
"""
for line in self:
amount_to_invoice = 0.0
if line.state not in ["draft"]:
price_subtotal = 0.0
price_subtotal = line.price_subtotal
if len(line.tax_ids.filtered(lambda tax: tax.price_include)) > 0:
# As included taxes are not excluded from the computed subtotal,
# `compute_all()` method
# has to be called to retrieve the subtotal without them.
price_subtotal = line.tax_ids.compute_all(
price_subtotal,
currency=line.currency_id,
quantity=line.nights,
product=line.room_type_id.product_id,
)["total_excluded"]
if any(
line.move_line_ids.mapped(lambda i: i.discount != line.discount)
):
# In case of re-invoicing with different discount we
# try to calculate manually the
# remaining amount to invoice
amount = 0
for move in line.move_line_ids:
if (
len(move.tax_ids.filtered(lambda tax: tax.price_include))
> 0
):
amount += move.tax_ids.compute_all(
move.currency_id._convert(
move.price_unit,
line.currency_id,
line.company_id,
move.date or fields.Date.today(),
round=False,
)
* move.quantity
)["total_excluded"]
else:
amount += (
move.currency_id._convert(
move.price_unit,
line.currency_id,
line.company_id,
move.date or fields.Date.today(),
round=False,
)
* move.quantity
)
amount_to_invoice = max(price_subtotal - amount, 0)
states = list(set(line.sale_line_ids.mapped("invoice_status")))
if len(states) == 1:
line.invoice_status = states[0]
elif len(states) >= 1:
if "to_invoice" in states:
line.invoice_status = "to_invoice"
elif "invoiced" in states:
line.invoice_status = "invoiced"
else:
amount_to_invoice = price_subtotal - line.untaxed_amount_invoiced
line.untaxed_amount_to_invoice = amount_to_invoice
line.invoice_status = "no"
else:
line.invoice_status = "no"
@api.depends("reservation_line_ids")
def _compute_nights(self):
@@ -1529,8 +1356,10 @@ class PmsReservation(models.Model):
self.show_update_pricelist = False
self.message_post(
body=_(
"Prices have been recomputed according to pricelist <b>%s<b> ",
"""Prices have been recomputed according to pricelist <b>%s</b>
and room type <b>%s</b>""",
self.pricelist_id.display_name,
self.room_type_id.name,
)
)

View File

@@ -41,12 +41,12 @@ class PmsReservationLine(models.Model):
store=True,
readonly=False,
)
move_line_ids = fields.Many2many(
"account.move.line",
"reservation_line_move_rel",
sale_line_ids = fields.Many2many(
"folio.sale.line",
"reservation_line_sale_line_rel",
"reservation_line_id",
"move_line_id",
string="Invoice Lines",
"sale_line_id",
string="Sales Lines",
readonly=True,
copy=False,
)
@@ -65,11 +65,6 @@ class PmsReservationLine(models.Model):
store=True,
readonly=False,
)
invoiced = fields.Boolean(
string="Invoiced",
compute="_compute_invoiced",
store=True,
)
cancel_discount = fields.Float(
string="Cancelation Discount (%)",
digits=("Discount"),
@@ -311,31 +306,15 @@ class PmsReservationLine(models.Model):
else:
line.occupies_availability = True
@api.depends("move_line_ids", "move_line_ids.move_id.state")
def _compute_invoiced(self):
for line in self:
qty_invoiced = 0
for invoice_line in line.move_line_ids:
if invoice_line.move_id.state != "cancel":
if invoice_line.move_id.move_type == "out_invoice":
qty_invoiced += 1
elif invoice_line.move_id.move_type == "out_refund":
qty_invoiced -= 1
line.invoiced = False if qty_invoiced < 1 else True
# TODO: Refact method and allowed cancelled single days
@api.depends("reservation_id.cancelled_reason")
def _compute_cancel_discount(self):
for line in self:
line.cancel_discount = 0
# TODO: Review cancel logic
# reservation = line.reservation_id
# pricelist = reservation.pricelist_id
# if reservation.state == "cancelled":
# # TODO: Set 0 qty on cancel room services change to compute day_qty
# # (view constrain service_line_days)
# for service in reservation.service_ids:
# service.service_line_ids.write({"day_qty": 0})
# service._compute_days_qty()
# if (
# reservation.cancelled_reason
# and pricelist

View File

@@ -208,6 +208,8 @@ class PmsRoomType(models.Model):
if room.pms_property_id not in record.pms_property_ids:
raise ValidationError(_("Property not allowed in room"))
# TODO: Not allowed repeat boardservice on room_type with
# same properties os without properties
@api.constrains("board_service_room_type_ids", "pms_property_ids")
def _check_integrity_property_board_service_room_type(self):
for record in self:

View File

@@ -5,7 +5,6 @@ import logging
from datetime import timedelta
from odoo import _, api, fields, models
from odoo.tools import float_compare, float_is_zero
_logger = logging.getLogger(__name__)
@@ -52,8 +51,17 @@ class PmsService(models.Model):
readonly=False,
store=True,
)
sale_line_ids = fields.One2many(
comodel_name="folio.sale.line",
inverse_name="service_id",
string="Sale Lines",
copy=False,
)
reservation_id = fields.Many2one(
"pms.reservation", "Room", default=_default_reservation_id
"pms.reservation",
"Room",
default=_default_reservation_id,
ondelete="cascade",
)
service_line_ids = fields.One2many(
"pms.service.line",
@@ -79,14 +87,6 @@ class PmsService(models.Model):
readonly=False,
domain=["|", ("active", "=", False), ("active", "=", True)],
)
move_line_ids = fields.Many2many(
"account.move.line",
"service_line_move_rel",
"service_id",
"move_line_id",
string="move Lines",
copy=False,
)
analytic_tag_ids = fields.Many2many("account.analytic.tag", string="Analytic Tags")
currency_id = fields.Many2one(
related="folio_id.currency_id", store=True, string="Currency", readonly=True
@@ -128,28 +128,6 @@ class PmsService(models.Model):
],
string="Sales Channel",
)
price_unit = fields.Float(
"Unit Price",
digits=("Product Price"),
compute="_compute_price_unit",
store=True,
readonly=False,
)
discount = fields.Float(string="Discount (%)", digits=("Discount"), default=0.0)
qty_to_invoice = fields.Float(
compute="_compute_get_to_invoice_qty",
string="To Invoice",
store=True,
readonly=True,
digits=("Product Unit of Measure"),
)
qty_invoiced = fields.Float(
compute="_compute_get_invoice_qty",
string="Invoiced",
store=True,
readonly=True,
digits=("Product Unit of Measure"),
)
price_subtotal = fields.Monetary(
string="Subtotal", readonly=True, store=True, compute="_compute_amount_service"
)
@@ -214,6 +192,7 @@ class PmsService(models.Model):
# cached (otherwise double the date)
pass
elif not old_line:
price_unit = service._get_price_unit_line(idate)
lines.append(
(
0,
@@ -221,6 +200,7 @@ class PmsService(models.Model):
{
"date": idate,
"day_qty": day_qty,
"price_unit": price_unit,
},
)
)
@@ -246,12 +226,11 @@ class PmsService(models.Model):
]
)
)
_logger.info(service)
_logger.info(lines)
service.service_line_ids = lines
else:
# TODO: Review (business logic refact) no per_day logic service
if not service.service_line_ids:
price_unit = service._get_price_unit_line()
service.service_line_ids = [
(
0,
@@ -259,6 +238,7 @@ class PmsService(models.Model):
{
"date": fields.Date.today(),
"day_qty": day_qty,
"price_unit": price_unit,
},
)
]
@@ -266,6 +246,7 @@ class PmsService(models.Model):
# TODO: Service without reservation(room) but with folio¿?
# example: tourist tour in group
if not service.service_line_ids:
price_unit = service._get_price_unit_line()
service.service_line_ids = [
(
0,
@@ -273,6 +254,7 @@ class PmsService(models.Model):
{
"date": fields.Date.today(),
"day_qty": day_qty,
"price_unit": price_unit,
},
)
]
@@ -300,79 +282,6 @@ class PmsService(models.Model):
qty = sum(service.service_line_ids.mapped("day_qty"))
service.product_qty = qty
@api.depends(
"product_id",
"service_line_ids",
"reservation_id.pricelist_id",
"reservation_id.pms_property_id",
"pms_property_id",
)
def _compute_price_unit(self):
for service in self:
folio = service.folio_id
reservation = service.reservation_id
origin = reservation if reservation else folio
if origin:
if service._recompute_price():
partner = origin.partner_id
pricelist = origin.pricelist_id
if reservation and service.is_board_service:
board_room_type = reservation.board_service_room_id
if board_room_type.price_type == "fixed":
service.price_unit = (
self.env["pms.board.service.room.type.line"]
.search(
[
(
"pms_board_service_room_type_id",
"=",
board_room_type.id,
),
("product_id", "=", service.product_id.id),
]
)
.amount
)
else:
service.price_unit = (
reservation.price_total
* self.env["pms.board.service.room.type.line"]
.search(
[
(
"pms_board_service_room_type_id",
"=",
board_room_type.id,
),
("product_id", "=", service.product_id.id),
]
)
.amount
) / 100
else:
product = service.product_id.with_context(
lang=partner.lang,
partner=partner.id,
quantity=service.product_qty,
date=folio.date_order if folio else fields.Date.today(),
pricelist=pricelist.id,
uom=service.product_id.uom_id.id,
fiscal_position=False,
property=service.pms_property_id.id,
)
service.price_unit = self.env[
"account.tax"
]._fix_tax_included_price_company(
service._get_display_price(product),
product.taxes_id,
service.tax_ids,
origin.company_id,
)
else:
service.price_unit = service._origin.price_unit
else:
service.price_unit = 0
@api.depends("reservation_id")
def _compute_folio_id(self):
for record in self:
@@ -381,133 +290,54 @@ class PmsService(models.Model):
elif not record.folio_id:
record.folio_id = False
def _recompute_price(self):
# REVIEW: Conditional to avoid overriding already calculated prices,
# I'm not sure it's the best way
self.ensure_one()
# folio/reservation origin service
folio_origin = self._origin.folio_id
reservation_origin = self._origin.reservation_id
origin = reservation_origin if reservation_origin else folio_origin
# folio/reservation new service
folio_new = self.folio_id
reservation_new = self.reservation_id
new = reservation_new if reservation_new else folio_new
price_fields = [
"pricelist_id",
"reservation_type",
"pms_property_id",
]
if (
any(origin[field] != new[field] for field in price_fields)
or self._origin.price_unit == 0
):
return True
return False
@api.depends("qty_invoiced", "product_qty", "folio_id.state")
def _compute_get_to_invoice_qty(self):
"""
Compute the quantity to invoice. If the invoice policy is order,
the quantity to invoice is calculated from the ordered quantity.
Otherwise, the quantity delivered is used.
"""
for line in self:
if line.folio_id.state not in ["draft"]:
line.qty_to_invoice = line.product_qty - line.qty_invoiced
else:
line.qty_to_invoice = 0
@api.depends("move_line_ids.move_id.state", "move_line_ids.quantity")
def _compute_get_invoice_qty(self):
"""
Compute the quantity invoiced. If case of a refund,
the quantity invoiced is decreased. Note that this is the case only
if the refund is generated from the Folio and that is intentional: if
a refund made would automatically decrease the invoiced quantity,
then there is a risk of reinvoicing it automatically, which may
not be wanted at all. That's why the refund has to be
created from the Folio
"""
for line in self:
qty_invoiced = 0.0
for invoice_line in line.move_line_ids:
if invoice_line.move_id.state != "cancel":
if invoice_line.move_id.move_type == "out_invoice":
qty_invoiced += invoice_line.product_uom_id._compute_quantity(
invoice_line.quantity, line.product_id.uom_id
)
elif invoice_line.move_id.move_type == "out_refund":
if (
not line.is_downpayment
or line.untaxed_amount_to_invoice == 0
):
qty_invoiced -= (
invoice_line.product_uom_id._compute_quantity(
invoice_line.quantity, line.product_id.uom_id
)
)
line.qty_invoiced = qty_invoiced
@api.depends("product_qty", "qty_to_invoice", "qty_invoiced")
@api.depends(
"sale_line_ids",
"sale_line_ids.invoice_status",
)
def _compute_invoice_status(self):
"""
Compute the invoice status of a SO line. Possible statuses:
- no: if the SO is not in status 'sale' or 'done',
we consider that there is nothing to invoice.
This is also hte default value if the conditions of no other
status is met.
- to invoice: we refer to the quantity to invoice of the line.
Refer to method `_compute_get_to_invoice_qty()` for more information on
how this quantity is calculated.
- upselling: this is possible only for a product invoiced on ordered
quantities for which we delivered more than expected.
The could arise if, for example, a project took more time than
expected but we decided not to invoice the extra cost to the
client. This occurs onyl in state 'sale', so that when a Folio
is set to done, the upselling opportunity is removed from the list.
- invoiced: the quantity invoiced is larger or equal to the
quantity ordered.
Compute the invoice status of a Reservation. Possible statuses:
Base on folio sale line invoice status
"""
precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
for line in self:
state = line.folio_id.state or "draft"
if state == "draft":
line.invoice_status = "no"
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
line.invoice_status = "to invoice"
elif (
float_compare(
line.qty_invoiced, line.product_qty, precision_digits=precision
)
>= 0
):
line.invoice_status = "invoiced"
states = list(set(line.sale_line_ids.mapped("invoice_status")))
if len(states) == 1:
line.invoice_status = states[0]
elif len(states) >= 1:
if "to_invoice" in states:
line.invoice_status = "to_invoice"
elif "invoiced" in states:
line.invoice_status = "invoiced"
else:
line.invoice_status = "no"
else:
line.invoice_status = "no"
@api.depends("product_qty", "discount", "price_unit", "tax_ids")
@api.depends("service_line_ids.price_day_total")
def _compute_amount_service(self):
for service in self:
folio = service.folio_id
reservation = service.reservation_id
currency = folio.currency_id if folio else reservation.currency_id
product = service.product_id
price = service.price_unit * (1 - (service.discount or 0.0) * 0.01)
taxes = service.tax_ids.compute_all(
price, currency, service.product_qty, product=product
)
service.update(
{
"price_tax": sum(
t.get("amount", 0.0) for t in taxes.get("taxes", [])
),
"price_total": taxes["total_included"],
"price_subtotal": taxes["total_excluded"],
}
)
if service.service_line_ids:
service.update(
{
"price_tax": sum(
service.service_line_ids.mapped("price_day_tax")
),
"price_total": sum(
service.service_line_ids.mapped("price_day_total")
),
"price_subtotal": sum(
service.service_line_ids.mapped("price_day_subtotal")
),
}
)
else:
service.update(
{
"price_tax": 0,
"price_total": 0,
"price_subtotal": 0,
}
)
# Action methods
def open_service_ids(self):
@@ -537,20 +367,12 @@ class PmsService(models.Model):
reservation = self.reservation_id
origin = folio if folio else reservation
if origin.pricelist_id.discount_policy == "with_discount":
return product.with_context(pricelist=origin.pricelist_id.id).price
product_context = dict(
self.env.context,
partner_id=origin.partner_id.id,
date=folio.date_order if folio else fields.Date.today(),
uom=self.product_id.uom_id.id,
)
return product.price
final_price, rule_id = origin.pricelist_id.with_context(
product_context
).get_product_price_rule(
self.product_id, self.product_qty or 1.0, origin.partner_id
)
product._context
).get_product_price_rule(product, self.product_qty or 1.0, origin.partner_id)
base_price, currency_id = self.with_context(
product_context
product._context
)._get_real_price_currency(
product,
rule_id,
@@ -562,12 +384,74 @@ class PmsService(models.Model):
base_price = (
self.env["res.currency"]
.browse(currency_id)
.with_context(product_context)
.with_context(product._context)
.compute(base_price, origin.pricelist_id.currency_id)
)
# negative discounts (= surcharge) are included in the display price
return max(base_price, final_price)
def _get_real_price_currency(self, product, rule_id, qty, uom, pricelist_id):
"""Retrieve the price before applying the pricelist
:param obj product: object of current product record
:parem float qty: total quantity of product
:param tuple price_and_rule: tuple(price, suitable_rule)
coming from pricelist computation
:param obj uom: unit of measure of current order line
:param integer pricelist_id: pricelist id of sales order"""
PricelistItem = self.env["product.pricelist.item"]
field_name = "lst_price"
currency_id = None
product_currency = product.currency_id
if rule_id:
pricelist_item = PricelistItem.browse(rule_id)
if pricelist_item.pricelist_id.discount_policy == "without_discount":
while (
pricelist_item.base == "pricelist"
and pricelist_item.base_pricelist_id
and pricelist_item.base_pricelist_id.discount_policy
== "without_discount"
):
price, rule_id = pricelist_item.base_pricelist_id.with_context(
uom=uom.id
).get_product_price_rule(product, qty, self.order_id.partner_id)
pricelist_item = PricelistItem.browse(rule_id)
if pricelist_item.base == "standard_price":
field_name = "standard_price"
product_currency = product.cost_currency_id
elif (
pricelist_item.base == "pricelist" and pricelist_item.base_pricelist_id
):
field_name = "price"
product = product.with_context(
pricelist=pricelist_item.base_pricelist_id.id
)
product_currency = pricelist_item.base_pricelist_id.currency_id
currency_id = pricelist_item.pricelist_id.currency_id
if not currency_id:
currency_id = product_currency
cur_factor = 1.0
else:
if currency_id.id == product_currency.id:
cur_factor = 1.0
else:
cur_factor = currency_id._get_conversion_rate(
product_currency,
currency_id,
self.company_id or self.env.company,
self.folio_id.date_order or fields.Date.today(),
)
product_uom = self.env.context.get("uom") or product.uom_id.id
if uom and uom.id != product_uom:
# the unit price is in a different uom
uom_factor = uom._compute_price(1.0, product.uom_id)
else:
uom_factor = 1.0
return product[field_name] * uom_factor * cur_factor, currency_id
# Businness Methods
def _service_day_qty(self):
self.ensure_one()
@@ -578,3 +462,38 @@ class PmsService(models.Model):
if self.product_id.per_person:
qty = self.reservation_id.adults
return qty
def _get_price_unit_line(self, date=False):
self.ensure_one()
folio = self.folio_id
reservation = self.reservation_id
origin = reservation if reservation else folio
if origin:
partner = origin.partner_id
pricelist = origin.pricelist_id
board_room_type = False
product_context = dict(
self.env.context,
lang=partner.lang,
partner=partner.id,
quantity=self.product_qty,
date=folio.date_order if folio else fields.Date.today(),
pricelist=pricelist.id,
board_service=board_room_type.id if board_room_type else False,
uom=self.product_id.uom_id.id,
fiscal_position=False,
property=self.pms_property_id.id,
)
if date:
product_context["date_overnight"] = date
if reservation and self.is_board_service:
product_context["board_service"] = reservation.board_service_room_id.id
product = self.product_id.with_context(product_context)
return self.env["account.tax"]._fix_tax_included_price_company(
self._get_display_price(product),
product.taxes_id,
self.tax_ids,
origin.company_id,
)
else:
return 0

View File

@@ -29,35 +29,144 @@ class PmsServiceLine(models.Model):
)
date = fields.Date("Date")
day_qty = fields.Integer("Units")
price_total = fields.Float(
"Price Total", compute="_compute_price_total", store=True
)
price_unit = fields.Float(
"Unit Price", related="service_id.price_unit", readonly=True, store=True
"Unit Price",
digits=("Product Price"),
)
price_day_subtotal = fields.Monetary(
string="Subtotal",
readonly=True,
store=True,
compute="_compute_day_amount_service",
)
price_day_total = fields.Monetary(
string="Total", readonly=True, store=True, compute="_compute_day_amount_service"
)
price_day_tax = fields.Float(
string="Taxes Amount",
readonly=True,
store=True,
compute="_compute_day_amount_service",
)
currency_id = fields.Many2one(
related="service_id.currency_id", store=True, string="Currency", readonly=True
)
room_id = fields.Many2one(
string="Room", related="service_id.reservation_id", readonly=True, store=True
)
discount = fields.Float(
"Discount", related="service_id.discount", readonly=True, store=True
string="Discount (%)",
digits=("Discount"),
default=0.0,
compute="_compute_discount",
readonly=False,
store=True,
)
cancel_discount = fields.Float(
"Cancelation Discount", compute="_compute_cancel_discount"
)
# Compute and Search methods
@api.depends("day_qty", "service_id.price_total")
def _compute_price_total(self):
@api.depends("day_qty", "discount", "price_unit", "tax_ids")
def _compute_day_amount_service(self):
for line in self:
amount_service = line.price_unit
if amount_service > 0:
currency = line.service_id.currency_id
product = line.product_id
price = amount_service * (1 - (line.discount or 0.0) * 0.01)
# REVIEW: line.day_qty is not the total qty (the total is on service_id)
taxes = line.tax_ids.compute_all(
price, currency, line.day_qty, product=product
)
line.update(
{
"price_day_tax": sum(
t.get("amount", 0.0) for t in taxes.get("taxes", [])
),
"price_day_total": taxes["total_included"],
"price_day_subtotal": taxes["total_excluded"],
}
)
else:
line.update(
{
"price_day_tax": 0,
"price_day_total": 0,
"price_day_subtotal": 0,
}
)
@api.depends("service_id.reservation_id", "service_id.reservation_id.discount")
def _compute_discount(self):
"""
Used to reports
On board service the line discount is always
equal to reservation line discount
"""
for record in self:
if record.service_id.product_qty != 0:
record.price_total = (
record.service_id.price_total * record.day_qty
) / record.service_id.product_qty
else:
record.price_total = 0
if record.is_board_service:
record.discount = (
record.service_id.reservation_id.reservation_line_ids.filtered(
lambda l: l.date == record.date
).discount
)
elif not record.discount:
record.discount = 0
# TODO: Refact method and allowed cancelled single days
@api.depends("service_id.reservation_id.cancelled_reason")
def _compute_cancel_discount(self):
for line in self:
line.cancel_discount = 0
# TODO: Review cancel logic
# reservation = line.reservation_id.reservation_id
# pricelist = reservation.pricelist_id
# if reservation.state == "cancelled":
# if (
# reservation.cancelled_reason
# and pricelist
# and pricelist.cancelation_rule_id
# ):
# date_start_dt = fields.Date.from_string(
# reservation.checkin
# )
# date_end_dt = fields.Date.from_string(
# reservation.checkout
# )
# days = abs((date_end_dt - date_start_dt).days)
# rule = pricelist.cancelation_rule_id
# if reservation.cancelled_reason == "late":
# discount = 100 - rule.penalty_late
# if rule.apply_on_late == "first":
# days = 1
# elif rule.apply_on_late == "days":
# days = rule.days_late
# elif reservation.cancelled_reason == "noshow":
# discount = 100 - rule.penalty_noshow
# if rule.apply_on_noshow == "first":
# days = 1
# elif rule.apply_on_noshow == "days":
# days = rule.days_late - 1
# elif reservation.cancelled_reason == "intime":
# discount = 100
# checkin = reservation.checkin
# dates = []
# for i in range(0, days):
# dates.append(
# (
# fields.Date.from_string(checkin) + timedelta(days=i)
# ).strftime(DEFAULT_SERVER_DATE_FORMAT)
# )
# reservation.reservation_line_ids.filtered(
# lambda r: r.date in dates
# ).update({"cancel_discount": discount})
# reservation.reservation_line_ids.filtered(
# lambda r: r.date not in dates
# ).update({"cancel_discount": 100})
# else:
# reservation.reservation_line_ids.update({"cancel_discount": 0})
# else:
# reservation.reservation_line_ids.update({"cancel_discount": 0})
# Constraints and onchanges
@api.constrains("day_qty")

View File

@@ -80,15 +80,16 @@ class ProductPricelist(models.Model):
def _compute_price_rule_get_items(
self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
):
if (
"property" in self._context
and self._context["property"]
and "date_overnight" in self._context
and self._context.get("date_overnight")
):
self.env["product.pricelist.item"].flush(
["price", "currency_id", "company_id"]
)
# board_service_id = self._context.get("board_service")
# on_board_service_bool = True if board_service_id else False
# self.env["product.pricelist.item"].flush(
# ["price", "currency_id", "company_id"]
# )
self.env.cr.execute(
"""
SELECT item.id
@@ -99,6 +100,8 @@ class ProductPricelist(models.Model):
ON item.pricelist_id = cab.product_pricelist_id
LEFT JOIN pms_property_product_pricelist_item_rel lin
ON item.id = lin.product_pricelist_item_id
LEFT JOIN board_service_pricelist_item_rel board
ON item.id = board.pricelist_item_id
WHERE (lin.pms_property_id = %s OR lin.pms_property_id IS NULL)
AND (cab.pms_property_id = %s OR cab.pms_property_id IS NULL)
AND (item.product_tmpl_id IS NULL
@@ -132,6 +135,8 @@ class ProductPricelist(models.Model):
prod_tmpl_ids,
prod_ids,
categ_ids,
# on_board_service_bool,
# board_service_id,
self.id,
date,
date,

View File

@@ -18,6 +18,17 @@ class ProductPricelistItem(models.Model):
string="End Date Overnight",
help="End date to apply daily pricelist items",
)
on_board_service = fields.Boolean("Those included in Board Services")
board_service_room_type_ids = fields.Many2many(
"pms.board.service.room.type",
"board_service_pricelist_item_rel",
"pricelist_item_id",
"board_service_id",
string="Board Services on Room Types",
ondelete="cascade", # check_company=True,
help="""Specify a Board services on Room Types.""",
# domain="[('pms_property_ids', 'in', [allowed_property_ids, False])]",
)
allowed_property_ids = fields.Many2many(
"pms.property",

View File

@@ -1,9 +1,16 @@
from odoo import api, models
from odoo import api, fields, models
class ProductProduct(models.Model):
_inherit = "product.product"
board_price = fields.Float(
"Board Service Price",
digits="Product Price",
compute="_compute_board_price",
help="Get price price on board service",
)
@api.depends_context(
"pricelist",
"partner",
@@ -15,3 +22,30 @@ class ProductProduct(models.Model):
)
def _compute_product_price(self):
super(ProductProduct, self)._compute_product_price()
def _compute_board_price(self):
for record in self:
if self._context.get("board_service"):
record.board_price = (
self.env["pms.board.service.room.type.line"]
.search(
[
(
"pms_board_service_room_type_id",
"=",
self._context.get("board_service"),
),
("product_id", "=", record.id),
]
)
.amount
)
else:
record.board_price = False
def price_compute(self, price_type, uom=False, currency=False, company=None):
if self._context.get("board_service"):
price_type = "board_price"
return super(ProductProduct, self).price_compute(
price_type, uom, currency, company
)

View File

@@ -133,7 +133,7 @@
<t t-if="service.is_board_service">
<t
t-set="price"
t-value="service.product_qty/line.product_uom_qty*service.price_unit*(1-(service.discount or 0.0)*0.01) + price"
t-value="service.product_qty/line.price_total*(1-(service.reservation_id.discount or 0.0)*0.01) + price"
/>
</t>
</t>

View File

@@ -57,8 +57,6 @@ user_access_pms_folio_availability_wizard,user_access_pms_folio_availability_wiz
user_access_pms_num_rooms_selection,user_access_pms_num_rooms_selection,model_pms_num_rooms_selection,pms.group_pms_user,1,1,1,1
user_access_pms_folio_sale_line,user_access_pms_folio_sale_line,model_folio_sale_line,pms.group_pms_user,1,0,0,0
user_access_folio_make_invoice_advance,user_access_folio_make_invoice_advance,model_folio_advance_payment_inv,pms.group_pms_user,1,1,1,1
user_access_pms_invoice_filter_days,user_access_pms_invoice_filter_days,model_pms_invoice_filter_days,pms.group_pms_user,1,1,1,1
user_access_pms_invoice_filter_days_items,user_access_pms_invoice_filter_days_items,model_pms_invoice_filter_days_items,pms.group_pms_user,1,1,1,1
user_access_wizard_payment_folio,user_access_wizard_payment_folio,model_wizard_payment_folio,pms.group_pms_user,1,1,1,1
user_access_wizard_folio_changes,user_access_wizard_folio_changes,model_wizard_folio_changes,pms.group_pms_user,1,1,1,1
user_access_pms_folio_portal,user_access_pms_folio_portal,model_pms_folio,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
57 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
58 user_access_pms_folio_sale_line user_access_pms_folio_sale_line model_folio_sale_line pms.group_pms_user 1 0 0 0
59 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
60 user_access_wizard_payment_folio user_access_wizard_payment_folio model_wizard_payment_folio pms.group_pms_user 1 1 1 1
61 user_access_wizard_folio_changes user_access_wizard_folio_changes model_wizard_folio_changes pms.group_pms_user 1 1 1 1
62 user_access_pms_folio_portal user_access_pms_folio_portal model_pms_folio base.group_portal 1 0 0 0

View File

@@ -1,5 +1,3 @@
from odoo.exceptions import ValidationError
from .common import TestHotel
@@ -39,32 +37,3 @@ class TestPmsBoardServiceRoomType(TestHotel):
"class_id": self.room_type_class.id,
}
)
def test_room_type_property_integrity(self):
self._create_common_scenario()
self.room_type.pms_property_ids = [self.property1.id]
with self.assertRaises(ValidationError):
self.board_service_room_type = self.env[
"pms.board.service.room.type"
].create(
{
"pms_board_service_id": self.board_service.id,
"pms_room_type_id": self.room_type.id,
"pms_property_ids": self.property2,
}
)
def test_pricelist_property_integrity(self):
self._create_common_scenario()
self.pricelist = self.env["product.pricelist"].create(
{"name": "pricelist_1", "pms_property_ids": [self.property1.id]}
)
with self.assertRaises(ValidationError):
self.env["pms.board.service.room.type"].create(
{
"pms_board_service_id": self.board_service.id,
"pms_room_type_id": self.room_type.id,
"pricelist_id": self.pricelist.id,
"pms_property_ids": self.property2,
}
)

View File

@@ -726,51 +726,52 @@ class TestRoomTypeCodePropertyUniqueness(TestRoomType):
)
r.pms_property_ids = [(4, self.p1.id)]
def test_check_board_service_property_integrity(self):
self.company1 = self.env["res.company"].create(
{
"name": "Pms_Company_Test",
}
)
self.property1 = self.env["pms.property"].create(
{
"name": "Pms_property_test1",
"company_id": self.company1.id,
"default_pricelist_id": self.env.ref("product.list0").id,
}
)
self.property2 = self.env["pms.property"].create(
{
"name": "Pms_property_test2",
"company_id": self.company1.id,
"default_pricelist_id": self.env.ref("product.list0").id,
}
)
self.room_type_class = self.env["pms.room.type.class"].create(
{"name": "Room Type Class", "code_class": "SIN1"}
)
self.room_type = self.env["pms.room.type"].create(
{
"name": "Room Type",
"code_type": "Type1",
"pms_property_ids": self.property2,
"class_id": self.room_type_class.id,
}
)
self.board_service = self.env["pms.board.service"].create(
{
"name": "Board Service",
}
)
with self.assertRaises(ValidationError):
self.env["pms.board.service.room.type"].create(
{
"pms_board_service_id": self.board_service.id,
"pms_room_type_id": self.room_type.id,
"pricelist_id": self.env.ref("product.list0").id,
"pms_property_ids": self.property2,
}
)
# TODO: pending multi property PR
# def test_check_board_service_property_integrity(self):
# self.company1 = self.env["res.company"].create(
# {
# "name": "Pms_Company_Test",
# }
# )
# self.property1 = self.env["pms.property"].create(
# {
# "name": "Pms_property_test1",
# "company_id": self.company1.id,
# "default_pricelist_id": self.env.ref("product.list0").id,
# }
# )
# self.property2 = self.env["pms.property"].create(
# {
# "name": "Pms_property_test2",
# "company_id": self.company1.id,
# "default_pricelist_id": self.env.ref("product.list0").id,
# }
# )
# self.room_type_class = self.env["pms.room.type.class"].create(
# {"name": "Room Type Class", "code_class": "SIN1"}
# )
# self.room_type = self.env["pms.room.type"].create(
# {
# "name": "Room Type",
# "code_type": "Type1",
# "pms_property_ids": self.property2,
# "class_id": self.room_type_class.id,
# }
# )
# self.board_service = self.env["pms.board.service"].create(
# {
# "name": "Board Service",
# "pms_property_ids": self.property1,
# }
# )
# with self.assertRaises(ValidationError):
# self.env["pms.board.service.room.type"].create(
# {
# "pms_board_service_id": self.board_service.id,
# "pms_room_type_id": self.room_type.id,
# "pms_property_ids": self.property2,
# }
# )
def test_check_amenities_property_integrity(self):
self.company1 = self.env["res.company"].create(

View File

@@ -6,37 +6,8 @@
<field name="arch" type="xml">
<xpath expr="//field[@name='invoice_date']" position="after">
<field name="folio_ids" widget="many2many_tags" />
<field name="from_reservation" invisible="1" />
<field name="pms_property_id" invisible="1" />
</xpath>
<xpath
expr="//field[@name='invoice_line_ids']/tree/field[@name='name']"
position="after"
>
<button
name="invoice_filter_days"
type="object"
icon="fa-calendar"
string="Filter-days"
aria-label="Change Period"
class="float-right"
attrs="{
'column_invisible': ['|',('parent.from_reservation', '=', False),('parent.state', '!=', 'draft')],
'invisible': [('reservation_line_ids', '=', [])]
}"
/>
<field name="reservation_line_ids" invisible="1" />
</xpath>
<!-- <xpath
expr="//field[@name='invoice_line_ids']/tree/field[@name='quantity']"
position="attributes"
>
<attribute name="attrs">
{
'readonly': [('reservation_line_ids', '!=', False)],
}
</attribute>
</xpath> -->
</field>
</record>

View File

@@ -303,7 +303,7 @@
<t t-if="service.is_board_service">
<t
t-set="price"
t-value="service.product_qty/line.product_uom_qty*service.price_unit*(1-(service.discount or 0.0)*0.01) + price"
t-value="service.product_qty/line.price_total*(1-(service.reservation_id.discount or 0.0)*0.01) + price"
/>
</t>
</t>

View File

@@ -190,6 +190,7 @@
attrs="{'invisible':[('state','not in',('cancel'))]}"
/>
<field name="internal_comment" />
<field name="user_id" />
</group>
<group
colspan="2"
@@ -286,8 +287,16 @@
<field name="sequence" widget="handle" />
<!-- We do not display the type because we don't want the user to be bothered with that information if he has no section or note. -->
<field name="display_type" invisible="1" />
<field name="is_board_service" invisible="1" />
<!-- <field name="product_updatable" invisible="1"/> -->
<button
title="Board Service"
class="oe_stat_button"
icon="fa-1x fa-bed"
name="open_service_ids"
attrs="{'invisible':[('is_board_service','=', False)]}"
/>
<field
name="product_id"
options="{'no_open': True}"
@@ -348,7 +357,6 @@
<field
name="discount"
string="Disc.%"
groups="product.group_discount_per_so_line"
optional="show"
widget="product_discount"
/>
@@ -419,7 +427,7 @@
/>
</group>
</page>
<page string="Other data" invisible="1">
<page string="Other data">
<group>
<field name="user_id" />
<field name="client_order_ref" />

View File

@@ -48,7 +48,7 @@
class="alert alert-info"
role="alert"
style="margin-bottom:0px;"
attrs="{'invisible': ['|',('shared_folio','=',False),('splitted', '=', True)]}"
attrs="{'invisible': [('shared_folio','=',False)]}"
>
This reservation has other reservantions and/or services in the
folio, you can check it in the
@@ -61,6 +61,24 @@
/>
</bold>
</div>
<div
class="alert alert-warning"
role="alert"
style="margin-bottom:0px;"
attrs="{'invisible': ['|', ('show_update_pricelist', '=', False), ('state', 'in', ['done'])]}"
>
Recompute all prices based on this pricelist
<bold>
<button
name="update_prices"
type="object"
string=" Update Prices"
class="btn-link mb-1 px-0"
icon="fa-refresh"
confirm="This will update all unit prices based on the currently set pricelist."
/>
</bold>
</div>
<div
class="alert alert-warning"
role="alert"
@@ -197,7 +215,6 @@
nolabel="1"
context="{'checkin': checkin, 'checkout': checkout, 'pms_property_id':pms_property_id, 'pricelist_id':pricelist_id}"
options="{'no_create': True,'no_open': True}"
attrs="{'readonly':[('state','not in',('draft'))]}"
style="margin-right: 30px;"
/>
<field
@@ -302,16 +319,6 @@
name="pricelist_id"
options="{'no_open':True,'no_create': True}"
/>
<button
name="update_prices"
type="object"
string=" Update Prices"
help="Recompute all prices based on this pricelist"
class="btn-link mb-1 px-0"
icon="fa-refresh"
confirm="This will update all unit prices based on the currently set pricelist."
attrs="{'invisible': ['|', ('show_update_pricelist', '=', False), ('state', 'in', ['done'])]}"
/>
<!--<field
name="agency_id"
options="{'no_create': True,'no_open': True}"
@@ -332,16 +339,12 @@
name="children_occupying"
attrs="{'invisible': [('children', '=', 0)]}"
/>
<field name="qty_invoiced" invisible="1" />
<field name="qty_to_invoice" invisible="1" />
<field name="allowed_room_ids" invisible="1" />
<field name="folio_payment_state" invisible="1" />
<!-- TODO: How to filter to avoid show False (generic) pricelist board when exist a specific pricelist board¿? -->
<field
name="board_service_room_id"
domain="[
('pms_room_type_id', '=', room_type_id),
('pricelist_id', 'in', [pricelist_id, False])]"
domain="[('pms_room_type_id', '=', room_type_id)]"
options="{'no_create': True,'no_open': True}"
attrs="{'invisible': [('reservation_type','in',('out'))]}"
/>
@@ -445,7 +448,6 @@
<field name="date" readonly="1" force_save="1" />
<field name="price" />
<field name="discount" />
<field name="move_line_ids" invisible="1" />
<field
name="cancel_discount"
attrs="{'column_invisible': [('parent.state','!=','cancelled')]}"
@@ -503,8 +505,6 @@
name="open_service_ids"
attrs="{'invisible': [('per_day','=',False)]}"
/>
<field name="price_unit" />
<field name="discount" />
<field name="tax_ids" widget="many2many_tags" />
<field name="price_subtotal" />
<field name="price_tax" />
@@ -513,6 +513,16 @@
<tree string="Days">
<field name="date" />
<field name="day_qty" />
<field
name="discount"
attrs="{'readonly':[('is_board_service','=', True)]}"
/>
<field name="price_unit" />
<field name="price_day_total" />
<field
name="is_board_service"
invisible="1"
/>
</tree>
</field>
</tree>
@@ -674,6 +684,7 @@
widget="badge"
optional="show"
/>
<field name="invoice_status" optional="show" />
<field
name="company_id"
groups="base.group_multi_company"

View File

@@ -64,9 +64,7 @@
<field name="by_default" />
<field name="pms_room_type_id" invisible="1" />
<field name="pms_board_service_id" />
<field name="price_type" />
<field name="amount" />
<field name="pricelist_id" />
<button
type="object"
class="oe_stat_button"

View File

@@ -10,7 +10,13 @@
<field name="day_qty" />
<field name="date" />
<field name="price_unit" />
<field
name="discount"
attrs="{'readonly':[('is_board_service','=', True)]}"
/>
<field name="price_day_total" />
<field name="pms_property_id" invisible="1" />
<field name="is_board_service" invisible="1" />
</group>
</form>
</field>
@@ -23,7 +29,14 @@
<field name="product_id" />
<field name="day_qty" />
<field name="date" />
<field name="price_unit" />
<field
name="discount"
attrs="{'readonly':[('is_board_service','=', True)]}"
/>
<field name="price_day_total" />
<field name="pms_property_id" invisible="1" />
<field name="is_board_service" invisible="1" />
</tree>
</field>
</record>
@@ -56,7 +69,7 @@
attrs="{'invisible':[('is_board_service','=', True)]}"
/>
<field
name="price_total"
name="price_day_total"
optional="show"
attrs="{'invisible':[('is_board_service','=', True)]}"
/>

View File

@@ -28,8 +28,6 @@
force_save="1"
/>
</group>
<field name="price_unit" invisible="1" />
<field name="discount" invisible="1" />
<field name="tax_ids" widget="many2many_tags" invisible="1" />
<field name="price_subtotal" invisible="1" />
<field name="price_tax" invisible="1" />
@@ -38,6 +36,13 @@
<tree string="Days" editable="bottom">
<field name="date" />
<field name="day_qty" />
<field name="price_unit" />
<field
name="discount"
attrs="{'readonly':[('is_board_service','=', True)]}"
/>
<field name="price_day_total" />
<field name="is_board_service" invisible="1" />
</tree>
</field>
</sheet>
@@ -87,8 +92,6 @@
name="open_service_ids"
attrs="{'invisible': [('per_day','=',False)]}"
/>
<field name="price_unit" />
<field name="discount" />
<field name="tax_ids" widget="many2many_tags" />
<field name="price_subtotal" />
<field name="price_tax" />
@@ -97,6 +100,12 @@
<tree string="Days">
<field name="date" />
<field name="day_qty" />
<field
name="discount"
attrs="{'readonly':[('is_board_service','=', True)]}"
/>
<field name="price_unit" />
<field name="is_board_service" invisible="1" />
</tree>
</field>
</tree>

View File

@@ -4,6 +4,16 @@
<field name="model">product.pricelist.item</field>
<field name="inherit_id" ref="product.product_pricelist_item_form_view" />
<field name="arch" type="xml">
<xpath expr="//field[@name='applied_on']" position="before">
<field name="on_board_service" />
<field name="allowed_property_ids" invisible="1" />
<field
name="board_service_room_type_ids"
widget="many2many_tags"
options="{'no_create': True,'no_open': True}"
attrs="{'invisible': [('on_board_service','=',False)]}"
/>
</xpath>
<xpath expr="//field[@name='min_quantity']" position="before">
<field
name="pms_property_ids"

View File

@@ -200,9 +200,6 @@
<th name="th_qty">
Quantity
</th>
<th name="th_price">
Price Unit
</th>
<th name="th_price_total">
Price Total
</th>
@@ -215,10 +212,6 @@
<td><span
t-field="service.product_qty"
/></td>
<td><span
t-field="service.price_unit"
t-options='{"widget": "monetary", "display_currency": reservation.pricelist_id.currency_id}'
/></td>
<td><span
t-field="service.price_total"
t-options='{"widget": "monetary", "display_currency": reservation.pricelist_id.currency_id}'

View File

@@ -4,6 +4,5 @@ from . import wizard_advanced_filters
from . import wizard_folio
from . import wizard_folio_availability
from . import folio_make_invoice_advance
from . import wizard_invoice_filter_days
from . import wizard_payment_folio
from . import wizard_folio_changes

View File

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

View File

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