Add oca-purchase submodule with 96 purchase modules moved from oca-workflow-process

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ernad Husremovic 2025-08-30 18:00:40 +02:00
parent b0628ee8ea
commit 7378b233e9
3994 changed files with 334316 additions and 0 deletions

View file

@ -0,0 +1,47 @@
# Purchase Order Duplicate Check
Odoo addon: purchase_order_duplicate_check
## Installation
```bash
pip install odoo-bringout-oca-purchase-workflow-purchase_order_duplicate_check
```
## Dependencies
This addon depends on:
- purchase_stock
- confirmation_wizard
## Manifest Information
- **Name**: Purchase Order Duplicate Check
- **Version**: 16.0.1.0.0
- **Category**: Inventory/Purchase
- **License**: AGPL-3
- **Installable**: False
## Source
Based on [OCA/purchase-workflow](https://github.com/OCA/purchase-workflow) branch 16.0, addon `purchase_order_duplicate_check`.
## License
This package maintains the original AGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph Purchase_order_duplicate_check Module - purchase_order_duplicate_check
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for purchase_order_duplicate_check. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,6 @@
# Dependencies
This addon depends on:
- [purchase_stock](https://github.com/bringout/oca-ocb-warehouse/tree/0ee5ffef60413a71dceb350918ad3fb572ec1875/odoo-bringout-oca-ocb-purchase_stock)
- [confirmation_wizard](https://github.com/bringout/oca-technical)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon purchase_order_duplicate_check or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-purchase-workflow-purchase_order_duplicate_check"
# or
uv pip install odoo-bringout-oca-purchase-workflow-purchase_order_duplicate_check"
```

View file

@ -0,0 +1,14 @@
# Models
Detected core models and extensions in purchase_order_duplicate_check.
```mermaid
classDiagram
class purchase_order
class purchase_order_line
class res_config_settings
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: purchase_order_duplicate_check. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon purchase_order_duplicate_check
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,8 @@
# Security
This module does not define custom security rules or access controls beyond Odoo defaults.
Default Odoo security applies:
- Base user access through standard groups
- Model access inherited from dependencies
- No custom row-level security rules

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon purchase_order_duplicate_check
```

View file

@ -0,0 +1,8 @@
# Wizards
Transient models exposed as UI wizards in purchase_order_duplicate_check.
```mermaid
classDiagram
class ConfirmationWizard
```

View file

@ -0,0 +1,119 @@
==============================
Purchase Order Duplicate Check
==============================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3bd2069265d0abb0aae33f9182b70e7ba193a9815829105cbe083da12f2c1ea0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |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_order_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_order_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_order_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
Contributors
~~~~~~~~~~~~
* `Cetmix <http://cetmix.com>`_
* Ivan Sokolov
* Mikhail Lapin
* Maksim Shurupov
* Dinar Gabbasov
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_order_duplicate_check>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,2 @@
from . import models
from . import wizard

View file

@ -0,0 +1,16 @@
{
"name": "Purchase Order 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",
"depends": ["purchase_stock", "confirmation_wizard"],
"data": [
"views/purchase_order_views.xml",
"views/res_config_settings_views.xml",
"wizard/confirmation_wizard_views.xml",
],
}

View file

@ -0,0 +1,69 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_order_duplicate_check
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \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_order_duplicate_check
#: model_terms:ir.ui.view,arch_db:purchase_order_duplicate_check.res_config_settings_form_view
msgid "<span class=\"o_form_label\">Create Activity for Repeating Orders</span>"
msgstr "<span class=\"o_form_label\">Create Activity for Repeating Nalogs</span>"
#. module: purchase_order_duplicate_check
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_res_config_settings__repeating_orders_activity_type_id
msgid "Activity"
msgstr "Aktivnost"
#. module: purchase_order_duplicate_check
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_res_config_settings__allow_create_activity_repeating_orders
msgid "Allow Create Activity Repeating Orders"
msgstr "Allow Create Activity Repeating Nalogs"
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_confirmation_wizard
msgid "Confirmation Wizard"
msgstr "Confirmation Čarobnjak"
#. module: purchase_order_duplicate_check
#: model_terms:ir.ui.view,arch_db:purchase_order_duplicate_check.purchase_order_form_view
msgid "Open"
msgstr "Otvori"
#. module: purchase_order_duplicate_check
#. odoo-python
#: code:addons/purchase_order_duplicate_check/models/purchase_order_line.py:0
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_purchase_order_line__pending_order_ids
#, python-format
msgid "Pending Orders"
msgstr "Pending Nalogs"
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_res_config_settings
msgid "Procurement purchase grouping settings"
msgstr "Postavke grupiranja nabavki za prokuraciju"
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_purchase_order
msgid "Purchase Order"
msgstr "Nalog za nabavu"
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_purchase_order_line
msgid "Purchase Order Line"
msgstr "Stavka naloga za nabavu"
#. module: purchase_order_duplicate_check
#. odoo-python
#: code:addons/purchase_order_duplicate_check/wizard/confirmation_wizard.py:0
#, python-format
msgid "There are existing Requests for Quotations for:"
msgstr "There are existing Requests for Quotations for:"

View file

@ -0,0 +1,72 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_order_duplicate_check
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-05-10 10:24+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.10.4\n"
#. module: purchase_order_duplicate_check
#: model_terms:ir.ui.view,arch_db:purchase_order_duplicate_check.res_config_settings_form_view
msgid "<span class=\"o_form_label\">Create Activity for Repeating Orders</span>"
msgstr "<span class=\"o_form_label\">Crea attività per ordini ripetitivi</span>"
#. module: purchase_order_duplicate_check
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_res_config_settings__repeating_orders_activity_type_id
msgid "Activity"
msgstr "Attività"
#. module: purchase_order_duplicate_check
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_res_config_settings__allow_create_activity_repeating_orders
msgid "Allow Create Activity Repeating Orders"
msgstr "Consente creazione ordini ripetitivi attività"
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_confirmation_wizard
msgid "Confirmation Wizard"
msgstr "Procedura guidata conferma"
#. module: purchase_order_duplicate_check
#: model_terms:ir.ui.view,arch_db:purchase_order_duplicate_check.purchase_order_form_view
msgid "Open"
msgstr "Apri"
#. module: purchase_order_duplicate_check
#. odoo-python
#: code:addons/purchase_order_duplicate_check/models/purchase_order_line.py:0
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_purchase_order_line__pending_order_ids
#, python-format
msgid "Pending Orders"
msgstr "Ordini in sospeso"
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_res_config_settings
msgid "Procurement purchase grouping settings"
msgstr "Impostazioni raggruppamento approvvigionamento acquisti"
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_purchase_order
msgid "Purchase Order"
msgstr "Ordine di acquisto"
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_purchase_order_line
msgid "Purchase Order Line"
msgstr "Riga ordine di acquisto"
#. module: purchase_order_duplicate_check
#. odoo-python
#: code:addons/purchase_order_duplicate_check/wizard/confirmation_wizard.py:0
#, python-format
msgid "There are existing Requests for Quotations for:"
msgstr "Ci sono richieste di preventivo per:"

View file

@ -0,0 +1,69 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_order_duplicate_check
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \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_order_duplicate_check
#: model_terms:ir.ui.view,arch_db:purchase_order_duplicate_check.res_config_settings_form_view
msgid "<span class=\"o_form_label\">Create Activity for Repeating Orders</span>"
msgstr ""
#. module: purchase_order_duplicate_check
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_res_config_settings__repeating_orders_activity_type_id
msgid "Activity"
msgstr ""
#. module: purchase_order_duplicate_check
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_res_config_settings__allow_create_activity_repeating_orders
msgid "Allow Create Activity Repeating Orders"
msgstr ""
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_confirmation_wizard
msgid "Confirmation Wizard"
msgstr ""
#. module: purchase_order_duplicate_check
#: model_terms:ir.ui.view,arch_db:purchase_order_duplicate_check.purchase_order_form_view
msgid "Open"
msgstr ""
#. module: purchase_order_duplicate_check
#. odoo-python
#: code:addons/purchase_order_duplicate_check/models/purchase_order_line.py:0
#: model:ir.model.fields,field_description:purchase_order_duplicate_check.field_purchase_order_line__pending_order_ids
#, python-format
msgid "Pending Orders"
msgstr ""
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_res_config_settings
msgid "Procurement purchase grouping settings"
msgstr ""
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_purchase_order
msgid "Purchase Order"
msgstr ""
#. module: purchase_order_duplicate_check
#: model:ir.model,name:purchase_order_duplicate_check.model_purchase_order_line
msgid "Purchase Order Line"
msgstr ""
#. module: purchase_order_duplicate_check
#. odoo-python
#: code:addons/purchase_order_duplicate_check/wizard/confirmation_wizard.py:0
#, python-format
msgid "There are existing Requests for Quotations for:"
msgstr ""

View file

@ -0,0 +1,3 @@
from . import purchase_order
from . import purchase_order_line
from . import res_config_settings

View file

@ -0,0 +1,43 @@
from odoo import models, tools
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.order_line.filtered(lambda l: l.product_id.id == product_id)
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
)
return "".join(message_parts)
def _check_pending_order(self):
"""Check for pending orders and trigger confirmation wizard if needed."""
if not tools.config["test_enable"] or self._context.get(
"test_purchase_duplicate_check"
):
action = self.env["confirmation.wizard"].confirm_pending_order(self)
if action:
return action
def button_confirm(self):
"""
Confirm the purchase order.
:return: action or super
"""
action = self._check_pending_order()
return action or super().button_confirm()

View file

@ -0,0 +1,87 @@
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):
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")._origin.ids)
order_ids = tuple(product_lines.mapped("order_id")._origin.ids)
if not product_ids or not order_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},
}

