Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
5
pricelist_cache/models/__init__.py
Executable file
5
pricelist_cache/models/__init__.py
Executable file
@@ -0,0 +1,5 @@
|
||||
from . import product_pricelist
|
||||
from . import product_pricelist_item
|
||||
from . import product_pricelist_cache
|
||||
from . import product_product
|
||||
from . import res_partner
|
||||
195
pricelist_cache/models/product_pricelist.py
Executable file
195
pricelist_cache/models/product_pricelist.py
Executable file
@@ -0,0 +1,195 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from datetime import date
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class Pricelist(models.Model):
|
||||
_inherit = "product.pricelist"
|
||||
|
||||
parent_pricelist_ids = fields.Many2many(
|
||||
"product.pricelist",
|
||||
relation="product_pricelist_cache__parent_pricelist_ids_rel",
|
||||
column1="pricelist_id",
|
||||
column2="parent_pricelist_id",
|
||||
compute="_compute_parent_pricelist_ids",
|
||||
store=True,
|
||||
)
|
||||
is_pricelist_cache_computed = fields.Boolean()
|
||||
is_pricelist_cache_available = fields.Boolean(
|
||||
compute="_compute_is_pricelist_cache_available"
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"item_ids", "item_ids.applied_on", "item_ids.base", "item_ids.base_pricelist_id"
|
||||
)
|
||||
def _compute_parent_pricelist_ids(self):
|
||||
for record in self:
|
||||
record.parent_pricelist_ids = record._get_parent_pricelists()
|
||||
|
||||
def _compute_is_pricelist_cache_available(self):
|
||||
for record in self:
|
||||
parents = record._get_parent_list_tree()
|
||||
record.is_pricelist_cache_available = all(
|
||||
parents.mapped("is_pricelist_cache_computed")
|
||||
)
|
||||
|
||||
def _get_parent_list_tree(self):
|
||||
self.ensure_one()
|
||||
query = """
|
||||
WITH RECURSIVE parent_pricelist AS (
|
||||
SELECT id
|
||||
FROM product_pricelist
|
||||
WHERE id = %(pricelist_id)s
|
||||
UNION SELECT item.base_pricelist_id AS id
|
||||
FROM product_pricelist_item item
|
||||
INNER JOIN parent_pricelist parent
|
||||
ON item.pricelist_id = parent.id
|
||||
)
|
||||
SELECT id FROM parent_pricelist;
|
||||
"""
|
||||
self.env.flush_all()
|
||||
self.env.cr.execute(query, {"pricelist_id": self.id})
|
||||
return self.search([("id", "in", [row[0] for row in self.env.cr.fetchall()])])
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
for record in res:
|
||||
if record._is_factor_pricelist() or record._is_global_pricelist():
|
||||
product_ids_to_cache = None
|
||||
else:
|
||||
product_ids_to_cache = record.item_ids.mapped("product_id").ids
|
||||
cache_model = self.env["product.pricelist.cache"].with_delay()
|
||||
cache_model.update_product_pricelist_cache(
|
||||
product_ids=product_ids_to_cache, pricelist_ids=record.ids
|
||||
)
|
||||
return res
|
||||
|
||||
def _get_product_prices(self, product_ids):
|
||||
self.ensure_one()
|
||||
# Search instead of browse, since products could have been unlinked
|
||||
# between the time where records have been created / modified
|
||||
# and the time this method is executed.
|
||||
products = self.env["product.product"].search([("id", "in", product_ids)])
|
||||
results = self._compute_price_rule(products, 1, date=date.today())
|
||||
product_prices = {prod: price[0] for prod, price in results.items()}
|
||||
return product_prices
|
||||
|
||||
def _get_root_pricelist_ids(self):
|
||||
"""Returns the id of all root pricelists.
|
||||
|
||||
A root pricelist have no item referencing another pricelist.
|
||||
"""
|
||||
no_parent_query = """
|
||||
SELECT id
|
||||
FROM product_pricelist pp
|
||||
WHERE id NOT IN (
|
||||
SELECT pricelist_id
|
||||
FROM product_pricelist_item
|
||||
WHERE (
|
||||
base_pricelist_id IS NOT NULL
|
||||
AND base = 'pricelist'
|
||||
)
|
||||
)
|
||||
AND active = TRUE;
|
||||
"""
|
||||
self.env.flush_all()
|
||||
self.env.cr.execute(no_parent_query)
|
||||
return [row[0] for row in self.env.cr.fetchall()]
|
||||
|
||||
def _get_factor_pricelist_ids(self):
|
||||
"""Returns the id of all factor pricelists.
|
||||
|
||||
A factor pricelist have an item referencing a pricelist,
|
||||
altering the price via price_discount or price_surcharge
|
||||
"""
|
||||
factor_pricelist_query = """
|
||||
SELECT id
|
||||
FROM product_pricelist
|
||||
WHERE id IN (
|
||||
SELECT pricelist_id
|
||||
FROM product_pricelist_item
|
||||
WHERE (
|
||||
base_pricelist_id IS NOT NULL
|
||||
AND base = 'pricelist'
|
||||
AND (
|
||||
price_discount != 0.0
|
||||
OR price_surcharge != 0.0
|
||||
)
|
||||
)
|
||||
)
|
||||
AND active = TRUE;
|
||||
"""
|
||||
self.env.flush_all()
|
||||
self.env.cr.execute(factor_pricelist_query)
|
||||
return [row[0] for row in self.env.cr.fetchall()]
|
||||
|
||||
def _get_global_pricelist_ids(self):
|
||||
"""Return factor pricelists and pricelists with no parents."""
|
||||
global_pricelist_ids = self._get_root_pricelist_ids()
|
||||
factor_pricelist_ids = self._get_factor_pricelist_ids()
|
||||
return global_pricelist_ids + factor_pricelist_ids
|
||||
|
||||
def _get_parent_pricelists(self):
|
||||
"""Returns the parent pricelists.
|
||||
|
||||
The parent pricelist is defined on a pricelist_item when it's applied
|
||||
globally, and based on another pricelist
|
||||
"""
|
||||
self.ensure_one()
|
||||
query = """
|
||||
SELECT base_pricelist_id
|
||||
FROM product_pricelist_item
|
||||
WHERE applied_on = '3_global'
|
||||
AND base = 'pricelist'
|
||||
AND base_pricelist_id IS NOT NULL
|
||||
AND pricelist_id = %(pricelist_id)s
|
||||
"""
|
||||
self.env.cr.execute(query, {"pricelist_id": self.id})
|
||||
return self.browse([row[0] for row in self.env.cr.fetchall()])
|
||||
|
||||
def _is_factor_pricelist(self):
|
||||
"""Returns whether a pricelist is a factor pricelist.
|
||||
|
||||
A factor pricelist is applied globally and refers to another pricelist.
|
||||
It also alters the "parent's price" by applying a discount or a surcharge
|
||||
on it.
|
||||
"""
|
||||
self.ensure_one()
|
||||
parent_pricelist_items = self.item_ids.filtered(
|
||||
lambda i: (
|
||||
i.applied_on == "3_global"
|
||||
and i.base == "pricelist"
|
||||
and i.base_pricelist_id
|
||||
and (i.price_discount or i.price_surcharge)
|
||||
)
|
||||
)
|
||||
return bool(parent_pricelist_items)
|
||||
|
||||
def _is_global_pricelist(self):
|
||||
"""Returns whether a pricelist is a factor global.
|
||||
|
||||
A factor pricelist is applied globally and refers to another pricelist.
|
||||
It also alters the "parent's price" by applying a discount or a surcharge
|
||||
on it.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return bool(not self._get_parent_pricelists())
|
||||
|
||||
def _recursive_get_items(self, product):
|
||||
"""Recursively searches on parent pricelists for items applied on product."""
|
||||
item_ids = self.item_ids.filtered(lambda i: i.product_id == product).ids
|
||||
for parent_pricelist in self._get_parent_pricelists():
|
||||
parent_items = parent_pricelist._recursive_get_items(product)
|
||||
item_ids.extend(parent_items.ids)
|
||||
return self.env["product.pricelist.item"].browse(item_ids)
|
||||
|
||||
def button_open_pricelist_cache_tree(self):
|
||||
cache_model = self.env["product.pricelist.cache"]
|
||||
products = self.env["product.product"].search([])
|
||||
prices = cache_model.get_cached_prices_for_pricelist(self, products)
|
||||
domain = [("id", "in", prices.ids)]
|
||||
return cache_model._get_tree_view(domain)
|
||||
268
pricelist_cache/models/product_pricelist_cache.py
Executable file
268
pricelist_cache/models/product_pricelist_cache.py
Executable file
@@ -0,0 +1,268 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from psycopg2 import sql
|
||||
|
||||
from odoo import fields, models, tools
|
||||
|
||||
|
||||
class PricelistCache(models.Model):
|
||||
"""This model aims to store all product prices depending on all pricelist.
|
||||
|
||||
Price cache is updated or created in the following cases:
|
||||
- Product price is created / modified;
|
||||
-> entrypoint "product_product.py::{create,write}"
|
||||
- Pricelist item is created / modified;
|
||||
-> entrypoint "product_pricelist_item.py::update_product_pricelist_cache"
|
||||
- Pricelist is created;
|
||||
-> entrypoint "product_pricelist.py::create"
|
||||
There's also a daily cron task that updates cache prices
|
||||
that have been skipped during the day:
|
||||
- see "cron_reset_pricelist_cache" for the cron method
|
||||
- see "product_pricelist_item.py::update_product_pricelist_cache"
|
||||
for skip conditions
|
||||
Every call to PricelistCache.update_product_pricelist_cache
|
||||
should be made in a job as computation might be slow, depending on the case.
|
||||
"""
|
||||
|
||||
_name = "product.pricelist.cache"
|
||||
_description = "Pricelist Cache"
|
||||
_rec_name = "pricelist_id"
|
||||
|
||||
pricelist_id = fields.Many2one(
|
||||
"product.pricelist",
|
||||
string="Pricelist",
|
||||
required=True,
|
||||
index=True,
|
||||
ondelete="cascade",
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
"product.product", string="Product Variant", index=True
|
||||
)
|
||||
price = fields.Float()
|
||||
|
||||
def _update_existing_records(self, product_prices):
|
||||
"""Update existing records with provided prices.
|
||||
|
||||
Args:
|
||||
- self : The recordset of cache records to update
|
||||
- product_prices : The new prices to apply
|
||||
"""
|
||||
# Write everything in single transaction
|
||||
values = [
|
||||
sql.SQL(", ").join(
|
||||
map(sql.Literal, (record.id, product_prices[record.product_id.id]))
|
||||
)
|
||||
for record in self
|
||||
]
|
||||
query = sql.SQL(
|
||||
"""
|
||||
UPDATE
|
||||
product_pricelist_cache AS pricelist_cache
|
||||
SET
|
||||
price = c.price
|
||||
FROM (VALUES ({}))
|
||||
AS c(id, price)
|
||||
WHERE
|
||||
c.id = pricelist_cache.id;
|
||||
"""
|
||||
).format(sql.SQL("), (").join(values))
|
||||
self.env.flush_all()
|
||||
self.env.cr.execute(query)
|
||||
self.invalidate_model(["price"])
|
||||
self.env.flush_all()
|
||||
|
||||
def _create_cache_records(self, pricelist_id, product_ids, product_prices):
|
||||
"""Create price cache records for a given pricelist, applied to a list of
|
||||
product ids.
|
||||
|
||||
args:
|
||||
- pricelist_id : The pricelist id on which prices are applied
|
||||
- product_ids : A list of product ids to cache
|
||||
- product_prices : A dict containing the prices for each product
|
||||
"""
|
||||
values = [
|
||||
sql.SQL(", ").join(
|
||||
map(sql.Literal, (p_id, pricelist_id, product_prices[p_id]))
|
||||
)
|
||||
for p_id in product_ids
|
||||
]
|
||||
if values:
|
||||
# create_everything from a single transaction
|
||||
query = sql.SQL(
|
||||
"""
|
||||
INSERT INTO product_pricelist_cache (product_id, pricelist_id, price)
|
||||
VALUES ({});
|
||||
"""
|
||||
).format(sql.SQL("), (").join(values))
|
||||
self.env.flush_all()
|
||||
self.env.cr.execute(query)
|
||||
|
||||
def _update_pricelist_cache(self, pricelist_id, product_prices):
|
||||
"""Updates the cache, for a given pricelist, and product prices.
|
||||
|
||||
Args:
|
||||
- pricelist: a product.pricelist record
|
||||
- product_prices: A dictionnary,
|
||||
with product.product id as keys, and prices as values
|
||||
"""
|
||||
product_ids = list(product_prices.keys())
|
||||
# First, update existing records
|
||||
existing_records = self.search(
|
||||
[
|
||||
("pricelist_id", "=", pricelist_id),
|
||||
("product_id", "in", product_ids),
|
||||
]
|
||||
)
|
||||
if existing_records:
|
||||
existing_records._update_existing_records(product_prices)
|
||||
# Then, create missing records with provided prices
|
||||
# Diff between products and already created records
|
||||
not_cached_product_ids = set(product_ids)
|
||||
if existing_records:
|
||||
not_cached_product_ids -= set(existing_records.mapped("product_id").ids)
|
||||
if not_cached_product_ids:
|
||||
self._create_cache_records(
|
||||
pricelist_id, not_cached_product_ids, product_prices
|
||||
)
|
||||
|
||||
def _get_product_ids_to_update(self, pricelist, product_ids):
|
||||
"""Returns a list of product_ids that are already cached
|
||||
for the given pricelist.
|
||||
|
||||
Args:
|
||||
- pricelist: The pricelist record on which new prices are applied
|
||||
- product_prices: The list of products to check
|
||||
"""
|
||||
product_ids_to_update = []
|
||||
# We need to be sure to not waste resources while updating the cache.
|
||||
# To do that, we ensure that prices are not coming from a parent
|
||||
# pricelist.
|
||||
if pricelist._get_parent_pricelists():
|
||||
# If this is a factor pricelist, then everything
|
||||
# have to be updated
|
||||
if pricelist._is_factor_pricelist():
|
||||
product_ids_to_update = product_ids
|
||||
# Otherwise, prices are fetched from parent pricelist
|
||||
# and only products in items have to be updated
|
||||
else:
|
||||
product_item_ids = pricelist.item_ids.filtered(
|
||||
lambda i: i.product_id.id in product_ids
|
||||
)
|
||||
product_ids_to_update = product_item_ids.mapped("product_id").ids
|
||||
else:
|
||||
# No parent (for instance public pricelist), then update everything
|
||||
product_ids_to_update = product_ids
|
||||
return product_ids_to_update
|
||||
|
||||
def update_product_pricelist_cache(self, product_ids=None, pricelist_ids=None):
|
||||
"""
|
||||
Updates price list cache given a product.product recordset and a pricelist,
|
||||
if specified.
|
||||
"""
|
||||
if not product_ids:
|
||||
product_ids = self.env["product.product"].search([]).ids
|
||||
if not pricelist_ids:
|
||||
pricelists = self.env["product.pricelist"].search([])
|
||||
else:
|
||||
# Search instead of browse, since pricelists could have been unlinked
|
||||
# between the time where records have been created / modified
|
||||
# and the time this method is executed.
|
||||
pricelists = self.env["product.pricelist"].search(
|
||||
[("id", "in", pricelist_ids)]
|
||||
)
|
||||
for pricelist in pricelists:
|
||||
product_ids_to_update = self._get_product_ids_to_update(
|
||||
pricelist, product_ids
|
||||
)
|
||||
product_prices = pricelist._get_product_prices(product_ids_to_update)
|
||||
self._update_pricelist_cache(pricelist.id, product_prices)
|
||||
# Once this is done, set pricelist cache as computed on pricelist
|
||||
pricelist.is_pricelist_cache_computed = True
|
||||
|
||||
def _update_pricelist_items_cache(self, pricelist_items):
|
||||
"""Updates cache for a given recordset of pricelist items, then update
|
||||
the items skipped state to False.
|
||||
"""
|
||||
pricelist_products = pricelist_items._get_pricelist_products_group()
|
||||
for pricelist_id, product_ids in pricelist_products.items():
|
||||
self.with_delay().update_product_pricelist_cache(
|
||||
product_ids=product_ids, pricelist_ids=[pricelist_id]
|
||||
)
|
||||
pricelist_items.write({"pricelist_cache_update_skipped": False})
|
||||
|
||||
def create_full_cache(self):
|
||||
"""Creates cache for all prices applied to all pricelists."""
|
||||
pricelist_model = self.env["product.pricelist"]
|
||||
# Here, we split price computation in 2.
|
||||
# Huge pricelists (root pricelists and factor pricelists) are computed
|
||||
# on their own, in order to avoid long sql transactions.pricelist_model
|
||||
# Smaller pricelists can be computed 3 by 3, since they are taking
|
||||
# less time to process.
|
||||
global_list_ids = pricelist_model._get_global_pricelist_ids()
|
||||
# Belt and braces. Just to ensure higher level lists are executed first
|
||||
global_list_ids.sort()
|
||||
for list_id in global_list_ids:
|
||||
self.with_delay().update_product_pricelist_cache(pricelist_ids=[list_id])
|
||||
regular_list_ids = self.env["product.pricelist"].search(
|
||||
[("id", "not in", global_list_ids)]
|
||||
)
|
||||
pricelist_ids = regular_list_ids.ids
|
||||
# Spawn a job every 3 pricelists (reduce the number of jobs created)
|
||||
for chunk_ids in tools.misc.split_every(3, pricelist_ids):
|
||||
self.with_delay().update_product_pricelist_cache(pricelist_ids=chunk_ids)
|
||||
|
||||
def flush_pricelist_cache(self):
|
||||
# flush table
|
||||
flush_query = "TRUNCATE TABLE product_pricelist_cache CASCADE;"
|
||||
self.env.cr.execute(flush_query)
|
||||
# reset sequence
|
||||
sequence_query = """
|
||||
ALTER SEQUENCE product_pricelist_cache_id_seq RESTART WITH 1;
|
||||
"""
|
||||
self.env.cr.execute(sequence_query)
|
||||
self.env["product.pricelist"].search([]).is_pricelist_cache_computed = False
|
||||
|
||||
def cron_reset_pricelist_cache(self):
|
||||
"""Recreates the whole price list cache."""
|
||||
self.flush_pricelist_cache()
|
||||
# Re-create everything
|
||||
self.create_full_cache()
|
||||
|
||||
def get_cached_prices_for_pricelist(self, pricelist, products):
|
||||
"""Retrieves product prices for a given pricelist."""
|
||||
# As some items might have been skipped during product_pricelist_item
|
||||
# updates, some cached prices might be wrong, since those records
|
||||
# will be updated during a daily cron task.
|
||||
# If any of those prices is queried here, update cache before retrieving it
|
||||
need_update_items = self.env["product.pricelist.item"].search(
|
||||
[
|
||||
("pricelist_id", "=", pricelist.id),
|
||||
("product_id", "in", products.ids),
|
||||
("pricelist_cache_update_skipped", "=", True),
|
||||
]
|
||||
)
|
||||
self._update_pricelist_items_cache(need_update_items)
|
||||
# Retrieve cache for the current pricelist first
|
||||
cached_prices = self.search(
|
||||
[
|
||||
("pricelist_id", "=", pricelist.id),
|
||||
("product_id", "in", products.ids),
|
||||
]
|
||||
)
|
||||
# Then, retrieves prices from parent pricelists
|
||||
remaining_products = products - cached_prices.mapped("product_id")
|
||||
parent_pricelists = pricelist._get_parent_pricelists()
|
||||
# There shouldn't be multiple parents for a pricelist, but it's possible…
|
||||
for parent_pricelist in parent_pricelists:
|
||||
cached_prices |= self.get_cached_prices_for_pricelist(
|
||||
parent_pricelist, remaining_products
|
||||
)
|
||||
return cached_prices
|
||||
|
||||
def _get_tree_view(self, domain=None):
|
||||
xmlid = "pricelist_cache.product_pricelist_cache_action"
|
||||
action = self.env["ir.actions.act_window"]._for_xml_id(xmlid)
|
||||
if domain is not None:
|
||||
action["domain"] = domain
|
||||
return action
|
||||
69
pricelist_cache/models/product_pricelist_item.py
Executable file
69
pricelist_cache/models/product_pricelist_item.py
Executable file
@@ -0,0 +1,69 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class PricelistItem(models.Model):
|
||||
_inherit = "product.pricelist.item"
|
||||
|
||||
base_pricelist_id = fields.Many2one(index=True)
|
||||
product_tmpl_id = fields.Many2one(index=True)
|
||||
product_id = fields.Many2one(index=True)
|
||||
date_start = fields.Datetime(index=True)
|
||||
date_end = fields.Datetime(index=True)
|
||||
applied_on = fields.Selection(index=True)
|
||||
categ_id = fields.Many2one(index=True)
|
||||
min_quantity = fields.Float(index=True)
|
||||
company_id = fields.Many2one(index=True)
|
||||
|
||||
pricelist_cache_update_skipped = fields.Boolean()
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
res.update_product_pricelist_cache()
|
||||
return res
|
||||
|
||||
def _has_date_range(self):
|
||||
"""Returns whether any of the item records in recordset is based on dates."""
|
||||
return any(bool(record.date_start or record.date_end) for record in self)
|
||||
|
||||
def _get_pricelist_products_group(self):
|
||||
"""Returns a mapping of products grouped by pricelist.
|
||||
|
||||
Result:
|
||||
keys: product.pricelist id
|
||||
values: product.product list of ids
|
||||
"""
|
||||
pricelist_products = defaultdict(list)
|
||||
for item in self:
|
||||
pricelist_products[item.pricelist_id.id].append(item.product_id.id)
|
||||
return pricelist_products
|
||||
|
||||
def update_product_pricelist_cache(self):
|
||||
"""Executed when a product item is modified. Filters items not based
|
||||
on variants or based on dates, then updates the cache.
|
||||
"""
|
||||
# Filter items applied on variants
|
||||
items = self.filtered(lambda i: i.applied_on == "0_product_variant")
|
||||
# Filter items based on dates
|
||||
item_ids_to_update = []
|
||||
for item in items:
|
||||
product_item_tree = item.pricelist_id._recursive_get_items(item.product_id)
|
||||
if product_item_tree._has_date_range():
|
||||
# skip if any of the item in the tree is date based
|
||||
item.pricelist_cache_update_skipped = True
|
||||
continue
|
||||
item_ids_to_update.append(item.id)
|
||||
items_to_update = self.env["product.pricelist.item"].browse(item_ids_to_update)
|
||||
# Group per pricelist
|
||||
pricelist_products = items_to_update._get_pricelist_products_group()
|
||||
# Update cache
|
||||
cache_object = self.env["product.pricelist.cache"]
|
||||
for pricelist_id, product_ids in pricelist_products.items():
|
||||
cache_object.with_delay().update_product_pricelist_cache(
|
||||
product_ids=product_ids, pricelist_ids=[pricelist_id]
|
||||
)
|
||||
23
pricelist_cache/models/product_product.py
Executable file
23
pricelist_cache/models/product_product.py
Executable file
@@ -0,0 +1,23 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import api, models
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals):
|
||||
"""Create a cache record for each newly created product, for each global
|
||||
pricelist.
|
||||
"""
|
||||
res = super().create(vals)
|
||||
pricelist_model = self.env["product.pricelist"]
|
||||
global_pricelist_ids = pricelist_model._get_global_pricelist_ids()
|
||||
if global_pricelist_ids and res:
|
||||
cache_model = self.env["product.pricelist.cache"]
|
||||
cache_model.with_delay().update_product_pricelist_cache(
|
||||
res.ids, global_pricelist_ids
|
||||
)
|
||||
return res
|
||||
58
pricelist_cache/models/res_partner.py
Executable file
58
pricelist_cache/models/res_partner.py
Executable file
@@ -0,0 +1,58 @@
|
||||
# Copyright 2021 Camptocamp SA
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||
|
||||
from odoo import exceptions, fields, models
|
||||
|
||||
|
||||
class Partner(models.Model):
|
||||
_inherit = "res.partner"
|
||||
|
||||
is_pricelist_cache_available = fields.Boolean(
|
||||
related="property_product_pricelist.is_pricelist_cache_available"
|
||||
)
|
||||
|
||||
def _default_pricelist_cache_product_filter_id(self):
|
||||
# When the module is installed, Odoo creates the new field and at the
|
||||
# same time tries to set the default value for all existing records in
|
||||
# the DB. However the XML data (and thus 'product_filter_default' filter)
|
||||
# is still not created at this stage.
|
||||
# In order to get the module installed, the 'raise_if_not_found' parameter
|
||||
# has been added, and to set the default value on existing partners
|
||||
# the post_init_hook 'set_default_partner_product_filter' has been defined.
|
||||
return self.env.ref(
|
||||
"pricelist_cache.product_filter_default", raise_if_not_found=False
|
||||
)
|
||||
|
||||
pricelist_cache_product_filter_id = fields.Many2one(
|
||||
comodel_name="ir.filters",
|
||||
domain=[("model_id", "=", "product.product")],
|
||||
default=lambda o: o._default_pricelist_cache_product_filter_id(),
|
||||
)
|
||||
|
||||
def _pricelist_cache_get_prices(self):
|
||||
if not self.is_pricelist_cache_available:
|
||||
raise exceptions.UserError(
|
||||
self.env._("Pricelist caching in progress. Retry later")
|
||||
)
|
||||
pricelist = self._pricelist_cache_get_pricelist()
|
||||
products = self._pricelist_cache_get_products()
|
||||
cache_model = self.env["product.pricelist.cache"]
|
||||
return cache_model.get_cached_prices_for_pricelist(pricelist, products)
|
||||
|
||||
def _pricelist_cache_get_pricelist(self):
|
||||
return self.property_product_pricelist
|
||||
|
||||
def _pricelist_cache_get_products(self):
|
||||
domain = self._pricelist_cache_product_domain()
|
||||
return self.env["product.product"].search(domain)
|
||||
|
||||
def _pricelist_cache_product_domain(self):
|
||||
if self.pricelist_cache_product_filter_id:
|
||||
return self.pricelist_cache_product_filter_id._get_eval_domain()
|
||||
return []
|
||||
|
||||
def button_open_pricelist_cache_tree(self):
|
||||
prices = self._pricelist_cache_get_prices()
|
||||
cache_model = self.env["product.pricelist.cache"]
|
||||
domain = [("id", "in", prices.ids)]
|
||||
return cache_model._get_tree_view(domain)
|
||||
Reference in New Issue
Block a user