Compare commits
5 commits
16.0
...
238-16.0-b
| Author | SHA1 | Date | |
|---|---|---|---|
| 45c1fd968d | |||
| a4c9a57ef5 | |||
| f513c0919f | |||
| c2ded5f96f | |||
| a9ade6a690 |
23 changed files with 1158 additions and 46 deletions
|
|
@ -25,6 +25,8 @@ NFU Sale Order Batch Packaging
|
|||
This module adds a minium and a maximum order quantity field to Sale Orders
|
||||
and adds a packaging default to sale order batch.
|
||||
|
||||
It adds an autosplitting methode that respects the maximum ordered qty for every order and spreads the open packeges evenly over all orders.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
|
|
@ -33,23 +35,24 @@ and adds a packaging default to sale order batch.
|
|||
Changelog
|
||||
=========
|
||||
|
||||
- 0.0.1: Initial module.
|
||||
- 1.0.0: rename to nfu_sale_order_batch_packaging
|
||||
- 1.1.0: Live
|
||||
- 1.2.0: Add regeneration action for batch products
|
||||
- 1.2.1: refactoring
|
||||
- 1.3.0: add packaging states
|
||||
- 1.3.1: add open_max_qty
|
||||
- 1.3.2: fix color indicator
|
||||
- 1.3.3: Enable packagings on installation
|
||||
- 1.3.4:
|
||||
- 16.0.0.0.1: Initial module.
|
||||
- 16.0.1.0.0: rename to nfu_sale_order_batch_packaging
|
||||
- 16.0.1.16.0.1.0: Live
|
||||
- 16.0.1.2.0: Add regeneration action for batch products
|
||||
- 16.0.1.2.1: refactoring
|
||||
- 16.0.1.3.0: add packaging states
|
||||
- 16.0.1.3.1: add open_max_qty
|
||||
- 16.0.1.3.2: fix color indicator
|
||||
- 16.0.1.3.3: Enable packagings on installation
|
||||
- 16.0.1.3.4:
|
||||
- on creation set max_qty to product_uom_qty if not set
|
||||
- remove zero exception for max_qty
|
||||
- store open_packaging_qty on batch product module to make it sortable
|
||||
- always adjust max_qty to be at least uom_qty except you only set max qty
|
||||
- 1.3.4: store all quantities and round open values
|
||||
- 1.3.6: make sure packages are readable by the user
|
||||
- 1.3.7: really make sure packages are readable by the user
|
||||
- 16.0.1.3.4: store all quantities and round open values
|
||||
- 16.0.1.3.6: make sure packages are readable by the user
|
||||
- 16.0.1.3.7: really make sure packages are readable by the user
|
||||
- 16.0.1.4.0: Add automatic batch fillup
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
|
|
|||
|
|
@ -1 +1,18 @@
|
|||
from . import models
|
||||
from . import wizard
|
||||
from odoo import api, SUPERUSER_ID
|
||||
|
||||
|
||||
def post_init_hook(cr, registry):
|
||||
env = api.Environment(cr, SUPERUSER_ID, {})
|
||||
stueck = env.ref(
|
||||
"nfu_sale_order_batch_packaging.product_uom_stueck",
|
||||
raise_if_not_found=False,
|
||||
)
|
||||
if not stueck:
|
||||
return
|
||||
imd = env["ir.model.data"].search(
|
||||
[("module", "=", "_import_"), ("name", "=", "uom.product_uom_piece")]
|
||||
)
|
||||
if imd:
|
||||
imd.write({"res_id": stueck.id})
|
||||
|
|
|
|||
|
|
@ -4,13 +4,18 @@
|
|||
"author": "BAKEUP",
|
||||
"website": "https://www.bakeup.org",
|
||||
"category": "Sale",
|
||||
"version": "16.0.1.3.7",
|
||||
"version": "16.0.1.4.0",
|
||||
"depends": ["sale", "sale_order_batch"],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"data/ir_config_parameter.xml",
|
||||
"data/uom_data.xml",
|
||||
"wizard/sale_order_batch_packaging_fill_views.xml",
|
||||
"views/uom_uom_views.xml",
|
||||
"views/sale_order_views.xml",
|
||||
"views/sale_order_batch_views.xml",
|
||||
"views/sale_order_batch_product_views.xml",
|
||||
],
|
||||
"license": "LGPL-3",
|
||||
"post_init_hook": "post_init_hook",
|
||||
}
|
||||
|
|
|
|||
15
nfu_sale_order_batch_packaging/data/uom_data.xml
Normal file
15
nfu_sale_order_batch_packaging/data/uom_data.xml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="uom.product_uom_unit" model="uom.uom">
|
||||
<field name="batch_fill_precision">1.0</field>
|
||||
</record>
|
||||
|
||||
<record id="product_uom_stueck" model="uom.uom">
|
||||
<field name="name">Stück</field>
|
||||
<field name="category_id" ref="uom.product_uom_categ_unit"/>
|
||||
<field name="uom_type">smaller</field>
|
||||
<field name="rounding">0.01</field>
|
||||
<field name="ratio">1.0</field>
|
||||
<field name="batch_fill_precision">1.0</field>
|
||||
</record>
|
||||
</odoo>
|
||||
274
nfu_sale_order_batch_packaging/i18n/de.po
Normal file
274
nfu_sale_order_batch_packaging/i18n/de.po
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * nfu_sale_order_batch_packaging
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-05-29 21:37+0000\n"
|
||||
"PO-Revision-Date: 2026-05-29 21:37+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__batch_id
|
||||
msgid "Batch"
|
||||
msgstr "Charge"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_uom_uom__batch_fill_precision
|
||||
msgid "Batch Fill Precision"
|
||||
msgstr "Füllgenauigkeit"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch_packaging_fill
|
||||
msgid "Batch Packaging Auto-Fill Wizard"
|
||||
msgstr "Assistent zur automatischen Befüllung"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch_packaging_fill_line
|
||||
msgid "Batch Packaging Auto-Fill Wizard Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch_packaging_fill_zero_line
|
||||
msgid "Batch Packaging Auto-Fill Zero-Out Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__batch_product_id
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__batch_product_id
|
||||
msgid "Batch Product"
|
||||
msgstr "Sammelprodukt"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_line__batch_uom_max_qty
|
||||
msgid "Batch UOM Max Qty"
|
||||
msgstr ""
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_line__batch_uom_qty
|
||||
msgid "Batch UOM Qty"
|
||||
msgstr ""
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_sale_order_batch_packaging_fill_form
|
||||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_sale_order_batch_packaging_fill_form
|
||||
msgid "Confirm"
|
||||
msgstr "Bestätigen"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__create_uid
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__create_uid
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__create_uid
|
||||
msgid "Created by"
|
||||
msgstr "Erstellt von"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__create_date
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__create_date
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__create_date
|
||||
msgid "Created on"
|
||||
msgstr "Erstellt am"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__display_name
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__display_name
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "Anzeigename"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_order_batch_form
|
||||
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_sale_order_batch_packaging_fill_form
|
||||
msgid "Fill Packages"
|
||||
msgstr "Pakete befüllen"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_order_batch_form
|
||||
msgid "Open Products"
|
||||
msgstr "Offene Produkte"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields.selection,name:nfu_sale_order_batch_packaging.selection__sale_order_batch_product__open_packaging_state__full
|
||||
msgid "Full"
|
||||
msgstr "Voll"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__has_fill_lines
|
||||
msgid "Has Fill Lines"
|
||||
msgstr "Hat Füllzeilen"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch__has_fillable_packages
|
||||
msgid "Has Fillable Packages"
|
||||
msgstr "Hat befüllbare Pakete"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch__has_qty_adjusted
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__has_qty_adjusted
|
||||
msgid "Has Qty Adjusted"
|
||||
msgstr "Menge angepasst"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__has_zero_lines
|
||||
msgid "Has Zero Lines"
|
||||
msgstr "Hat Nullzeilen"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__id
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__id
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__id
|
||||
msgid "ID"
|
||||
msgstr "ID"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__keep_qty
|
||||
msgid "Keep"
|
||||
msgstr "Behalten"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill____last_update
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line____last_update
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line____last_update
|
||||
msgid "Last Modified on"
|
||||
msgstr "Zuletzt geändert am"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__write_uid
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__write_uid
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "Zuletzt aktualisiert von"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__write_date
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__write_date
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "Zuletzt aktualisiert am"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields.selection,name:nfu_sale_order_batch_packaging.selection__sale_order_batch_product__open_packaging_state__last_open
|
||||
msgid "Last package open"
|
||||
msgstr "Letztes Paket offen"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__line_ids
|
||||
msgid "Line"
|
||||
msgstr "Zeile"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__product_uom_max_qty
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_line__product_uom_max_qty
|
||||
msgid "Max Qty"
|
||||
msgstr "Max. Menge"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields.selection,name:nfu_sale_order_batch_packaging.selection__sale_order_batch_product__open_packaging_state__open
|
||||
msgid "Open"
|
||||
msgstr "Offen"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__open_packaging_max_qty
|
||||
msgid "Open Packaging Max Qty"
|
||||
msgstr "Max. Menge der offenen Verpackung"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__open_packaging_qty
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__open_packaging_qty
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__open_packaging_qty
|
||||
msgid "Open Packaging Qty"
|
||||
msgstr "Menge der offenen Verpackung"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_product__open_packaging_state
|
||||
msgid "Open Packaging State"
|
||||
msgstr "Status der offenen Verpackung"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_line__product_uom_ordered_qty
|
||||
msgid "Ordered Qty"
|
||||
msgstr "Bestellte Menge"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__batch_fill_precision
|
||||
msgid "Precision"
|
||||
msgstr "Genauigkeit"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_product_template
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__product_id
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__product_id
|
||||
msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_uom_uom
|
||||
msgid "Product Unit of Measure"
|
||||
msgstr "Maßeinheit des Produkts"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_product_product
|
||||
msgid "Product Variant"
|
||||
msgstr "Produktvariante"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_order_batch_form
|
||||
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_order_batch_product_form
|
||||
msgid "Restore Ordered Qty"
|
||||
msgstr "Bestellte Menge wiederherstellen"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,help:nfu_sale_order_batch_packaging.field_uom_uom__batch_fill_precision
|
||||
msgid ""
|
||||
"Rounding step used by the batch fill wizard for this UOM (e.g. 1.0 for whole"
|
||||
" units, 0.1 for one decimal)."
|
||||
msgstr ""
|
||||
"Rundungsschritt des Füll-Assistenten für diese Maßeinheit (z.B. 1.0"
|
||||
" für ganze Einheiten, 0.1 für eine Dezimalstelle)."
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch_product
|
||||
msgid "Sale Order Batch Product"
|
||||
msgstr ""
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_batch
|
||||
msgid "Sales Order Batch"
|
||||
msgstr "Sammelverkaufsauftrag"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model,name:nfu_sale_order_batch_packaging.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Verkaufsauftragszeile"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:uom.uom,name:nfu_sale_order_batch_packaging.product_uom_stueck
|
||||
msgid "Stück"
|
||||
msgstr "Stück"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_line__wizard_id
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill_zero_line__wizard_id
|
||||
msgid "Wizard"
|
||||
msgstr "Assistent"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model:ir.model.fields,field_description:nfu_sale_order_batch_packaging.field_sale_order_batch_packaging_fill__zero_line_ids
|
||||
msgid "Zero Line"
|
||||
msgstr "Nullzeile"
|
||||
|
||||
#. module: nfu_sale_order_batch_packaging
|
||||
#: model_terms:ir.ui.view,arch_db:nfu_sale_order_batch_packaging.view_sale_order_batch_packaging_fill_form
|
||||
msgid "Zero Out"
|
||||
msgstr "Auf Null setzen"
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
from . import product_product
|
||||
from . import product_template
|
||||
from . import sale_order_batch
|
||||
from . import sale_order_batch_product
|
||||
from . import sale_order_line
|
||||
from . import uom_uom
|
||||
|
|
|
|||
82
nfu_sale_order_batch_packaging/models/sale_order_batch.py
Normal file
82
nfu_sale_order_batch_packaging/models/sale_order_batch.py
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
from odoo import api, fields, models
|
||||
from odoo.addons.sale_order_batch.models.sale_order_batch import READONLY_FIELD_STATES
|
||||
from odoo.tools import float_is_zero
|
||||
|
||||
|
||||
class SaleOrderBatch(models.Model):
|
||||
_inherit = "sale.order.batch"
|
||||
|
||||
has_qty_adjusted = fields.Boolean(
|
||||
compute="_compute_has_qty_adjusted",
|
||||
)
|
||||
has_fillable_packages = fields.Boolean(
|
||||
compute="_compute_has_fillable_packages",
|
||||
)
|
||||
open_product_ids = fields.Many2many(
|
||||
comodel_name="sale.order.batch.product",
|
||||
compute="_compute_open_product_ids",
|
||||
inverse="_inverse_open_product_ids",
|
||||
states=READONLY_FIELD_STATES,
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"sale_order_line_ids.product_uom_qty",
|
||||
"sale_order_line_ids.product_uom_ordered_qty",
|
||||
)
|
||||
def _compute_has_qty_adjusted(self):
|
||||
for batch in self:
|
||||
batch.has_qty_adjusted = any(
|
||||
line.product_uom_qty != line.product_uom_ordered_qty
|
||||
for line in batch.sale_order_line_ids
|
||||
)
|
||||
|
||||
@api.depends("product_ids.open_packaging_qty")
|
||||
def _compute_has_fillable_packages(self):
|
||||
for batch in self:
|
||||
batch.has_fillable_packages = any(
|
||||
p.open_packaging_qty > 0 for p in batch.product_ids
|
||||
)
|
||||
|
||||
@api.depends("product_ids.open_packaging_qty")
|
||||
def _compute_open_product_ids(self):
|
||||
for batch in self:
|
||||
batch.open_product_ids = batch.product_ids.filtered(
|
||||
lambda p: p.open_packaging_qty > 0
|
||||
)
|
||||
|
||||
def _inverse_open_product_ids(self):
|
||||
pass
|
||||
|
||||
def action_restore_ordered_qty(self):
|
||||
self.ensure_one()
|
||||
lines = self.sale_order_line_ids.filtered(
|
||||
lambda l: l.product_uom_qty != l.product_uom_ordered_qty
|
||||
)
|
||||
for line in lines:
|
||||
line.write({"product_uom_qty": line.product_uom_ordered_qty})
|
||||
|
||||
def action_fill_packages(self):
|
||||
self.ensure_one()
|
||||
eligible = self.product_ids.filtered(
|
||||
lambda p: p.open_packaging_qty > 0 and float_is_zero(p.open_packaging_max_qty, precision_rounding=p.product_id.uom_id.batch_fill_precision)
|
||||
)
|
||||
ineligible = self.product_ids.filtered(
|
||||
lambda p: p.open_packaging_qty > 0 and not float_is_zero(p.open_packaging_max_qty, precision_rounding=p.product_id.uom_id.batch_fill_precision)
|
||||
)
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].create(
|
||||
{
|
||||
"batch_id": self.id,
|
||||
"line_ids": [(0, 0, {"batch_product_id": p.id}) for p in eligible],
|
||||
"zero_line_ids": [
|
||||
(0, 0, {"batch_product_id": p.id}) for p in ineligible
|
||||
],
|
||||
}
|
||||
)
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"name": "Fill Open Packages",
|
||||
"res_model": "sale.order.batch.packaging.fill",
|
||||
"res_id": wizard.id,
|
||||
"view_mode": "form",
|
||||
"target": "new",
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
from odoo import api, fields, models
|
||||
from odoo.tools import float_is_zero, float_round
|
||||
|
||||
|
||||
PACKAGING_STATES = [
|
||||
|
|
@ -80,6 +81,102 @@ class SaleOrderBatchProduct(models.Model):
|
|||
else:
|
||||
product.open_packaging_state = "last_open"
|
||||
|
||||
has_qty_adjusted = fields.Boolean(
|
||||
compute="_compute_has_qty_adjusted",
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"sale_order_line_ids.product_uom_qty",
|
||||
"sale_order_line_ids.product_uom_ordered_qty",
|
||||
)
|
||||
def _compute_has_qty_adjusted(self):
|
||||
for product in self:
|
||||
product.has_qty_adjusted = any(
|
||||
line.product_uom_qty != line.product_uom_ordered_qty
|
||||
for line in product.sale_order_line_ids
|
||||
)
|
||||
|
||||
def action_fill_packages(self):
|
||||
self.ensure_one()
|
||||
if self.open_packaging_qty > 0 and self.open_packaging_max_qty == 0.0:
|
||||
line_ids = [(0, 0, {"batch_product_id": self.id})]
|
||||
zero_line_ids = []
|
||||
elif self.open_packaging_qty > 0 and self.open_packaging_max_qty != 0.0:
|
||||
line_ids = []
|
||||
zero_line_ids = [(0, 0, {"batch_product_id": self.id})]
|
||||
else:
|
||||
return
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].create(
|
||||
{
|
||||
"batch_id": self.batch_id.id,
|
||||
"line_ids": line_ids,
|
||||
"zero_line_ids": zero_line_ids,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"name": "Fill Open Packages",
|
||||
"res_model": "sale.order.batch.packaging.fill",
|
||||
"res_id": wizard.id,
|
||||
"view_mode": "form",
|
||||
"target": "new",
|
||||
}
|
||||
|
||||
def action_restore_ordered_qty(self):
|
||||
self.ensure_one()
|
||||
lines = self.sale_order_line_ids.filtered(
|
||||
lambda l: l.product_uom_ordered_qty != 0
|
||||
and l.product_uom_qty != l.product_uom_ordered_qty
|
||||
)
|
||||
for line in lines:
|
||||
line.write({"product_uom_qty": line.product_uom_ordered_qty})
|
||||
|
||||
def _fill_packaging(self, rounding):
|
||||
self.ensure_one()
|
||||
for line in self.sale_order_line_ids.filtered(
|
||||
lambda l: l.product_uom_ordered_qty == 0
|
||||
):
|
||||
line.write({"product_uom_ordered_qty": line.product_uom_qty})
|
||||
target = self.open_packaging_qty
|
||||
eligible = sorted(
|
||||
[
|
||||
(line, line.product_uom_max_qty - line.product_uom_qty)
|
||||
for line in self.sale_order_line_ids
|
||||
if line.product_uom_max_qty > line.product_uom_qty
|
||||
],
|
||||
key=lambda x: x[1],
|
||||
)
|
||||
n = len(eligible)
|
||||
distributed = 0.0
|
||||
last_line = None
|
||||
for line, room in eligible:
|
||||
remaining = target - distributed
|
||||
fair_share = float_round(remaining / n, precision_rounding=rounding)
|
||||
addition = min(room, fair_share)
|
||||
if addition > 0:
|
||||
new_qty = float_round(
|
||||
line.product_uom_qty + addition, precision_rounding=rounding
|
||||
)
|
||||
new_qty = min(new_qty, line.product_uom_max_qty)
|
||||
line.write({"product_uom_qty": new_qty})
|
||||
distributed += addition
|
||||
last_line = line
|
||||
n -= 1
|
||||
if float_is_zero(target - distributed, precision_rounding=rounding):
|
||||
break
|
||||
# Attach any sub-rounding remainder directly to the last eligible line
|
||||
remainder = target - distributed
|
||||
if remainder > 1e-9 and last_line:
|
||||
new_qty = min(
|
||||
last_line.product_uom_qty + remainder, last_line.product_uom_max_qty
|
||||
)
|
||||
last_line.write({"product_uom_qty": new_qty})
|
||||
|
||||
def _zero_packaging(self):
|
||||
self.ensure_one()
|
||||
for line in self.sale_order_line_ids:
|
||||
line.write({"product_uom_qty": 0.0})
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.tools import float_round
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = "sale.order.line"
|
||||
|
||||
product_uom_ordered_qty = fields.Float(
|
||||
string="Ordered Qty", digits="Product Unit of Measure", default=1.0
|
||||
string="Ordered Qty", digits="Product Unit of Measure", default=0.0
|
||||
)
|
||||
product_uom_max_qty = fields.Float(
|
||||
string="Max Qty", digits="Product Unit of Measure"
|
||||
|
|
@ -21,7 +22,13 @@ class SaleOrderLine(models.Model):
|
|||
@api.constrains("product_uom_qty", "product_uom_max_qty")
|
||||
def _check_product_uom_qty(self):
|
||||
for order_line in self:
|
||||
if order_line.product_uom_qty > order_line.product_uom_max_qty:
|
||||
if (
|
||||
float_round(
|
||||
order_line.product_uom_max_qty - order_line.product_uom_qty,
|
||||
precision_rounding=order_line.product_uom.rounding,
|
||||
)
|
||||
< 0
|
||||
):
|
||||
raise UserError(
|
||||
_(
|
||||
f"{order_line.order_id.name}, {order_line.product_id.name}: "
|
||||
|
|
@ -32,16 +39,18 @@ class SaleOrderLine(models.Model):
|
|||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
product_qty = vals.get("product_uom_qty", 0.0)
|
||||
if (
|
||||
not vals.get("product_uom_max_qty")
|
||||
or vals.get("product_uom_max_qty") == 0
|
||||
):
|
||||
product_qty = vals.get("product_uom_qty")
|
||||
vals["product_uom_max_qty"] = product_qty
|
||||
if not vals.get("product_uom_ordered_qty"):
|
||||
vals["product_uom_ordered_qty"] = product_qty
|
||||
if vals.get("product_id") and not vals.get("product_packaging_id"):
|
||||
product_id = vals.get("product_id")
|
||||
# Make sure we're not sudo
|
||||
# e.x. when comming from webshop
|
||||
# e.x. when coming from webshop
|
||||
packaging = self.env["product.packaging"].search(
|
||||
[
|
||||
("product_id", "=", product_id),
|
||||
|
|
|
|||
17
nfu_sale_order_batch_packaging/models/uom_uom.py
Normal file
17
nfu_sale_order_batch_packaging/models/uom_uom.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class UomUom(models.Model):
|
||||
_inherit = "uom.uom"
|
||||
|
||||
batch_fill_precision = fields.Float(
|
||||
compute="_compute_batch_fill_precision",
|
||||
store=True,
|
||||
readonly=False,
|
||||
help="Rounding step used by the batch fill wizard for this UOM (e.g. 1.0 for whole units, 0.1 for one decimal).",
|
||||
)
|
||||
|
||||
@api.depends("rounding")
|
||||
def _compute_batch_fill_precision(self):
|
||||
for uom in self:
|
||||
uom.batch_fill_precision = uom.rounding
|
||||
|
|
@ -1,2 +1,4 @@
|
|||
This module adds a minium and a maximum order quantity field to Sale Orders
|
||||
and adds a packaging default to sale order batch.
|
||||
and adds a packaging default to sale order batch.
|
||||
|
||||
It adds an autosplitting methode that respects the maximum ordered qty for every order and spreads the open packeges evenly over all orders.
|
||||
|
|
@ -1,17 +1,18 @@
|
|||
- 0.0.1: Initial module.
|
||||
- 1.0.0: rename to nfu_sale_order_batch_packaging
|
||||
- 1.1.0: Live
|
||||
- 1.2.0: Add regeneration action for batch products
|
||||
- 1.2.1: refactoring
|
||||
- 1.3.0: add packaging states
|
||||
- 1.3.1: add open_max_qty
|
||||
- 1.3.2: fix color indicator
|
||||
- 1.3.3: Enable packagings on installation
|
||||
- 1.3.4:
|
||||
- 16.0.0.0.1: Initial module.
|
||||
- 16.0.1.0.0: rename to nfu_sale_order_batch_packaging
|
||||
- 16.0.1.1.0: Live
|
||||
- 16.0.1.2.0: Add regeneration action for batch products
|
||||
- 16.0.1.2.1: refactoring
|
||||
- 16.0.1.3.0: add packaging states
|
||||
- 16.0.1.3.1: add open_max_qty
|
||||
- 16.0.1.3.2: fix color indicator
|
||||
- 16.0.1.3.3: Enable packagings on installation
|
||||
- 16.0.1.3.4:
|
||||
- on creation set max_qty to product_uom_qty if not set
|
||||
- remove zero exception for max_qty
|
||||
- store open_packaging_qty on batch product module to make it sortable
|
||||
- always adjust max_qty to be at least uom_qty except you only set max qty
|
||||
- 1.3.4: store all quantities and round open values
|
||||
- 1.3.6: make sure packages are readable by the user
|
||||
- 1.3.7: really make sure packages are readable by the user
|
||||
- 16.0.1.3.4: store all quantities and round open values
|
||||
- 16.0.1.3.6: make sure packages are readable by the user
|
||||
- 16.0.1.3.7: really make sure packages are readable by the user
|
||||
- 16.0.1.4.0: Add automatic batch fillup
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_batch_packaging_fill,sale.order.batch.packaging.fill,model_sale_order_batch_packaging_fill,base.group_user,1,1,1,1
|
||||
access_batch_packaging_fill_line,sale.order.batch.packaging.fill.line,model_sale_order_batch_packaging_fill_line,base.group_user,1,1,1,1
|
||||
access_batch_packaging_fill_zero_line,sale.order.batch.packaging.fill.zero.line,model_sale_order_batch_packaging_fill_zero_line,base.group_user,1,1,1,1
|
||||
|
|
|
@ -372,6 +372,7 @@ ul.auto-toc {
|
|||
<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/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/bakeupboys/nfu/tree/16.0/nfu_sale_order_batch_packaging"><img alt="bakeupboys/nfu" src="https://img.shields.io/badge/github-bakeupboys%2Fnfu-lightgray.png?logo=github" /></a></p>
|
||||
<p>This module adds a minium and a maximum order quantity field to Sale Orders
|
||||
and adds a packaging default to sale order batch.</p>
|
||||
<p>It adds an autosplitting methode that respects the maximum ordered qty for every order and spreads the open packeges evenly over all orders.</p>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
|
|
@ -388,17 +389,17 @@ and adds a packaging default to sale order batch.</p>
|
|||
<div class="section" id="changelog">
|
||||
<h1><a class="toc-backref" href="#toc-entry-1">Changelog</a></h1>
|
||||
<ul class="simple">
|
||||
<li>0.0.1: Initial module.</li>
|
||||
<li>1.0.0: rename to nfu_sale_order_batch_packaging</li>
|
||||
<li>1.1.0: Live</li>
|
||||
<li>1.2.0: Add regeneration action for batch products</li>
|
||||
<li>1.2.1: refactoring</li>
|
||||
<li>1.3.0: add packaging states</li>
|
||||
<li>1.3.1: add open_max_qty</li>
|
||||
<li>1.3.2: fix color indicator</li>
|
||||
<li>1.3.3: Enable packagings on installation</li>
|
||||
<li>16.0.0.0.1: Initial module.</li>
|
||||
<li>16.0.1.0.0: rename to nfu_sale_order_batch_packaging</li>
|
||||
<li>16.0.1.16.0.1.0: Live</li>
|
||||
<li>16.0.1.2.0: Add regeneration action for batch products</li>
|
||||
<li>16.0.1.2.1: refactoring</li>
|
||||
<li>16.0.1.3.0: add packaging states</li>
|
||||
<li>16.0.1.3.1: add open_max_qty</li>
|
||||
<li>16.0.1.3.2: fix color indicator</li>
|
||||
<li>16.0.1.3.3: Enable packagings on installation</li>
|
||||
<li><dl class="first docutils">
|
||||
<dt>1.3.4:</dt>
|
||||
<dt>16.0.1.3.4:</dt>
|
||||
<dd><ul class="first last">
|
||||
<li>on creation set max_qty to product_uom_qty if not set</li>
|
||||
<li>remove zero exception for max_qty</li>
|
||||
|
|
@ -408,9 +409,10 @@ and adds a packaging default to sale order batch.</p>
|
|||
</dd>
|
||||
</dl>
|
||||
</li>
|
||||
<li>1.3.4: store all quantities and round open values</li>
|
||||
<li>1.3.6: make sure packages are readable by the user</li>
|
||||
<li>1.3.7: really make sure packages are readable by the user</li>
|
||||
<li>16.0.1.3.4: store all quantities and round open values</li>
|
||||
<li>16.0.1.3.6: make sure packages are readable by the user</li>
|
||||
<li>16.0.1.3.7: really make sure packages are readable by the user</li>
|
||||
<li>16.0.1.4.0: Add automatic batch fillup</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
|
|
|
|||
1
nfu_sale_order_batch_packaging/tests/__init__.py
Normal file
1
nfu_sale_order_batch_packaging/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import test_batch_packaging_fill
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
from odoo.tests import tagged
|
||||
from odoo.tests.common import TransactionCase
|
||||
|
||||
|
||||
@tagged("post_install", "-at_install")
|
||||
class TestBatchPackagingFill(TransactionCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.uom_kg = self.env.ref("uom.product_uom_kgm")
|
||||
self.uom_unit = self.env.ref("uom.product_uom_unit")
|
||||
self.partner = self.env["res.partner"].create({"name": "Test Partner"})
|
||||
self.batch = self.env["sale.order.batch"].create(
|
||||
{"name": "Test Batch", "state": "in_progress"}
|
||||
)
|
||||
|
||||
def _make_product(self, uom, packaging_qty):
|
||||
product = self.env["product.product"].create(
|
||||
{
|
||||
"name": f"Product {uom.name} {packaging_qty}",
|
||||
"uom_id": uom.id,
|
||||
"uom_po_id": uom.id,
|
||||
"type": "consu",
|
||||
}
|
||||
)
|
||||
self.env["product.packaging"].create(
|
||||
{
|
||||
"name": "Pack",
|
||||
"product_id": product.id,
|
||||
"qty": packaging_qty,
|
||||
"sales": True,
|
||||
}
|
||||
)
|
||||
return product
|
||||
|
||||
def _add_lines(self, product, lines_data):
|
||||
"""Create one sale order in self.batch with one line per (qty, max_qty) pair.
|
||||
Returns the sale.order.batch.product that aggregates all lines."""
|
||||
order = self.env["sale.order"].create(
|
||||
{"partner_id": self.partner.id, "batch_id": self.batch.id}
|
||||
)
|
||||
for qty, max_qty in lines_data:
|
||||
self.env["sale.order.line"].create(
|
||||
{
|
||||
"order_id": order.id,
|
||||
"product_id": product.id,
|
||||
"product_uom_qty": qty,
|
||||
"product_uom_max_qty": max_qty,
|
||||
"price_unit": 10.0,
|
||||
}
|
||||
)
|
||||
return self.batch.product_ids.filtered(lambda p: p.product_id == product)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# _fill_packaging
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_even_split(self):
|
||||
"""All lines with identical room each receive an equal share."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
# total = 24, open = 6; each line has exactly 3 kg of room
|
||||
bp = self._add_lines(product, [(7.0, 10.0), (8.0, 11.0), (9.0, 12.0)])
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 6.0, places=2)
|
||||
|
||||
bp._fill_packaging(0.001)
|
||||
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||
# each line must have gained exactly 2 kg
|
||||
for line in bp.sale_order_line_ids:
|
||||
self.assertAlmostEqual(
|
||||
line.product_uom_qty,
|
||||
line.product_uom_max_qty - 1.0,
|
||||
places=3,
|
||||
)
|
||||
|
||||
def test_constrained_line_capped_at_max(self):
|
||||
"""A line that cannot take its fair share is capped; remainder is spread evenly."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
# total = 23, open = 7
|
||||
# rooms: 1 (capped), 10, 7 → sorted: 1, 7, 10
|
||||
bp = self._add_lines(product, [(8.0, 9.0), (5.0, 15.0), (10.0, 17.0)])
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 7.0, places=2)
|
||||
|
||||
bp._fill_packaging(0.001)
|
||||
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||
capped = bp.sale_order_line_ids.filtered(
|
||||
lambda line: line.product_uom_max_qty == 9.0
|
||||
)
|
||||
# capped line must be at its max
|
||||
self.assertAlmostEqual(capped.product_uom_qty, 9.0, places=3)
|
||||
# every line must stay within its max
|
||||
for line in bp.sale_order_line_ids:
|
||||
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
|
||||
|
||||
def test_whole_units_rounding_produces_integers(self):
|
||||
"""precision=1.0 yields only whole-number additions."""
|
||||
product = self._make_product(self.uom_unit, 6.0)
|
||||
# total = 7, open = 5; rooms: 3 and 5
|
||||
bp = self._add_lines(product, [(3.0, 6.0), (4.0, 9.0)])
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 5.0, places=2)
|
||||
orig = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
|
||||
|
||||
bp._fill_packaging(1.0)
|
||||
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||
for line in bp.sale_order_line_ids:
|
||||
added = line.product_uom_qty - orig[line.id]
|
||||
self.assertAlmostEqual(added, round(added), places=6)
|
||||
|
||||
def test_decimal_rounding_uses_tenth_steps(self):
|
||||
"""precision=0.1 distributes in 0.1-unit steps."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
# total = 7, open = 3; rooms: 7 and 6 → sorted: line2(6), line1(7)
|
||||
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 3.0, places=2)
|
||||
orig = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
|
||||
|
||||
bp._fill_packaging(0.1)
|
||||
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||
for line in bp.sale_order_line_ids:
|
||||
added = round(line.product_uom_qty - orig[line.id], 6)
|
||||
# must be a multiple of 0.1 (within floating-point tolerance)
|
||||
self.assertAlmostEqual(round(added / 0.1) * 0.1, added, places=5)
|
||||
|
||||
def test_sub_rounding_remainder_is_attached(self):
|
||||
"""A remainder smaller than the rounding step is added directly to the last line."""
|
||||
product = self._make_product(self.uom_kg, 3.0)
|
||||
# total = 2.7, open = 0.3; with precision=1.0 every fair_share rounds to 0
|
||||
bp = self._add_lines(product, [(0.9, 1.5), (0.9, 1.5), (0.9, 1.5)])
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.3, places=2)
|
||||
|
||||
bp._fill_packaging(1.0)
|
||||
|
||||
# The whole 0.3 remainder must have been placed on one line
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||
# Every line must still respect its max qty
|
||||
for line in bp.sale_order_line_ids:
|
||||
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
|
||||
|
||||
def test_no_line_exceeds_max_qty(self):
|
||||
"""After filling no sale order line may exceed its max qty."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
bp = self._add_lines(product, [(6.0, 8.0), (7.0, 12.0), (4.0, 9.0)])
|
||||
bp._fill_packaging(0.001)
|
||||
for line in bp.sale_order_line_ids:
|
||||
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Wizard
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_wizard_includes_eligible_products_only(self):
|
||||
"""Eligible products land in line_ids; ineligible ones land in zero_line_ids."""
|
||||
eligible = self._make_product(self.uom_kg, 10.0)
|
||||
ineligible = self._make_product(self.uom_kg, 10.0)
|
||||
# eligible: total max (30) covers needed (23+7=30)
|
||||
self._add_lines(eligible, [(23.0, 30.0)])
|
||||
# ineligible: total max (24) < 23+7=30 → open_packaging_max_qty != 0
|
||||
self._add_lines(ineligible, [(23.0, 24.0)])
|
||||
|
||||
action = self.batch.action_fill_packages()
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||
|
||||
self.assertEqual(len(wizard.line_ids), 1)
|
||||
self.assertEqual(wizard.line_ids.product_id, eligible)
|
||||
self.assertEqual(len(wizard.zero_line_ids), 1)
|
||||
self.assertEqual(wizard.zero_line_ids.product_id, ineligible)
|
||||
|
||||
def test_wizard_default_precision_for_unit_uom(self):
|
||||
"""UOMs with batch_fill_precision=1.0 default to precision 1.0."""
|
||||
product = self._make_product(self.uom_unit, 6.0)
|
||||
self._add_lines(product, [(3.0, 9.0)])
|
||||
|
||||
action = self.batch.action_fill_packages()
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||
|
||||
self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 1.0)
|
||||
|
||||
def test_wizard_default_precision_for_stueck_uom(self):
|
||||
"""Stück UOM (batch_fill_precision=1.0) defaults to precision 1.0."""
|
||||
uom_stueck = self.env.ref("nfu_sale_order_batch_packaging.product_uom_stueck")
|
||||
product = self._make_product(uom_stueck, 6.0)
|
||||
self._add_lines(product, [(3.0, 9.0)])
|
||||
|
||||
action = self.batch.action_fill_packages()
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||
|
||||
self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 1.0)
|
||||
|
||||
def test_wizard_default_precision_for_kg_uom(self):
|
||||
"""UOMs with default batch_fill_precision use 0.1."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
self._add_lines(product, [(23.0, 30.0)])
|
||||
|
||||
action = self.batch.action_fill_packages()
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||
|
||||
self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 0.1)
|
||||
|
||||
def test_wizard_fill_closes_open_packaging(self):
|
||||
"""End-to-end: confirming the wizard brings open_packaging_qty to zero."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
bp = self._add_lines(product, [(23.0, 30.0)])
|
||||
self.assertGreater(bp.open_packaging_qty, 0.0)
|
||||
|
||||
action = self.batch.action_fill_packages()
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||
wizard.action_fill()
|
||||
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Restore
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_restore_batch_resets_all_lines(self):
|
||||
"""action_restore_ordered_qty on the batch sets every line back to ordered qty."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
bp = self._add_lines(product, [(23.0, 30.0)])
|
||||
bp._fill_packaging(0.1)
|
||||
# fill must have increased some qty
|
||||
self.assertTrue(
|
||||
any(
|
||||
line.product_uom_qty != line.product_uom_ordered_qty
|
||||
for line in bp.sale_order_line_ids
|
||||
)
|
||||
)
|
||||
self.batch.action_restore_ordered_qty()
|
||||
for line in bp.sale_order_line_ids:
|
||||
self.assertAlmostEqual(
|
||||
line.product_uom_qty, line.product_uom_ordered_qty, places=3
|
||||
)
|
||||
|
||||
def test_restore_batch_product_resets_its_lines_only(self):
|
||||
"""action_restore_ordered_qty on a batch product only touches its own lines."""
|
||||
product_a = self._make_product(self.uom_kg, 10.0)
|
||||
product_b = self._make_product(self.uom_kg, 10.0)
|
||||
bp_a = self._add_lines(product_a, [(23.0, 30.0)])
|
||||
bp_b = self._add_lines(product_b, [(23.0, 30.0)])
|
||||
bp_a._fill_packaging(0.1)
|
||||
bp_b._fill_packaging(0.1)
|
||||
|
||||
bp_a.action_restore_ordered_qty()
|
||||
|
||||
for line in bp_a.sale_order_line_ids:
|
||||
self.assertAlmostEqual(
|
||||
line.product_uom_qty, line.product_uom_ordered_qty, places=3
|
||||
)
|
||||
# product_b lines must be unchanged
|
||||
self.assertTrue(
|
||||
any(
|
||||
line.product_uom_qty != line.product_uom_ordered_qty
|
||||
for line in bp_b.sale_order_line_ids
|
||||
)
|
||||
)
|
||||
|
||||
def test_has_qty_adjusted_false_before_fill(self):
|
||||
"""has_qty_adjusted is False when no line has been modified."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
self._add_lines(product, [(10.0, 15.0)])
|
||||
self.assertFalse(self.batch.has_qty_adjusted)
|
||||
|
||||
def test_has_qty_adjusted_true_after_fill(self):
|
||||
"""has_qty_adjusted becomes True once a line quantity is increased."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
bp = self._add_lines(product, [(23.0, 30.0)])
|
||||
bp._fill_packaging(0.1)
|
||||
self.assertTrue(self.batch.has_qty_adjusted)
|
||||
|
||||
def test_has_qty_adjusted_false_after_restore(self):
|
||||
"""has_qty_adjusted returns to False after restoring ordered quantities."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
bp = self._add_lines(product, [(23.0, 30.0)])
|
||||
bp._fill_packaging(0.1)
|
||||
self.batch.action_restore_ordered_qty()
|
||||
self.assertFalse(self.batch.has_qty_adjusted)
|
||||
|
||||
def test_has_fillable_packages_false_when_no_open_qty(self):
|
||||
"""has_fillable_packages is False when all products have complete packages."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
self._add_lines(product, [(10.0, 15.0)]) # qty is exact multiple of packaging
|
||||
self.assertFalse(self.batch.has_fillable_packages)
|
||||
|
||||
def test_has_fillable_packages_true_for_eligible(self):
|
||||
"""has_fillable_packages is True when an eligible product has open packaging qty."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
self._add_lines(product, [(23.0, 30.0)]) # open_packaging_max_qty == 0
|
||||
self.assertTrue(self.batch.has_fillable_packages)
|
||||
|
||||
def test_has_fillable_packages_true_for_ineligible(self):
|
||||
"""has_fillable_packages is True even for ineligible (zero-out) products."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
self._add_lines(
|
||||
product, [(23.0, 24.0)]
|
||||
) # max too tight → open_packaging_max_qty != 0
|
||||
self.assertGreater(
|
||||
self.batch.product_ids.filtered(
|
||||
lambda p: p.product_id == product
|
||||
).open_packaging_max_qty,
|
||||
0.0,
|
||||
)
|
||||
self.assertTrue(self.batch.has_fillable_packages)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Zero-out
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def test_zero_packaging_sets_lines_to_zero(self):
|
||||
"""_zero_packaging sets all sale order line quantities to 0."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
bp = self._add_lines(product, [(23.0, 24.0)]) # ineligible (max too tight)
|
||||
self.assertGreater(bp.open_packaging_qty, 0.0)
|
||||
|
||||
bp._zero_packaging()
|
||||
|
||||
for line in bp.sale_order_line_ids:
|
||||
self.assertAlmostEqual(line.product_uom_qty, 0.0, places=3)
|
||||
|
||||
def test_wizard_zeros_ineligible_by_default(self):
|
||||
"""Confirming the wizard zeros ineligible lines (keep_qty=False default)."""
|
||||
ineligible = self._make_product(self.uom_kg, 10.0)
|
||||
self._add_lines(ineligible, [(23.0, 24.0)])
|
||||
bp = self.batch.product_ids.filtered(lambda p: p.product_id == ineligible)
|
||||
|
||||
action = self.batch.action_fill_packages()
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||
self.assertFalse(wizard.zero_line_ids.keep_qty)
|
||||
|
||||
wizard.action_fill()
|
||||
|
||||
for line in bp.sale_order_line_ids:
|
||||
self.assertAlmostEqual(line.product_uom_qty, 0.0, places=3)
|
||||
|
||||
def test_wizard_keeps_ineligible_when_keep_qty_true(self):
|
||||
"""Lines with keep_qty=True are not zeroed out."""
|
||||
ineligible = self._make_product(self.uom_kg, 10.0)
|
||||
bp = self._add_lines(ineligible, [(23.0, 24.0)])
|
||||
orig_qtys = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
|
||||
|
||||
action = self.batch.action_fill_packages()
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||
wizard.zero_line_ids.write({"keep_qty": True})
|
||||
wizard.action_fill()
|
||||
|
||||
for line in bp.sale_order_line_ids:
|
||||
self.assertAlmostEqual(line.product_uom_qty, orig_qtys[line.id], places=3)
|
||||
|
||||
def test_wizard_respects_precision_override(self):
|
||||
"""Overriding batch_fill_precision on a wizard line changes the applied rounding."""
|
||||
product = self._make_product(self.uom_kg, 10.0)
|
||||
# total = 7, open = 3; use 2 lines (rooms: 7 and 6)
|
||||
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
|
||||
|
||||
action = self.batch.action_fill_packages()
|
||||
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||
# Force whole-units even though UOM is kg
|
||||
wizard.line_ids.write({"batch_fill_precision": 1.0})
|
||||
wizard.action_fill()
|
||||
|
||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||
for line in bp.sale_order_line_ids:
|
||||
# additions must be whole numbers when precision=1.0
|
||||
self.assertAlmostEqual(
|
||||
line.product_uom_qty, round(line.product_uom_qty), places=6
|
||||
)
|
||||
|
|
@ -15,6 +15,23 @@
|
|||
<field name="open_packaging_max_qty" decoration-danger="(open_packaging_max_qty != 0.0)"/>
|
||||
<field name="open_packaging_state"/>
|
||||
</xpath>
|
||||
<xpath expr="//header" position="inside">
|
||||
<field name="has_qty_adjusted" invisible="1"/>
|
||||
<field name="open_packaging_qty" invisible="1"/>
|
||||
<button
|
||||
name="action_fill_packages"
|
||||
type="object"
|
||||
string="Fill Packages"
|
||||
class="btn-primary"
|
||||
attrs="{'invisible': [('open_packaging_qty', '=', 0.0)]}"
|
||||
/>
|
||||
<button
|
||||
name="action_restore_ordered_qty"
|
||||
type="object"
|
||||
string="Restore Ordered Qty"
|
||||
attrs="{'invisible': [('has_qty_adjusted', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//field[@name='sale_order_line_ids']/tree/field[@name='product_uom_qty']"
|
||||
position="before"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,37 @@
|
|||
<field name="model">sale.order.batch</field>
|
||||
<field name="inherit_id" ref="sale_order_batch.view_order_batch_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Header buttons -->
|
||||
<xpath expr="//button[@id='action_confirm_primary']" position="before">
|
||||
<field name="has_qty_adjusted" invisible="1"/>
|
||||
<field name="has_fillable_packages" invisible="1"/>
|
||||
<button
|
||||
name="action_fill_packages"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
string="Fill Packages"
|
||||
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath expr="//button[@id='action_confirm_primary']" position="attributes">
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{'invisible': ['|', ('state', 'not in', ['in_progress']), ('has_fillable_packages', '=', True)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@id='action_confirm_secondary']" position="attributes">
|
||||
<attribute
|
||||
name="attrs"
|
||||
>{"invisible": ["|", ["state", "not in", ["draft", "in_progress"]], ["has_fillable_packages", "=", False]]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//button[@id='create_invoices']" position="before">
|
||||
<button
|
||||
name="action_restore_ordered_qty"
|
||||
type="object"
|
||||
string="Restore Ordered Qty"
|
||||
attrs="{'invisible': ['|', ('state', '!=', 'in_progress'),('has_qty_adjusted', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
<!-- Fields -->
|
||||
<xpath
|
||||
expr="//field[@name='sale_order_line_ids']/tree/field[@name='product_uom_qty']"
|
||||
position="before"
|
||||
|
|
@ -26,6 +57,30 @@
|
|||
<xpath expr="//field[@name='product_ids']/tree/field[@name='product_id']" position="attributes">
|
||||
<attribute name="decoration-warning">(open_packaging_qty != 0)</attribute>
|
||||
</xpath>
|
||||
<!-- Open Products tab -->
|
||||
<xpath expr="//page[@name='products']" position="after">
|
||||
<page string="Open Products" name="open_products">
|
||||
<field name="open_product_ids">
|
||||
<tree create="0" delete="0">
|
||||
<field name="product_template_id" optional="hide"/>
|
||||
<field name="product_id"/>
|
||||
<field name="product_uom_qty"/>
|
||||
<field name="product_uom_max_qty"/>
|
||||
<field
|
||||
name="open_packaging_qty"
|
||||
decoration-danger="open_packaging_qty != 0"
|
||||
/>
|
||||
<field name="open_packaging_max_qty" optional="hide"/>
|
||||
<field
|
||||
name="open_packaging_state"
|
||||
widget="badge"
|
||||
decoration-warning="open_packaging_state == 'open'"
|
||||
decoration-danger="open_packaging_state == 'last_open'"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
13
nfu_sale_order_batch_packaging/views/uom_uom_views.xml
Normal file
13
nfu_sale_order_batch_packaging/views/uom_uom_views.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="product_uom_form_view_batch_fill" model="ir.ui.view">
|
||||
<field name="name">uom.uom.form.batch.fill</field>
|
||||
<field name="model">uom.uom</field>
|
||||
<field name="inherit_id" ref="uom.product_uom_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="rounding" position="after">
|
||||
<field name="batch_fill_precision"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
1
nfu_sale_order_batch_packaging/wizard/__init__.py
Normal file
1
nfu_sale_order_batch_packaging/wizard/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import sale_order_batch_packaging_fill
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class SaleOrderBatchPackagingFill(models.TransientModel):
|
||||
_name = "sale.order.batch.packaging.fill"
|
||||
_description = "Batch Packaging Auto-Fill Wizard"
|
||||
|
||||
batch_id = fields.Many2one("sale.order.batch", readonly=True)
|
||||
line_ids = fields.One2many("sale.order.batch.packaging.fill.line", "wizard_id")
|
||||
zero_line_ids = fields.One2many(
|
||||
"sale.order.batch.packaging.fill.zero.line", "wizard_id"
|
||||
)
|
||||
has_fill_lines = fields.Boolean(compute="_compute_has_fill_lines")
|
||||
has_zero_lines = fields.Boolean(compute="_compute_has_zero_lines")
|
||||
|
||||
@api.depends("line_ids")
|
||||
def _compute_has_fill_lines(self):
|
||||
for wizard in self:
|
||||
wizard.has_fill_lines = bool(wizard.line_ids)
|
||||
|
||||
@api.depends("zero_line_ids")
|
||||
def _compute_has_zero_lines(self):
|
||||
for wizard in self:
|
||||
wizard.has_zero_lines = bool(wizard.zero_line_ids)
|
||||
|
||||
def action_fill(self):
|
||||
for line in self.line_ids:
|
||||
line.batch_product_id._fill_packaging(line.batch_fill_precision)
|
||||
for zero_line in self.zero_line_ids.filtered(lambda l: not l.keep_qty):
|
||||
zero_line.batch_product_id._zero_packaging()
|
||||
return {"type": "ir.actions.act_window_close"}
|
||||
|
||||
|
||||
class SaleOrderBatchPackagingFillLine(models.TransientModel):
|
||||
_name = "sale.order.batch.packaging.fill.line"
|
||||
_description = "Batch Packaging Auto-Fill Wizard Line"
|
||||
|
||||
wizard_id = fields.Many2one("sale.order.batch.packaging.fill", ondelete="cascade")
|
||||
batch_product_id = fields.Many2one("sale.order.batch.product", required=True)
|
||||
product_id = fields.Many2one(
|
||||
related="batch_product_id.product_id", string="Product"
|
||||
)
|
||||
open_packaging_qty = fields.Float(
|
||||
related="batch_product_id.open_packaging_qty",
|
||||
string="Open Packaging Qty",
|
||||
digits=[12, 3],
|
||||
)
|
||||
batch_fill_precision = fields.Float(
|
||||
string="Precision",
|
||||
compute="_compute_batch_fill_precision",
|
||||
precompute=True,
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
@api.depends("batch_product_id.product_id.uom_id.batch_fill_precision")
|
||||
def _compute_batch_fill_precision(self):
|
||||
for line in self:
|
||||
line.batch_fill_precision = (
|
||||
line.batch_product_id.product_id.uom_id.batch_fill_precision
|
||||
)
|
||||
|
||||
|
||||
class SaleOrderBatchPackagingFillZeroLine(models.TransientModel):
|
||||
_name = "sale.order.batch.packaging.fill.zero.line"
|
||||
_description = "Batch Packaging Auto-Fill Zero-Out Line"
|
||||
|
||||
wizard_id = fields.Many2one("sale.order.batch.packaging.fill", ondelete="cascade")
|
||||
batch_product_id = fields.Many2one("sale.order.batch.product", required=True)
|
||||
product_id = fields.Many2one(
|
||||
related="batch_product_id.product_id", string="Product"
|
||||
)
|
||||
open_packaging_qty = fields.Float(
|
||||
related="batch_product_id.open_packaging_qty",
|
||||
string="Open Packaging Qty",
|
||||
digits=[12, 3],
|
||||
)
|
||||
keep_qty = fields.Boolean(string="Keep", default=False)
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_sale_order_batch_packaging_fill_form" model="ir.ui.view">
|
||||
<field name="name">sale.order.batch.packaging.fill.form</field>
|
||||
<field name="model">sale.order.batch.packaging.fill</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page
|
||||
string="Fill Packages"
|
||||
name="fill_packages"
|
||||
attrs="{'invisible': [('has_fill_lines', '=', False)]}"
|
||||
>
|
||||
<field name="has_fill_lines" invisible="1"/>
|
||||
<field name="line_ids">
|
||||
<tree editable="bottom" delete="0">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="open_packaging_qty" readonly="1"/>
|
||||
<field name="batch_fill_precision"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
<page string="Zero Out" name="zero_out" attrs="{'invisible': [('has_zero_lines', '=', False)]}">
|
||||
<field name="has_zero_lines" invisible="1"/>
|
||||
<field name="zero_line_ids">
|
||||
<tree editable="bottom" delete="0">
|
||||
<field name="product_id" readonly="1"/>
|
||||
<field name="open_packaging_qty" readonly="1"/>
|
||||
<field name="keep_qty"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<footer>
|
||||
<button name="action_fill" type="object" string="Confirm" class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -6,12 +6,20 @@
|
|||
<field name="inherit_id" ref="sale_order_batch.view_order_batch_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//header" position="inside">
|
||||
<field name="has_fillable_packages" invisible="1"/>
|
||||
<button
|
||||
name="export_wholesaler_csv"
|
||||
type="object"
|
||||
string="Export Wholesaler CSV"
|
||||
class="oe_highlight"
|
||||
attrs="{'invisible': [('state', 'not in', ['in_progress','closed'])]}"
|
||||
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress', 'closed']), ('has_fillable_packages', '=', True)]}"
|
||||
/>
|
||||
<button
|
||||
name="export_wholesaler_csv"
|
||||
type="object"
|
||||
string="Export Wholesaler CSV"
|
||||
class="btn-secondary"
|
||||
attrs="{'invisible': ['|', ('state', 'not in', ['in_progress', 'closed']), ('has_fillable_packages', '=', False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
|
|
|
|||
Loading…
Reference in a new issue