View file

@ -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_order_duplicate_check.allow_create_activity_repeating_orders"
)
repeating_orders_activity_type_id = fields.Many2one(
comodel_name="mail.activity.type",
config_parameter="purchase_order_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

View file

@ -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

View file

@ -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).

View file

@ -0,0 +1,6 @@
* `Cetmix <http://cetmix.com>`_
* Ivan Sokolov
* Mikhail Lapin
* Maksim Shurupov
* Dinar Gabbasov

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,467 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Purchase Order Duplicate Check</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="purchase-order-duplicate-check">
<h1 class="title">Purchase Order Duplicate Check</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3bd2069265d0abb0aae33f9182b70e7ba193a9815829105cbe083da12f2c1ea0
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/purchase-workflow/tree/16.0/purchase_order_duplicate_check"><img alt="OCA/purchase-workflow" src="https://img.shields.io/badge/github-OCA%2Fpurchase--workflow-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/purchase-workflow-16-0/purchase-workflow-16-0-purchase_order_duplicate_check"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/purchase-workflow&amp;target_branch=16.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This module adds the following features to the Purchase App:</p>
<p>Pending RFQ or Purchase orders with associated incoming
stock picking not in the “Done” state for Product in PO Line:</p>
<ul class="simple">
<li>RFQ Confirmation Wizard if theres existing RFQ for the same products</li>
<li>Add “Pending orders” field to the PO Line</li>
<li>Assign Acitivty for the responsible user to check the associated orders</li>
</ul>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#use-cases-context" id="toc-entry-1">Use Cases / Context</a></li>
<li><a class="reference internal" href="#configuration" id="toc-entry-2">Configuration</a></li>
<li><a class="reference internal" href="#usage" id="toc-entry-3">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-4">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-5">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-6">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-7">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-8">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="use-cases-context">
<h1><a class="toc-backref" href="#toc-entry-1">Use Cases / Context</a></h1>
<p>This module helps prevent overordering of the same products by streamlining the control of Requests for Quotations (RFQs) and undelivered Purchase Orders (POs).</p>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-2">Configuration</a></h1>
<p>To Configure the Activity for a responsible person to check the repeating RFQ/PO:</p>
<p>Go to Settings -&gt; Purchase - &gt; Orders</p>
<ul class="simple">
<li>Enable the “Create Activity for Repeating Orders” Checkbox</li>
<li>Select the activity type in the “Activity” Field</li>
<li>Press the “Arrow button” to Configure the Default user for activity</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-3">Usage</a></h1>
<p>Pending RFQ for Product in PO Line:</p>
<p>Go to the Purchase App:</p>
<ul class="simple">
<li>Create a new order</li>
<li>Press the “Add a Line” button to add purchase line</li>
<li>Select a Product for the PO Line</li>
<li>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</li>
<li>Press the “Confirm Order” button to confirm the RFQ</li>
<li>If theres a Pending RFQs for the products in PO Lines, the Confirmation wizard with Pending RFQs information will appear</li>
<li>Press the “Confirm” button to process with the order or “Cancel” to get back to RFQ</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-4">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/purchase-workflow/issues">GitHub Issues</a>.
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
<a class="reference external" href="https://github.com/OCA/purchase-workflow/issues/new?body=module:%20purchase_order_duplicate_check%0Aversion:%2016.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-5">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-6">Authors</a></h2>
<ul class="simple">
<li>Cetmix</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-7">Contributors</a></h2>
<ul class="simple">
<li><a class="reference external" href="http://cetmix.com">Cetmix</a><ul>
<li>Ivan Sokolov</li>
<li>Mikhail Lapin</li>
<li>Maksim Shurupov</li>
<li>Dinar Gabbasov</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-8">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>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.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/purchase-workflow/tree/16.0/purchase_order_duplicate_check">OCA/purchase-workflow</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,133 @@
from odoo import SUPERUSER_ID, api
from odoo.tests import Form, TransactionCase
class TestPurchaseOrder(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env["res.partner"].create({"name": "Vendor #1"})
cls.env = api.Environment(
cls.cr, SUPERUSER_ID, {"test_purchase_duplicate_check": True}
)
cls.activity_type_email = cls.env.ref("mail.mail_activity_data_email")
cls.env["ir.config_parameter"].sudo().set_param(
"purchase_order_duplicate_check.allow_create_activity_repeating_orders",
True,
)
cls.env["ir.config_parameter"].sudo().set_param(
"purchase_order_duplicate_check.repeating_orders_activity_type_id",
cls.activity_type_email.id,
)
cls.product_1 = cls.env["product.product"].create(
{"name": "Product 1", "detailed_type": "product"}
)
cls.product_2 = cls.env["product.product"].create(
{"name": "Product 2", "detailed_type": "product"}
)
form = Form(cls.env["purchase.order"])
form.partner_id = cls.partner
with form.order_line.new() as line:
line.product_id = cls.product_1
line.product_qty = 10.0
cls.order1 = form.save()
def _get_and_create_purchase_order(self):
form = Form(self.env["purchase.order"])
form.partner_id = self.partner
with form.order_line.new() as line:
line.product_id = self.product_1
line.product_qty = 5.0
with form.order_line.new() as line:
line.product_id = self.product_2
line.product_qty = 5.0
return form.save()
def test_prepare_pending_orders_message(self):
"""Test flow where prepare message for purchase order"""
message = self.order1._prepare_pending_orders_message(self.product_2.id)
self.assertFalse(message, "Message must be empty")
expected_message = f"RFQ: <a href='/web#id={self.order1.id}&model=purchase.order'>{self.order1.name}</a>; date: {self.order1.create_date.date()}; Qty: 10.0;<br/>" # noqa
message = self.order1._prepare_pending_orders_message(self.product_1.id)
self.assertEqual(message, expected_message, "Messages must be the same")
def test_check_pending_order(self):
"""
Test flow where check purchase order
by exists pending orders for order lines
"""
result = self.order1._check_pending_order()
self.assertIsNone(result, "Result should be None")
order2 = self._get_and_create_purchase_order()
result = order2.with_context(skip_confirm_message=True)._check_pending_order()
self.assertIsNone(result, "Result should be None")
result = order2._check_pending_order()
self.assertIsInstance(result, dict, "Result should be dict")
def test_button_confirm(self):
"""
Test flow where check confirmation wizard at
the confirmation purchase order
"""
order1 = self.order1
order2 = self._get_and_create_purchase_order()
order3 = self._get_and_create_purchase_order()
order3.with_context(skip_confirm_message=True).button_confirm()
self.assertEqual(order2.state, "draft", "Order should be draft")
line1, line2 = order2.order_line
self.assertEqual(
line1.pending_order_ids,
order1 | order3,
"Pending orders should be the same",
)
self.assertEqual(
line2.pending_order_ids, order3, "Pending orders should be the same"
)
result = order2.button_confirm()
self.assertIsInstance(result, dict, "Result should be dict")
wizard = self.env["confirmation.wizard"].browse(result["res_id"])
self.assertEqual(wizard.res_ids, str(order2.ids), "Res IDS must be the same")
self.assertEqual(wizard.res_model, order1._name, "Res Model must be the same")
self.assertEqual(
wizard.callback_method,
"button_confirm",
"Callback method must be 'nutton_confirm'",
)
result = wizard.with_context(skip_confirm_message=True).action_confirm()
self.assertTrue(result, "Result should be True")
self.assertEqual(order2.state, "purchase", "Order state must be 'purchase'")
activity = order2.activity_ids
self.assertEqual(len(activity), 1)
self.assertEqual(activity.user_id, self.env.user)
self.assertEqual(activity.activity_type_id, self.activity_type_email)
def test_action_open_pending_orders(self):
"""
Test flow where check open pending orders wizard
"""
with self.assertRaises(ValueError):
self.env["purchase.order.line"].action_open_pending_orders()
order2 = self._get_and_create_purchase_order()
line = self.order1.order_line
self.assertEqual(len(line), 1, "Lines count must be equal to 1")
with self.assertRaises(ValueError):
order2.order_line.action_open_pending_orders()
action = line.action_open_pending_orders()
expected_action = {
"name": "Pending Orders",
"views": [[False, "tree"], [False, "form"]],
"res_model": "purchase.order",
"type": "ir.actions.act_window",
"domain": [("id", "in", order2.ids)],
"context": {"create": False},
}
self.assertDictEqual(action, expected_action, "Dicts must be the same")

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="purchase_order_form_view" model="ir.ui.view">
<field name="name">purchase.order.form.view</field>
<field name="model">purchase.order</field>
<field
name="inherit_id"
ref="purchase_stock.purchase_order_view_form_inherit"
/>
<field name="arch" type="xml">
<xpath
expr="//button[@name='action_product_forecast_report'][2]"
position="after"
>
<field
name="pending_order_ids"
widget="many2many_tags"
optional="show"
attrs="{
'invisible': [
'|',
('id', '=', False),
('product_type', '!=', 'product')
]
}"
/>
<button
name="action_open_pending_orders"
type="object"
class="oe_link"
optional="show"
icon="fa-credit-card"
string="Open"
attrs="{'invisible': [('pending_order_ids', '=', False)]}"
/>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="res_config_settings_form_view" model="ir.ui.view">
<field
name="name"
>res.config.settings.form.view.inherit.purchase_order_duplicate_check</field>
<field name="model">res.config.settings</field>
<field
name="inherit_id"
ref="purchase.res_config_settings_view_form_purchase"
/>
<field name="arch" type="xml">
<div name="purchase_setting_container" position="inside">
<div
class="col-12 col-lg-6 o_setting_box"
id="allow_create_activity_repeating_orders"
>
<div class="o_setting_left_pane">
<field name="allow_create_activity_repeating_orders" />
</div>
<div class="o_setting_right_pane">
<span
class="o_form_label"
>Create Activity for Repeating Orders</span>
<div
class="content-group"
attrs="{'invisible': [('allow_create_activity_repeating_orders', '=', False)]}"
>
<div class="row mt16">
<label
for="repeating_orders_activity_type_id"
class="col-lg-4 o_light_label"
/>
<field name="repeating_orders_activity_type_id" />
</div>
</div>
</div>
</div>
</div>
</field>
</record>
</odoo>

