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

[16.0][ADD] purchase_duplicate_check: Module added #2516

Open
wants to merge 6 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
109 changes: 109 additions & 0 deletions purchase_duplicate_check/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
========================
Purchase Duplicate Check
========================

..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:7107035505d1a2624df4c1bce4fc742fd17319625c9a8ea54b4558c8e7c35107
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github
:target: https://github.com/OCA/purchase-workflow/tree/16.0/purchase_duplicate_check
:alt: OCA/purchase-workflow
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/purchase-workflow-16-0/purchase-workflow-16-0-purchase_duplicate_check
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/purchase-workflow&target_branch=16.0
:alt: Try me on Runboat

|badge1| |badge2| |badge3| |badge4| |badge5|

This module adds the following features to the Purchase App:

Pending RFQ or Purchase orders with associated incoming
stock picking not in the "Done" state for Product in PO Line:

- RFQ Confirmation Wizard if there's existing RFQ for the same products
- Add "Pending orders" field to the PO Line
- Assign Acitivty for the responsible user to check the associated orders

**Table of contents**

.. contents::
:local:

Use Cases / Context
===================

This module helps prevent overordering of the same products by streamlining the control of Requests for Quotations (RFQs) and undelivered Purchase Orders (POs).

Configuration
=============

To Configure the Activity for a responsible person to check the repeating RFQ/PO:

Go to Settings -> Purchase - > Orders

- Enable the "Create Activity for Repeating Orders" Checkbox
- Select the activity type in the "Activity" Field
- Press the "Arrow button" to Configure the Default user for activity

Usage
=====

Pending RFQ for Product in PO Line:

Go to the Purchase App:

- Create a new order
- Press the "Add a Line" button to add purchase line
- Select a Product for the PO Line
- If there are pending RFQs for the same product in the "Draft"/"Sent" state, or Purchase orders with associated incoming stock picking not in the "Done" state it will be shown in the "Pending order" field
- Press the "Confirm Order" button to confirm the RFQ
- If there's a Pending RFQ's for the products in PO Lines, the Confirmation wizard with Pending RFQ's information will appear
- Press the "Confirm" button to process with the order or "Cancel" to get back to RFQ

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 to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/purchase-workflow/issues/new?body=module:%20purchase_duplicate_check%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Do not contact contributors directly about support or help with technical issues.

Credits
=======

Authors
~~~~~~~

* Cetmix

Maintainers
~~~~~~~~~~~

This module is maintained by the OCA.

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

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.

