← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~numerigraphe-team/purchase-wkfl/7.0-add-purchase_budget_limit into lp:purchase-wkfl

 

Lionel Sausin - Numérigraphe has proposed merging lp:~numerigraphe-team/purchase-wkfl/7.0-add-purchase_budget_limit into lp:purchase-wkfl.

Requested reviews:
  Romain Deheele - Camptocamp (romaindeheele): code review
  Loïc Bellier - Numérigraphe (lb-b)
  Purchase Core Editors (purchase-core-editors)

For more details, see:
https://code.launchpad.net/~numerigraphe-team/purchase-wkfl/7.0-add-purchase_budget_limit/+merge/219829

Here is a new module to block purchase orders when the budget is at risk of being exhausted.

Only validated budgets are checked.
Also, only invoiced purchases are counted (because only invoices are counted in the budget). I'll soon be publishing an additional module to count draft purchases in the budgets too.

Unfortunately, no automatic test bundled because the account_budget does not provide any either.
-- 
https://code.launchpad.net/~numerigraphe-team/purchase-wkfl/7.0-add-purchase_budget_limit/+merge/219829
Your team Purchase Core Editors is requested to review the proposed merge of lp:~numerigraphe-team/purchase-wkfl/7.0-add-purchase_budget_limit into lp:purchase-wkfl.
=== added directory 'purchase_budget_limit'
=== added file 'purchase_budget_limit/__init__.py'
--- purchase_budget_limit/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/__init__.py	2014-05-16 12:31:09 +0000
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import wizard
+from . import purchase

=== added file 'purchase_budget_limit/__openerp__.py'
--- purchase_budget_limit/__openerp__.py	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/__openerp__.py	2014-05-16 12:31:09 +0000
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    'name': "Block over-budget Purchase Orders",
+    'version': '1.1',
+    'author': u'Numérigraphe SARL',
+    'category': 'Purchase Management',
+    'description': '''
+Let Budget managers define limits on Purchase Orders
+====================================================
+
+When new Purchase Orders are being confirmed, this module will put them in a
+special state if the remaining budget is not sufficient to pay the expected
+invoice in one of the Budget Lines in the same period.
+
+Purchase managers can :
+- either wait until the financial situation changes
+- or override the budget and approve the Purchase Order
+- or cancel the Purchase Order.
+''',
+    'depends': ['account_budget', 'purchase'],
+    'data': [
+        'purchase_workflow.xml',
+        'purchase_view.xml',
+        'wizard/purchase_budget_view.xml',
+        'security/ir.model.access.csv',
+    ],
+    'test': [
+        # TODO add an automatic test:
+        # - create a budget line for 10000 EUR
+        # - create a PO for 100 EUR and validate it, check it's not blocked
+        # - create a budget line for 150 EUR
+        # - create a PO for 200 EUR and validate it, check it's blocked
+        # - override the budget and check the PO is validated
+    ]
+}

=== added directory 'purchase_budget_limit/i18n'
=== added file 'purchase_budget_limit/i18n/fr.po'
--- purchase_budget_limit/i18n/fr.po	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/i18n/fr.po	2014-05-16 12:31:09 +0000
@@ -0,0 +1,48 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* purchase_budget_limit
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.4\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2013-09-17 14:14+0000\n"
+"PO-Revision-Date: 2013-09-17 14:14+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: purchase_budget_limit
+#: field:purchase.budget.wizard,budget_line_ids:0
+msgid "Budget Lines"
+msgstr "Lignes de budget"
+
+#. module: purchase_budget_limit
+#: view:purchase.budget.wizard:0
+msgid "Budget warning"
+msgstr "Avertissement sur le budget"
+
+#. module: purchase_budget_limit
+#: view:purchase.budget.wizard:0
+msgid "Cancel"
+msgstr "Annuler"
+
+#. module: purchase_budget_limit
+#: selection:purchase.order,state:0
+msgid "Over Budget"
+msgstr "Budget dépassé"
+
+#. module: purchase_budget_limit
+#: view:purchase.budget.wizard:0
+#: view:purchase.order:0
+msgid "Override Budgets"
+msgstr "Outrepasser le budget"
+
+#. module: purchase_budget_limit
+#: view:purchase.budget.wizard:0
+msgid "The following Budget Lines will be exhausted if you confirm the Purchase Order: you may have dificulties paying the Supplier Invoice."
+msgstr "Les lignes de budget suivantes seront épuisées si vous confirmez cette commande : il vous sera peut-être difficile de payer la facture d'achat."
+

