From dd98a204cc994c23372bf58bf8fa2e771d9f0676 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Niels=20G=C3=B6ttsch?=
Date: Fri, 29 May 2026 22:56:22 +0200
Subject: [PATCH] [IMP] nfu_sale_order_batch_packaging: use ordered qty logic
for customer orders
---
nfu_sale_order_batch_packaging/README.rst | 2 +-
.../__manifest__.py | 3 +-
.../data/uom_data.xml | 4 +-
nfu_sale_order_batch_packaging/i18n/de.po | 269 ++++++++++++++++++
.../models/sale_order_batch.py | 5 +-
.../models/sale_order_batch_product.py | 9 +-
.../models/sale_order_line.py | 8 +-
.../models/uom_uom.py | 16 +-
.../readme/HISTORY.rst | 2 +-
.../security/ir.model.access.csv | 6 +-
.../static/description/index.html | 2 +-
.../tests/__init__.py | 2 +-
...oncile.py => test_batch_packaging_fill.py} | 113 ++++----
.../views/uom_uom_views.xml | 13 +
.../wizard/__init__.py | 2 +-
....py => sale_order_batch_packaging_fill.py} | 49 ++--
...sale_order_batch_packaging_fill_views.xml} | 12 +-
17 files changed, 396 insertions(+), 121 deletions(-)
create mode 100644 nfu_sale_order_batch_packaging/i18n/de.po
rename nfu_sale_order_batch_packaging/tests/{test_batch_packaging_reconcile.py => test_batch_packaging_fill.py} (81%)
create mode 100644 nfu_sale_order_batch_packaging/views/uom_uom_views.xml
rename nfu_sale_order_batch_packaging/wizard/{sale_order_batch_packaging_reconcile.py => sale_order_batch_packaging_fill.py} (54%)
rename nfu_sale_order_batch_packaging/wizard/{sale_order_batch_packaging_reconcile_views.xml => sale_order_batch_packaging_fill_views.xml} (79%)
diff --git a/nfu_sale_order_batch_packaging/README.rst b/nfu_sale_order_batch_packaging/README.rst
index 45c7970..9d14fd3 100644
--- a/nfu_sale_order_batch_packaging/README.rst
+++ b/nfu_sale_order_batch_packaging/README.rst
@@ -50,7 +50,7 @@ Changelog
- 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 automatiok batch fillup
+- 16.0.1.4.0: Add automatic batch fillup
Bug Tracker
===========
diff --git a/nfu_sale_order_batch_packaging/__manifest__.py b/nfu_sale_order_batch_packaging/__manifest__.py
index 3805122..ddd0586 100644
--- a/nfu_sale_order_batch_packaging/__manifest__.py
+++ b/nfu_sale_order_batch_packaging/__manifest__.py
@@ -10,7 +10,8 @@
"security/ir.model.access.csv",
"data/ir_config_parameter.xml",
"data/uom_data.xml",
- "wizard/sale_order_batch_packaging_reconcile_views.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",
diff --git a/nfu_sale_order_batch_packaging/data/uom_data.xml b/nfu_sale_order_batch_packaging/data/uom_data.xml
index 7dc0480..24246db 100644
--- a/nfu_sale_order_batch_packaging/data/uom_data.xml
+++ b/nfu_sale_order_batch_packaging/data/uom_data.xml
@@ -1,7 +1,7 @@
- True
+ 1.0
@@ -10,6 +10,6 @@
smaller
0.01
1.0
- True
+ 1.0
diff --git a/nfu_sale_order_batch_packaging/i18n/de.po b/nfu_sale_order_batch_packaging/i18n/de.po
new file mode 100644
index 0000000..ab39287
--- /dev/null
+++ b/nfu_sale_order_batch_packaging/i18n/de.po
@@ -0,0 +1,269 @@
+# 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: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"
diff --git a/nfu_sale_order_batch_packaging/models/sale_order_batch.py b/nfu_sale_order_batch_packaging/models/sale_order_batch.py
index c7f877d..f28d741 100644
--- a/nfu_sale_order_batch_packaging/models/sale_order_batch.py
+++ b/nfu_sale_order_batch_packaging/models/sale_order_batch.py
@@ -45,7 +45,7 @@ class SaleOrderBatch(models.Model):
ineligible = self.product_ids.filtered(
lambda p: p.open_packaging_qty > 0 and p.open_packaging_max_qty != 0.0
)
- wizard = self.env["sale.order.batch.packaging.reconcile"].create(
+ 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],
@@ -56,7 +56,8 @@ class SaleOrderBatch(models.Model):
)
return {
"type": "ir.actions.act_window",
- "res_model": "sale.order.batch.packaging.reconcile",
+ "name": "Fill Open Packages",
+ "res_model": "sale.order.batch.packaging.fill",
"res_id": wizard.id,
"view_mode": "form",
"target": "new",
diff --git a/nfu_sale_order_batch_packaging/models/sale_order_batch_product.py b/nfu_sale_order_batch_packaging/models/sale_order_batch_product.py
index e7cabbd..a8ce5e6 100644
--- a/nfu_sale_order_batch_packaging/models/sale_order_batch_product.py
+++ b/nfu_sale_order_batch_packaging/models/sale_order_batch_product.py
@@ -99,13 +99,18 @@ class SaleOrderBatchProduct(models.Model):
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
+ 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 _reconcile_packaging(self, rounding):
+ 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(
[
diff --git a/nfu_sale_order_batch_packaging/models/sale_order_line.py b/nfu_sale_order_batch_packaging/models/sale_order_line.py
index 4a5bc0c..10c7bd7 100644
--- a/nfu_sale_order_batch_packaging/models/sale_order_line.py
+++ b/nfu_sale_order_batch_packaging/models/sale_order_line.py
@@ -6,7 +6,7 @@ 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"
@@ -32,16 +32,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),
diff --git a/nfu_sale_order_batch_packaging/models/uom_uom.py b/nfu_sale_order_batch_packaging/models/uom_uom.py
index bf9d5d5..dd38559 100644
--- a/nfu_sale_order_batch_packaging/models/uom_uom.py
+++ b/nfu_sale_order_batch_packaging/models/uom_uom.py
@@ -1,11 +1,17 @@
-from odoo import fields, models
+from odoo import api, fields, models
class UomUom(models.Model):
_inherit = "uom.uom"
- reconcile_whole_units = fields.Boolean(
- string="Reconcile as Whole Units",
- default=False,
- help="When set, the packaging reconciliation wizard defaults to rounding=1 for this 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
diff --git a/nfu_sale_order_batch_packaging/readme/HISTORY.rst b/nfu_sale_order_batch_packaging/readme/HISTORY.rst
index df31a30..6350d08 100644
--- a/nfu_sale_order_batch_packaging/readme/HISTORY.rst
+++ b/nfu_sale_order_batch_packaging/readme/HISTORY.rst
@@ -15,4 +15,4 @@
- 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 automatiok batch fillup
\ No newline at end of file
+- 16.0.1.4.0: Add automatic batch fillup
\ No newline at end of file
diff --git a/nfu_sale_order_batch_packaging/security/ir.model.access.csv b/nfu_sale_order_batch_packaging/security/ir.model.access.csv
index 3f4b2f2..201b7d5 100644
--- a/nfu_sale_order_batch_packaging/security/ir.model.access.csv
+++ b/nfu_sale_order_batch_packaging/security/ir.model.access.csv
@@ -1,4 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
-access_batch_packaging_reconcile,sale.order.batch.packaging.reconcile,model_sale_order_batch_packaging_reconcile,base.group_user,1,1,1,1
-access_batch_packaging_reconcile_line,sale.order.batch.packaging.reconcile.line,model_sale_order_batch_packaging_reconcile_line,base.group_user,1,1,1,1
-access_batch_packaging_reconcile_zero_line,sale.order.batch.packaging.reconcile.zero.line,model_sale_order_batch_packaging_reconcile_zero_line,base.group_user,1,1,1,1
+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
diff --git a/nfu_sale_order_batch_packaging/static/description/index.html b/nfu_sale_order_batch_packaging/static/description/index.html
index 012dcf4..1dee347 100644
--- a/nfu_sale_order_batch_packaging/static/description/index.html
+++ b/nfu_sale_order_batch_packaging/static/description/index.html
@@ -411,7 +411,7 @@ and adds a packaging default to sale order batch.
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 automatiok batch fillup
+16.0.1.4.0: Add automatic batch fillup
diff --git a/nfu_sale_order_batch_packaging/tests/__init__.py b/nfu_sale_order_batch_packaging/tests/__init__.py
index 4bde748..e23cadf 100644
--- a/nfu_sale_order_batch_packaging/tests/__init__.py
+++ b/nfu_sale_order_batch_packaging/tests/__init__.py
@@ -1 +1 @@
-from . import test_batch_packaging_reconcile
+from . import test_batch_packaging_fill
diff --git a/nfu_sale_order_batch_packaging/tests/test_batch_packaging_reconcile.py b/nfu_sale_order_batch_packaging/tests/test_batch_packaging_fill.py
similarity index 81%
rename from nfu_sale_order_batch_packaging/tests/test_batch_packaging_reconcile.py
rename to nfu_sale_order_batch_packaging/tests/test_batch_packaging_fill.py
index 027cc1d..2caba11 100644
--- a/nfu_sale_order_batch_packaging/tests/test_batch_packaging_reconcile.py
+++ b/nfu_sale_order_batch_packaging/tests/test_batch_packaging_fill.py
@@ -3,7 +3,7 @@ from odoo.tests.common import TransactionCase
@tagged("post_install", "-at_install")
-class TestBatchPackagingReconcile(TransactionCase):
+class TestBatchPackagingFill(TransactionCase):
def setUp(self):
super().setUp()
self.uom_kg = self.env.ref("uom.product_uom_kgm")
@@ -51,7 +51,7 @@ class TestBatchPackagingReconcile(TransactionCase):
return self.batch.product_ids.filtered(lambda p: p.product_id == product)
# ------------------------------------------------------------------
- # _reconcile_packaging
+ # _fill_packaging
# ------------------------------------------------------------------
def test_even_split(self):
@@ -61,7 +61,7 @@ class TestBatchPackagingReconcile(TransactionCase):
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._reconcile_packaging(0.001)
+ bp._fill_packaging(0.001)
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
# each line must have gained exactly 2 kg
@@ -80,7 +80,7 @@ class TestBatchPackagingReconcile(TransactionCase):
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._reconcile_packaging(0.001)
+ bp._fill_packaging(0.001)
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
capped = bp.sale_order_line_ids.filtered(
@@ -93,14 +93,14 @@ class TestBatchPackagingReconcile(TransactionCase):
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
def test_whole_units_rounding_produces_integers(self):
- """rounding=1.0 yields only whole-number additions."""
+ """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._reconcile_packaging(1.0)
+ bp._fill_packaging(1.0)
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
for line in bp.sale_order_line_ids:
@@ -108,29 +108,29 @@ class TestBatchPackagingReconcile(TransactionCase):
self.assertAlmostEqual(added, round(added), places=6)
def test_decimal_rounding_uses_tenth_steps(self):
- """rounding=0.1 distributes in 0.1-unit steps."""
+ """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._reconcile_packaging(0.1)
+ 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(added % 0.1, 0.0, places=5)
+ 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 rounding=1.0 every fair_share rounds to 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._reconcile_packaging(1.0)
+ 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)
@@ -139,10 +139,10 @@ class TestBatchPackagingReconcile(TransactionCase):
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
def test_no_line_exceeds_max_qty(self):
- """After reconciliation no sale order line may exceed its max qty."""
+ """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._reconcile_packaging(0.001)
+ 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)
@@ -160,63 +160,53 @@ class TestBatchPackagingReconcile(TransactionCase):
self._add_lines(ineligible, [(23.0, 24.0)])
action = self.batch.action_fill_packages()
- wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
- action["res_id"]
- )
+ 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_whole_units_for_unit_uom(self):
- """UOMs with reconcile_whole_units=True default use_whole_units to True."""
+ 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.reconcile"].browse(
- action["res_id"]
- )
+ wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
- self.assertTrue(wizard.line_ids.use_whole_units)
+ self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 1.0)
- def test_wizard_default_whole_units_for_stueck_uom(self):
- """Stück UOM (reconcile_whole_units=True) defaults use_whole_units to True."""
+ 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.reconcile"].browse(
- action["res_id"]
- )
+ wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
- self.assertTrue(wizard.line_ids.use_whole_units)
+ self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 1.0)
- def test_wizard_default_decimal_for_kg_uom(self):
- """UOMs without reconcile_whole_units default use_whole_units to False."""
+ 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.reconcile"].browse(
- action["res_id"]
- )
+ wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
- self.assertFalse(wizard.line_ids.use_whole_units)
+ self.assertAlmostEqual(wizard.line_ids.batch_fill_precision, 0.1)
- def test_wizard_reconcile_closes_open_packaging(self):
+ 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.reconcile"].browse(
- action["res_id"]
- )
- wizard.action_reconcile()
+ 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)
@@ -228,8 +218,8 @@ class TestBatchPackagingReconcile(TransactionCase):
"""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._reconcile_packaging(0.1)
- # reconciliation must have increased some qty
+ bp._fill_packaging(0.1)
+ # fill must have increased some qty
self.assertTrue(
any(
line.product_uom_qty != line.product_uom_ordered_qty
@@ -248,8 +238,8 @@ class TestBatchPackagingReconcile(TransactionCase):
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._reconcile_packaging(0.1)
- bp_b._reconcile_packaging(0.1)
+ bp_a._fill_packaging(0.1)
+ bp_b._fill_packaging(0.1)
bp_a.action_restore_ordered_qty()
@@ -265,24 +255,24 @@ class TestBatchPackagingReconcile(TransactionCase):
)
)
- def test_has_qty_adjusted_false_before_reconcile(self):
+ 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_reconcile(self):
+ 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._reconcile_packaging(0.1)
+ 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._reconcile_packaging(0.1)
+ bp._fill_packaging(0.1)
self.batch.action_restore_ordered_qty()
self.assertFalse(self.batch.has_qty_adjusted)
@@ -334,12 +324,10 @@ class TestBatchPackagingReconcile(TransactionCase):
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.reconcile"].browse(
- action["res_id"]
- )
+ wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
self.assertFalse(wizard.zero_line_ids.keep_qty)
- wizard.action_reconcile()
+ wizard.action_fill()
for line in bp.sale_order_line_ids:
self.assertAlmostEqual(line.product_uom_qty, 0.0, places=3)
@@ -351,33 +339,28 @@ class TestBatchPackagingReconcile(TransactionCase):
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.reconcile"].browse(
- action["res_id"]
- )
+ wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
wizard.zero_line_ids.write({"keep_qty": True})
- wizard.action_reconcile()
+ 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_use_whole_units_override(self):
- """Toggling use_whole_units on a wizard line changes the applied rounding."""
+ 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; 3 lines with room 1 each won't cover 3 with rounding=1
- # but with rounding=0.1 each gets 1.0 exactly — use 2 lines (rooms: 7 and 6)
+ # 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.reconcile"].browse(
- action["res_id"]
- )
+ wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
# Force whole-units even though UOM is kg
- wizard.line_ids.write({"use_whole_units": True})
- wizard.action_reconcile()
+ 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 use_whole_units=True
+ # additions must be whole numbers when precision=1.0
self.assertAlmostEqual(
line.product_uom_qty, round(line.product_uom_qty), places=6
)
diff --git a/nfu_sale_order_batch_packaging/views/uom_uom_views.xml b/nfu_sale_order_batch_packaging/views/uom_uom_views.xml
new file mode 100644
index 0000000..d0faeb8
--- /dev/null
+++ b/nfu_sale_order_batch_packaging/views/uom_uom_views.xml
@@ -0,0 +1,13 @@
+
+
+
+ uom.uom.form.batch.fill
+ uom.uom
+
+
+
+
+
+
+
+
diff --git a/nfu_sale_order_batch_packaging/wizard/__init__.py b/nfu_sale_order_batch_packaging/wizard/__init__.py
index f468d6b..3b6be7a 100644
--- a/nfu_sale_order_batch_packaging/wizard/__init__.py
+++ b/nfu_sale_order_batch_packaging/wizard/__init__.py
@@ -1 +1 @@
-from . import sale_order_batch_packaging_reconcile
+from . import sale_order_batch_packaging_fill
diff --git a/nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_reconcile.py b/nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_fill.py
similarity index 54%
rename from nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_reconcile.py
rename to nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_fill.py
index 6fb523e..ff3def3 100644
--- a/nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_reconcile.py
+++ b/nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_fill.py
@@ -1,14 +1,14 @@
from odoo import api, fields, models
-class SaleOrderBatchPackagingReconcile(models.TransientModel):
- _name = "sale.order.batch.packaging.reconcile"
- _description = "Batch Packaging Auto-Reconcile Wizard"
+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.reconcile.line", "wizard_id")
+ line_ids = fields.One2many("sale.order.batch.packaging.fill.line", "wizard_id")
zero_line_ids = fields.One2many(
- "sale.order.batch.packaging.reconcile.zero.line", "wizard_id"
+ "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")
@@ -23,22 +23,19 @@ class SaleOrderBatchPackagingReconcile(models.TransientModel):
for wizard in self:
wizard.has_zero_lines = bool(wizard.zero_line_ids)
- def action_reconcile(self):
+ def action_fill(self):
for line in self.line_ids:
- rounding = 1.0 if line.use_whole_units else 0.1
- line.batch_product_id._reconcile_packaging(rounding)
+ 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 SaleOrderBatchPackagingReconcileLine(models.TransientModel):
- _name = "sale.order.batch.packaging.reconcile.line"
- _description = "Batch Packaging Auto-Reconcile Wizard Line"
+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.reconcile", ondelete="cascade"
- )
+ 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"
@@ -48,29 +45,27 @@ class SaleOrderBatchPackagingReconcileLine(models.TransientModel):
string="Open Packaging Qty",
digits=[12, 3],
)
- use_whole_units = fields.Boolean(
- string="Round to 1",
- compute="_compute_use_whole_units",
+ 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.reconcile_whole_units")
- def _compute_use_whole_units(self):
+ @api.depends("batch_product_id.product_id.uom_id.batch_fill_precision")
+ def _compute_batch_fill_precision(self):
for line in self:
- line.use_whole_units = (
- line.batch_product_id.product_id.uom_id.reconcile_whole_units
+ line.batch_fill_precision = (
+ line.batch_product_id.product_id.uom_id.batch_fill_precision
)
-class SaleOrderBatchPackagingReconcileZeroLine(models.TransientModel):
- _name = "sale.order.batch.packaging.reconcile.zero.line"
- _description = "Batch Packaging Auto-Reconcile Zero-Out Line"
+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.reconcile", ondelete="cascade"
- )
+ 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"
diff --git a/nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_reconcile_views.xml b/nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_fill_views.xml
similarity index 79%
rename from nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_reconcile_views.xml
rename to nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_fill_views.xml
index 96430d0..afb40a3 100644
--- a/nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_reconcile_views.xml
+++ b/nfu_sale_order_batch_packaging/wizard/sale_order_batch_packaging_fill_views.xml
@@ -1,10 +1,10 @@
-
- sale.order.batch.packaging.reconcile.form
- sale.order.batch.packaging.reconcile
+
+ sale.order.batch.packaging.fill.form
+ sale.order.batch.packaging.fill
-
@@ -34,7 +34,7 @@