From a08e3b5b10102adb3c8692a5870352c3bcaa2494 Mon Sep 17 00:00:00 2001 From: "Alexandre Fayolle @ camptocamp" Date: Tue, 15 May 2012 14:42:21 +0200 Subject: [PATCH] [IMP] product_cost_incl_bom, product_get_cost_field, sale_markup: extracted code to new modules extracted the bits about the computation of product cost with BoM from sale_markup to product_cost_incl_bom (lp:c2c-addons/6.1 rev 40.1.13) --- product_cost_incl_bom/__openerp__.py | 7 +- .../product_cost_incl_bom.py | 91 ++++---- product_get_cost_field/__init__.py | 2 +- product_get_cost_field/__openerp__.py | 18 +- .../product_get_cost_field.py | 21 +- product_get_cost_field/product_view.xml | 10 +- product_historical_margin/__init__.py | 24 --- product_historical_margin/__openerp__.py | 65 ------ .../account_invoice_view.xml | 81 ------- product_historical_margin/invoice.py | 204 ------------------ .../product_historical_margin.py | 114 ---------- product_historical_margin/product_view.xml | 51 ----- product_historical_margin/wizard/__init__.py | 22 -- .../wizard/historical_margin.py | 90 -------- .../wizard/historical_margin_view.xml | 50 ----- product_historical_margin_report/__init__.py | 22 -- .../__openerp__.py | 42 ---- .../product_historical_margin_report.py | 169 --------------- .../product_historical_margin_report_view.xml | 48 ----- product_standard_margin/__init__.py | 22 -- product_standard_margin/__openerp__.py | 58 ----- product_standard_margin/product_std_margin.py | 106 --------- .../product_std_margin_view.xml | 21 -- 23 files changed, 75 insertions(+), 1263 deletions(-) delete mode 100644 product_historical_margin/__init__.py delete mode 100644 product_historical_margin/__openerp__.py delete mode 100644 product_historical_margin/account_invoice_view.xml delete mode 100644 product_historical_margin/invoice.py delete mode 100644 product_historical_margin/product_historical_margin.py delete mode 100644 product_historical_margin/product_view.xml delete mode 100644 product_historical_margin/wizard/__init__.py delete mode 100644 product_historical_margin/wizard/historical_margin.py delete mode 100644 product_historical_margin/wizard/historical_margin_view.xml delete mode 100644 product_historical_margin_report/__init__.py delete mode 100644 product_historical_margin_report/__openerp__.py delete mode 100644 product_historical_margin_report/product_historical_margin_report.py delete mode 100644 product_historical_margin_report/product_historical_margin_report_view.xml delete mode 100644 product_standard_margin/__init__.py delete mode 100644 product_standard_margin/__openerp__.py delete mode 100644 product_standard_margin/product_std_margin.py delete mode 100644 product_standard_margin/product_std_margin_view.xml diff --git a/product_cost_incl_bom/__openerp__.py b/product_cost_incl_bom/__openerp__.py index 135eef3c..19f2840a 100644 --- a/product_cost_incl_bom/__openerp__.py +++ b/product_cost_incl_bom/__openerp__.py @@ -22,14 +22,11 @@ 'version' : '0.1', 'author' : 'Camptocamp', 'maintainer': 'Camptocamp', - 'category': 'Products', + 'category': 'Hidden', 'complexity': "normal", # easy, normal, expert 'depends' : ['product_get_cost_field', 'mrp'], - 'description': """ - Compute product cost price by recursively summing parts cost prices according to product BOM. It takes into - account the BoM costing (cost per cycle and so...). If no BOM define for a product, the cost_price is always - equal to the standard_price field of the product, so we always have a value to base our reporting on. + 'description': """Compute product base price by recursively summing parts base prices according to product BOM """, 'website': 'http://www.camptocamp.com/', 'init_xml': [], diff --git a/product_cost_incl_bom/product_cost_incl_bom.py b/product_cost_incl_bom/product_cost_incl_bom.py index 0767f1f2..db72ec3b 100644 --- a/product_cost_incl_bom/product_cost_incl_bom.py +++ b/product_cost_incl_bom/product_cost_incl_bom.py @@ -26,15 +26,25 @@ class Product(Model): _inherit = 'product.product' + ## def get_cost_field(self): + ## """overriden from product_get_cost_field""" + ## return self.standard_price # XXX or a string? + + + def _compute_purchase_price(self, cursor, user, ids, + pricelist=None, product_uom=None, - bom_properties=None, - context=None): + bom_properties=None): ''' - Compute the purchase price, taking into account sub products and routing + Compute the purchase price + + As it explodes the sub product on 1 level + + This is not implemented for BoM having sub BoM producing more than 1 + product qty. + Rewrite _compute_purchase_price and remove mrp constraint to fix this. ''' - if context is None: - context = {} if bom_properties is None: bom_properties = [] bom_obj = self.pool.get('mrp.bom') @@ -48,51 +58,52 @@ def _compute_purchase_price(self, cursor, user, ids, # Workaround for first loading in V5 as some columns are not created #if not hasattr(pr, 'standard_price'): return False bom_id = bom_obj._bom_find(cursor, user, pr.id, product_uom=None, properties=bom_properties) - if not bom_id: # no BoM: use standard_price + + if bom_id: + bom = bom_obj.browse(cursor, user, bom_id) + + sub_products, routes = bom_obj._bom_explode(cursor, user, bom, + factor=1, + properties=bom_properties, + addthis=True) + + res[pr.id] = 0.0 + for sub_prod_dict in sub_products: + sub_product = self.browse(cursor, user, sub_product_dict['product_id']) + std_price = sub_product.standard_price + qty = uom_obj._compute_qty(cursor, user, + from_uom_id = sub_product_dict['product_uom'], + qty = sub_product_dict['product_qty'], + to_uom_id = sub_product.uom_po_id.id) + + res[pr.id] += std_price * qty + # TODO alf use routes to compute cost of manufacturing (following is from product_extended) + ## if bom.routing_id: + ## for wline in bom.routing_id.workcenter_lines: + ## wc = wline.workcenter_id + ## cycle = wline.cycle_nbr + ## hour = (wc.time_start + wc.time_stop + cycle * wc.time_cycle) * (wc.time_efficiency or 1.0) + ## price += wc.costs_cycle * cycle + wc.costs_hour * hour + ## price = self.pool.get('product.uom')._compute_price(cr,uid,bom.product_uom.id,price,bom.product_id.uom_id.id) + + else: res[pr.id] = pr.standard_price - continue - bom = bom_obj.browse(cursor, user, bom_id) - sub_products, routes = bom_obj._bom_explode(cursor, user, bom, - factor=1, - properties=bom_properties, - addthis=True) - price = 0. - for sub_product_dict in sub_products: - sub_product = self.browse(cursor, user, sub_product_dict['product_id']) - std_price = sub_product.standard_price - qty = uom_obj._compute_qty(cursor, user, - from_uom_id = sub_product_dict['product_uom'], - qty = sub_product_dict['product_qty'], - to_uom_id = sub_product.uom_po_id.id) - price += std_price * qty - if bom.routing_id: - for wline in bom.routing_id.workcenter_lines: - wc = wline.workcenter_id - cycle = wline.cycle_nbr - hour = (wc.time_start + wc.time_stop + cycle * wc.time_cycle) * (wc.time_efficiency or 1.0) - price += wc.costs_cycle * cycle + wc.costs_hour * hour - price /= bom.product_qty - price = uom_obj._compute_price(cursor, user, bom.product_uom.id, price, bom.product_id.uom_id.id) - res[pr.id] = price + return res - def get_cost_field(self, cr, uid, ids, context=None): - return self._cost_price(cr, uid, ids, '', [], context) - def _cost_price(self, cr, uid, ids, field_name, arg, context=None): + def _cost_price(self, cr, uid, ids, context=None): if context is None: context = {} + pricelist = context.get('pricelist') product_uom = context.get('product_uom') bom_properties = context.get('properties') - res = self._compute_purchase_price(cr, uid, ids, product_uom, bom_properties) - return res - + return _compute_purchase_price(cr, uid, ids, pricelist, product_uom, bom_properties) + _columns = { 'cost_price': fields.function(_cost_price, - method=True, + methode=True, string='Cost Price (incl. BoM)', digits_compute = dp.get_precision('Sale Price'), - help="The cost price is the standard price or, if the product has a bom, " - "the sum of all standard price of its components. it take also care of the " - "bom costing like cost per cylce.") + help="The cost is the standard price") } diff --git a/product_get_cost_field/__init__.py b/product_get_cost_field/__init__.py index 2a47e6e5..bf559d97 100644 --- a/product_get_cost_field/__init__.py +++ b/product_get_cost_field/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Author: Alexandre Fayolle, Joel Grand-Guillaume +# Author: Alexandre Fayolle # Copyright 2012 Camptocamp SA # # This program is free software: you can redistribute it and/or modify diff --git a/product_get_cost_field/__openerp__.py b/product_get_cost_field/__openerp__.py index 7ad540d8..7b38bb61 100644 --- a/product_get_cost_field/__openerp__.py +++ b/product_get_cost_field/__openerp__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Author: Alexandre Fayolle, Joel Grand-Guillaume +# Author: Alexandre Fayolle # Copyright 2012 Camptocamp SA # # This program is free software: you can redistribute it and/or modify @@ -22,23 +22,17 @@ 'version' : '0.1', 'author' : 'Camptocamp', 'maintainer': 'Camptocamp', - 'category': 'Products', + 'category': 'Hidden', 'complexity': "normal", # easy, normal, expert 'depends' : ['product', ], - 'description': """ - Provides an overridable method on product which compute the cost_price field of a product. - By default it just return the value of standard_price field, but using the product_cost_incl_bom - module, it will return the costing from the bom. - - As it is a generic module, you can also setup your own way of computing the cost_price for your - product. - - All our modules to compute margin are based on it, so you'll ba able to use them in your own way. + 'description': """Provides an overridable method on product which returns the name of + the field used to compute the product cost, allowing for customization + of product cost computation. """, 'website': 'http://www.camptocamp.com/', 'init_xml': [], - 'update_xml': ['product_view.xml'], + 'update_xml': [], 'demo_xml': [], 'tests': [], 'installable': True, diff --git a/product_get_cost_field/product_get_cost_field.py b/product_get_cost_field/product_get_cost_field.py index 9a12b8b7..1a0196e3 100644 --- a/product_get_cost_field/product_get_cost_field.py +++ b/product_get_cost_field/product_get_cost_field.py @@ -18,33 +18,30 @@ # along with this program. If not, see . # ############################################################################## -import logging from openerp.osv.orm import Model from openerp.osv import fields import decimal_precision as dp - class Product(Model): _inherit = 'product.product' - def _cost_price(self, cr, uid, ids, field_name, arg, context=None): - if context is None: - context = {} - logger = logging.getLogger('product.get_cost_field') - logger.debug("get cost field _cost_price %s, %s, %s", field_name, arg, context) + ## def get_cost_field(self): + ## """override in subclass if you need to setup a custom way of computing the standard price""" + ## " XXX fonction statique qui renvoie le prix standard en fonction d'une liste d'id de product" + ## return self.standard_price # XXX or a string? + + + def _cost_price(self, cr, uid, ids, context=None): res = {} for product in self.browse(cr, uid, ids): res[product.id] = product.standard_price return res - def get_cost_field(self, cr, uid, ids, context=None): - return self._cost_price(cr, uid, ids, '', [], context) - _columns = { 'cost_price': fields.function(_cost_price, - method=True, + methode=True, string='Cost Price', digits_compute = dp.get_precision('Sale Price'), - help="The cost price is the standard price unless you install the product_cost_incl_bom module.") + help="The cost is the standard price") } diff --git a/product_get_cost_field/product_view.xml b/product_get_cost_field/product_view.xml index 0a872ab1..bc57e478 100644 --- a/product_get_cost_field/product_view.xml +++ b/product_get_cost_field/product_view.xml @@ -8,19 +8,21 @@ - + - + product.get_cost_field.view.tree tree product.product - - + + diff --git a/product_historical_margin/__init__.py b/product_historical_margin/__init__.py deleted file mode 100644 index a774388d..00000000 --- a/product_historical_margin/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Alexandre Fayolle, Joel Grand-Guillaume -# Copyright 2012 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from . import product_historical_margin -from . import invoice -from . import wizard diff --git a/product_historical_margin/__openerp__.py b/product_historical_margin/__openerp__.py deleted file mode 100644 index 9fc328d6..00000000 --- a/product_historical_margin/__openerp__.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Alexandre Fayolle, Joel Grand-Guillaume -# Copyright 2012 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -{'name' : 'Product Historical Margin', - 'version' : '0.2', - 'author' : 'Camptocamp', - 'maintainer': 'Camptocamp', - 'category': 'Accounting & Finance', - 'complexity': "normal", # easy, normal, expert - 'depends' : ['product_get_cost_field', - 'product_standard_margin', - 'account', - 'sale', - ], - 'description': """ - This module will store in the invoice line all the historical informations to allow you to compute - your margin on product. We will always work in company currency and in invoice currency (to respect the OpenERP - philosophy, as it is done in accounting entries for example). You now have the possibility to open a list - view of your products and having the margin computed for a given period of time. - - In the product form, you also will have a computed field that show you the info based on all invoice lines - historic. - - You can use this module in conjonction with the product_cost_incl_bom one to have the right margin - computed from BOM costing. - - WARNING: - - 1) The amount of the unit price in company currency in the invoice line is converted with the currency - rate of the date of the creation/modification of the invoice line (not the invoice date). This choice - has been made mainly because the cost price of the product is known at the invoice creation, but we don't - have it at the date of the invoice (no historical values in the cost price...) ! - - - """, - 'website': 'http://www.camptocamp.com/', - 'init_xml': [], - 'update_xml': ["account_invoice_view.xml", - "wizard/historical_margin_view.xml", - "product_view.xml", - ], - 'demo_xml': [], - 'tests': [], - 'installable': True, - 'auto_install': False, - 'license': 'AGPL-3', - 'application': False - } diff --git a/product_historical_margin/account_invoice_view.xml b/product_historical_margin/account_invoice_view.xml deleted file mode 100644 index 6eb99e89..00000000 --- a/product_historical_margin/account_invoice_view.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - account.invoice.line.from.product.tree - account.invoice.line - tree - 20 - - - - - - - - - - - - - - - - - - - - - - - - - - - - account.invoice.line.select - account.invoice.line - search - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/product_historical_margin/invoice.py b/product_historical_margin/invoice.py deleted file mode 100644 index e316b22e..00000000 --- a/product_historical_margin/invoice.py +++ /dev/null @@ -1,204 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Alexandre Fayolle, Joel Grand-Guillaume -# Copyright 2012 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -import logging -from openerp.osv.orm import Model -from osv import fields -import decimal_precision as dp - -class account_invoice(Model): - _inherit = 'account.invoice' - - def _refund_cleanup_lines(self, cr, uid, lines): - for line in lines: - del line['invoice_user_id'] - return super(account_invoice, self)._refund_cleanup_lines(cr, uid, lines) - - -class account_invoice_line(Model): - """ - The goal on the invoice line model is only to store the minimum needed values to - allow analysis and computation of margin at product level. - Later, we'll be able to provide as well a view based on an invoice line SQL view, - so user will be allowed to play with filter and group by. - """ - _inherit = 'account.invoice.line' - - def _compute_line_values(self, cr, uid, ids, field_names, arg, context=None): - """ - Compute cost_price, cost_price in company currency and subtotal in company currency - that will be used for margin analysis. This method is called by the function fields. - - Those values will be stored, so we'll be able to use them in analysis. - - WARNING !! All subtotal in company currency will be with the right sign. For example, - if I have a customer refund, the sign will be - For all infos in invoice currency, we - kept the standard logic => always positif. - - :return dict of dict of the form : - {INT Line ID : { - float subtotal_cost_price_company, - float subtotal_cost_price, - float subtotal_company - float margin_absolute - }} - """ - if context is None: - context = {} - res = {} - if not ids: - return res - logger = logging.getLogger('product_historical_margin') - user_obj = self.pool.get('res.users') - company_currency_id = user_obj.browse(cr, uid, uid).company_id.currency_id.id - currency_obj = self.pool.get('res.currency') - for line_id in ids: - res[line_id] = { - 'subtotal_cost_price_company': 0.0, - 'subtotal_cost_price': 0.0, - 'subtotal_company': 0.0, - 'margin_absolute': 0.0, - 'margin_relative': 0.0, - } - for obj in self.browse(cr, uid, ids): - product = obj.product_id - if obj.invoice_id.currency_id is None: - currency_id = company_currency_id - else: - currency_id = obj.invoice_id.currency_id.id - if obj.invoice_type in ('out_refund','in_invoice'): - factor = -1. - else: - factor = 1. - - subtotal_cost_price_company = factor * product.cost_price * obj.quantity - # Convert price_subtotal from invoice currency to company currency - subtotal_company = factor * currency_obj.compute(cr, uid, currency_id, - company_currency_id, - obj.price_subtotal, - round=False, - context=context) - # Compute the subtotal_cost_price values from company currency in invoice currency - subtotal_cost_price = currency_obj.compute(cr, uid, company_currency_id, - currency_id, - subtotal_cost_price_company, - round=False, - context=context) - margin_absolute = subtotal_company - subtotal_cost_price_company - if subtotal_company == 0: - margin_relative = 999. - else: - margin_relative = (margin_absolute / subtotal_company) * 100 - res[obj.id] = { - 'subtotal_cost_price_company': subtotal_cost_price_company, - 'subtotal_cost_price': subtotal_cost_price, - 'subtotal_company': subtotal_company, - 'margin_absolute': margin_absolute, - 'margin_relative': margin_relative, - } - logger.debug("The following values has been computed for product ID %d: subtotal_cost_price=%f" - "subtotal_cost_price_company=%f, subtotal_company=%f", product.id, subtotal_cost_price, - subtotal_cost_price_company, subtotal_company) - return res - - def _recalc_margin(self, cr, uid, ids, context=None): - return ids - - def _recalc_margin_parent(self, cr, uid, ids, context=None): - if context is None: - context = {} - res=[] - for inv in self.browse(cr,uid,ids): - for line in inv.invoice_line: - res.append(line.id) - return res - - _col_store = {'account.invoice.line': (_recalc_margin, - ['price_unit', 'product_id', 'discount','invoice_line_tax_id'], - 20), - 'account.invoice': (_recalc_margin_parent, - ['currency_id'], - 20), - } - - _columns = { - 'subtotal_cost_price_company': fields.function(_compute_line_values, method=True, readonly=True,type='float', - string='Cost', - multi='product_historical_margin', - store=_col_store, - digits_compute=dp.get_precision('Account'), - help="The cost subtotal of the line at the time of the creation of the invoice, " - "express in the company currency."), - 'subtotal_cost_price': fields.function(_compute_line_values, method=True, readonly=True,type='float', - string='Cost in currency', - multi='product_historical_margin', - store=_col_store, - digits_compute=dp.get_precision('Account'), - help="The cost subtotal of the line at the time of the creation of the invoice, " - "express in the invoice currency."), - 'subtotal_company': fields.function(_compute_line_values, method=True, readonly=True,type='float', - string='Net Sales', - multi='product_historical_margin', - store=_col_store, - digits_compute=dp.get_precision('Account'), - help="The subtotal (VAT excluded) of the line at the time of the creation of the invoice, " - "express in the company currency (computed with the rate at invoice creation time, as we " - "don't have the cost price of the product at the date of the invoice)."), - 'margin_absolute': fields.function(_compute_line_values, method=True, readonly=True,type='float', - string='Real Margin', - multi='product_historical_margin', - store=_col_store, - digits_compute=dp.get_precision('Account'), - help="The Real Margin [ net sale - cost ] of the line."), - 'margin_relative': fields.function(_compute_line_values, method=True, readonly=True,type='float', - string='Real Margin (%)', - multi='product_historical_margin', - store=_col_store, - digits_compute=dp.get_precision('Account'), - help="The Real Margin % [ (Real Margin / net sale) * 100 ] of the line." - "If no real margin set, will display 999.0 (if not invoiced yet for example)."), - - # Those field are here to better report to the user from where the margin is computed - # this will allow him to understand why a margin is of that amount using the link - # from product to invoice lines - 'invoice_state': fields.related('invoice_id', 'state', type='selection', - selection=[ - ('draft','Draft'), - ('proforma','Pro-forma'), - ('proforma2','Pro-forma'), - ('open','Open'), - ('paid','Paid'), - ('cancel','Cancelled') - ], - readonly=True, string="Invoice state", - help='The parent invoice state'), - 'invoice_type': fields.related('invoice_id', 'type', type='selection', store=True, - selection=[ - ('out_invoice','Customer Invoice'), - ('in_invoice','Supplier Invoice'), - ('out_refund','Customer Refund'), - ('in_refund','Supplier Refund'), - ], - readonly=True, string="Invoice type", - help='The parent invoice type'), - 'invoice_user_id': fields.related('invoice_id','user_id',type='many2one',relation='res.users',string='Salesman', store=True), - 'invoice_date': fields.related('invoice_id','date_invoice',type='date',string='Invoice Date'), - - } diff --git a/product_historical_margin/product_historical_margin.py b/product_historical_margin/product_historical_margin.py deleted file mode 100644 index 5ee90874..00000000 --- a/product_historical_margin/product_historical_margin.py +++ /dev/null @@ -1,114 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Alexandre Fayolle, Joel Grand-Guillaume -# Copyright 2012 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -import logging - -from openerp.osv.orm import Model -from osv import fields - -import decimal_precision as dp - -# Don't Forget to remove supplier (in_invoice et in_refund) from the product margin computation -# And remove out_refund from the computation -# et ne prendre que les factures paid. -class product_product(Model): - _inherit = 'product.product' - def _compute_margin(self, cr, uid, ids, field_names, arg, context): - """ - Compute the absolute and relativ margin based on price without tax, and - always in company currency. We exclude the (in_invoice, in_refund) from the - computation as we only want to see in the product form the margin made on - our sales. - The base calculation is made from the informations stored in the invoice line - of paid and open invoices. - We return 999 as relativ margin if no sale price is define. We made that choice - to differenciate the 0.0 margin from null ! - - :return dict of dict of the form : - {INT Product ID : { - float margin_absolute, - float margin_relative - }} - """ - res = {} - tot_sale = {} - if context is None: - context = {} - if not ids: - return res - user_obj = self.pool.get('res.users') - logger = logging.getLogger('product_historical_margin') - company_id = user_obj.browse(cr, uid, uid).company_id.id - for product_id in ids: - res[product_id] = {'margin_absolute': 0, 'margin_relative': 0} - tot_sale[product_id] = 0 - # get information about invoice lines relative to our products - # belonging to open or paid invoices in the considered period - query = '''SELECT product_id, type, - SUM(subtotal_cost_price_company), - SUM(subtotal_company) - FROM account_invoice_line AS line INNER JOIN account_invoice AS inv ON (inv.id = line.invoice_id) - WHERE %s inv.state IN ('open', 'paid') - AND type NOT IN ('in_invoice', 'in_refund') - AND product_id IN %%(product_ids)s - AND inv.company_id = %%(company_id)s - GROUP BY product_id, type - HAVING SUM(subtotal_cost_price_company) != 0 - AND SUM(subtotal_company) != 0 - ''' - substs = context.copy() - substs['product_ids'] = tuple(res) - substs['company_id'] = company_id - date_clause = [] - if 'from_date' in substs: - date_clause.append('inv.date_invoice >= %(from_date)s AND') - if 'to_date' in substs: - date_clause.append('inv.date_invoice <= %(to_date)s AND') - query %= ' '.join(date_clause) - cr.execute(query, substs) - for product_id, inv_type, cost, sale in cr.fetchall(): - res[product_id]['margin_absolute'] += (sale - cost) - tot_sale[product_id] += sale - for product_id in tot_sale.keys(): - if tot_sale[product_id] == 0: - logger.debug("Sale price for product ID %d is 0, cannot compute margin rate...", product_id) - res[product_id]['margin_relative'] = 999. - else: - res[product_id]['margin_relative'] = (res[product_id]['margin_absolute'] / tot_sale[product_id]) * 100 - return res - - _columns = { - 'margin_absolute': fields.function(_compute_margin, method=True, - readonly=True, type='float', - string='Real Margin', - multi='product_historical_margin', - digits_compute=dp.get_precision('Sale Price'), - help="The Real Margin [ sale price - cost price ] of the product in absolute value " - "based on historical values computed from open and paid invoices."), - 'margin_relative': fields.function(_compute_margin, method=True, - readonly=True, type='float', - string='Real Margin (%)', - multi='product_historical_margin', - digits_compute=dp.get_precision('Sale Price'), - help="The Real Margin [ Real Margin / sale price ] of the product in relative value " - "based on historical values computed from open and paid invoices." - "If no real margin set, will display 999.0 (if not invoiced yet for example)."), - } - diff --git a/product_historical_margin/product_view.xml b/product_historical_margin/product_view.xml deleted file mode 100644 index 684e4274..00000000 --- a/product_historical_margin/product_view.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - product_historical_margin.form - product.product - - form - - - - - - - - - - product.product.historical_margin.tree - product.product - tree - - - - - - - - - - - - - - - - - - - - - diff --git a/product_historical_margin/wizard/__init__.py b/product_historical_margin/wizard/__init__.py deleted file mode 100644 index 141ca82c..00000000 --- a/product_historical_margin/wizard/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Alexandre Fayolle, Joel Grand-Guillaume -# Copyright 2012 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -from . import historical_margin diff --git a/product_historical_margin/wizard/historical_margin.py b/product_historical_margin/wizard/historical_margin.py deleted file mode 100644 index ec3f70a4..00000000 --- a/product_historical_margin/wizard/historical_margin.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# Author: Alexandre Fayolle, Joel Grand-Guillaume -# Copyright 2012 Camptocamp SA -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## -import time - -from openerp.osv.orm import TransientModel -from openerp.osv import fields -from openerp.tools.translate import _ - - -class historical_margin(TransientModel): - _name = 'historical.margin' - _description = 'Product historical margin' - - def _get_product_ids(self, cr, uid, context=None): - if context is None: context = {} - res = False - if (context.get('active_model', False) == 'product.product' and - context.get('active_ids', False)): - res = context['active_ids'] - return res - - - _columns = { - 'from_date': fields.date('From', help='Date of the first invoice to take into account. ' - 'The earliest existing invoice will be used if left empty'), - 'to_date': fields.date('To', help='Date of the last invoice to take into account. ' - 'The latest existing invoice will be used if left empty'), - 'product_ids': fields.many2many('product.product', string='Products'), - } - _defaults = { - 'from_date': time.strftime('%Y-01-01'), - 'to_date': time.strftime('%Y-12-31'), - 'product_ids': _get_product_ids, - } - - def action_open_window(self, cr, uid, ids, context=None): - """ - Open the historical margin view - """ - - if context is None: - context = {} - wiz = self.read(cr, uid, ids, [], context)[0] - ctx = context.copy() - ctx['from_date'] = wiz.get('from_date') - ctx['to_date'] = wiz.get('to_date') - product_ids = wiz.get('product_ids') - data_pool = self.pool.get('ir.model.data') - filter_ids = data_pool.get_object_reference(cr, uid, 'product', - 'product_search_form_view') - product_view_id = data_pool.get_object_reference(cr, uid, - 'product_historical_margin', - 'view_product_historical_margin') - if filter_ids: - filter_id = filter_ids[1] - else: - filter_id = 0 - if product_ids: - domain = [('id','in',product_ids)] - else: - domain = False - return { - 'type': 'ir.actions.act_window', - 'name': _('Historical Margins'), - 'context': ctx, - 'domain':domain, - 'view_type': 'form', - 'view_mode': 'tree', - 'res_model': 'product.product', - 'view_id': product_view_id[1], - 'search_view_id': filter_id, - } diff --git a/product_historical_margin/wizard/historical_margin_view.xml b/product_historical_margin/wizard/historical_margin_view.xml deleted file mode 100644 index 22ff02ca..00000000 --- a/product_historical_margin/wizard/historical_margin_view.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - historical.margin.form - historical.margin - form - -
- - - - - - - -