Add update price button with notification to product views
- Add action_update_list_price button in form and list views - Button shows only when compute type is not manual_update - Return notification message with update results - Invalidate cache to refresh UI automatically - Show updated products with old and new prices - Display skipped products with manual update mode
This commit is contained in:
parent
4d23e98f7b
commit
2a480b74bb
2 changed files with 77 additions and 9 deletions
|
|
@ -3,10 +3,12 @@
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||||
|
|
||||||
|
|
||||||
from odoo import exceptions, models, fields, api, _
|
|
||||||
from odoo.exceptions import UserError
|
|
||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
|
from odoo import _
|
||||||
|
from odoo import fields
|
||||||
|
from odoo import models
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -77,14 +79,14 @@ class ProductTemplate(models.Model):
|
||||||
partial_price = template.product_variant_id._get_price(
|
partial_price = template.product_variant_id._get_price(
|
||||||
qty=1, pricelist=pricelist
|
qty=1, pricelist=pricelist
|
||||||
)
|
)
|
||||||
|
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"[PRICE DEBUG] Product %s [%s]: partial_price result = %s",
|
"[PRICE DEBUG] Product %s [%s]: partial_price result = %s",
|
||||||
template.default_code or template.name,
|
template.default_code or template.name,
|
||||||
template.id,
|
template.id,
|
||||||
partial_price,
|
partial_price,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compute taxes to add
|
# Compute taxes to add
|
||||||
if not template.taxes_id:
|
if not template.taxes_id:
|
||||||
raise UserError(
|
raise UserError(
|
||||||
|
|
@ -93,7 +95,7 @@ class ProductTemplate(models.Model):
|
||||||
)
|
)
|
||||||
% template.name
|
% template.name
|
||||||
)
|
)
|
||||||
|
|
||||||
base_price = partial_price.get("value", 0.0) or 0.0
|
base_price = partial_price.get("value", 0.0) or 0.0
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"[PRICE DEBUG] Product %s [%s]: base_price from pricelist = %.2f, last_purchase_price = %.2f",
|
"[PRICE DEBUG] Product %s [%s]: base_price from pricelist = %.2f, last_purchase_price = %.2f",
|
||||||
|
|
@ -102,10 +104,10 @@ class ProductTemplate(models.Model):
|
||||||
base_price,
|
base_price,
|
||||||
template.last_purchase_price_received,
|
template.last_purchase_price_received,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use base price without taxes (taxes will be calculated automatically on sales)
|
# Use base price without taxes (taxes will be calculated automatically on sales)
|
||||||
theoretical_price = base_price
|
theoretical_price = base_price
|
||||||
|
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"[PRICE] Product %s [%s]: Computed theoretical price %.2f (previous: %.2f, current list_price: %.2f)",
|
"[PRICE] Product %s [%s]: Computed theoretical price %.2f (previous: %.2f, current list_price: %.2f)",
|
||||||
template.default_code or template.name,
|
template.default_code or template.name,
|
||||||
|
|
@ -131,11 +133,14 @@ class ProductTemplate(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
def action_update_list_price(self):
|
def action_update_list_price(self):
|
||||||
|
updated_products = []
|
||||||
|
skipped_products = []
|
||||||
|
|
||||||
for template in self:
|
for template in self:
|
||||||
if template.last_purchase_price_compute_type != "manual_update":
|
if template.last_purchase_price_compute_type != "manual_update":
|
||||||
# First compute the theoretical price
|
# First compute the theoretical price
|
||||||
template._compute_theoritical_price()
|
template._compute_theoritical_price()
|
||||||
|
|
||||||
old_price = template.list_price
|
old_price = template.list_price
|
||||||
template.list_price = template.list_price_theoritical
|
template.list_price = template.list_price_theoritical
|
||||||
template.last_purchase_price_updated = False
|
template.last_purchase_price_updated = False
|
||||||
|
|
@ -146,6 +151,53 @@ class ProductTemplate(models.Model):
|
||||||
old_price,
|
old_price,
|
||||||
template.list_price,
|
template.list_price,
|
||||||
)
|
)
|
||||||
|
updated_products.append(
|
||||||
|
(
|
||||||
|
template.name or template.default_code,
|
||||||
|
old_price,
|
||||||
|
template.list_price,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
skipped_products.append(template.name or template.default_code)
|
||||||
|
|
||||||
|
# Invalidate cache to refresh UI
|
||||||
|
self.invalidate_recordset(
|
||||||
|
["list_price", "list_price_theoritical", "last_purchase_price_updated"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build notification message
|
||||||
|
message = ""
|
||||||
|
if updated_products:
|
||||||
|
message += _("✓ Products updated: %s") % len(updated_products)
|
||||||
|
if len(updated_products) <= 5:
|
||||||
|
message += "\n\n"
|
||||||
|
for name, old, new in updated_products:
|
||||||
|
message += _("• %s: %.2f → %.2f\n") % (name, old, new)
|
||||||
|
|
||||||
|
if skipped_products:
|
||||||
|
if message:
|
||||||
|
message += "\n\n"
|
||||||
|
message += _("⚠ Skipped (manual update): %s") % len(skipped_products)
|
||||||
|
if len(skipped_products) <= 5:
|
||||||
|
message += "\n"
|
||||||
|
for name in skipped_products:
|
||||||
|
message += _("• %s\n") % name
|
||||||
|
|
||||||
|
if not updated_products and not skipped_products:
|
||||||
|
message = _("No products to update.")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.client",
|
||||||
|
"tag": "display_notification",
|
||||||
|
"params": {
|
||||||
|
"title": _("Price Update"),
|
||||||
|
"message": message,
|
||||||
|
"type": "success" if updated_products else "warning",
|
||||||
|
"sticky": False,
|
||||||
|
"next": {"type": "ir.actions.act_window_close"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def price_compute(
|
def price_compute(
|
||||||
self, price_type, uom=None, currency=None, company=False, date=False
|
self, price_type, uom=None, currency=None, company=False, date=False
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,14 @@
|
||||||
<xpath expr="//group[@name='group_standard_price']" position="after">
|
<xpath expr="//group[@name='group_standard_price']" position="after">
|
||||||
<group string="Automatic Price Update">
|
<group string="Automatic Price Update">
|
||||||
<field name="last_purchase_price_compute_type" />
|
<field name="last_purchase_price_compute_type" />
|
||||||
|
<button
|
||||||
|
name="action_update_list_price"
|
||||||
|
type="object"
|
||||||
|
string="Update Price"
|
||||||
|
class="oe_highlight"
|
||||||
|
icon="fa-refresh"
|
||||||
|
invisible="last_purchase_price_compute_type == 'manual_update'"
|
||||||
|
/>
|
||||||
<field
|
<field
|
||||||
name="last_purchase_price_received"
|
name="last_purchase_price_received"
|
||||||
widget="monetary"
|
widget="monetary"
|
||||||
|
|
@ -42,6 +50,14 @@
|
||||||
<xpath expr="//field[@name='list_price']" position="after">
|
<xpath expr="//field[@name='list_price']" position="after">
|
||||||
<field name="list_price_theoritical" optional="hide" />
|
<field name="list_price_theoritical" optional="hide" />
|
||||||
<field name="last_purchase_price_received" optional="hide" />
|
<field name="last_purchase_price_received" optional="hide" />
|
||||||
|
<field name="last_purchase_price_compute_type" column_invisible="1" />
|
||||||
|
<button
|
||||||
|
name="action_update_list_price"
|
||||||
|
type="object"
|
||||||
|
string="Update Price"
|
||||||
|
icon="fa-refresh"
|
||||||
|
invisible="last_purchase_price_compute_type == 'manual_update'"
|
||||||
|
/>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue