Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] purchase_exception to 11.0 #467

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions oca_dependencies.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
server-tools
56 changes: 56 additions & 0 deletions purchase_exception/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3

==================
Purchase Exception
==================

This module allows you attach several customizable exceptions to your
purchase order in a way that you can filter orders by exceptions type and fix them.

This is especially useful in an scenario for mass purchases order import, because it's likely some orders have
errors when you import them (like product not found in Odoo, wrong line
format etc.)

Usage
=====

.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
:alt: Try me on Runbot
:target: https://runbot.odoo-community.org/runbot/167/11.0


Bug Tracker
===========


Bugs are tracked on `GitHub Issues
<https://github.com/OCA/purchase-workflow/issues>`_. In case of trouble, please
check there if your issue has already been reported. If you spotted it first,
help us smash it by providing detailed and welcomed feedback.

Images
------

* Odoo Community Association: `Icon <https://github.com/OCA/maintainer-tools/blob/master/template/module/static/description/icon.svg>`_.

Contributors
------------

* Mourad EL HADJ MIMOUNE <[email protected]>

Maintainer
----------

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

This module is maintained by the OCA.

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

To contribute to this module, please visit https://odoo-community.org.
2 changes: 2 additions & 0 deletions purchase_exception/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

from . import models, wizard
19 changes: 19 additions & 0 deletions purchase_exception/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2017 Akretion (http://www.akretion.com)
# Mourad EL HADJ MIMOUNE <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{'name': 'Purchase Exception',
'summary': 'Custom exceptions on purchase order',
'version': '11.0.1.0.0',
'category': 'Generic Modules/Purchase',
'author': "Akretion, Odoo Community Association (OCA)",
'website': 'http://www.akretion.com',
'depends': ['purchase', 'base_exception'],
'license': 'AGPL-3',
'data': [
'data/purchase_exception_data.xml',
'wizard/purchase_exception_confirm_view.xml',
'views/purchase_view.xml',
],
'installable': True,
}
39 changes: 39 additions & 0 deletions purchase_exception/data/purchase_exception_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo noupdate="1">
<!-- Test Purchase Exceptions Scheduler-->
<record model="ir.cron" forcecreate="True"
id="ir_cron_test_po_order_except">
<field name="name">Purchase: Test Draft Orders Exception</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="state">code</field>
<field name="code">model.test_all_draft_orders(True)</field>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">20</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="active" eval="False" />
</record>

<record id ="po_excep_no_email" model="exception.rule">
<field name="name">No email on vendor</field>
<field name="description">No Email for Vendor</field>
<field name="sequence">50</field>
<field name="rule_group">purchase</field>
<field name="model">purchase.order</field>
<field name="code">if not purchase.partner_id.email:
failed=True</field>
<field name="active" eval="False"/>
</record>

<record id ="pol_excep_qty_check" model="exception.rule">
<field name="name">Quantity not negative</field>
<field name="description">Purchase line quantity must be positive</field>
<field name="sequence">50</field>
<field name="rule_group">purchase</field>
<field name="model">purchase.order.line</field>
<field name="code">if purchase_line.product_qty &lt; 0:
failed=True</field>
<field name="active" eval="False"/>
</record>
</odoo>
2 changes: 2 additions & 0 deletions purchase_exception/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