=== added file 'purchase_budget_limit/purchase.py'
--- purchase_budget_limit/purchase.py	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/purchase.py	2014-05-16 12:31:09 +0000
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import time
+
+from openerp import netsvc
+from openerp.osv import orm
+from openerp.tools.translate import _
+
+
+class PurchaseOrder (orm.Model):
+    """Display a warning when confirming a Purchase if the budget is too low"""
+    _inherit = 'purchase.order'
+
+    # Initialization (no context)
+    def __init__(self, pool, cr):
+        """
+        Add a new state value.
+
+        Doing it in __init__ is cleaner than copying and pasting the field
+        definition in _columns and should be compatible with future/customized
+        versions.
+        """
+        super(PurchaseOrder, self).__init__(pool, cr)
+        super(PurchaseOrder, self).STATE_SELECTION.append(
+            ('over_budget', 'Over Budget'))
+
+    # Workflow Action methods (no context)
+    def wkf_over_budget(self, cr, uid, ids):
+        """Change the Purchase Order's state to 'Over Budget'"""
+        self.write(cr, uid, ids, {'state': 'over_budget'})
+        return True
+
+    # Model methods (context except when called by the Workflow Engine)
+    def exhausted_budget_lines(self, cr, uid, ids, context=None):
+        """
+        Find the Budget Line which do not have enough funds left to pay the POs
+
+        @return: IDs of the Budget Lines
+        """
+        if not isinstance(ids, list):
+            ids = [ids]
+
+        budget_line_ids = set()
+        b_line_obj = self.pool['crossovered.budget.lines']
+        today = time.strftime('%Y-%m-%d')
+        for po_id in ids:
+            for ol in self.browse(cr, uid, po_id, context=context).order_line:
+                if ol.account_analytic_id:
+                    # should we check sub-accounts too?
+                    bl_ids = b_line_obj.search(cr, uid,
+                        [('analytic_account_id',
+                            '=', ol.account_analytic_id.id),
+                         ('date_from', '<=', today),
+                         ('date_to', '>=', today)],
+                        context=context)
+                    budget_line_ids.update(
+                        [l.id for l in b_line_obj.browse(cr, uid,
+                             bl_ids, context=context)
+                         if l.crossovered_budget_id.state == 'validate'
+                            and (l.practical_amount - ol.price_subtotal
+                                    < l.theoritical_amount)]
+                   )
+        return list(budget_line_ids)
+
+    def button_confirm(self, cr, uid, ids, context=None):
+        """Advance the workflow and pop up a message on 'Over Budget' state.
+
+        @return: True if all orders are OK, or an client action dictionary to
+                 open a confirmation wizard if any order is over-budget.
+        """
+        if context is None:
+            context = {}
+        if not isinstance(ids, list):
+            ids = [ids]
+
+        # Send the workflow signal on every purchase order
+        wf_service = netsvc.LocalService("workflow")
+        for po_id in ids:
+            wf_service.trg_validate(
+                uid, self._name, po_id, 'purchase_confirm', cr)
+
+        # Return True or an Action dictionary
+        orders_ok = all([o.state != 'over_budget'
+                         for o in self.browse(cr, uid, ids, context=context)])
+
+        return orders_ok or {
+            'name': _('Budget warning'),
+            'type': 'ir.actions.act_window',
+            'res_model': 'purchase.budget.wizard',
+            'view_type': 'form',
+            'view_mode': 'form',
+            'target': 'new',
+            'nodestroy': True,
+            'context': dict(context,
+                            active_model='purchase.order',
+                            active_ids=ids,
+                            active_id=ids[0])
+        }

=== added file 'purchase_budget_limit/purchase_view.xml'
--- purchase_budget_limit/purchase_view.xml	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/purchase_view.xml	2014-05-16 12:31:09 +0000
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <!--  Adapt the buttons in the form view to handle the new workflow and the state "Over Budget" -->
+        <record id="purchase_order_form_over_budget" model="ir.ui.view">
+            <field name="name">purchase.order.form.over_budget</field>
+            <field name="inherit_id" ref="purchase.purchase_order_form" />
+            <field name="model">purchase.order</field>
+            <field name="arch" type="xml">
+                <data>
+                    <!-- Tweak the button "Confirm" to start a wizard when Budget Lines are exhausted  -->
+                    <xpath expr="//button[@name='purchase_confirm']" position="attributes">
+                        <attribute name="name">button_confirm</attribute>
+                        <attribute name="type">object</attribute>
+                    </xpath>
+                    <!-- Add a button to confirm over-budget POs
+                         Let users cancel over-budget POs -->
+                    <xpath expr="//button[@name='purchase_cancel']" position="after">
+                        <button name="purchase_cancel" states="over_budget" string="Cancel Order"/>
+                        <button name="purchase_confirm_overbudget" states="over_budget" string="Override Budgets" class="oe_highlight"/>
+                    </xpath>
+                </data>
+            </field>
+        </record>
+        
+        <!--  Add the state "Over Budget" to the "Draft" search filter and list -->
+        <record id="purchase.purchase_rfq" model="ir.actions.act_window">
+            <field name="domain">[('state','in',('draft','sent','confirmed','over_budget'))]</field>
+        </record>
+    </data>
+</openerp>

