[DONE] consider SALE dates & OVERNIGHT dates on pricelist items (#38)

* [FIX] pms: fix timezones (test,compute & create) @ massive changes

* [REF] pms: fiels date_start/end_overnight (pricelist_items)

* [IMP] pms: SQL to consider overnight dates and priorize items (_compute_price_rule_get_items)

* [REF] pms: add tests for overnight dates and unify test cases with subtests

* [FIX] pms: fix apply partially pricelist items on several reservation lines

* [FIX] pms: consider services to priorize pricelists

* [FIX] test pricelist priority without taxs

Co-authored-by: Darío Lodeiros <dario@commitsun.com>
This commit is contained in:
Miguel Padin
2021-01-20 12:19:42 +01:00
committed by GitHub
parent 19fffec6ba
commit cb96f3a9d0
15 changed files with 550 additions and 898 deletions

View File

@@ -21,6 +21,7 @@ from . import pms_room_type
from . import pms_service
from . import account_move
from . import product_template
from . import product_product
from . import res_company
from . import account_payment
from . import pms_room_type_availability_plan

View File

@@ -468,8 +468,18 @@ class PmsReservation(models.Model):
readonly=False,
store=True,
)
date_order = fields.Date(
compute="_compute_pms_creation_date",
store=True,
readonly=False,
)
# Compute and Search methods
def _compute_pms_creation_date(self):
for record in self:
record.date_order = datetime.datetime.today()
@api.depends("checkin", "checkout", "room_type_id")
def _compute_name(self):
for reservation in self:

View File

@@ -275,7 +275,8 @@ class PmsReservationLine(models.Model):
lang=partner.lang,
partner=partner.id,
quantity=1,
date=line.date,
date=line.reservation_id.date_order,
date_overnight=line.date,
pricelist=reservation.pricelist_id.id,
uom=product.uom_id.id,
property=reservation.pms_property_id.id,

View File

