mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[DONE] 14.0 improve compute folio sale lines and name invoice lines on change qty (#78)
* [IMP] pms: improve compute sale lines & generate new names when qty is modified @ invoice lines * [ADD] pms: add tests for compute folio sale lines * [FIX] pms: fix old name for av. plan & common base test class * [FIX] pms: pre-commit fix * [FIX] pms: fix view according to pr code review
This commit is contained in:
@@ -1,13 +1,14 @@
|
|||||||
# 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 fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class AccountMoveLine(models.Model):
|
class AccountMoveLine(models.Model):
|
||||||
_inherit = "account.move.line"
|
_inherit = "account.move.line"
|
||||||
|
|
||||||
# Fields declaration
|
# Fields declaration
|
||||||
|
# TODO: REVIEW why not a Many2one?
|
||||||
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",
|
||||||
@@ -23,6 +24,50 @@ class AccountMoveLine(models.Model):
|
|||||||
"folio_id",
|
"folio_id",
|
||||||
string="Folios",
|
string="Folios",
|
||||||
)
|
)
|
||||||
|
name_changed_by_user = fields.Boolean(
|
||||||
|
default=False,
|
||||||
|
readonly=False,
|
||||||
|
store=True,
|
||||||
|
string="Custom label",
|
||||||
|
compute="_compute_name_changed_by_user",
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("name")
|
||||||
|
def _compute_name_changed_by_user(self):
|
||||||
|
for record in self:
|
||||||
|
# if not record._context.get("auto_name"):
|
||||||
|
if not self._context.get("auto_name"):
|
||||||
|
record.name_changed_by_user = True
|
||||||
|
else:
|
||||||
|
record.name_changed_by_user = False
|
||||||
|
|
||||||
|
name = fields.Char(
|
||||||
|
compute="_compute_name",
|
||||||
|
store=True,
|
||||||
|
readonly=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("quantity")
|
||||||
|
def _compute_name(self):
|
||||||
|
for record in self:
|
||||||
|
record.name = self.env["folio.sale.line"].generate_folio_sale_name(
|
||||||
|
record.folio_line_ids.reservation_id,
|
||||||
|
record.product_id,
|
||||||
|
record.folio_line_ids.service_id,
|
||||||
|
record.folio_line_ids.reservation_line_ids,
|
||||||
|
record.folio_line_ids.service_line_ids,
|
||||||
|
qty=record.quantity,
|
||||||
|
)
|
||||||
|
# TODO: check why this code doesn't work
|
||||||
|
# if not record.name_changed_by_user:
|
||||||
|
# record.with_context(auto_name=True).name = self
|
||||||
|
# .env["folio.sale.line"].generate_folio_sale_name(
|
||||||
|
# record.folio_line_ids.service_id,
|
||||||
|
# record.folio_line_ids.reservation_line_ids,
|
||||||
|
# record.product_id,
|
||||||
|
# qty=record.quantity)
|
||||||
|
# record.with_context(auto_name=True)
|
||||||
|
# ._compute_name_changed_by_user()
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Copyright 2020 Dario Lodeiros
|
# Copyright 2020 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 math import ceil
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.osv import expression
|
from odoo.osv import expression
|
||||||
from odoo.tools import float_compare, float_is_zero
|
from odoo.tools import float_compare, float_is_zero
|
||||||
@@ -9,7 +11,8 @@ from odoo.tools import float_compare, float_is_zero
|
|||||||
class FolioSaleLine(models.Model):
|
class FolioSaleLine(models.Model):
|
||||||
_name = "folio.sale.line"
|
_name = "folio.sale.line"
|
||||||
_description = "Folio Sale Line"
|
_description = "Folio Sale Line"
|
||||||
_order = "folio_id, sequence, id"
|
_order = "folio_id, sequence, reservation_order desc, service_order, date_order"
|
||||||
|
|
||||||
_check_company_auto = True
|
_check_company_auto = True
|
||||||
|
|
||||||
@api.depends("state", "product_uom_qty", "qty_to_invoice", "qty_invoiced")
|
@api.depends("state", "product_uom_qty", "qty_to_invoice", "qty_invoiced")
|
||||||
@@ -41,26 +44,34 @@ class FolioSaleLine(models.Model):
|
|||||||
else:
|
else:
|
||||||
line.invoice_status = "no"
|
line.invoice_status = "no"
|
||||||
|
|
||||||
@api.depends("reservation_line_ids", "service_id")
|
@api.depends("reservation_line_ids", "service_line_ids", "service_id")
|
||||||
def _compute_name(self):
|
def _compute_name(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
if not record.name_updated:
|
record.name = self.generate_folio_sale_name(
|
||||||
record.name = record._get_compute_name()
|
record.reservation_id,
|
||||||
|
record.product_id,
|
||||||
|
record.service_id,
|
||||||
|
record.reservation_line_ids,
|
||||||
|
record.service_line_ids,
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends("name")
|
@api.model
|
||||||
def _compute_name_updated(self):
|
def generate_folio_sale_name(
|
||||||
self.name_updated = False
|
self,
|
||||||
for record in self.filtered("name"):
|
reservation_id,
|
||||||
if record.name != record._get_compute_name():
|
product_id,
|
||||||
record.name_updated = True
|
service_id,
|
||||||
|
reservation_line_ids,
|
||||||
def _get_compute_name(self):
|
service_line_ids,
|
||||||
self.ensure_one()
|
qty=False,
|
||||||
if self.reservation_line_ids:
|
):
|
||||||
|
if reservation_line_ids:
|
||||||
month = False
|
month = False
|
||||||
name = False
|
name = False
|
||||||
lines = self.reservation_line_ids.sorted("date")
|
lines = reservation_line_ids.sorted(key="date")
|
||||||
for date in lines.mapped("date"):
|
for index, date in enumerate(lines.mapped("date")):
|
||||||
|
if qty and index > (qty - 1):
|
||||||
|
break
|
||||||
if date.month != month:
|
if date.month != month:
|
||||||
name = name + "\n" if name else ""
|
name = name + "\n" if name else ""
|
||||||
name += date.strftime("%B-%Y") + ": "
|
name += date.strftime("%B-%Y") + ": "
|
||||||
@@ -68,11 +79,28 @@ class FolioSaleLine(models.Model):
|
|||||||
month = date.month
|
month = date.month
|
||||||
else:
|
else:
|
||||||
name += ", " + date.strftime("%d")
|
name += ", " + date.strftime("%d")
|
||||||
return name
|
|
||||||
elif self.service_id:
|
return "{} ({}).".format(product_id.name, name)
|
||||||
return self.service_id.name
|
elif service_line_ids:
|
||||||
|
month = False
|
||||||
|
name = False
|
||||||
|
lines = service_line_ids.filtered(
|
||||||
|
lambda x: x.service_id == service_id
|
||||||
|
).sorted(key="date")
|
||||||
|
|
||||||
|
for index, date in enumerate(lines.mapped("date")):
|
||||||
|
if qty and index > (ceil(qty / reservation_id.adults) - 1):
|
||||||
|
break
|
||||||
|
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 "{} ({}).".format(service_id.name, name)
|
||||||
else:
|
else:
|
||||||
return False
|
return service_id.name
|
||||||
|
|
||||||
@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):
|
||||||
@@ -133,7 +161,7 @@ class FolioSaleLine(models.Model):
|
|||||||
@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):
|
||||||
for record in self:
|
for record in self:
|
||||||
if record.reservation_id:
|
if record.reservation_id and not record.service_id:
|
||||||
record.product_id = record.reservation_id.room_type_id.product_id
|
record.product_id = record.reservation_id.room_type_id.product_id
|
||||||
elif record.service_id:
|
elif record.service_id:
|
||||||
record.product_id = record.service_id.product_id
|
record.product_id = record.service_id.product_id
|
||||||
@@ -367,7 +395,6 @@ class FolioSaleLine(models.Model):
|
|||||||
name = fields.Text(
|
name = fields.Text(
|
||||||
string="Description", compute="_compute_name", store=True, readonly=False
|
string="Description", compute="_compute_name", store=True, readonly=False
|
||||||
)
|
)
|
||||||
name_updated = fields.Boolean(compute="_compute_name_updated", store=True)
|
|
||||||
reservation_line_ids = fields.Many2many(
|
reservation_line_ids = fields.Many2many(
|
||||||
"pms.reservation.line",
|
"pms.reservation.line",
|
||||||
string="Nights",
|
string="Nights",
|
||||||
@@ -562,6 +589,68 @@ class FolioSaleLine(models.Model):
|
|||||||
help="Technical field for UX purpose.",
|
help="Technical field for UX purpose.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
service_order = fields.Integer(
|
||||||
|
string="Service id",
|
||||||
|
compute="_compute_service_order",
|
||||||
|
help="Field to order by service id",
|
||||||
|
store=True,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
reservation_order = fields.Integer(
|
||||||
|
string="Reservation id",
|
||||||
|
compute="_compute_reservation_order",
|
||||||
|
help="Field to order by reservation id",
|
||||||
|
store=True,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
date_order = fields.Date(
|
||||||
|
string="Date",
|
||||||
|
compute="_compute_date_order",
|
||||||
|
help="Field to order by service date",
|
||||||
|
store=True,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("qty_to_invoice")
|
||||||
|
def _compute_service_order(self):
|
||||||
|
for record in self:
|
||||||
|
record.service_order = (
|
||||||
|
record.service_id
|
||||||
|
if record.service_id
|
||||||
|
else -1
|
||||||
|
if record.display_type
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends("service_order")
|
||||||
|
def _compute_date_order(self):
|
||||||
|
for record in self:
|
||||||
|
if record.display_type:
|
||||||
|
record.date_order = 0
|
||||||
|
elif record.reservation_id and not record.service_id:
|
||||||
|
record.date_order = (
|
||||||
|
min(record.reservation_line_ids.mapped("date"))
|
||||||
|
if record.reservation_line_ids
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
elif record.reservation_id and record.service_id:
|
||||||
|
record.date_order = (
|
||||||
|
min(record.service_line_ids.mapped("date"))
|
||||||
|
if record.service_line_ids
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
record.date_order = 0
|
||||||
|
|
||||||
|
@api.depends("date_order")
|
||||||
|
def _compute_reservation_order(self):
|
||||||
|
for record in self:
|
||||||
|
record.reservation_order = (
|
||||||
|
record.reservation_id if record.reservation_id else 0
|
||||||
|
)
|
||||||
|
|
||||||
@api.depends("reservation_line_ids", "service_line_ids", "service_line_ids.day_qty")
|
@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:
|
||||||
|
|||||||
@@ -123,8 +123,6 @@ class PmsFolio(models.Model):
|
|||||||
readonly=True,
|
readonly=True,
|
||||||
required=True,
|
required=True,
|
||||||
ondelete="restrict",
|
ondelete="restrict",
|
||||||
# comodel_name="res.currency",
|
|
||||||
# compute="_compute_currency_id"
|
|
||||||
)
|
)
|
||||||
pricelist_id = fields.Many2one(
|
pricelist_id = fields.Many2one(
|
||||||
"product.pricelist",
|
"product.pricelist",
|
||||||
@@ -259,7 +257,6 @@ class PmsFolio(models.Model):
|
|||||||
client_order_ref = fields.Char(string="Customer Reference", copy=False)
|
client_order_ref = fields.Char(string="Customer Reference", copy=False)
|
||||||
reservation_type = fields.Selection(
|
reservation_type = fields.Selection(
|
||||||
[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")],
|
[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")],
|
||||||
required=True,
|
|
||||||
string="Type",
|
string="Type",
|
||||||
default=lambda *a: "normal",
|
default=lambda *a: "normal",
|
||||||
)
|
)
|
||||||
@@ -397,87 +394,139 @@ class PmsFolio(models.Model):
|
|||||||
"service_ids.service_line_ids.price_day_total",
|
"service_ids.service_line_ids.price_day_total",
|
||||||
"service_ids.service_line_ids.discount",
|
"service_ids.service_line_ids.discount",
|
||||||
"service_ids.service_line_ids.cancel_discount",
|
"service_ids.service_line_ids.cancel_discount",
|
||||||
|
"service_ids.service_line_ids.day_qty",
|
||||||
|
"service_ids.service_line_ids.tax_ids",
|
||||||
"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",
|
||||||
"reservation_ids.reservation_line_ids.cancel_discount",
|
"reservation_ids.reservation_line_ids.cancel_discount",
|
||||||
|
"reservation_ids.tax_ids",
|
||||||
)
|
)
|
||||||
def _compute_sale_line_ids(self):
|
def _compute_sale_line_ids(self):
|
||||||
for folio in self:
|
for folio in self:
|
||||||
sale_lines = [(5, 0, 0)]
|
for reservation in folio.reservation_ids:
|
||||||
reservations = folio.reservation_ids
|
# RESERVATION LINES
|
||||||
services_without_room = folio.service_ids.filtered(
|
# res = self.env['pms.reservation'].browse(reservation.id)
|
||||||
lambda s: not s.reservation_id
|
self.generate_reservation_lines_sale_lines(folio, reservation)
|
||||||
)
|
|
||||||
# TODO: Not delete old sale line ids
|
# RESERVATION SERVICES
|
||||||
for reservation in reservations:
|
self.generate_reservation_services_sale_lines(folio, reservation)
|
||||||
sale_lines.append(
|
|
||||||
(
|
# FOLIO SERVICES
|
||||||
0,
|
self.generate_folio_services_sale_lines(folio)
|
||||||
False,
|
|
||||||
{
|
@api.model
|
||||||
"display_type": "line_section",
|
def generate_reservation_lines_sale_lines(self, folio, reservation):
|
||||||
"name": reservation.name,
|
if not reservation.sale_line_ids.filtered(lambda x: x.name == reservation.name):
|
||||||
},
|
reservation.sale_line_ids = [
|
||||||
)
|
(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
"name": reservation.name,
|
||||||
|
"display_type": "line_section",
|
||||||
|
"folio_id": folio.id,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
group_reservation_lines = {}
|
]
|
||||||
for line in reservation.reservation_line_ids:
|
expected_reservation_lines = self.env["pms.reservation.line"].read_group(
|
||||||
# On resevations the price, and discounts fields are used
|
[
|
||||||
# by group, we need pass this in the create line
|
("reservation_id", "=", reservation.id),
|
||||||
group_key = (
|
("cancel_discount", "<", 100),
|
||||||
reservation.id,
|
],
|
||||||
line.price,
|
["price", "discount", "cancel_discount"],
|
||||||
line.discount,
|
["price", "discount", "cancel_discount"],
|
||||||
line.cancel_discount,
|
lazy=False,
|
||||||
)
|
)
|
||||||
if line.cancel_discount == 100:
|
current_sale_line_ids = reservation.sale_line_ids.filtered(
|
||||||
continue
|
lambda x: x.reservation_id.id == reservation.id
|
||||||
discount_factor = 1.0
|
and not x.display_type
|
||||||
for discount in [line.discount, line.cancel_discount]:
|
and not x.service_id
|
||||||
discount_factor = discount_factor * ((100.0 - discount) / 100.0)
|
)
|
||||||
final_discount = 100.0 - (discount_factor * 100.0)
|
|
||||||
if group_key not in group_reservation_lines:
|
for index, item in enumerate(expected_reservation_lines):
|
||||||
group_reservation_lines[group_key] = {
|
lines_to = self.env["pms.reservation.line"].search(item["__domain"])
|
||||||
"reservation_id": reservation.id,
|
final_discount = self.concat_discounts(
|
||||||
"discount": final_discount,
|
item["discount"], item["cancel_discount"]
|
||||||
"price_unit": line.price,
|
)
|
||||||
"reservation_line_ids": [(4, line.id)],
|
|
||||||
}
|
if current_sale_line_ids and index <= (len(current_sale_line_ids) - 1):
|
||||||
else:
|
current_sale_line_ids[index].price_unit = item["price"]
|
||||||
group_reservation_lines[group_key][
|
current_sale_line_ids[index].discount = final_discount
|
||||||
("reservation_line_ids")
|
current_sale_line_ids[index].reservation_line_ids = lines_to.ids
|
||||||
].append((4, line.id))
|
else:
|
||||||
for item in group_reservation_lines.items():
|
new = {
|
||||||
sale_lines.append((0, False, item[1]))
|
"reservation_id": reservation.id,
|
||||||
for service in reservation.service_ids:
|
"price_unit": item["price"],
|
||||||
# Service days with different prices,
|
"discount": final_discount,
|
||||||
# go to differente sale lines
|
"folio_id": folio.id,
|
||||||
group_service_lines = {}
|
"reservation_line_ids": [(6, 0, lines_to.ids)],
|
||||||
for service_line in service.service_line_ids:
|
}
|
||||||
service_group_key = (
|
reservation.sale_line_ids = [(0, 0, new)]
|
||||||
service_line.price_unit,
|
if len(expected_reservation_lines) < len(current_sale_line_ids):
|
||||||
service_line.discount,
|
folio_sale_lines_to_remove = [
|
||||||
service_line.cancel_discount,
|
value.id
|
||||||
)
|
for index, value in enumerate(current_sale_line_ids)
|
||||||
if service_group_key not in group_service_lines:
|
if index > (len(expected_reservation_lines) - 1)
|
||||||
# On service the price, and discounts fields are
|
]
|
||||||
# compute in the sale.order.line
|
for fsl in folio_sale_lines_to_remove:
|
||||||
group_service_lines[service_group_key] = {
|
self.env["folio.sale.line"].browse(fsl).unlink()
|
||||||
"name": service.name,
|
|
||||||
"service_id": service.id,
|
@api.model
|
||||||
"discount": service_line.discount,
|
def generate_reservation_services_sale_lines(self, folio, reservation):
|
||||||
"price_unit": service_line.price_unit,
|
for service in reservation.service_ids:
|
||||||
"service_line_ids": [(4, service_line.id)],
|
expected_reservation_services = self.env["pms.service.line"].read_group(
|
||||||
}
|
[
|
||||||
else:
|
("reservation_id", "=", reservation.id),
|
||||||
group_service_lines[service_group_key][
|
("service_id", "=", service.id),
|
||||||
("service_line_ids")
|
("cancel_discount", "<", 100),
|
||||||
].append((4, service_line.id))
|
],
|
||||||
for item in group_service_lines.items():
|
["price_unit", "discount", "cancel_discount"],
|
||||||
sale_lines.append((0, False, item[1]))
|
["price_unit", "discount", "cancel_discount"],
|
||||||
if services_without_room:
|
lazy=False,
|
||||||
sale_lines.append(
|
)
|
||||||
|
current_sale_service_ids = reservation.sale_line_ids.filtered(
|
||||||
|
lambda x: x.reservation_id.id == reservation.id
|
||||||
|
and not x.display_type
|
||||||
|
and x.service_id.id == service.id
|
||||||
|
)
|
||||||
|
|
||||||
|
for index, item in enumerate(expected_reservation_services):
|
||||||
|
lines_to = self.env["pms.service.line"].search(item["__domain"])
|
||||||
|
final_discount = self.concat_discounts(
|
||||||
|
item["discount"], item["cancel_discount"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if current_sale_service_ids and index <= (
|
||||||
|
len(current_sale_service_ids) - 1
|
||||||
|
):
|
||||||
|
current_sale_service_ids[index].price_unit = item["price_unit"]
|
||||||
|
current_sale_service_ids[index].discount = final_discount
|
||||||
|
current_sale_service_ids[index].service_line_ids = lines_to.ids
|
||||||
|
else:
|
||||||
|
new = {
|
||||||
|
"service_id": service.id,
|
||||||
|
"price_unit": item["price_unit"],
|
||||||
|
"discount": final_discount,
|
||||||
|
"folio_id": folio.id,
|
||||||
|
"service_line_ids": [(6, 0, lines_to.ids)],
|
||||||
|
}
|
||||||
|
reservation.sale_line_ids = [(0, 0, new)]
|
||||||
|
if len(expected_reservation_services) < len(current_sale_service_ids):
|
||||||
|
folio_sale_lines_to_remove = [
|
||||||
|
value.id
|
||||||
|
for index, value in enumerate(current_sale_service_ids)
|
||||||
|
if index > (len(expected_reservation_services) - 1)
|
||||||
|
]
|
||||||
|
for fsl in folio_sale_lines_to_remove:
|
||||||
|
self.env["folio.sale.line"].browse(fsl).unlink()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def generate_folio_services_sale_lines(self, folio):
|
||||||
|
folio_services = folio.service_ids.filtered(lambda x: not x.reservation_id)
|
||||||
|
if folio_services:
|
||||||
|
if not folio.sale_line_ids.filtered(lambda x: x.name == _("Others")):
|
||||||
|
folio.sale_line_ids = [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
False,
|
False,
|
||||||
@@ -486,19 +535,65 @@ class PmsFolio(models.Model):
|
|||||||
"name": _("Others"),
|
"name": _("Others"),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
for folio_service in folio_services:
|
||||||
|
expected_folio_services = self.env["pms.service.line"].read_group(
|
||||||
|
[
|
||||||
|
("service_id.folio_id", "=", folio.id),
|
||||||
|
("service_id", "=", folio_service.id),
|
||||||
|
("reservation_id", "=", False),
|
||||||
|
("cancel_discount", "<", 100),
|
||||||
|
],
|
||||||
|
["price_unit", "discount", "cancel_discount"],
|
||||||
|
["price_unit", "discount", "cancel_discount"],
|
||||||
|
lazy=False,
|
||||||
)
|
)
|
||||||
for service in services_without_room:
|
current_folio_service_ids = folio.sale_line_ids.filtered(
|
||||||
sale_lines.append(
|
lambda x: x.service_id.folio_id.id == folio.id
|
||||||
(
|
and not x.display_type
|
||||||
0,
|
and not x.reservation_id
|
||||||
False,
|
and x.service_id.id == folio_service.id
|
||||||
{
|
)
|
||||||
"name": service.name,
|
|
||||||
"service_id": service.id,
|
for index, item in enumerate(expected_folio_services):
|
||||||
},
|
lines_to = self.env["pms.service.line"].search(item["__domain"])
|
||||||
)
|
final_discount = self.concat_discounts(
|
||||||
|
item["discount"], item["cancel_discount"]
|
||||||
)
|
)
|
||||||
folio.sale_line_ids = sale_lines
|
if current_folio_service_ids and index <= (
|
||||||
|
len(current_folio_service_ids) - 1
|
||||||
|
):
|
||||||
|
current_folio_service_ids[index].price_unit = item["price_unit"]
|
||||||
|
current_folio_service_ids[index].discount = final_discount
|
||||||
|
current_folio_service_ids[index].service_line_ids = lines_to.ids
|
||||||
|
else:
|
||||||
|
new = {
|
||||||
|
"service_id": folio_service.id,
|
||||||
|
"price_unit": item["price_unit"],
|
||||||
|
"discount": final_discount,
|
||||||
|
"folio_id": folio.id,
|
||||||
|
"service_line_ids": [(6, 0, lines_to.ids)],
|
||||||
|
}
|
||||||
|
folio.sale_line_ids = [(0, 0, new)]
|
||||||
|
if len(expected_folio_services) < len(current_folio_service_ids):
|
||||||
|
folio_sale_lines_to_remove = [
|
||||||
|
value.id
|
||||||
|
for index, value in enumerate(current_folio_service_ids)
|
||||||
|
if index > (len(expected_folio_services) - 1)
|
||||||
|
]
|
||||||
|
for fsl in folio_sale_lines_to_remove:
|
||||||
|
self.env["folio.sale.line"].browse(fsl).unlink()
|
||||||
|
else:
|
||||||
|
to_unlink = folio.sale_line_ids.filtered(lambda x: x.name == _("Others"))
|
||||||
|
to_unlink.unlink()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def concat_discounts(self, discount, cancel_discount):
|
||||||
|
discount_factor = 1.0
|
||||||
|
for discount in [discount, cancel_discount]:
|
||||||
|
discount_factor = discount_factor * ((100.0 - discount) / 100.0)
|
||||||
|
final_discount = 100.0 - (discount_factor * 100.0)
|
||||||
|
return final_discount
|
||||||
|
|
||||||
@api.depends("partner_id", "agency_id")
|
@api.depends("partner_id", "agency_id")
|
||||||
def _compute_pricelist_id(self):
|
def _compute_pricelist_id(self):
|
||||||
@@ -1168,7 +1263,7 @@ class PmsFolio(models.Model):
|
|||||||
moves = (
|
moves = (
|
||||||
self.env["account.move"]
|
self.env["account.move"]
|
||||||
.sudo()
|
.sudo()
|
||||||
.with_context(default_move_type="out_invoice")
|
.with_context(default_move_type="out_invoice", auto_name=True)
|
||||||
.create(invoice_vals_list)
|
.create(invoice_vals_list)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ class PmsServiceLine(models.Model):
|
|||||||
store=True,
|
store=True,
|
||||||
related="service_id.currency_id",
|
related="service_id.currency_id",
|
||||||
)
|
)
|
||||||
room_id = fields.Many2one(
|
reservation_id = fields.Many2one(
|
||||||
string="Room",
|
string="Reservation",
|
||||||
help="Room to which the services will be applied",
|
help="Room to which the services will be applied",
|
||||||
readonly=True,
|
readonly=True,
|
||||||
store=True,
|
store=True,
|
||||||
@@ -104,7 +104,11 @@ class PmsServiceLine(models.Model):
|
|||||||
compute="_compute_discount",
|
compute="_compute_discount",
|
||||||
)
|
)
|
||||||
cancel_discount = fields.Float(
|
cancel_discount = fields.Float(
|
||||||
string="Cancelation Discount", help="", compute="_compute_cancel_discount"
|
string="Cancelation Discount",
|
||||||
|
help="",
|
||||||
|
compute="_compute_cancel_discount",
|
||||||
|
readonly=True,
|
||||||
|
store=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends("day_qty", "discount", "price_unit", "tax_ids")
|
@api.depends("day_qty", "discount", "price_unit", "tax_ids")
|
||||||
|
|||||||
@@ -38,3 +38,4 @@ from . import test_pms_board_service_line
|
|||||||
from . import test_pms_board_service_room_type
|
from . import test_pms_board_service_room_type
|
||||||
from . import test_pms_board_service_room_type_line
|
from . import test_pms_board_service_room_type_line
|
||||||
from . import test_pms_folio_invoice
|
from . import test_pms_folio_invoice
|
||||||
|
from . import test_pms_folio_sale_line
|
||||||
|
|||||||
1336
pms/tests/test_pms_folio_sale_line.py
Normal file
1336
pms/tests/test_pms_folio_sale_line.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,9 @@
|
|||||||
<field name="folio_ids" widget="many2many_tags" />
|
<field name="folio_ids" widget="many2many_tags" />
|
||||||
<field name="pms_property_id" invisible="1" />
|
<field name="pms_property_id" invisible="1" />
|
||||||
</xpath>
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='quantity']" position="before">
|
||||||
|
<field name="name_changed_by_user" invisible="1" />
|
||||||
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|||||||
@@ -265,6 +265,7 @@
|
|||||||
<field
|
<field
|
||||||
name="sale_line_ids"
|
name="sale_line_ids"
|
||||||
widget="section_and_note_one2many"
|
widget="section_and_note_one2many"
|
||||||
|
default_order="folio_id, sequence, reservation_id asc, service_order, date_order, display_type"
|
||||||
>
|
>
|
||||||
<tree string="Sales Lines" editable="bottom">
|
<tree string="Sales Lines" editable="bottom">
|
||||||
<control>
|
<control>
|
||||||
@@ -283,7 +284,10 @@
|
|||||||
context="{'default_display_type': 'line_note'}"
|
context="{'default_display_type': 'line_note'}"
|
||||||
/>
|
/>
|
||||||
</control>
|
</control>
|
||||||
|
<field name="service_order" invisible="1" />
|
||||||
|
<field name="date_order" invisible="1" />
|
||||||
|
<field name="reservation_id" invisible="1" />
|
||||||
|
<field name="service_id" invisible="1" />
|
||||||
<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" />
|
||||||
@@ -291,11 +295,22 @@
|
|||||||
|
|
||||||
<!-- <field name="product_updatable" invisible="1"/> -->
|
<!-- <field name="product_updatable" invisible="1"/> -->
|
||||||
<button
|
<button
|
||||||
title="Board Service"
|
title="Room"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
icon="fa-1x fa-bed"
|
icon="fa-1x fa-bed"
|
||||||
name="open_service_ids"
|
attrs="{'invisible':[('service_id','!=', False)]}"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
title="Board Service"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-1x fa-cutlery"
|
||||||
attrs="{'invisible':[('is_board_service','=', False)]}"
|
attrs="{'invisible':[('is_board_service','=', False)]}"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
title="Extra Service"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-1x fa-tags"
|
||||||
|
attrs="{'invisible':['|',('is_board_service','=',True),('service_id','=',False)]}"
|
||||||
/>
|
/>
|
||||||
<field
|
<field
|
||||||
name="product_id"
|
name="product_id"
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
attrs="{'invisible':[('is_board_service','=', True)]}"
|
attrs="{'invisible':[('is_board_service','=', True)]}"
|
||||||
/>
|
/>
|
||||||
<field name="service_id" optional="show" />
|
<field name="service_id" optional="show" />
|
||||||
<field name="room_id" />
|
<field name="reservation_id" />
|
||||||
<field name="tax_ids" optional="show" />
|
<field name="tax_ids" optional="show" />
|
||||||
<field
|
<field
|
||||||
name="discount"
|
name="discount"
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
>
|
>
|
||||||
<field name="service_id" />
|
<field name="service_id" />
|
||||||
<field name="product_id" filters="1" />
|
<field name="product_id" filters="1" />
|
||||||
<field name="room_id" filters="1" />
|
<field name="reservation_id" filters="1" />
|
||||||
</calendar>
|
</calendar>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
Reference in New Issue
Block a user