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 Only By Packaging
Odoo addon: purchase_only_by_packaging
## Installation
```bash
pip install odoo-bringout-oca-purchase-workflow-purchase_only_by_packaging
```
## Dependencies
This addon depends on:
- product_packaging_level_purchasable
- purchase_stock
## Manifest Information
- **Name**: Purchase Only By Packaging
- **Version**: 16.0.1.0.2
- **Category**: Warehouse Management
- **License**: AGPL-3
- **Installable**: True
## Source
Based on [OCA/purchase-workflow](https://github.com/OCA/purchase-workflow) branch 16.0, addon `purchase_only_by_packaging`.
## 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_only_by_packaging Module - purchase_only_by_packaging
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_only_by_packaging. 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:
- product_packaging_level_purchasable
- [purchase_stock](https://github.com/bringout/oca-ocb-warehouse/tree/0ee5ffef60413a71dceb350918ad3fb572ec1875/odoo-bringout-oca-ocb-purchase_stock)

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_only_by_packaging or install in UI.

View file

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

View file

@ -0,0 +1,16 @@
# Models
Detected core models and extensions in purchase_only_by_packaging.
```mermaid
classDiagram
class product_packaging
class product_packaging_level
class product_product
class product_template
class purchase_order_line
```
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_only_by_packaging. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon purchase_only_by_packaging
- 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_only_by_packaging
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,122 @@
==========================
Purchase Only By Packaging
==========================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3527f937091d5d74dfa0aedf59b254c90dd97c333bc49b569d77a17afcccc062
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |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_only_by_packaging
: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_only_by_packaging
: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 provides different configuration option to manage packagings on
purchase orders.
The creation/update of purchase order line will be blocked (by constraints) if the data on the
purchase.order.line does not fit with the configuration of the product's packagings.
It's also possible to force the quantity to purchase during creation/modification of the purchase order line
if the "Force purchase quantity" is ticked on the packaging and the "Purchase only by packaging" is ticked on product.
For example, if your packaging is set to purchase by 5 units and the employee fill
the quantity with 3, the quantity will be automatically replaced by 5 (it always rounds up).
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Configuration
=============
Following options are available to define which packaging level can be purchased and
which product can only be purchased by packaging.
* Purchase only by packaging: On product template model, this checkbox restricts
purchases of these products if no packaging is selected on the purchase order line.
If no packaging is selected, it will either be auto-assigned if the quantity
on the purchase order line matches a packaging quantity or an error will be raised.
* Force purchase quantity (on the packaging): force rounds up the quantity during
creation/modification of the purchase order line with the factor set on the packaging.
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_only_by_packaging%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
~~~~~~~
* Camptocamp
* BCIM
Contributors
~~~~~~~~~~~~
* Akim Juillerat <akim.juillerat@camptocamp.com>
* Thomas Nowicki <thomas.nowicki@camptocamp.com>
* François Honoré <francois.honore@acsone.eu>
* Hiep (Nguyen Hoang) <hiepnh@trobz.com>
* Phuc (Tran Thanh) <phuc@trobz.com>
* Duong (Tran Quoc) <duongtq@trobz.com>
* Telmo Santos <telmo.santos@camptocamp.com>
* Jacques-Etienne Baudoux (BCIM) <je@bcim.be>
Other credits
~~~~~~~~~~~~~
The development of this module has been financially supported by:
* Camptocamp
This module feature was extracted from the original `sale-workflow/sale_by_packaging <https://github.com/oca/sale-workflow/tree/14.0/sale_by_packaging>`_ module.
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_only_by_packaging>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View file

@ -0,0 +1,20 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
{
"name": "Purchase Only By Packaging",
"summary": "Manage purchase of packaging",
"version": "16.0.1.0.2",
"development_status": "Alpha",
"category": "Warehouse Management",
"website": "https://github.com/OCA/purchase-workflow",
"author": "Camptocamp, BCIM, Odoo Community Association (OCA)",
"license": "AGPL-3",
"application": False,
"installable": True,
"depends": ["product_packaging_level_purchasable", "purchase_stock"],
"data": [
"views/product_packaging.xml",
"views/product_template.xml",
"views/product_product.xml",
],
}

View file

@ -0,0 +1,116 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_only_by_packaging
#
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_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid ""
"Determine if during the creation of a purchase order line, the quantity should be forced with a multiple of the packaging.\n"
"Example:\n"
"You purchase a product by packaging of 5 products.\n"
"When the user will put 3 as quantity, the system can force the quantity to the superior unit (5 for this example)."
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid "Force purchase quantity"
msgstr "Force purchase quantity"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging_level
msgid "Level management for product.packaging"
msgstr "Level management for product.packaging"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid "Min Purchasable Qty"
msgstr "Min Purchasable Qty"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid ""
"Minimum purchasable quantity, according to the available packagings, if Only"
" Purchase by Packaging is set."
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid "Only purchase by packaging"
msgstr "Only purchase by packaging"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_packaging_level.py:0
#, python-format
msgid ""
"Packaging level \"{}\" must stay with \"Can be purchased\", at least one "
"product configured as \"purchase only by packaging\" is using it."
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_template
msgid "Product"
msgstr "Artikal"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/purchase_order_line.py:0
#, python-format
msgid ""
"Product %s can only be purchased with a packaging and a packaging quantity."
msgstr ""
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it cannot "
"be purchased."
msgstr ""
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it does "
"not have any packaging that can be purchased defined."
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging
msgid "Product Packaging"
msgstr "Pakiranje proizvoda"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_product
msgid "Product Variant"
msgstr "Varijanta proizvoda"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_purchase_order_line
msgid "Purchase Order Line"
msgstr "Stavka naloga za nabavu"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid ""
"Restrict the usage of this product on purchase order lines without packaging"
" defined"
msgstr ""

View file

@ -0,0 +1,137 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_only_by_packaging
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-04-25 12:23+0000\n"
"Last-Translator: davidbeckercbl <becker@cbl-computer.de>\n"
"Language-Team: none\n"
"Language: de\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_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid ""
"Determine if during the creation of a purchase order line, the quantity should be forced with a multiple of the packaging.\n"
"Example:\n"
"You purchase a product by packaging of 5 products.\n"
"When the user will put 3 as quantity, the system can force the quantity to the superior unit (5 for this example)."
msgstr ""
"Bestimmt während der Erstellung von Bestellzeilen, ob die Menge "
"erzwungenermaßen ein Vielfaches der Produktverpackung ist.\n"
"Beispiel:\n"
"Sie bestellen ein Produkt mit einer Produktverpackung von 5 Produkten.\n"
"Wenn der Nutzer nun Menge 3 bestellen will, kann das System die Menge auf "
"das nächste Vielfache setzen (5 in diesem Beispiel)."
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid "Force purchase quantity"
msgstr "Erzwinge Bestellmenge"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging_level
msgid "Level management for product.packaging"
msgstr "Levelverwaltung für Produktverpackungen"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid "Min Purchasable Qty"
msgstr "Minimale Bestellmenge"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid ""
"Minimum purchasable quantity, according to the available packagings, if Only"
" Purchase by Packaging is set."
msgstr ""
"Mindestbestellmenge, basierend auf den verfügbaren Verpackungen. Nur wenn "
"Verpackungsweise bestellbar aktiviert ist."
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid "Only purchase by packaging"
msgstr "Nur Verpackungsweise bestellbar"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_packaging_level.py:0
#, python-format
msgid ""
"Packaging level \"{}\" must stay with \"Can be purchased\", at least one "
"product configured as \"purchase only by packaging\" is using it."
msgstr ""
"Verpackungslevel \"{}\" muss einkaufbar sein. Mindestens ein Produkt mit "
"\"Nur Verpackungsweise bestellbar\" verwendet es."
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_template
msgid "Product"
msgstr "Produkt"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/purchase_order_line.py:0
#, python-format
msgid ""
"Product %s can only be purchased with a packaging and a packaging quantity."
msgstr ""
"Produkt %s kann nur mit einer Verpackung und einer Verpackungsmenge "
"eingekauft werden."
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it cannot "
"be purchased."
msgstr ""
"Produkt %s kann nicht als nur Verpackungsweise bestellbar definiert werden, "
"wenn es nicht eingekauft werden kann."
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it does "
"not have any packaging that can be purchased defined."
msgstr ""
"Produkt %s kann nicht als nur Verpackungsweise bestellbar definiert werden, "
"wenn es keine einkaufbaren Produktverpackungen definiert hat."
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging
msgid "Product Packaging"
msgstr "Produktverpackung"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_product
msgid "Product Variant"
msgstr "Produktvariante"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_purchase_order_line
msgid "Purchase Order Line"
msgstr "Bestellposition"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid ""
"Restrict the usage of this product on purchase order lines without packaging"
" defined"
msgstr ""
"Schränkt die Verwendung dieses Produktes auf Bestellzeilen ohne definierte "
"Produktverpackung ein"

View file

@ -0,0 +1,137 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_only_by_packaging
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-12-29 12:12+0000\n"
"Last-Translator: Ivorra78 <informatica@totmaterial.es>\n"
"Language-Team: none\n"
"Language: es\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 4.17\n"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid ""
"Determine if during the creation of a purchase order line, the quantity should be forced with a multiple of the packaging.\n"
"Example:\n"
"You purchase a product by packaging of 5 products.\n"
"When the user will put 3 as quantity, the system can force the quantity to the superior unit (5 for this example)."
msgstr ""
"Determinar si durante la creación de una línea de pedido, se debe forzar la "
"cantidad con un múltiplo del embalaje.\n"
"Ejemplo:\n"
"Se compra un producto por embalaje de 5 productos.\n"
"Cuando el usuario va a poner 3 como cantidad, el sistema puede forzar la "
"cantidad a la unidad superior (5 para este ejemplo)."
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid "Force purchase quantity"
msgstr "Forzar cantidad de compra"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging_level
msgid "Level management for product.packaging"
msgstr "Gestión de niveles para productos.envasados"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid "Min Purchasable Qty"
msgstr "Cantidad Mínima Adquirible"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid ""
"Minimum purchasable quantity, according to the available packagings, if Only"
" Purchase by Packaging is set."
msgstr ""
"Cantidad mínima adquirible, en función de los embalajes disponibles, si se "
"configura Sólo Compra por Embalaje."
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid "Only purchase by packaging"
msgstr "Sólo compra por paquete"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_packaging_level.py:0
#, python-format
msgid ""
"Packaging level \"{}\" must stay with \"Can be purchased\", at least one "
"product configured as \"purchase only by packaging\" is using it."
msgstr ""
"El nivel de embalaje \"{}\" debe permanecer con \"Se puede comprar\", al "
"menos un producto configurado como \"comprar sólo por embalaje\" lo está "
"utilizando."
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_template
msgid "Product"
msgstr "Producto"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/purchase_order_line.py:0
#, python-format
msgid ""
"Product %s can only be purchased with a packaging and a packaging quantity."
msgstr ""
"El producto %s solo se puede vender con un embalaje y una cantidad de "
"embalaje."
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it cannot "
"be purchased."
msgstr ""
"El producto %s no puede ser definido para ser comprado sólo por embalaje si "
"no puede ser comprado."
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it does "
"not have any packaging that can be purchased defined."
msgstr ""
"El producto %s no puede definirse para ser comprado sólo por envase si no "
"tiene definido ningún envase que pueda ser comprado."
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging
msgid "Product Packaging"
msgstr "Empaquetado de Producto"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_product
msgid "Product Variant"
msgstr "Variante del Producto"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_purchase_order_line
msgid "Purchase Order Line"
msgstr "Línea de Orden de Compra"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid ""
"Restrict the usage of this product on purchase order lines without packaging"
" defined"
msgstr ""
"Restringir el uso de este producto en líneas de pedido sin embalaje definido"

View file

@ -0,0 +1,138 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_only_by_packaging
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-12-31 13:06+0000\n"
"Last-Translator: samibc2c <sami.bouzidi@camptocamp.com>\n"
"Language-Team: none\n"
"Language: fr\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.6.2\n"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid ""
"Determine if during the creation of a purchase order line, the quantity should be forced with a multiple of the packaging.\n"
"Example:\n"
"You purchase a product by packaging of 5 products.\n"
"When the user will put 3 as quantity, the system can force the quantity to the superior unit (5 for this example)."
msgstr ""
"Détermine si lors de la création d'une ligne de commande d'achat, la "
"quantité doit être forcée avec un multiple du conditionnement.\n"
"Exemple:\n"
"Vous achetez un produit par conditionnement de 5 produits.\n"
"Lorsque l'utilisateur indique 3 comme quantité, le système peut forcer la "
"quantité à l'unité supérieure (5 pour cet exemple)."
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid "Force purchase quantity"
msgstr "Forcer les quantité d'achat"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging_level
msgid "Level management for product.packaging"
msgstr "Gestion des niveau pour product.packaging"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid "Min Purchasable Qty"
msgstr "Quantité achetable minimum"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid ""
"Minimum purchasable quantity, according to the available packagings, if Only"
" Purchase by Packaging is set."
msgstr ""
"Quantité minimale achetable, en fonction des condtionnements disponibles, si "
"l'option Achat par conditionnement est activée."
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid "Only purchase by packaging"
msgstr "Achat uniquement par conditionnement"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_packaging_level.py:0
#, python-format
msgid ""
"Packaging level \"{}\" must stay with \"Can be purchased\", at least one "
"product configured as \"purchase only by packaging\" is using it."
msgstr ""
"Le niveau de contionnement \"{}\" doit rester en \"Peut-être acheté\" car au "
"moins un produit configuré comme \"achat uniquement par condionnement\" "
"l'utilise."
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_template
msgid "Product"
msgstr "Produit"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/purchase_order_line.py:0
#, python-format
msgid ""
"Product %s can only be purchased with a packaging and a packaging quantity."
msgstr ""
"Le produit %s ne peut être acheté qu'avec un conditionnement et une quantité "
"de conditionnement."
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it cannot "
"be purchased."
msgstr ""
"Le product %s ne peut-être défini comme achetable uniquement par "
"conditionnement s'il n'est pas achetable."
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it does "
"not have any packaging that can be purchased defined."
msgstr ""
"Le product %s ne peut-être défini comme achetable uniquement par "
"conditionnement s'il aucun conditionnement achetable n'est défini."
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging
msgid "Product Packaging"
msgstr "Conditionnement"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_product
msgid "Product Variant"
msgstr "Variante de produit"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_purchase_order_line
msgid "Purchase Order Line"
msgstr "Ligne de commande d'achat"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid ""
"Restrict the usage of this product on purchase order lines without packaging"
" defined"
msgstr ""
"Restreindre l'utilisation de ce produit sur les lignes de commande d'achat "
"sans conditionnement défini"

View file

@ -0,0 +1,138 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_only_by_packaging
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2023-12-24 12:38+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 4.17\n"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid ""
"Determine if during the creation of a purchase order line, the quantity should be forced with a multiple of the packaging.\n"
"Example:\n"
"You purchase a product by packaging of 5 products.\n"
"When the user will put 3 as quantity, the system can force the quantity to the superior unit (5 for this example)."
msgstr ""
"Determina se durante la creazione di una riga ordine di acquisto la quantità "
"debba essere forzata con un multiplo dell'imballaggio.\n"
"Esempio:\n"
"si vende un prodotto con imballaggio di 5 prodotti.\n"
"Quando l'utente inserisce 3 come quantità, il sistema può forzare la "
"quantità all'unità superiore (5 per questo esempio)."
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid "Force purchase quantity"
msgstr "Forza quantità acquisto"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging_level
msgid "Level management for product.packaging"
msgstr "Gestione livello per product.packaging"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid "Min Purchasable Qty"
msgstr "Q.tà minima acquistabile"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid ""
"Minimum purchasable quantity, according to the available packagings, if Only"
" Purchase by Packaging is set."
msgstr ""
"Quantità minima acquistabile, in accordo con gli imballi disponibili, se è "
"impostato \"Acquisto solo per imballo\"."
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid "Only purchase by packaging"
msgstr "Acquisto solo per imballo"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_packaging_level.py:0
#, python-format
msgid ""
"Packaging level \"{}\" must stay with \"Can be purchased\", at least one "
"product configured as \"purchase only by packaging\" is using it."
msgstr ""
"Il livello imballaggio \"{}\" deve essere impostato come \"Può essere "
"acquistato\", dato che almeno un prodotto impostato con \"Acquista solo per "
"imballaggio\" lo sta utilizzando."
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_template
msgid "Product"
msgstr "Prodotto"
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/purchase_order_line.py:0
#, python-format
msgid ""
"Product %s can only be purchased with a packaging and a packaging quantity."
msgstr ""
"Il prodotto %s può essere acquistato solo con un imballaggio e una quantità "
"imballaggio."
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it cannot "
"be purchased."
msgstr ""
"Il prodotto %s non può essere definito per essere acquistato solo con "
"imballaggio se non può essere acquistato."
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it does "
"not have any packaging that can be purchased defined."
msgstr ""
"Il prodotto %s non può essere definito per essere acquistato solo con "
"imballaggio se non è impostato alcun imballaggio che può essere acquistato."
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging
msgid "Product Packaging"
msgstr "Imballaggio prodotto"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_product
msgid "Product Variant"
msgstr "Variante prodotto"
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_purchase_order_line
msgid "Purchase Order Line"
msgstr "Riga ordine di acquisto"
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid ""
"Restrict the usage of this product on purchase order lines without packaging"
" defined"
msgstr ""
"Non permettere l'uso di questo prodotto nelle righe ordine di acquisto senza "
"imballaggio definito"

View file

@ -0,0 +1,116 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_only_by_packaging
#
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_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid ""
"Determine if during the creation of a purchase order line, the quantity should be forced with a multiple of the packaging.\n"
"Example:\n"
"You purchase a product by packaging of 5 products.\n"
"When the user will put 3 as quantity, the system can force the quantity to the superior unit (5 for this example)."
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_packaging__force_purchase_qty
msgid "Force purchase quantity"
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging_level
msgid "Level management for product.packaging"
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid "Min Purchasable Qty"
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__min_purchasable_qty
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__min_purchasable_qty
msgid ""
"Minimum purchasable quantity, according to the available packagings, if Only"
" Purchase by Packaging is set."
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,field_description:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid "Only purchase by packaging"
msgstr ""
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_packaging_level.py:0
#, python-format
msgid ""
"Packaging level \"{}\" must stay with \"Can be purchased\", at least one "
"product configured as \"purchase only by packaging\" is using it."
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_template
msgid "Product"
msgstr ""
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/purchase_order_line.py:0
#, python-format
msgid ""
"Product %s can only be purchased with a packaging and a packaging quantity."
msgstr ""
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it cannot "
"be purchased."
msgstr ""
#. module: purchase_only_by_packaging
#. odoo-python
#: code:addons/purchase_only_by_packaging/models/product_template.py:0
#, python-format
msgid ""
"Product %s cannot be defined to be purchased only by packaging if it does "
"not have any packaging that can be purchased defined."
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_packaging
msgid "Product Packaging"
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_product_product
msgid "Product Variant"
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model,name:purchase_only_by_packaging.model_purchase_order_line
msgid "Purchase Order Line"
msgstr ""
#. module: purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_product__purchase_only_by_packaging
#: model:ir.model.fields,help:purchase_only_by_packaging.field_product_template__purchase_only_by_packaging
msgid ""
"Restrict the usage of this product on purchase order lines without packaging"
" defined"
msgstr ""

View file

@ -0,0 +1,5 @@
from . import product_packaging
from . import product_packaging_level
from . import product_product
from . import product_template
from . import purchase_order_line

View file

@ -0,0 +1,18 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import fields, models
class ProductPackaging(models.Model):
_inherit = "product.packaging"
force_purchase_qty = fields.Boolean(
string="Force purchase quantity",
help="Determine if during the creation of a purchase order line, the "
"quantity should be forced with a multiple of the packaging.\n"
"Example:\n"
"You purchase a product by packaging of 5 products.\n"
"When the user will put 3 as quantity, the system can force the "
"quantity to the superior unit (5 for this example).",
)

View file

@ -0,0 +1,26 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, exceptions, models
class ProductPackagingLevel(models.Model):
_inherit = "product.packaging.level"
@api.constrains("can_be_purchased")
def _check_purchase_only_by_packaging_can_be_purchased_packaging_ids(self):
for record in self:
if record.can_be_purchased:
continue
products = record.packaging_ids.product_id
templates = products.product_tmpl_id
try:
templates._check_purchase_only_by_packaging_can_be_purchased_packaging_ids()
except exceptions.ValidationError as e:
raise exceptions.ValidationError(
_(
'Packaging level "{}" must stay with "Can be purchased",'
' at least one product configured as "purchase only'
' by packaging" is using it.'
).format(record.display_name)
) from e

View file

@ -0,0 +1,61 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import api, fields, models
from odoo.tools import float_compare, float_round
class ProductProduct(models.Model):
_inherit = "product.product"
min_purchasable_qty = fields.Float(
compute="_compute_variant_min_purchasable_qty",
help=(
"Minimum purchasable quantity, according to the available packagings, "
"if Only Purchase by Packaging is set."
),
)
@api.depends(
"purchase_only_by_packaging",
"packaging_ids.qty",
"packaging_ids.can_be_purchased",
)
def _compute_variant_min_purchasable_qty(self):
for record in self:
record.min_purchasable_qty = 0.0
if record.purchase_only_by_packaging and record.packaging_ids:
purchasable_pkgs = record.packaging_ids.filtered(
lambda p: p.can_be_purchased
)
record.min_purchasable_qty = fields.first(
purchasable_pkgs.sorted(lambda p: p.qty)
).qty
def _convert_purchase_packaging_qty(self, qty, uom, packaging):
"""
Convert the given qty with given UoM to the packaging uom.
To do that, first transform the qty to the reference UoM and then
transform using the packaging UoM.
The given qty is not updated if the product has purchase_only_by_packaging
set to False or if the packaging is not set.
:param qty: float
:return: float
"""
if not self or not packaging:
return qty
self.ensure_one()
if self.purchase_only_by_packaging and packaging.force_purchase_qty:
q = self.uom_id._compute_quantity(packaging.qty, uom)
if (
qty
and q
and float_compare(
qty / q,
float_round(qty / q, precision_rounding=1.0),
precision_rounding=0.001,
)
!= 0
):
qty = qty - (qty % q) + q
return qty

View file

@ -0,0 +1,75 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ProductTemplate(models.Model):
_inherit = "product.template"
purchase_only_by_packaging = fields.Boolean(
string="Only purchase by packaging",
company_dependent=True,
default=False,
help="Restrict the usage of this product on purchase order lines without "
"packaging defined",
)
min_purchasable_qty = fields.Float(
compute="_compute_template_min_purchasable_qty",
help=(
"Minimum purchasable quantity, according to the available packagings, "
"if Only Purchase by Packaging is set."
),
)
@api.depends(
"purchase_only_by_packaging",
"uom_id.factor",
"product_variant_ids.min_purchasable_qty",
)
def _compute_template_min_purchasable_qty(self):
for record in self:
record.min_purchasable_qty = 0.0
if len(record.product_variant_ids) == 1:
# Pick the value from the variant if there's only 1
record.min_purchasable_qty = (
record.product_variant_ids.min_purchasable_qty
)
@api.constrains("purchase_only_by_packaging", "purchase_ok")
def _check_purchase_only_by_packaging_purchase_ok(self):
for product in self:
if product.purchase_only_by_packaging and not product.purchase_ok:
raise ValidationError(
_(
"Product %s cannot be defined to be purchased only by "
"packaging if it cannot be purchased."
)
% product.name
)
@api.constrains("purchase_only_by_packaging", "packaging_ids")
def _check_purchase_only_by_packaging_can_be_purchased_packaging_ids(self):
for product in self:
if product.purchase_only_by_packaging:
if (
# Product template only condition
len(product.product_variant_ids) == 1
and not any(pack.can_be_purchased for pack in product.packaging_ids)
# Product variants condition
or len(product.product_variant_ids) > 1
and not any(
pack.can_be_purchased
for pack in product.product_variant_ids.mapped("packaging_ids")
)
):
raise ValidationError(
_(
"Product %s cannot be defined to be purchased only by "
"packaging if it does not have any packaging that "
"can be purchased defined."
)
% product.name
)

View file

@ -0,0 +1,64 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from math import ceil
from odoo import _, api, models
from odoo.exceptions import ValidationError
from odoo.tools import float_compare
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
@api.constrains(
"product_id", "product_packaging_id", "product_packaging_qty", "product_qty"
)
def _check_product_packaging_purchase_only_by_packaging(self):
for line in self:
if not line.product_id.purchase_only_by_packaging:
continue
if (
not line.product_packaging_id
or float_compare(
line.product_packaging_qty,
int(line.product_packaging_qty),
precision_digits=2,
)
!= 0
):
raise ValidationError(
_(
"Product %s can only be purchased with a packaging and a "
"packaging quantity."
)
% line.product_id.name
)
def _force_qty_with_package(self):
"""
:return:
"""
self.ensure_one()
qty = self.product_id._convert_purchase_packaging_qty(
self.product_qty, self.product_uom, packaging=self.product_packaging_id
)
self.product_qty = qty
return True
@api.onchange("product_packaging_id")
def _onchange_product_packaging_id(self):
# Round up to the next integer and avoid the Warning raised by
# _onchange_product_packaging_id defined in the purchase addon
# The issue exists for sale order => odoo issue to fix proposed
# here: https://github.com/odoo/odoo/issues/197598
ceiled_product_packaging_qty = ceil(self.product_packaging_qty)
self.product_packaging_qty = ceiled_product_packaging_qty or 1
return super()._onchange_product_packaging_id()
@api.onchange("product_qty")
def _onchange_product_qty(self):
self._force_qty_with_package()

View file

@ -0,0 +1,10 @@
Following options are available to define which packaging level can be purchased and
which product can only be purchased by packaging.
* Purchase only by packaging: On product template model, this checkbox restricts
purchases of these products if no packaging is selected on the purchase order line.
If no packaging is selected, it will either be auto-assigned if the quantity
on the purchase order line matches a packaging quantity or an error will be raised.
* Force purchase quantity (on the packaging): force rounds up the quantity during
creation/modification of the purchase order line with the factor set on the packaging.

View file

@ -0,0 +1,8 @@
* Akim Juillerat <akim.juillerat@camptocamp.com>
* Thomas Nowicki <thomas.nowicki@camptocamp.com>
* François Honoré <francois.honore@acsone.eu>
* Hiep (Nguyen Hoang) <hiepnh@trobz.com>
* Phuc (Tran Thanh) <phuc@trobz.com>
* Duong (Tran Quoc) <duongtq@trobz.com>
* Telmo Santos <telmo.santos@camptocamp.com>
* Jacques-Etienne Baudoux (BCIM) <je@bcim.be>

View file

@ -0,0 +1,5 @@
The development of this module has been financially supported by:
* Camptocamp
This module feature was extracted from the original `sale-workflow/sale_by_packaging <https://github.com/oca/sale-workflow/tree/14.0/sale_by_packaging>`_ module.

View file

@ -0,0 +1,11 @@
This module provides different configuration option to manage packagings on
purchase orders.
The creation/update of purchase order line will be blocked (by constraints) if the data on the
purchase.order.line does not fit with the configuration of the product's packagings.
It's also possible to force the quantity to purchase during creation/modification of the purchase order line
if the "Force purchase quantity" is ticked on the packaging and the "Purchase only by packaging" is ticked on product.
For example, if your packaging is set to purchase by 5 units and the employee fill
the quantity with 3, the quantity will be automatically replaced by 5 (it always rounds up).

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 Only By Packaging</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-only-by-packaging">
<h1 class="title">Purchase Only By Packaging</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3527f937091d5d74dfa0aedf59b254c90dd97c333bc49b569d77a17afcccc062
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.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_only_by_packaging"><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_only_by_packaging"><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 provides different configuration option to manage packagings on
purchase orders.</p>
<p>The creation/update of purchase order line will be blocked (by constraints) if the data on the
purchase.order.line does not fit with the configuration of the products packagings.</p>
<p>Its also possible to force the quantity to purchase during creation/modification of the purchase order line
if the “Force purchase quantity” is ticked on the packaging and the “Purchase only by packaging” is ticked on product.</p>
<p>For example, if your packaging is set to purchase by 5 units and the employee fill
the quantity with 3, the quantity will be automatically replaced by 5 (it always rounds up).</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#configuration" id="toc-entry-1">Configuration</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-6">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="configuration">
<h1><a class="toc-backref" href="#toc-entry-1">Configuration</a></h1>
<p>Following options are available to define which packaging level can be purchased and
which product can only be purchased by packaging.</p>
<ul class="simple">
<li>Purchase only by packaging: On product template model, this checkbox restricts
purchases of these products if no packaging is selected on the purchase order line.
If no packaging is selected, it will either be auto-assigned if the quantity
on the purchase order line matches a packaging quantity or an error will be raised.</li>
<li>Force purchase quantity (on the packaging): force rounds up the quantity during
creation/modification of the purchase order line with the factor set on the packaging.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">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_only_by_packaging%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-3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>Camptocamp</li>
<li>BCIM</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul class="simple">
<li>Akim Juillerat &lt;<a class="reference external" href="mailto:akim.juillerat&#64;camptocamp.com">akim.juillerat&#64;camptocamp.com</a>&gt;</li>
<li>Thomas Nowicki &lt;<a class="reference external" href="mailto:thomas.nowicki&#64;camptocamp.com">thomas.nowicki&#64;camptocamp.com</a>&gt;</li>
<li>François Honoré &lt;<a class="reference external" href="mailto:francois.honore&#64;acsone.eu">francois.honore&#64;acsone.eu</a>&gt;</li>
<li>Hiep (Nguyen Hoang) &lt;<a class="reference external" href="mailto:hiepnh&#64;trobz.com">hiepnh&#64;trobz.com</a>&gt;</li>
<li>Phuc (Tran Thanh) &lt;<a class="reference external" href="mailto:phuc&#64;trobz.com">phuc&#64;trobz.com</a>&gt;</li>
<li>Duong (Tran Quoc) &lt;<a class="reference external" href="mailto:duongtq&#64;trobz.com">duongtq&#64;trobz.com</a>&gt;</li>
<li>Telmo Santos &lt;<a class="reference external" href="mailto:telmo.santos&#64;camptocamp.com">telmo.santos&#64;camptocamp.com</a>&gt;</li>
<li>Jacques-Etienne Baudoux (BCIM) &lt;<a class="reference external" href="mailto:je&#64;bcim.be">je&#64;bcim.be</a>&gt;</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#toc-entry-6">Other credits</a></h2>
<p>The development of this module has been financially supported by:</p>
<ul class="simple">
<li>Camptocamp</li>
</ul>
<p>This module feature was extracted from the original <a class="reference external" href="https://github.com/oca/sale-workflow/tree/14.0/sale_by_packaging">sale-workflow/sale_by_packaging</a> module.</p>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">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_only_by_packaging">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,3 @@
from . import test_minimum_purchasable_qty
from . import test_purchase_only_by_packaging
from . import test_purchase_line_onchanges

View file

@ -0,0 +1,94 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo.tests.common import Form, TransactionCase
TU_PRODUCT_QTY = 20
PL_PRODUCT_QTY = TU_PRODUCT_QTY * 30
class Common(TransactionCase):
at_install = False
post_install = True
@classmethod
def setUpClass(cls):
super(Common, cls).setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.env.user.groups_id += cls.env.ref("product.group_stock_packaging")
cls.setUpClassPartner()
cls.setUpClassProduct()
cls.setUpClassPackagingType()
cls.setUpClassPackaging()
cls.setUpClassSaleOrder()
cls.setUpClassConfig()
@classmethod
def setUpClassConfig(cls):
cls.precision = cls.env["decimal.precision"].precision_get("Product Price")
@classmethod
def setUpClassPartner(cls):
cls.partner = cls.env.ref("base.res_partner_12")
@classmethod
def setUpClassProduct(cls):
cls.product = cls.env.ref("product.product_product_9")
@classmethod
def setUpClassPackagingType(cls):
cls.packaging_level_tu = cls.env["product.packaging.level"].create(
{"name": "Transport Unit", "code": "TU", "sequence": 1}
)
cls.packaging_level_pl = cls.env["product.packaging.level"].create(
{"name": "Pallet", "code": "PL", "sequence": 2}
)
cls.packaging_level_cannot_be_purchased = cls.env[
"product.packaging.level"
].create(
{
"name": "Can not be purchased",
"code": "CNBP",
"sequence": 30,
"can_be_purchased": False,
}
)
@classmethod
def setUpClassPackaging(cls):
cls.packaging_tu = cls.env["product.packaging"].create(
{
"name": "PACKAGING TU",
"product_id": cls.product.id,
"packaging_level_id": cls.packaging_level_tu.id,
"qty": TU_PRODUCT_QTY,
}
)
cls.packaging_pl = cls.env["product.packaging"].create(
{
"name": "PACKAGING PL",
"product_id": cls.product.id,
"packaging_level_id": cls.packaging_level_pl.id,
"qty": PL_PRODUCT_QTY,
}
)
cls.packaging_cannot_be_purchased = cls.env["product.packaging"].create(
{
"name": "Test packaging cannot be purchased",
"product_id": cls.product.id,
"qty": 10.0,
"packaging_level_id": cls.packaging_level_cannot_be_purchased.id,
}
)
cls.purchasable_packagings = cls.packaging_tu | cls.packaging_pl
@classmethod
def setUpClassSaleOrder(cls):
cls.po_model = cls.env["purchase.order"]
purchase_form = Form(cls.po_model)
purchase_form.partner_id = cls.partner
with purchase_form.order_line.new() as line:
line.product_id = cls.product
line.product_uom = cls.product.uom_id
cls.order = purchase_form.save()
cls.order_line = cls.order.order_line

View file

@ -0,0 +1,58 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo.tests import TransactionCase
class TestMinimumPurchasableQty(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True))
cls.product = cls.env.ref("product.product_product_9")
cls.packaging_level_1 = cls.env["product.packaging.level"].create(
{"name": "Packaging level 1", "code": "PL1", "sequence": 1}
)
cls.packaging_level_2 = cls.env["product.packaging.level"].create(
{"name": "Packaging level 1", "code": "PL2", "sequence": 2}
)
cls.packaging_level_3 = cls.env["product.packaging.level"].create(
{"name": "Packaging level 3", "code": "PL3", "sequence": 3}
)
cls.packaging1 = cls.env["product.packaging"].create(
{
"name": "Packaging of five",
"product_id": cls.product.id,
"qty": 5.0,
"packaging_level_id": cls.packaging_level_1.id,
}
)
cls.packaging2 = cls.env["product.packaging"].create(
{
"name": "Packing of 3",
"product_id": cls.product.id,
"qty": 3.0,
"packaging_level_id": cls.packaging_level_2.id,
}
)
def test_min_purchasable_qty(self):
"""Check the computation of the minimum purchasable quantity."""
self.product.product_tmpl_id.purchase_only_by_packaging = False
self.assertEqual(self.product.product_tmpl_id.min_purchasable_qty, 0)
self.product.product_tmpl_id.purchase_only_by_packaging = True
self.assertEqual(self.product.min_purchasable_qty, 3)
self.assertEqual(self.product.product_tmpl_id.min_purchasable_qty, 3)
self.packaging2.can_be_purchased = False
self.assertEqual(self.product.min_purchasable_qty, 5)
self.assertEqual(self.product.product_tmpl_id.min_purchasable_qty, 5)
self.packaging3 = self.env["product.packaging"].create(
{
"name": "Packing of 2",
"product_id": self.product.id,
"qty": 2.0,
"packaging_level_id": self.packaging_level_3.id,
}
)
self.assertEqual(self.product.min_purchasable_qty, 2)
self.assertEqual(self.product.product_tmpl_id.min_purchasable_qty, 2)

View file

@ -0,0 +1,23 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo.tests.common import Form
from .common import PL_PRODUCT_QTY, TU_PRODUCT_QTY, Common
class TestPackaging(Common):
def test_compute_qties(self):
with Form(self.order) as po:
with po.order_line.edit(0) as line:
line.product_packaging_id = self.packaging_tu
line.product_packaging_qty = 31
# (20*30)+20 = 31*20 = 620
expected_qty = TU_PRODUCT_QTY + PL_PRODUCT_QTY
self.assertEqual(self.order_line.product_qty, expected_qty)
with Form(self.order) as po:
with po.order_line.edit(0) as line:
line.product_packaging_qty = 30
# 20*30 = 600
expected_qty = PL_PRODUCT_QTY
self.assertEqual(self.order_line.product_qty, expected_qty)

View file

@ -0,0 +1,92 @@
# Copyright 2023 Camptocamp SA
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
from odoo.exceptions import ValidationError
from odoo.tests import Form
from odoo.tools import mute_logger
from .common import Common
class TestSaleProductByPackagingOnly(Common):
def test_write_auto_fill_packaging(self):
order_line = self.order.order_line
self.assertFalse(order_line.product_packaging_id)
self.assertFalse(order_line.product_packaging_qty)
order_line.write({"product_qty": 3.0})
self.assertFalse(order_line.product_packaging_id)
self.assertFalse(order_line.product_packaging_qty)
self.product.write({"purchase_only_by_packaging": True})
self.assertFalse(order_line.product_packaging_id)
self.assertFalse(order_line.product_packaging_qty)
order_line.write({"product_qty": self.packaging_tu.qty * 2})
self.assertTrue(order_line.product_packaging_id)
self.assertTrue(order_line.product_packaging_qty)
self.assertEqual(order_line.product_packaging_id, self.packaging_tu)
self.assertEqual(order_line.product_packaging_qty, 2)
with self.assertRaises(ValidationError):
order_line.write({"product_packaging_id": False})
def test_error_purchase_packaging(self):
# If qty does not match a packaging qty, an exception should be raised
self.product.purchase_only_by_packaging = True
with self.assertRaises(ValidationError):
with Form(self.order) as so:
with so.order_line.new() as so_line:
so_line.product_id = self.product
so_line.product_qty = 2
@mute_logger("odoo.tests.common.onchange")
def test_convert_packaging_qty(self):
"""
Test if the function _convert_packaging_qty is correctly applied
during SO line create/edit and if qties are corrects.
:return:
"""
self.product.purchase_only_by_packaging = True
packaging = self.packaging_tu
self.order_line.product_packaging_id = packaging
# For this step, the qty is not forced on the packaging
# But the warning will be raise because the value of packaging qty is
# not integer package
with self.assertRaises(ValidationError):
self.order_line.product_packaging_qty = 0.6
self.assertAlmostEqual(
self.order_line.product_qty, 12, places=self.precision
)
# Now force the qty on the packaging
packaging.force_purchase_qty = True
with Form(self.order) as purchase_order:
with purchase_order.order_line.edit(0) as so_line:
so_line.product_packaging_id = packaging
so_line.product_qty = 52
self.assertAlmostEqual(so_line.product_qty, 60, places=self.precision)
so_line.product_qty = 40
self.assertAlmostEqual(so_line.product_qty, 40, places=self.precision)
so_line.product_qty = 38
self.assertAlmostEqual(so_line.product_qty, 40, places=self.precision)
so_line.product_qty = 22
self.assertAlmostEqual(so_line.product_qty, 40, places=self.precision)
so_line.product_qty = 72
self.assertAlmostEqual(so_line.product_qty, 80, places=self.precision)
so_line.product_qty = 209.98
self.assertAlmostEqual(so_line.product_qty, 220, places=self.precision)
def test_onchange_qty_is_not_pack_multiple(self):
"""Check package when qantity is not a multiple of package quantity.
When the uom quantity is changed for a value not a multpile of a
possible package an error is raised.
"""
self.product.write({"purchase_only_by_packaging": True})
self.order_line.product_qty = 40 # 2 packs
with self.assertRaises(ValidationError):
self.order_line.product_packaging_qty = 0.9
self.assertAlmostEqual(
self.order_line.product_qty, 18, places=self.precision
)

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="product_packaging_tree_view_inherit" model="ir.ui.view">
<field name="name">product.packaging.tree.view.inherit</field>
<field name="model">product.packaging</field>
<field name="inherit_id" ref="product.product_packaging_tree_view" />
<field name="arch" type="xml">
<field name="qty" position="after">
<field name="force_purchase_qty" />
</field>
</field>
</record>
<record id="product_packaging_form_view_inherit" model="ir.ui.view">
<field name="name">product.packaging.form.view.inherit</field>
<field name="model">product.packaging</field>
<field name="inherit_id" ref="product.product_packaging_form_view" />
<field name="arch" type="xml">
<field name="barcode" position="before">
<field name="force_purchase_qty" />
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 Camptocamp SA
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="product_product_tree_view" model="ir.ui.view">
<field name="name">product.product.tree.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_product_tree_view" />
<field name="arch" type="xml">
<field name="lst_price" position="after">
<field name="purchase_only_by_packaging" invisible="1" />
<field
name="min_purchasable_qty"
attrs="{'invisible':['|', ('type', '!=', 'product'), ('purchase_only_by_packaging', '=', False)]}"
optional="show"
/>
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 Camptocamp License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="product_template_form_view" model="ir.ui.view">
<field name="name">product.template.form.view</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view" />
<field name="arch" type="xml">
<div name="options" position="inside">
<div attrs="{'invisible': [('purchase_ok', '=', False)]}">
<field name="purchase_only_by_packaging" />
<label for="purchase_only_by_packaging" />
</div>
</div>
<group name="group_general" position="inside">
<field name="min_purchasable_qty" />
</group>
</field>
</record>
<record id="product_template_tree_view" model="ir.ui.view">
<field name="name">product.template.tree.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_tree_view" />
<field name="arch" type="xml">
<field name="standard_price" position="after">
<field name="purchase_only_by_packaging" invisible="1" />
<field name="min_purchasable_qty" optional="show" />
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,43 @@
[project]
name = "odoo-bringout-oca-purchase-workflow-purchase_only_by_packaging"
version = "16.0.0"
description = "Purchase Only By Packaging - Manage purchase of packaging"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-purchase-workflow-product_packaging_level_purchasable>=16.0.0",
"odoo-bringout-oca-purchase-workflow-purchase_stock>=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_only_by_packaging"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]