@@ -65,27 +65,67 @@ class ProductPricelist(models.Model):
def _compute_price_rule_get_items(
self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
):
items = super(ProductPricelist, self)._compute_price_rule_get_items(
products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
)
# Discard the rules with defined properties other than the context,
# and we reorder the rules to return the most concrete property rule first
if "property" in self._context:
items_filtered = items.filtered(
lambda i: not i.pms_property_ids
or self._context["property"] in i.pms_property_ids.ids
if "property" in self._context and self._context["property"]:
self.env["product.pricelist.item"].flush(
["price", "currency_id", "company_id"]
)
reverse_id = items_filtered.sorted(id, reverse=True)
return items_filtered.sorted(
key=lambda s: (
s.applied_on,
True if (not s.date_end or not s.date_start) else False,
True
if (not s.date_end or not s.date_start)
else (s.date_end - s.date_start).days,
((not s.pms_property_ids, s), len(s.pms_property_ids)),
reverse_id,
)
self.env.cr.execute(
"""
SELECT item.id
FROM product_pricelist_item item
LEFT JOIN product_category categ
ON item.categ_id = categ.id
LEFT JOIN pms_property_product_pricelist_rel cab
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
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
OR item.product_tmpl_id = ANY(%s))
AND (item.product_id IS NULL OR item.product_id = ANY(%s))
AND (item.categ_id IS NULL OR item.categ_id = ANY(%s))
AND (item.pricelist_id = %s)
AND (item.date_start IS NULL OR item.date_start <=%s)
AND (item.date_end IS NULL OR item.date_end >=%s)
AND (item.date_start_overnight IS NULL
OR item.date_start_overnight <=%s)
AND (item.date_end_overnight IS NULL
OR item.date_end_overnight >=%s)
GROUP BY item.id
ORDER BY item.applied_on,
/* REVIEW: priotrity date sale / date overnight */
item.date_end - item.date_start ASC,
item.date_end_overnight - item.date_start_overnight ASC,
NULLIF((SELECT COUNT(1)
FROM pms_property_product_pricelist_item_rel l
WHERE item.id = l.product_pricelist_item_id)
+ (SELECT COUNT(1)
FROM pms_property_product_pricelist_rel c
WHERE item.pricelist_id = c.product_pricelist_id),0)
NULLS LAST,
item.id DESC;
""",
(
self._context["property"],
self._context["property"],
prod_tmpl_ids,
prod_ids,
categ_ids,
self.id,
date,
date,
self._context["date_overnight"],
self._context["date_overnight"],
),
)
item_ids = [x[0] for x in self.env.cr.fetchall()]
items = self.env["product.pricelist.item"].browse(item_ids)
else:
items = super(ProductPricelist, self)._compute_price_rule_get_items(
products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
)
return items

View File

@@ -9,3 +9,11 @@ class ProductPricelistItem(models.Model):
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
)
date_start_overnight = fields.Date(
string="Start Date Overnight",
help="Start date to apply daily pricelist items",
)
date_end_overnight = fields.Date(
string="End Date Overnight",
help="End date to apply daily pricelist items",
)

View File

@@ -0,0 +1,17 @@
from odoo import api, models
class ProductProduct(models.Model):
_inherit = "product.product"
@api.depends_context(
"pricelist",
"partner",
"quantity",
"uom",
"date",
"date_overnight",
"no_variant_attributes_price_extra",
)
def _compute_product_price(self):
super(ProductProduct, self)._compute_product_price()

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,3 @@
# import datetime
# from freezegun import freeze_time
#
import datetime
from freezegun import freeze_time
@@ -246,16 +243,13 @@ class TestPmsWizardMassiveChanges(TestHotel):
# expected price
expected_price_total = days * price_today * num_double_rooms
# convert dates to datetimes
dates = self.env["pms.folio.wizard"].get_datetime_from_start_end(checkin)
# set pricelist item for current day
product_tmpl_id = self.test_room_type_double.product_id.product_tmpl_id.id
pricelist_item = self.env["product.pricelist.item"].create(
{
"pricelist_id": self.test_pricelist.id,
"date_start": dates[0],
"date_end": dates[1],
"date_start_overnight": checkin,
"date_end_overnight": checkin,
"compute_price": "fixed",
"applied_on": "1_product",
"product_tmpl_id": product_tmpl_id,
@@ -316,16 +310,13 @@ class TestPmsWizardMassiveChanges(TestHotel):
checkout = fields.date.today() + datetime.timedelta(days=1)
days = (checkout - checkin).days
# convert dates to datetimes
dates = self.env["pms.folio.wizard"].get_datetime_from_start_end(checkin)
# set pricelist item for current day
product_tmpl_id = self.test_room_type_double.product_id.product_tmpl_id.id
pricelist_item = self.env["product.pricelist.item"].create(
{
"pricelist_id": self.test_pricelist.id,
"date_start": dates[0],
"date_end": dates[1],
"date_start_overnight": checkin,
"date_end_overnight": checkin,
"compute_price": "fixed",
"applied_on": "1_product",
"product_tmpl_id": product_tmpl_id,

View File

@@ -1,6 +1,5 @@
import datetime
import pytz
from freezegun import freeze_time
from odoo import fields
@@ -301,16 +300,11 @@ class TestPmsWizardMassiveChanges(TestHotel):
price = 20
min_quantity = 3
vals = {
"pricelist_id": self.test_pricelist,
"date_start": datetime.datetime.combine(
date_from,
datetime.time.min,
),
"date_end": datetime.datetime.combine(
date_to,
datetime.time.max,
),
"date_start": date_from,
"date_end": date_to,
"compute_price": "fixed",
"applied_on": "1_product",
"product_tmpl_id": self.test_room_type_double.product_id.product_tmpl_id,
@@ -330,26 +324,20 @@ class TestPmsWizardMassiveChanges(TestHotel):
"min_quantity": min_quantity,
}
).apply_massive_changes()
vals["date_start"] = pytz.timezone("Europe/Madrid").localize(vals["date_start"])
vals["date_end"] = pytz.timezone("Europe/Madrid").localize(vals["date_end"])
vals["date_start_overnight"] = date_from
vals["date_end_overnight"] = date_to
del vals["date_start"]
del vals["date_end"]
# ASSERT
for key in vals:
with self.subTest(k=key):
if key == "date_start" or key == "date_end":
self.assertEqual(
fields.Datetime.context_timestamp(
self.test_pricelist.item_ids[0],
self.test_pricelist.item_ids[0][key],
),
vals[key],
"The value of " + key + " is not correctly established",
)
else:
self.assertEqual(
self.test_pricelist.item_ids[0][key],
vals[key],
"The value of " + key + " is not correctly established",
)
self.assertEqual(
self.test_pricelist.item_ids[0][key],
vals[key],
"The value of " + key + " is not correctly established",
)
@freeze_time("1980-12-01")
def test_day_of_week_pricelist_items_create(self):
@@ -398,17 +386,12 @@ class TestPmsWizardMassiveChanges(TestHotel):
# ASSERT
pricelist_items = self.test_pricelist.item_ids.sorted(
key=lambda s: s.date_start
key=lambda s: s.date_start_overnight
)
# ASSERT
self.assertTrue(
(
fields.Datetime.context_timestamp(
pricelist_items[index], pricelist_items[index].date_start
)
).timetuple()[6]
== index
pricelist_items[index].date_start_overnight.timetuple()[6] == index
and test_case[index],
"Rule not created on correct day of week",
)

View File

@@ -11,6 +11,10 @@
options="{'no_create': True,'no_open': True}"
/>
</xpath>
<xpath expr="//field[@name='date_end']" position="after">
<field name="date_start_overnight" />
<field name="date_end_overnight" />
</xpath>
</field>
</record>
</odoo>

View File

@@ -18,13 +18,16 @@
expr="//field[@name='item_ids']/tree/field[@name='base']"
position="after"
>
<field name="date_start_overnight" />
<field name="date_end_overnight" />
<field
name="pms_property_ids"
widget="many2many_tags"
options="{'no_create': True,'no_open': True}"
/>
</xpath>
<xpath expr="//widget[@name='web_ribbon']" position="after">
<div class="oe_button_box " name="button_box">
<button

View File

@@ -1,7 +1,5 @@
import datetime
import pytz
from odoo import api, fields, models
@@ -101,13 +99,12 @@ class FolioWizard(models.TransientModel):
)
num_rooms_available_by_date.append(len(rooms_available))
datetimes = self.get_datetime_from_start_end(date_iterator)
pricelist_item = self.env["product.pricelist.item"].search(
[
("pricelist_id", "=", record.pricelist_id.id),
("date_start", ">=", datetimes[0]),
("date_end", "<=", datetimes[1]),
("date_start_overnight", ">=", date_iterator),
("date_end_overnight", "<=", date_iterator),
("applied_on", "=", "1_product"),
(
"product_tmpl_id",
@@ -164,27 +161,6 @@ class FolioWizard(models.TransientModel):
key=lambda s: s.num_rooms_available, reverse=True
)
@api.model
def get_datetime_from_start_end(self, date):
tz = "Europe/Madrid"
dt_from = datetime.datetime.combine(
date,
datetime.time.min,
)
dt_to = datetime.datetime.combine(
date,
datetime.time.max,
)
dt_from = pytz.timezone(tz).localize(dt_from)
dt_to = pytz.timezone(tz).localize(dt_to)
dt_from = dt_from.astimezone(pytz.utc)
dt_to = dt_to.astimezone(pytz.utc)
dt_from = dt_from.replace(tzinfo=None)
dt_to = dt_to.replace(tzinfo=None)
return dt_from, dt_to
# actions
def create_folio(self):
for record in self:

View File

@@ -54,12 +54,6 @@ class AvailabilityWizard(models.TransientModel):
string="Total price", default=0, compute="_compute_price_total"
)
splitted_availability = fields.Boolean(
compute="_compute_splitted_availability",
store=True,
readonly=False,
)
@api.depends("num_rooms_selected", "checkin", "checkout")
def _compute_price_total(self):
for record in self:
@@ -84,16 +78,13 @@ class AvailabilityWizard(models.TransientModel):
pricelist=record.folio_wizard_id.pricelist_id.id,
)
num_rooms_available_by_date.append(len(rooms_available))
datetimes = self.env["pms.folio.wizard"].get_datetime_from_start_end(
date_iterator
)
# get pricelist item
pricelist_item = self.env["product.pricelist.item"].search(
[
("pricelist_id", "=", record.folio_wizard_id.pricelist_id.id),
("date_start", ">=", datetimes[0]),
("date_end", "<=", datetimes[1]),
("date_start_overnight", ">=", date_iterator),
("date_end_overnight", "<=", date_iterator),
("applied_on", "=", "1_product"),
(
"product_tmpl_id",

View File

@@ -1,7 +1,5 @@
import datetime
import pytz
from odoo import api, fields, models
@@ -10,6 +8,12 @@ class AvailabilityWizard(models.TransientModel):
_name = "pms.massive.changes.wizard"
_description = "Wizard for massive changes on Availability Plans & Pricelists."
def _default_avail_readonly(self):
return True if self._context.get("availability_plan_id") else False
def _default_pricelist_readonly(self):
return True if self._context.get("pricelist_id") else False
# Fields declaration
massive_changes_on = fields.Selection(
[("pricelist", "Pricelist"), ("availability_plan", "Availability Plan")],
@@ -135,9 +139,10 @@ class AvailabilityWizard(models.TransientModel):
store=False,
readonly=True,
)
avail_readonly = fields.Boolean(compute="_compute_avail_readonly")
pricelist_readonly = fields.Boolean(compute="_compute_pricelist_readonly")
avail_readonly = fields.Boolean(default=_default_avail_readonly)
pricelist_readonly = fields.Boolean(default=_default_pricelist_readonly)
@api.depends("massive_changes_on")
def _compute_allowed_pricelist_ids(self):
for record in self:
record.allowed_pricelist_ids = self.env["product.pricelist"].search(
@@ -234,10 +239,10 @@ class AvailabilityWizard(models.TransientModel):
]
if record.start_date:
domain.append(("date_start", ">=", record.start_date))
domain.append(("date_start_overnight", ">=", record.start_date))
if record.end_date:
domain.append(("date_end", "<=", record.end_date))
domain.append(("date_end_overnight", "<=", record.end_date))
if record.room_type_id:
domain.append(
(
@@ -265,7 +270,9 @@ class AvailabilityWizard(models.TransientModel):
and record.end_date
):
record.pricelist_items_to_overwrite = items.filtered(
lambda x: week_days_to_apply[x.date_start.timetuple()[6]]
lambda x: week_days_to_apply[
x.date_end_overnight.timetuple()[6]
]
)
else:
record.pricelist_items_to_overwrite = items
@@ -290,21 +297,9 @@ class AvailabilityWizard(models.TransientModel):
record.pricelist_items_to_overwrite
)
def _compute_avail_readonly(self):
for record in self:
record.avail_readonly = (
True if self._context.get("availability_plan_id") else False
)
def _compute_pricelist_readonly(self):
for record in self:
record.pricelist_readonly = (
True if self._context.get("pricelist_id") else False
)
# actions
def apply_massive_changes(self):
tz = "Europe/Madrid"
for record in self:
# remove old rules
record.rules_to_overwrite.unlink()
@@ -336,33 +331,13 @@ class AvailabilityWizard(models.TransientModel):
else:
rooms = [record.room_type_id]
for room in rooms:
# REVIEW -> maybe would be more efficient creating a list
# and write all data in 1 operation
if record.massive_changes_on == "pricelist":
dt_from = datetime.datetime.combine(
date,
datetime.time.min,
)
dt_to = datetime.datetime.combine(
date,
datetime.time.max,
)
dt_from = pytz.timezone(tz).localize(dt_from)
dt_to = pytz.timezone(tz).localize(dt_to)
dt_from = dt_from.astimezone(pytz.utc)
dt_to = dt_to.astimezone(pytz.utc)
dt_from = dt_from.replace(tzinfo=None)
dt_to = dt_to.replace(tzinfo=None)
self.env["product.pricelist.item"].create(
{
"pricelist_id": record.pricelist_id.id,
"date_start": dt_from,
"date_end": dt_to,
"date_start_overnight": date,
"date_end_overnight": date,
"compute_price": "fixed",
"applied_on": "1_product",
"product_tmpl_id": room.product_id.product_tmpl_id.id,
@@ -370,7 +345,6 @@ class AvailabilityWizard(models.TransientModel):
"min_quantity": record.min_quantity,
}
)
else:
self.env["pms.room.type.availability.rule"].create(
{
@@ -388,5 +362,23 @@ class AvailabilityWizard(models.TransientModel):
"closed_departure": record.closed_departure,
}
)
# return {}
if (
record.massive_changes_on == "pricelist"
and not record.pricelist_readonly
):
action = self.env.ref("product.product_pricelist_action2").read()[0]
action["views"] = [
(self.env.ref("pms.product_pricelist_view_form").id, "form")
]
action["res_id"] = record.pricelist_id.id
return action
if (
record.massive_changes_on == "availability_plan"
and not record.avail_readonly
):
action = self.env.ref("pms.room_type_availability_action").read()[0]
action["views"] = [
(self.env.ref("pms.room_type_availability_view_form").id, "form")
]
action["res_id"] = record.availability_plan_id.id
return action

View File

@@ -182,7 +182,8 @@
<div class="col-4">
<group>
<field name="price" />
<field name="min_quantity" />
<!-- REVIEW 'min_quantity'-->
<!--<field name="min_quantity" />-->
</group>
</div>
</div>
@@ -199,21 +200,28 @@
>
<span style="text-decoration:underline;">
<field name="num_pricelist_items_to_overwrite" />
</span>
pricelist items
<u>will be overwritten:</u>
</b>
<!-- REVIEW: a calendar view could be more understandable -->
<field
name="rules_to_overwrite"
attrs="{'invisible':[('massive_changes_on','=','pricelist')]}"
/>
<field
name="pricelist_items_to_overwrite"
nolabel="1"
attrs="{'invisible':[('massive_changes_on','=','availability_plan')]}"
/>
>
<tree>
<field name="pricelist_id" />
<field string="Applicable on" name="name" />
<field name="date_start_overnight" />
<field name="date_end_overnight" />
<field name="price" />
</tree>
</field>
<footer>
<button
name="apply_massive_changes"