View file

@ -0,0 +1,59 @@
# Copyright (C) 2024 Cetmix OÜ
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from ast import literal_eval
from odoo import _, models
class ConfirmationWizard(models.TransientModel):
_inherit = "confirmation.wizard"
def confirm_pending_order(self, order):
invalid_lines = order.order_line.filtered("pending_order_ids")
if not invalid_lines:
return
message = invalid_lines._get_order_confirm_message()
if not message:
return
return self.confirm_message(
message,
order,
title=_("There are existing Requests for Quotations for:"),
method="button_confirm",
)
def _create_po_activity(self, activity_type_id):
res_ids = literal_eval(self.res_ids) if self.res_ids else []
message = self.message
records = self.env[self.res_model].browse(res_ids)
model_id = self.env["ir.model"]._get_id(records._name)
activity_type = self.env["mail.activity.type"].browse(activity_type_id)
user_id = activity_type.default_user_id.id or self.env.user.id
activity_type_id = activity_type.id
activity_vals_list = []
for record in records:
activity_vals_list.append(
{
"user_id": user_id,
"activity_type_id": activity_type_id,
"res_id": record.id,
"res_model_id": model_id,
"note": message,
}
)
if activity_vals_list:
self.env["mail.activity"].create(activity_vals_list)
def action_confirm(self):
action_type_id = (
self.env["ir.config_parameter"]
.sudo()
.get_param(
"purchase_order_duplicate_check.repeating_orders_activity_type_id",
False,
)
)
if action_type_id:
self._create_po_activity(int(action_type_id))
return super().action_confirm()

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="confirmation_wizard_form_view" model="ir.ui.view">
<field name="name">confirmation.wizard.inherit.form.view</field>
<field name="model">confirmation.wizard</field>
<field
name="inherit_id"
ref="confirmation_wizard.confirmation_wizard_form_view"
/>
<field name="arch" type="xml">
<field name="message" position="attributes">
<attribute name="widget">html</attribute>
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,43 @@
[project]
name = "odoo-bringout-oca-purchase-workflow-purchase_order_duplicate_check"
version = "16.0.0"
description = "Purchase Order Duplicate Check - Prevents overordering in the Purchase app with a Confirmation Wizard, 'Pending Orders' field, and activity tracking for repeated orders."
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-purchase-workflow-purchase_stock>=16.0.0",
"odoo-bringout-oca-purchase-workflow-confirmation_wizard>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["purchase_order_duplicate_check"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]