From 1bd3fab4dc3d754059decd6b735082ea2f458f7d Mon Sep 17 00:00:00 2001 From: luis Date: Mon, 15 Jan 2024 20:25:34 +0100 Subject: [PATCH] #15 new module product_update_price_last_purchase based on product_margin_classification to update sell prices on products when purchase price changes. --- .../__init__.py | 2 - .../__manifest__.py | 49 +++-- .../data/report_paperformat.xml | 20 -- .../models/__init__.py | 4 - .../models/ir_actions_report.py | 21 -- .../models/product_pricelist.py | 25 --- .../models/product_pricelist_item.py | 12 -- .../models/product_product.py | 47 ++++- .../models/product_template.py | 182 +++++++----------- .../models/res_company.py | 17 -- .../models/res_config.py | 36 +--- .../models/stock_move.py | 58 ++++-- .../report/product_barcode.xml | 74 ------- .../report/report_product_barcode.xml | 35 ---- .../report/report_product_shelf_tag.xml | 44 ----- .../views/actions.xml | 12 +- .../views/product_view.xml | 145 ++++++-------- .../views/res_config.xml | 32 +++ 18 files changed, 279 insertions(+), 536 deletions(-) delete mode 100644 product_update_price_last_purchase/data/report_paperformat.xml delete mode 100644 product_update_price_last_purchase/models/ir_actions_report.py delete mode 100644 product_update_price_last_purchase/models/product_pricelist.py delete mode 100644 product_update_price_last_purchase/models/product_pricelist_item.py delete mode 100644 product_update_price_last_purchase/models/res_company.py delete mode 100644 product_update_price_last_purchase/report/product_barcode.xml delete mode 100644 product_update_price_last_purchase/report/report_product_barcode.xml delete mode 100644 product_update_price_last_purchase/report/report_product_shelf_tag.xml create mode 100644 product_update_price_last_purchase/views/res_config.xml diff --git a/product_update_price_last_purchase/__init__.py b/product_update_price_last_purchase/__init__.py index 2ed39cb..0650744 100644 --- a/product_update_price_last_purchase/__init__.py +++ b/product_update_price_last_purchase/__init__.py @@ -1,3 +1 @@ from . import models -from . import report - diff --git a/product_update_price_last_purchase/__manifest__.py b/product_update_price_last_purchase/__manifest__.py index 8e94770..1159df3 100644 --- a/product_update_price_last_purchase/__manifest__.py +++ b/product_update_price_last_purchase/__manifest__.py @@ -1,32 +1,29 @@ { - 'name': 'Product Update Price From Last Purchase', - 'version': "16.0.1.0.0", - 'category': 'purchase', - 'summary' : 'Product Update Price From Last Purchase', - 'description' : """ + "name": "Product Update Price From Last Purchase", + "version": "16.0.1.0.0", + "category": "purchase", + "summary": """" Personaliza el comportamiento de Product para supermercados: - * setea los impuestos del proveedor al mismo tipo que el impuesto de venta. - * Actualiza el precio de venta según el precio de coste aplicado a una tarifa. - * Filtro para productos actualizados el precio de coste. + * Campo que guarda el último precio de compra en vez de usar standard_price + y no afectar a la valoración de inventario. + * Actualiza el precio de venta según el último precio de coste aplicado a una tarifa. + * Filtro para productos actualizados el último precio de coste. * Filtro para productos que necesitan etiqueta nueva. - * Campo que guarda el último precio de compra en vez de usar standard_price y no afectar a la valoración de inventario. - """, - 'author': 'Criptomart', - 'website': 'https://criptomart.net', - 'license': 'AGPL-3', - 'depends': [ - 'base', - 'product', - 'account', - 'stock_account', + """, + "author": "Criptomart", + "website": "https://criptomart.net", + "license": "AGPL-3", + "depends": [ + "account", + "stock_account", + "sale", + "product_margin_classification", + "product_print_category", ], - 'data': [ - 'views/actions.xml', - 'views/product_view.xml', - 'data/report_paperformat.xml', - 'report/report_product_shelf_tag.xml', - #'report/report_product_barcode.xml', - #'report/product_barcode.xml' + "data": [ + "views/actions.xml", + "views/product_view.xml", + "views/res_config.xml", ], - 'installable': True, + "installable": True, } diff --git a/product_update_price_last_purchase/data/report_paperformat.xml b/product_update_price_last_purchase/data/report_paperformat.xml deleted file mode 100644 index 03e74be..0000000 --- a/product_update_price_last_purchase/data/report_paperformat.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Barcodes stickers format - - A4 - 0 - 0 - Portrait - 10 - 5 - 8 - 8 - - 0 - 75 - - - diff --git a/product_update_price_last_purchase/models/__init__.py b/product_update_price_last_purchase/models/__init__.py index e23f31b..6440ced 100644 --- a/product_update_price_last_purchase/models/__init__.py +++ b/product_update_price_last_purchase/models/__init__.py @@ -1,8 +1,4 @@ from . import product_template from . import product_product -from . import res_company from . import res_config -from . import ir_actions_report from . import stock_move -from . import product_pricelist -from . import product_pricelist_item diff --git a/product_update_price_last_purchase/models/ir_actions_report.py b/product_update_price_last_purchase/models/ir_actions_report.py deleted file mode 100644 index 4faf6d1..0000000 --- a/product_update_price_last_purchase/models/ir_actions_report.py +++ /dev/null @@ -1,21 +0,0 @@ -import logging - -from odoo import api, fields, models - -_logger = logging.getLogger(__name__) - -class IrActionsReport(models.Model): - _inherit = 'ir.actions.report' - - def render_qweb_pdf(self, res_ids=None, data=None): - _logger.debug("render_qweb_pdf : %s -- %s" %(res_ids, data)) - if res_ids: - Model = self.env[self.model] - record_ids = Model.browse(res_ids) - wk_record_ids = Model - for record_id in record_ids: - if self.report_name == "product.report_producttemplatelabel": - record_id.to_print = False - return super(IrActionsReport, self).render_qweb_pdf(res_ids, data) - - diff --git a/product_update_price_last_purchase/models/product_pricelist.py b/product_update_price_last_purchase/models/product_pricelist.py deleted file mode 100644 index 15ffb29..0000000 --- a/product_update_price_last_purchase/models/product_pricelist.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (C) 2020: Criptomart (https://criptomart.net) -# @author Santi Noreña () -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import api, models - - -class ProductPricelist(models.Model): - _inherit = 'product.pricelist' - - def _compute_price_rule(self, products_qty_partner, date=False, uom_id=False): - ProductPricelistItem = self.env['product.pricelist.item'] - ProductProduct = self.env['product.product'] - res = super()._compute_price_rule(products_qty_partner, date=date, uom_id=uom_id) - new_res = res.copy() - item_id = [] - for product_id, values in res.items(): - if values[1]: - item_id = values[1] - if item_id: - item = ProductPricelistItem.browse(item_id) - if item.base == 'last_purchase_price': - product = ProductProduct.browse(product_id) - new_res[product_id] = (product.last_purchase_price, item_id) - return new_res diff --git a/product_update_price_last_purchase/models/product_pricelist_item.py b/product_update_price_last_purchase/models/product_pricelist_item.py deleted file mode 100644 index 36f1b80..0000000 --- a/product_update_price_last_purchase/models/product_pricelist_item.py +++ /dev/null @@ -1,12 +0,0 @@ -# Copyright (C) 2020: Criptomart (https://criptomart.net) -# @author Santi Noreña () -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). - -from odoo import fields, models - - -class ProductPricelistItem(models.Model): - _inherit = 'product.pricelist.item' - - base = fields.Selection(selection_add=[ - ('last_purchase_price', 'Last purchase price')]) diff --git a/product_update_price_last_purchase/models/product_product.py b/product_update_price_last_purchase/models/product_product.py index aa2d836..ca2d585 100644 --- a/product_update_price_last_purchase/models/product_product.py +++ b/product_update_price_last_purchase/models/product_product.py @@ -4,21 +4,54 @@ import logging -from odoo import models, fields, api +from odoo import api, fields, models _logger = logging.getLogger(__name__) + class ProductProduct(models.Model): _inherit = "product.product" - def write(self, vals): - if vals.get('last_purchase_price'): - vals['list_price_updated'] = True - return super(ProductProduct, self).write(vals) + @api.depends( + "standard_price", + "last_purchase_price_received", + "lst_price", + "margin_classification_id", + "margin_classification_id.markup", + "margin_classification_id.price_round", + "margin_classification_id.price_surcharge", + "product_tmpl_id.taxes_id", + "product_tmpl_id.list_price", + ) + def _compute_theoretical_multi(self): + res = super()._compute_theoretical_multi() + for product in self: + if product.last_purchase_price_received != 0: + ( + product.margin_state, + product.theoretical_price, + product.theoretical_difference, + ) = self._get_margin_info( + product.margin_classification_id, + product.taxes_id, + product.name, + product.last_purchase_price_received, + product.lst_price, + ) + return res + + def use_theoretical_price(self): + res = super().use_theoretical_price() + for product in self: + product.to_print_label = True + return res class ProductPackaging(models.Model): _inherit = "product.packaging" - qty = fields.Float('Contained Quantity', help="The total number of products you can have per pallet or box.", digits='Product Unit of Measure') - + qty = fields.Float( + "Contained Quantity", + help="The total number of products you can have per pallet or box.", + digits="Product Unit of Measure", + ) diff --git a/product_update_price_last_purchase/models/product_template.py b/product_update_price_last_purchase/models/product_template.py index d94e7e1..a6c766e 100644 --- a/product_update_price_last_purchase/models/product_template.py +++ b/product_update_price_last_purchase/models/product_template.py @@ -3,136 +3,88 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). import logging -import math -from odoo import exceptions, models, fields, api, _ -from odoo.exceptions import UserError +from odoo import api, fields, models _logger = logging.getLogger(__name__) +MARGIN_STATE_SELECTION = [ + ("correct", "Correct Margin"), + ("too_cheap", "Too Cheap"), + ("too_expensive", "Too Expensive"), +] + + class ProductTemplate(models.Model): _inherit = "product.template" - def _compute_theoritical_price(self): - partner = self.env['res.users'].browse(self.env.uid).partner_id - pricelist_obj = self.env['product.pricelist'] - pricelist_id = self.env['ir.config_parameter'].sudo().get_param('product_update_price_last_purchase.product_pricelist_automatic') or False - pricelist = pricelist_obj.browse(int(pricelist_id)) - _logger.debug("Calculating price with PriceList : %s" %pricelist.name) - if pricelist: - for template in self: - if template.name and template.id and template.product_variant_id and template.last_purchase_price_compute_type != 'manual_update': - partial_price = pricelist.get_product_price(template.product_variant_id, "1", partner) - _logger.debug("partial_price : %s" %partial_price) - # Suma el IVA - for tax in template.taxes_id: - partial_price = partial_price * ( 1 + ( tax.amount / 100) ) - _logger.debug("partial_price after taxes : %s" %partial_price) - # Redondea a 0,05 de precisión - template.list_price = round(round(partial_price / 0.05) * 0.05, -int(math.floor(math.log10(0.05)))) - _logger.debug("final price : %s" %template.list_price) - template.list_price_updated = False - else: - raise UserError(_('Not found a valid pricelist to compute sale price. Check configuration in General Settings.')) - return False - - list_price = fields.Float( - string="Sale price with VAT", - help="Price calculated according to the configured pricelist, including VAT.", - digits='Product Price' + margin_state = fields.Selection( + string="Theoretical Price State", + compute="_compute_theoretical_multi_template", + selection=MARGIN_STATE_SELECTION, + store=True, ) - list_price_updated = fields.Boolean( - string="Last purchase price updated", - help="The last cost price has been updated and you need to update the selling price in the database, shelves and scales.", - default=False, - readonly=True - ) - - list_price_automatic = fields.Boolean( - string="Automatic Sale Price", - help="Automatic computation of the PVP from the cost price and the rate defined in the configuration.", - default=True - ) - - to_print = fields.Boolean( + to_print_label = fields.Boolean( string="To print label", - help="The sale price has been updated on this product and needs to be updated on the shelf.", + help="""The sale price has been updated on this product + and needs to be updated on the shelf.""", default=False, - readonly=True + readonly=True, ) - last_purchase_price = fields.Float( + last_purchase_price_received = fields.Float( string="Last purchase price", - help="The last price at which the product was purchased. It is used as the base price field for calculating the product sale price.", - #readonly=True, - digits='Product Price' + help="The last price at which the product was purchased. " + "It is used as the base price field for calculating the product sale price.", + readonly=True, + digits="Product Price", ) - - last_purchase_price_compute_type = fields.Selection([ - ('without_discounts', 'Without discounts'), - ('with_discount', 'First discount'), - ('with_three_discounts', 'Triple discount'), - ('manual_update', 'Manual update')], + + last_purchase_price_received_compute_type = fields.Selection( + [ + ("without_discounts", "Without discounts"), + ("with_discount", "First discount"), + ("with_three_discounts", "Triple discount"), + ("manual_update", "Manual update"), + ], string="Last purchase price calculation type", - help='Choose whether discounts should influence the calculation of the last purchase price. Select Never update for manual configuration of cost and sale prices.\n' - '\n* Without discounts: does not take into account discounts when updating the last purchase price.\n' - '* First discount: take into account only first discount when updating the last purchase price.\n' - '* Triple discount: take into account all discounts when updating the last purchase price. Needs "Purchase Triple Discount" OCA module.\n' - '* Manual update: Select this for manual configuration of cost and sale price. The sales price will not be calculated automatically.', - default='without_discounts', - required=True + help=""" + Choose whether discounts should influence the calculation of the last purchase price. + Select Never update for manual configuration of cost and sale prices.\n + * Without discounts: does not take into account discounts when updating + the last purchase price.\n + * First discount: take into account only first discount when updating + the last purchase price.\n + * Triple discount: take into account all discounts when updating + the last purchase price. + Needs "Purchase Triple Discount" OCA module.\n + * Manual update: Select this for manual configuration of cost and sale price. + The sales price will not be calculated automatically. + """, + default="without_discounts", + required=True, ) - @api.model - def create(self, vals): - # Set supplier tax same than customer tax - if vals.get('taxes_id'): - tax = self.get_supplier_tax(vals.get('taxes_id')) - vals['supplier_taxes_id'] = tax - vals['list_price_updated'] = True - return super(ProductTemplate, self).create(vals) - - def write(self, vals): - # Set supplier tax same than customer tax - if vals.get('taxes_id'): - tax = self.get_supplier_tax(vals.get('taxes_id')) - vals['supplier_taxes_id'] = tax - # Mark if product needs list price update - if vals.get('last_purchase_price'): - vals['list_price_updated'] = True - # Mark if product needs update the shelf tag - if vals.get('list_price'): - vals['to_print'] = True - return super(ProductTemplate, self).write(vals) - - def get_supplier_tax(self, taxes_id): - tax_obj = self.env['account.tax'] - tax_res = [] - if taxes_id: - if "6, False, []" not in taxes_id: - for tupla in taxes_id: - a, b, c = tupla - if c: - for i in c: - tax = tax_obj.browse(i) - val = tax.amount - if val == 21: - record_id = self.env.ref('l10n_es.1_account_tax_template_p_iva21_bc').id - elif val == 10: - record_id = self.env.ref('l10n_es.1_account_tax_template_p_iva10_bc').id - elif val == 4: - record_id = self.env.ref('l10n_es.1_account_tax_template_p_iva4_bc').id - else: - record_id = self.env['account.tax'].search([ - ('amount','=', val), - ('active','=', True), - ('type_tax_use','=','purchase') - ], - limit=1).id - tax_res.append(record_id) - - return [(6, 0, tax_res)] - - - + @api.onchange( + "last_purchase_price_received", + "standard_price", + "taxes_id", + "margin_classification_id", + "list_price", + ) + def _onchange_standard_price(self): + res = super()._onchange_standard_price() + if self.last_purchase_price_received != 0: + ( + self.margin_state, + self.theoretical_price, + self.theoretical_difference, + ) = self.env["product.product"]._get_margin_info( + self.margin_classification_id, + self.taxes_id, + self.name, + self.last_purchase_price_received, + self.list_price, + ) + return res diff --git a/product_update_price_last_purchase/models/res_company.py b/product_update_price_last_purchase/models/res_company.py deleted file mode 100644 index 4d7b39b..0000000 --- a/product_update_price_last_purchase/models/res_company.py +++ /dev/null @@ -1,17 +0,0 @@ -#import logging - -from odoo import models, fields, api - -#_logger = logging.getLogger(__name__) - -class ResCompany(models.Model): - _inherit = "res.company" - - product_pricelist_automatic = fields.Many2one( - comodel_name='product.pricelist', - string='Pricelist applied to the automatic selling price', - help='Rate that applies to all products that update the selling price automatically.', - ) - - - diff --git a/product_update_price_last_purchase/models/res_config.py b/product_update_price_last_purchase/models/res_config.py index 5aa1510..a44ac0b 100644 --- a/product_update_price_last_purchase/models/res_config.py +++ b/product_update_price_last_purchase/models/res_config.py @@ -1,30 +1,12 @@ -from odoo import models, fields, api +from odoo import fields, models + class ResConfigSettings(models.TransientModel): - _inherit = 'res.config.settings' - - product_pricelist_automatic = fields.Many2one( - related='company_id.product_pricelist_automatic', - comodel_name='product.pricelist', - string='Pricelist applied to automatic calculation of sales price', - readonly=False, - ) - - @api.model - def get_values(self): - res = super(ResConfigSettings, self).get_values() - config_obj = self.env["ir.config_parameter"] - product_pricelist_automatic = config_obj.sudo().get_param( - "product_update_price_last_purchase.product_pricelist_automatic", default=0) - return res - - def set_values(self): - super(ResConfigSettings, self).set_values() - config_obj = self.env["ir.config_parameter"] - config_obj.sudo().set_param( - "product_update_price_last_purchase.product_pricelist_automatic", - int(self.product_pricelist_automatic.id) - ) - - + _inherit = "res.config.settings" + product_pricelist_automatic = fields.Many2one( + required=False, + comodel_name="product.pricelist", + string="Pricelist applied to automatic calculation of sales price", + config_parameter="product_update_price_last_purchase.product_pricelist_automatic", + ) diff --git a/product_update_price_last_purchase/models/stock_move.py b/product_update_price_last_purchase/models/stock_move.py index 41a0b5e..aad3091 100644 --- a/product_update_price_last_purchase/models/stock_move.py +++ b/product_update_price_last_purchase/models/stock_move.py @@ -5,28 +5,56 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import logging -import math -from odoo import exceptions, models, fields, api, _ -from odoo.tools import float_is_zero, float_round, float_compare +from odoo import models +from odoo.tools import float_compare, float_is_zero, float_round _logger = logging.getLogger(__name__) class StockMove(models.Model): - _inherit = 'stock.move' + _inherit = "stock.move" def product_price_update_before_done(self): - super(StockMove, self).product_price_update_before_done() - for move in self.filtered(lambda move: move.location_id.usage == 'supplier'): - if move.product_id.last_purchase_price_compute_type == 'with_three_discounts': - price_updated = float_round(move.purchase_line_id.price_subtotal / move.purchase_line_id.product_qty, precision_digits=2) - elif move.product_id.last_purchase_price_compute_type == 'with_discount': - price_updated = float_round(move.purchase_line_id.price_unit * (1 - move.purchase_line_id.discount / 100), precision_digits=2) + res = super(StockMove, self).product_price_update_before_done() + for move in self.filtered(lambda move: move.location_id.usage == "supplier"): + if ( + move.product_id.last_purchase_price_received_compute_type + == "with_three_discounts" + ): + price_updated = float_round( + move.purchase_line_id.price_subtotal + / move.purchase_line_id.product_qty, + precision_digits=2, + ) + elif ( + move.product_id.last_purchase_price_received_compute_type + == "with_discount" + ): + price_updated = float_round( + move.purchase_line_id.price_unit + * (1 - move.purchase_line_id.discount / 100), + precision_digits=2, + ) else: price_updated = move.purchase_line_id.price_unit - - if float_compare(move.product_id.last_purchase_price, price_updated, precision_digits=2) and not float_is_zero(move.quantity_done, precision_digits=3): - _logger.info("Update last_purchase_price: %s for product %s Previous price: %s" % (price_updated, move.product_id.default_code, move.product_id.last_purchase_price)) - # Write the last purchase price, as SUPERUSER_ID because a warehouse manager may not have the right to write on products - move.product_id.with_context(force_company=move.company_id.id).sudo().write({'last_purchase_price': price_updated}) + + if float_compare( + move.product_id.last_purchase_price_received, + price_updated, + precision_digits=2, + ) and not float_is_zero(move.quantity_done, precision_digits=3): + _logger.info( + "Update last_purchase_price_received: %s for product %s Previous price: %s" + % ( + price_updated, + move.product_id.default_code, + move.product_id.last_purchase_price_received, + ) + ) + # Write last purchase price as SUPERUSER_ID + # warehouse manager may not have the right to write on products + move.product_id.with_company(move.company_id.id).sudo().write( + {"last_purchase_price_received": price_updated} + ) + return res diff --git a/product_update_price_last_purchase/report/product_barcode.xml b/product_update_price_last_purchase/report/product_barcode.xml deleted file mode 100644 index 1cbce8c..0000000 --- a/product_update_price_last_purchase/report/product_barcode.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/product_update_price_last_purchase/report/report_product_barcode.xml b/product_update_price_last_purchase/report/report_product_barcode.xml deleted file mode 100644 index e7a69fc..0000000 --- a/product_update_price_last_purchase/report/report_product_barcode.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - diff --git a/product_update_price_last_purchase/report/report_product_shelf_tag.xml b/product_update_price_last_purchase/report/report_product_shelf_tag.xml deleted file mode 100644 index 399d3d4..0000000 --- a/product_update_price_last_purchase/report/report_product_shelf_tag.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - diff --git a/product_update_price_last_purchase/views/actions.xml b/product_update_price_last_purchase/views/actions.xml index b95ef88..04c6e36 100644 --- a/product_update_price_last_purchase/views/actions.xml +++ b/product_update_price_last_purchase/views/actions.xml @@ -1,24 +1,23 @@ - + - Update Sales Price - - + + code - records._compute_theoritical_price() + records.use_theoretical_price() - - diff --git a/product_update_price_last_purchase/views/product_view.xml b/product_update_price_last_purchase/views/product_view.xml index 2201e36..e534cbf 100644 --- a/product_update_price_last_purchase/views/product_view.xml +++ b/product_update_price_last_purchase/views/product_view.xml @@ -1,91 +1,66 @@ - + - + - - - product.list.price.automatic.form - product.template - form - - - - - - - - - - - - - - + + + product.list.price.automatic.form + product.template + form + + + + + + + + - - - product_update_price_last_purchase.res.config.settings.form - res.config.settings - - - - -

Default pricelists for Coops

-
-
-
-
-
-
-
+ + product.template.product.tree + product.template + + + + + + + + - - - view.product.search.form.inherit.updated - product.template - - - - - - - - - - - - product_update_price_last_purchase.product.search.form.inherit.tags - product.template - - - - - - - - + + + view.product.search.form.inherit.updated + product.template + + + + + + + + + -
+
diff --git a/product_update_price_last_purchase/views/res_config.xml b/product_update_price_last_purchase/views/res_config.xml new file mode 100644 index 0000000..1903939 --- /dev/null +++ b/product_update_price_last_purchase/views/res_config.xml @@ -0,0 +1,32 @@ + + + + + product.update.price.last.purchase.res.config.settings.form + res.config.settings + + + + +
+
+
+
+
+
+
+
+