=== added file 'purchase_budget_limit/purchase_workflow.xml'
--- purchase_budget_limit/purchase_workflow.xml	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/purchase_workflow.xml	2014-05-16 12:31:09 +0000
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+        <!-- New activity for purchase orders when Budget Lines are exhausted -->
+        <!-- Whenever a PO is Over-budget, the signal "purchase_confirm_overbudget" must be sent to override -->
+        <record id="act_over_budget" model="workflow.activity">
+            <field name="wkf_id" ref="purchase.purchase_order" />
+            <field name="name">over_budget</field>
+            <field name="kind">function</field>
+            <field name="action">wkf_over_budget()</field>
+        </record>
+        <record id="trans_draft_over_budget" model="workflow.transition">
+            <field name="act_from" ref="purchase.act_draft"/>
+            <field name="act_to" ref="act_over_budget"/>
+            <field name="signal">purchase_confirm</field>
+            <field name="condition">exhausted_budget_lines()</field>
+        </record>
+        <record id="trans_over_budget_confirm" model="workflow.transition">
+            <field name="act_from" ref="act_over_budget"/>
+            <field name="act_to" ref="purchase.act_confirmed"/>
+            <field name="signal">purchase_confirm_overbudget</field>
+        </record>
+        <record id="trans_over_budget_cancel" model="workflow.transition">
+            <field name="act_from" ref="act_over_budget"/>
+            <field name="act_to" ref="purchase.act_cancel"/>
+            <field name="signal">purchase_cancel</field>
+        </record>
+        
+        <!-- only move from draft to confirm if budgets are OK -->
+        <record id="purchase.trans_draft_confirmed" model="workflow.transition">
+            <field name="condition">not exhausted_budget_lines()</field>
+        </record>
+    </data>
+</openerp>

=== added directory 'purchase_budget_limit/security'
=== added file 'purchase_budget_limit/security/ir.model.access.csv'
--- purchase_budget_limit/security/ir.model.access.csv	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/security/ir.model.access.csv	2014-05-16 12:31:09 +0000
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_budget_purchase_user,Purchase users can read budgets,account_budget.model_crossovered_budget_lines,purchase.group_purchase_user,1,0,0,0
+access_budget_purchase_user_manager,Purchase managers can read budgets,account_budget.model_crossovered_budget_lines,purchase.group_purchase_manager,1,0,0,0

=== added directory 'purchase_budget_limit/wizard'
=== added file 'purchase_budget_limit/wizard/__init__.py'
--- purchase_budget_limit/wizard/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/wizard/__init__.py	2014-05-16 12:31:09 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2011 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from . import purchase_budget

=== added file 'purchase_budget_limit/wizard/purchase_budget.py'
--- purchase_budget_limit/wizard/purchase_budget.py	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/wizard/purchase_budget.py	2014-05-16 12:31:09 +0000
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2014 Numérigraphe SARL. All Rights Reserved.
+#
+#    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 <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import openerp.netsvc as netsvc
+from openerp.osv import fields, osv
+
+
+class PurchaseBudget(osv.TransientModel):
+    _name = 'purchase.budget.wizard'
+    _columns = {
+        'budget_line_ids': fields.many2many("crossovered.budget.lines",
+                                            rel='purchase_budget_lines_rel',
+                                            id1='order_id', id2='line_id',
+                                            string='Budget Lines',
+                                            readonly=True),
+    }
+
+    def _get_budget_line_ids(self, cr, uid, context=None):
+        """Load the exhausted budget lines related to the Purchase Orders"""
+        if context is None:
+            context = {}
+        if context.get('active_model') != 'purchase.order':
+            return []
+        return self.pool['purchase.order'].exhausted_budget_lines(cr, uid,
+            context.get('active_ids'), context=context)
+
+    def override_budget(self, cr, uid, ids, context=None):
+        """Override the Budgets and confirm the Purchase Order"""
+        if context is None:
+            context = {}
+        if context.get('active_model') != 'purchase.order':
+            return False
+
+        # Send the workflow signal on every purchase order
+        wf_service = netsvc.LocalService("workflow")
+        for po_id in context.get('active_ids'):
+            wf_service.trg_validate(
+                uid, 'purchase.order', po_id, 'purchase_confirm_overbudget',
+                cr)
+        return {'type': 'ir.actions.act_window_close'}
+
+    _defaults = {
+        'budget_line_ids': _get_budget_line_ids
+     }
+PurchaseBudget()

=== added file 'purchase_budget_limit/wizard/purchase_budget_view.xml'
--- purchase_budget_limit/wizard/purchase_budget_view.xml	1970-01-01 00:00:00 +0000
+++ purchase_budget_limit/wizard/purchase_budget_view.xml	2014-05-16 12:31:09 +0000
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<openerp>
+    <data>
+        <record id="purchase_budget_wizard_form" model="ir.ui.view">
+            <field name="name">purchase.budget.wizard.form</field>
+            <field name="model">purchase.budget.wizard</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Budget warning" version="7.0">
+                    <label string="The following Budget Lines will be exhausted if you confirm the Purchase Order: you may have dificulties paying the Supplier Invoice."/>
+                    <field name="budget_line_ids" nolabel="1"/>
+                    <footer>
+                        <button special="cancel" string="Block Purchase Order" class="oe_highlight"/>
+                        <button name="override_budget" string="Override Budgets" type="object"/>
+                    </footer>
+                </form>
+            </field>
+        </record>
+    </data>
+</openerp>