This module is part of the `OCA/purchase-workflow <https://github.com/OCA/purchase-workflow/tree/16.0/purchase_duplicate_check>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
2 changes: 2 additions & 0 deletions purchase_duplicate_check/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizard
21 changes: 21 additions & 0 deletions purchase_duplicate_check/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "Purchase Duplicate Check",
"version": "16.0.1.0.0",
"summary": "Prevents overordering in the Purchase app with a Confirmation Wizard, "
"'Pending Orders' field, and activity tracking for repeated orders.",
"author": "Cetmix, Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Inventory/Purchase",
"website": "https://github.com/OCA/purchase-workflow",
"live_test_url": "https://demo.cetmix.com",
geomer198 marked this conversation as resolved.
Show resolved Hide resolved
"depends": ["purchase_stock", "confirmation_wizard"],
"external_dependencies": {},
geomer198 marked this conversation as resolved.
Show resolved Hide resolved
"data": [
"views/purchase_order_views.xml",
"views/res_config_settings_views.xml",
"wizard/confirmation_wizard_views.xml",
],
"assets": {},
geomer198 marked this conversation as resolved.
Show resolved Hide resolved
"installable": True,
"application": False,
}
3 changes: 3 additions & 0 deletions purchase_duplicate_check/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import purchase_order
from . import purchase_order_line
from . import res_config_settings
57 changes: 57 additions & 0 deletions purchase_duplicate_check/models/purchase_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from odoo import models


class PurchaseOrder(models.Model):
_inherit = "purchase.order"

def _prepare_pending_orders_message(self, product_id):
"""
Prepare pending order line message

:param product_id: product.product record id
:return str: message
"""
message_parts = []
order_lines = self.env["purchase.order.line"].search(
[("product_id", "=", product_id), ("order_id", "in", self.ids)]
)
geomer198 marked this conversation as resolved.
Show resolved Hide resolved
for line in order_lines:
order = line.order_id
order_href = (
f"<a href='/web#id={order.id}&model={order._name}'>{order.name}</a>"
)
type_ = order.state in ["draft", "sent"] and "RFQ" or "PO"
message_parts.append(
f"{type_}: {order_href} date: {order.create_date.date()} Qty: {line.product_qty}<br/>" # noqa

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will looks like RFQ: PO001 date: 01/01/2025 Qty: 10.0, it would be much better to be shown as RFQ: PO001 orders 10.0 unit, to confirm by 01/01/2025 and PO: PO001 orders 10.0 unit, expected to received by 01/01/2025

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will looks like RFQ: PO001 date: 01/01/2025 Qty: 10.0, it would be much better to be shown as RFQ: PO001 orders 10.0 unit, to confirm by 01/01/2025 and PO: PO001 orders 10.0 unit, expected to received by 01/01/2025

I think this is too much.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then at least separate the components with - or ;

)
return "".join(message_parts)

def _is_activity_enabled(self) -> bool:
"""Check if activity for repeating orders is enabled"""

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is repeating orders here mean duplicated orders?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is repeating orders here mean duplicated orders?

Yes

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think let's use duplicated instead of repeating then

return (
self.env["ir.config_parameter"]
.sudo()
.get_param(
"purchase_duplicate_check.allow_create_activity_repeating_orders", False
)
)

def _check_pending_order(self):
"""Check for pending orders and trigger confirmation wizard if needed."""
if self._is_activity_enabled() and not self._context.get(
"skip_rfq_confirmation"
):
return (
self.env["confirmation.wizard"]
.with_context(skip_rfq_confirmation=True)
.confirm_pending_order(self)
)

def button_confirm(self):
"""
Confirm the purchase order.

:return: action or super
"""
action = self._check_pending_order()
return action or super().button_confirm()
88 changes: 88 additions & 0 deletions purchase_duplicate_check/models/purchase_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
from odoo import _, fields, models


class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"

pending_order_ids = fields.Many2many(
"purchase.order",
string="Pending Orders",
compute="_compute_pending_order_ids",
)

def _compute_pending_order_ids(self):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you do need at least @api.depends('product_id') here

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geomer198 any comment?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geomer198 any comment?

This compute method have many dependencies, that is why I don't write depends decorator here.

product_lines = self.filtered(lambda rec: rec.product_type == "product")
other_lines = self - product_lines

if other_lines:
other_lines.pending_order_ids = False

if not product_lines:
return

product_ids = tuple(product_lines.mapped("product_id.id"))
order_ids = tuple(product_lines.mapped("order_id.id"))

if not product_ids:
product_lines.pending_order_ids = False
return
query = """
SELECT po.id, pol.product_id
FROM purchase_order po
JOIN purchase_order_line pol ON pol.order_id = po.id
LEFT JOIN stock_move sm ON sm.purchase_line_id = pol.id
LEFT JOIN stock_picking sp ON sp.id = sm.picking_id

WHERE pol.product_id IN %s
AND po.id NOT IN %s
AND (
po.state IN ('draft', 'sent')
OR (
po.state NOT IN ('draft', 'sent')
AND sp.picking_type_id IN (
SELECT id FROM stock_picking_type WHERE code = 'incoming'
)
AND sp.state NOT IN ('done', 'cancel')
)
)
"""
self.env.cr.execute(query, (product_ids, order_ids))
result = self.env.cr.fetchall()
product_orders_map = {}
for order_id, product_id in result:
if product_id not in product_orders_map:
product_orders_map[product_id] = []
product_orders_map[product_id].append(order_id)

for rec in product_lines:
rec.pending_order_ids = [
(6, 0, product_orders_map.get(rec.product_id.id, []))
]

def _get_order_confirm_message(self):
"""Get order confirmation message for pending orders"""
message = ""
for line in self:
pending_orders = line.pending_order_ids
if not pending_orders:
continue
product_line_msg = pending_orders._prepare_pending_orders_message(
line.product_id.id
)
message += f"""
Product <b>{line.product_id.name}</b><br/>
{product_line_msg}<br/>
"""
return message

def action_open_pending_orders(self):
"""Action open pending purchase orders"""
self.ensure_one()
return {
"name": _("Pending Orders"),
"views": [[False, "tree"], [False, "form"]],
"res_model": "purchase.order",
"type": "ir.actions.act_window",
"domain": [("id", "in", self.pending_order_ids.ids)],
"context": {"create": False},
}
19 changes: 19 additions & 0 deletions purchase_duplicate_check/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from odoo import api, fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

allow_create_activity_repeating_orders = fields.Boolean(
config_parameter="purchase_duplicate_check.allow_create_activity_repeating_orders"
)
repeating_orders_activity_type_id = fields.Many2one(
comodel_name="mail.activity.type",
config_parameter="purchase_duplicate_check.repeating_orders_activity_type_id",
string="Activity",
)

@api.onchange("allow_create_activity_repeating_orders")
def _onchange_allow_create_activity_repeating_orders(self):
if not self.allow_create_activity_repeating_orders:
self.repeating_orders_activity_type_id = False
7 changes: 7 additions & 0 deletions purchase_duplicate_check/readme/CONFIGURE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
To Configure the Activity for a responsible person to check the repeating RFQ/PO:

Go to Settings -> Purchase - > Orders

- Enable the "Create Activity for Repeating Orders" Checkbox
- Select the activity type in the "Activity" Field
- Press the "Arrow button" to Configure the Default user for activity
1 change: 1 addition & 0 deletions purchase_duplicate_check/readme/CONTEXT.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This module helps prevent overordering of the same products by streamlining the control of Requests for Quotations (RFQs) and undelivered Purchase Orders (POs).
8 changes: 8 additions & 0 deletions purchase_duplicate_check/readme/DESCRIPTION.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
This module adds the following features to the Purchase App:

Pending RFQ or Purchase orders with associated incoming
stock picking not in the "Done" state for Product in PO Line:

- RFQ Confirmation Wizard if there's existing RFQ for the same products
- Add "Pending orders" field to the PO Line
- Assign Acitivty for the responsible user to check the associated orders
11 changes: 11 additions & 0 deletions purchase_duplicate_check/readme/USAGE.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Pending RFQ for Product in PO Line:

Go to the Purchase App:

- Create a new order
- Press the "Add a Line" button to add purchase line
- Select a Product for the PO Line
- If there are pending RFQs for the same product in the "Draft"/"Sent" state, or Purchase orders with associated incoming stock picking not in the "Done" state it will be shown in the "Pending order" field
- Press the "Confirm Order" button to confirm the RFQ
- If there's a Pending RFQ's for the products in PO Lines, the Confirmation wizard with Pending RFQ's information will appear
- Press the "Confirm" button to process with the order or "Cancel" to get back to RFQ
Loading
Loading