from . import purchase
72 changes: 72 additions & 0 deletions purchase_exception/models/purchase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright 2017 Akretion (http://www.akretion.com)
# Mourad EL HADJ MIMOUNE <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import api, models, fields


class ExceptionRule(models.Model):
_inherit = 'exception.rule'

rule_group = fields.Selection(
selection_add=[('purchase', 'Purchase')],
)
model = fields.Selection(
selection_add=[
('purchase.order', 'Purchase order'),
('purchase.order.line', 'Purchase order line'),
])


class PurchaseOrder(models.Model):
_inherit = ['purchase.order', 'base.exception']
_name = 'purchase.order'
_order = 'main_exception_id asc, date_order desc, name desc'

rule_group = fields.Selection(
selection_add=[('purchase', 'Purchase')],
default='purchase',
)

@api.model
def test_all_draft_orders(self):
order_set = self.search([('state', '=', 'draft')])
order_set.test_exceptions()
return True

@api.constrains('ignore_exception', 'order_line', 'state')
def purchase_check_exception(self):
orders = self.filtered(lambda s: s.state == 'purchase')
if orders:
orders._check_exception()

@api.onchange('order_line')
def onchange_ignore_exception(self):
if self.state == 'purchase':
self.ignore_exception = False

@api.multi
def button_confirm(self):
if self.detect_exceptions() and not self.ignore_exception:
return self._popup_exceptions()
else:
return super(PurchaseOrder, self).button_confirm()

@api.multi
def button_draft(self):
res = super(PurchaseOrder, self).button_draft()
for rec in self:
rec.exception_ids = False
rec.main_exception_id = False
rec.ignore_exception = False
return res

def _purchase_get_lines(self):
self.ensure_one()
return self.order_line

@api.model
def _get_popup_action(self):
action = self.env.ref(
'purchase_exception.action_purchase_exception_confirm')
return action
Binary file added purchase_exception/static/description/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions purchase_exception/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

from . import test_purchase_exception
113 changes: 113 additions & 0 deletions purchase_exception/tests/test_purchase_exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Copyright 2017 Akretion (http://www.akretion.com)
# Mourad EL HADJ MIMOUNE <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from datetime import datetime

from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tests.common import TransactionCase


class TestPurchaseException(TransactionCase):

def setUp(self):
super(TestPurchaseException, self).setUp()
# Useful models
self.PurchaseOrder = self.env['purchase.order']
self.PurchaseOrderLine = self.env['purchase.order.line']
self.partner_id = self.env.ref('base.res_partner_1')
self.product_id_1 = self.env.ref('product.product_product_6')
self.product_id_2 = self.env.ref('product.product_product_7')
self.product_id_3 = self.env.ref('product.product_product_7')
self.date_planned = datetime.today().strftime(
DEFAULT_SERVER_DATETIME_FORMAT)
self.purchase_exception_confirm = \
self.env['purchase.exception.confirm']
self.exception_noemail = self.env.ref(
'purchase_exception.po_excep_no_email')
self.exception_qtycheck = self.env.ref(
'purchase_exception.pol_excep_qty_check')
self.po_vals = {
'partner_id': self.partner_id.id,
'order_line': [
(0, 0, {
'name': self.product_id_1.name,
'product_id': self.product_id_1.id,
'product_qty': 5.0,
'product_uom': self.product_id_1.uom_po_id.id,
'price_unit': 500.0,
'date_planned': self.date_planned,
}),
(0, 0, {
'name': self.product_id_2.name,
'product_id': self.product_id_2.id,
'product_qty': 5.0,
'product_uom': self.product_id_2.uom_po_id.id,
'price_unit': 250.0,
'date_planned': self.date_planned,
})],
}

def test_purchase_order_exception(self):
self.exception_noemail.active = True
self.exception_noemail.next_state = False
self.exception_qtycheck.active = True
self.partner_id.email = False
self.po = self.PurchaseOrder.create(self.po_vals.copy())

# confirm quotation
self.po.button_confirm()
self.assertEqual(self.po.state, 'draft')
# test all draft po
self.po2 = self.PurchaseOrder.create(self.po_vals.copy())

self.PurchaseOrder.test_all_draft_orders()
self.assertEqual(self.po2.state, 'draft')
# Set ignore_exception flag (Done after ignore is selected at wizard)
self.po.ignore_exception = True
self.po.button_confirm()
self.assertEqual(self.po.state, 'purchase')

# Add a order line to test after PO is confirmed
# set ignore_exception = False (Done by onchange of order_line)
field_onchange = self.PurchaseOrder._onchange_spec()
self.assertEqual(field_onchange.get('order_line'), '1')
self.env.cache.invalidate()
self.po3New = self.PurchaseOrder.new(self.po_vals.copy())
self.po3New.ignore_exception = True
self.po3New.state = 'purchase'
self.po3New.onchange_ignore_exception()
self.assertFalse(self.po3New.ignore_exception)
self.po.write(
{
'order_line':
[(0, 0,
{'name': self.product_id_3.name,
'product_id': self.product_id_3.id,
'product_qty': 2,
'product_uom': self.product_id_3.uom_id.id,
'price_unit': 30,
'date_planned': self.date_planned,
})]
})

# Set ignore exception True (Done manually by user)
self.po.ignore_exception = True
self.po.button_cancel()
self.po.button_draft()
self.assertEqual(self.po.state, 'draft')
self.assertTrue(not self.po.ignore_exception)
# test next_state
self.exception_noemail.next_state = 'to approve'
self.po.button_confirm()
self.assertEqual(self.po.state, 'to approve')

# Simulation the opening of the wizard purchase_exception_confirm and
# set ignore_exception to True
po_except_confirm = self.purchase_exception_confirm.with_context(
{
'active_id': self.po.id,
'active_ids': [self.po.id],
'active_model': self.po._name
}).create({'ignore': True})
po_except_confirm.action_confirm()
self.assertTrue(self.po.ignore_exception)
74 changes: 74 additions & 0 deletions purchase_exception/views/purchase_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?xml version="1.0" ?>
<odoo>
<record id="action_purchase_test_tree" model="ir.actions.act_window">
<field name="name">Purchase Exception Rules</field>
<field name="res_model">exception.rule</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="base_exception.view_exception_rule_tree"/>
<field name="domain">[('rule_group', '=', 'purchase')]</field>
<field name="context">{'active_test': False, 'default_rule_group' : 'purchase'}</field>
</record>

<menuitem
id="menu_purchase_exception_parent"
parent="purchase.menu_product_in_config_purchase"
sequence="20"
name="Exceptions"
/>


<menuitem
action="action_purchase_test_tree"
id="menu_purchase_test"
parent="menu_purchase_exception_parent"
/>

<record id="view_order_form" model="ir.ui.view">
<field name="name">purchase_exception.view_order_form</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<header position="after">
<div groups="purchase.group_purchase_user"
class="alert alert-danger alert-dismissable" role="alert" style="margin-bottom:0px;" attrs="{'invisible':[('main_exception_id','=', False)]}">
<bold>You have an outstanding
exception to manage:
<field name="main_exception_id" options='{"no_open": True}'/></bold>
</div>
</header>
<xpath expr="//field[@name='date_order']/.." position="inside">
<field name="ignore_exception" states="purchase" groups='base_exception.group_exception_rule_manager'/>
</xpath>
<xpath expr="//field[@name='company_id']"
position="after">
<!-- <newline /> -->
<field name="exception_ids" widget="many2many_tags" readonly="True"/>
</xpath>
</field>
</record>

<record id="view_order_tree" model="ir.ui.view">
<field name="name">purchase_exception.view_order_tree</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_tree"/>
<field name="arch" type="xml">
<field name="state" position="after">
<field name="main_exception_id"/>
</field>
</field>
</record>

<record id="view_purchase_order_filter" model="ir.ui.view">
<field name="name">purchase_exception.view_purchases_order_filter</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.view_purchase_order_filter" />
<field name="arch" type="xml">
<filter name="activities_my" position="after">
<separator orientation="vertical"/>
<filter icon="terp-emblem-important" name="tofix" string="Blocked in draft" domain="[('main_exception_id','!=',False)]"/>
</filter>
</field>
</record>

</odoo>
2 changes: 2 additions & 0 deletions purchase_exception/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

from . import purchase_exception_confirm
Loading