[IMP] nfu_sale_order_batch_packaging: use ordered qty logic for customer orders
This commit is contained in:
parent
7efa474617
commit
dd98a204cc
17 changed files with 396 additions and 121 deletions
|
|
@ -50,7 +50,7 @@ Changelog
|
||||||
- 16.0.1.3.4: store all quantities and round open values
|
- 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.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.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
|
Bug Tracker
|
||||||
===========
|
===========
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@
|
||||||
"security/ir.model.access.csv",
|
"security/ir.model.access.csv",
|
||||||
"data/ir_config_parameter.xml",
|
"data/ir_config_parameter.xml",
|
||||||
"data/uom_data.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_views.xml",
|
||||||
"views/sale_order_batch_views.xml",
|
"views/sale_order_batch_views.xml",
|
||||||
"views/sale_order_batch_product_views.xml",
|
"views/sale_order_batch_product_views.xml",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="uom.product_uom_unit" model="uom.uom">
|
<record id="uom.product_uom_unit" model="uom.uom">
|
||||||
<field name="reconcile_whole_units">True</field>
|
<field name="batch_fill_precision">1.0</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="product_uom_stueck" model="uom.uom">
|
<record id="product_uom_stueck" model="uom.uom">
|
||||||
|
|
@ -10,6 +10,6 @@
|
||||||
<field name="uom_type">smaller</field>
|
<field name="uom_type">smaller</field>
|
||||||
<field name="rounding">0.01</field>
|
<field name="rounding">0.01</field>
|
||||||
<field name="ratio">1.0</field>
|
<field name="ratio">1.0</field>
|
||||||
<field name="reconcile_whole_units">True</field>
|
<field name="batch_fill_precision">1.0</field>
|
||||||
</record>
|
</record>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|
|
||||||
269
nfu_sale_order_batch_packaging/i18n/de.po
Normal file
269
nfu_sale_order_batch_packaging/i18n/de.po
Normal file
|
|
@ -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"
|
||||||
|
|
@ -45,7 +45,7 @@ class SaleOrderBatch(models.Model):
|
||||||
ineligible = self.product_ids.filtered(
|
ineligible = self.product_ids.filtered(
|
||||||
lambda p: p.open_packaging_qty > 0 and p.open_packaging_max_qty != 0.0
|
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,
|
"batch_id": self.id,
|
||||||
"line_ids": [(0, 0, {"batch_product_id": p.id}) for p in eligible],
|
"line_ids": [(0, 0, {"batch_product_id": p.id}) for p in eligible],
|
||||||
|
|
@ -56,7 +56,8 @@ class SaleOrderBatch(models.Model):
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"type": "ir.actions.act_window",
|
"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,
|
"res_id": wizard.id,
|
||||||
"view_mode": "form",
|
"view_mode": "form",
|
||||||
"target": "new",
|
"target": "new",
|
||||||
|
|
|
||||||
|
|
@ -99,13 +99,18 @@ class SaleOrderBatchProduct(models.Model):
|
||||||
def action_restore_ordered_qty(self):
|
def action_restore_ordered_qty(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
lines = self.sale_order_line_ids.filtered(
|
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:
|
for line in lines:
|
||||||
line.write({"product_uom_qty": line.product_uom_ordered_qty})
|
line.write({"product_uom_qty": line.product_uom_ordered_qty})
|
||||||
|
|
||||||
def _reconcile_packaging(self, rounding):
|
def _fill_packaging(self, rounding):
|
||||||
self.ensure_one()
|
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
|
target = self.open_packaging_qty
|
||||||
eligible = sorted(
|
eligible = sorted(
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ class SaleOrderLine(models.Model):
|
||||||
_inherit = "sale.order.line"
|
_inherit = "sale.order.line"
|
||||||
|
|
||||||
product_uom_ordered_qty = fields.Float(
|
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(
|
product_uom_max_qty = fields.Float(
|
||||||
string="Max Qty", digits="Product Unit of Measure"
|
string="Max Qty", digits="Product Unit of Measure"
|
||||||
|
|
@ -32,16 +32,18 @@ class SaleOrderLine(models.Model):
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
def create(self, vals_list):
|
||||||
for vals in vals_list:
|
for vals in vals_list:
|
||||||
|
product_qty = vals.get("product_uom_qty", 0.0)
|
||||||
if (
|
if (
|
||||||
not vals.get("product_uom_max_qty")
|
not vals.get("product_uom_max_qty")
|
||||||
or vals.get("product_uom_max_qty") == 0
|
or vals.get("product_uom_max_qty") == 0
|
||||||
):
|
):
|
||||||
product_qty = vals.get("product_uom_qty")
|
|
||||||
vals["product_uom_max_qty"] = product_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"):
|
if vals.get("product_id") and not vals.get("product_packaging_id"):
|
||||||
product_id = vals.get("product_id")
|
product_id = vals.get("product_id")
|
||||||
# Make sure we're not sudo
|
# Make sure we're not sudo
|
||||||
# e.x. when comming from webshop
|
# e.x. when coming from webshop
|
||||||
packaging = self.env["product.packaging"].search(
|
packaging = self.env["product.packaging"].search(
|
||||||
[
|
[
|
||||||
("product_id", "=", product_id),
|
("product_id", "=", product_id),
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
from odoo import fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class UomUom(models.Model):
|
class UomUom(models.Model):
|
||||||
_inherit = "uom.uom"
|
_inherit = "uom.uom"
|
||||||
|
|
||||||
reconcile_whole_units = fields.Boolean(
|
batch_fill_precision = fields.Float(
|
||||||
string="Reconcile as Whole Units",
|
compute="_compute_batch_fill_precision",
|
||||||
default=False,
|
store=True,
|
||||||
help="When set, the packaging reconciliation wizard defaults to rounding=1 for this UOM.",
|
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
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,4 @@
|
||||||
- 16.0.1.3.4: store all quantities and round open values
|
- 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.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.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
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
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_fill,sale.order.batch.packaging.fill,model_sale_order_batch_packaging_fill,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_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_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_zero_line,sale.order.batch.packaging.fill.zero.line,model_sale_order_batch_packaging_fill_zero_line,base.group_user,1,1,1,1
|
||||||
|
|
|
||||||
|
|
|
@ -411,7 +411,7 @@ and adds a packaging default to sale order batch.</p>
|
||||||
<li>16.0.1.3.4: store all quantities and round open values</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.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.3.7: really make sure packages are readable by the user</li>
|
||||||
<li>16.0.1.4.0: Add automatiok batch fillup</li>
|
<li>16.0.1.4.0: Add automatic batch fillup</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="section" id="bug-tracker">
|
<div class="section" id="bug-tracker">
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
from . import test_batch_packaging_reconcile
|
from . import test_batch_packaging_fill
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ from odoo.tests.common import TransactionCase
|
||||||
|
|
||||||
|
|
||||||
@tagged("post_install", "-at_install")
|
@tagged("post_install", "-at_install")
|
||||||
class TestBatchPackagingReconcile(TransactionCase):
|
class TestBatchPackagingFill(TransactionCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.uom_kg = self.env.ref("uom.product_uom_kgm")
|
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)
|
return self.batch.product_ids.filtered(lambda p: p.product_id == product)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# _reconcile_packaging
|
# _fill_packaging
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
def test_even_split(self):
|
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)])
|
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)
|
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)
|
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||||
# each line must have gained exactly 2 kg
|
# 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)])
|
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)
|
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)
|
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||||
capped = bp.sale_order_line_ids.filtered(
|
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)
|
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
|
||||||
|
|
||||||
def test_whole_units_rounding_produces_integers(self):
|
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)
|
product = self._make_product(self.uom_unit, 6.0)
|
||||||
# total = 7, open = 5; rooms: 3 and 5
|
# total = 7, open = 5; rooms: 3 and 5
|
||||||
bp = self._add_lines(product, [(3.0, 6.0), (4.0, 9.0)])
|
bp = self._add_lines(product, [(3.0, 6.0), (4.0, 9.0)])
|
||||||
self.assertAlmostEqual(bp.open_packaging_qty, 5.0, places=2)
|
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}
|
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)
|
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||||
for line in bp.sale_order_line_ids:
|
for line in bp.sale_order_line_ids:
|
||||||
|
|
@ -108,29 +108,29 @@ class TestBatchPackagingReconcile(TransactionCase):
|
||||||
self.assertAlmostEqual(added, round(added), places=6)
|
self.assertAlmostEqual(added, round(added), places=6)
|
||||||
|
|
||||||
def test_decimal_rounding_uses_tenth_steps(self):
|
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)
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
# total = 7, open = 3; rooms: 7 and 6 → sorted: line2(6), line1(7)
|
# 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)])
|
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
|
||||||
self.assertAlmostEqual(bp.open_packaging_qty, 3.0, places=2)
|
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}
|
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)
|
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||||
for line in bp.sale_order_line_ids:
|
for line in bp.sale_order_line_ids:
|
||||||
added = round(line.product_uom_qty - orig[line.id], 6)
|
added = round(line.product_uom_qty - orig[line.id], 6)
|
||||||
# must be a multiple of 0.1 (within floating-point tolerance)
|
# 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):
|
def test_sub_rounding_remainder_is_attached(self):
|
||||||
"""A remainder smaller than the rounding step is added directly to the last line."""
|
"""A remainder smaller than the rounding step is added directly to the last line."""
|
||||||
product = self._make_product(self.uom_kg, 3.0)
|
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)])
|
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)
|
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
|
# The whole 0.3 remainder must have been placed on one line
|
||||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
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)
|
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
|
||||||
|
|
||||||
def test_no_line_exceeds_max_qty(self):
|
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)
|
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 = 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:
|
for line in bp.sale_order_line_ids:
|
||||||
self.assertLessEqual(line.product_uom_qty, line.product_uom_max_qty + 1e-6)
|
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)])
|
self._add_lines(ineligible, [(23.0, 24.0)])
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||||
action["res_id"]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(len(wizard.line_ids), 1)
|
self.assertEqual(len(wizard.line_ids), 1)
|
||||||
self.assertEqual(wizard.line_ids.product_id, eligible)
|
self.assertEqual(wizard.line_ids.product_id, eligible)
|
||||||
self.assertEqual(len(wizard.zero_line_ids), 1)
|
self.assertEqual(len(wizard.zero_line_ids), 1)
|
||||||
self.assertEqual(wizard.zero_line_ids.product_id, ineligible)
|
self.assertEqual(wizard.zero_line_ids.product_id, ineligible)
|
||||||
|
|
||||||
def test_wizard_default_whole_units_for_unit_uom(self):
|
def test_wizard_default_precision_for_unit_uom(self):
|
||||||
"""UOMs with reconcile_whole_units=True default use_whole_units to True."""
|
"""UOMs with batch_fill_precision=1.0 default to precision 1.0."""
|
||||||
product = self._make_product(self.uom_unit, 6.0)
|
product = self._make_product(self.uom_unit, 6.0)
|
||||||
self._add_lines(product, [(3.0, 9.0)])
|
self._add_lines(product, [(3.0, 9.0)])
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||||
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):
|
def test_wizard_default_precision_for_stueck_uom(self):
|
||||||
"""Stück UOM (reconcile_whole_units=True) defaults use_whole_units to True."""
|
"""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")
|
uom_stueck = self.env.ref("nfu_sale_order_batch_packaging.product_uom_stueck")
|
||||||
product = self._make_product(uom_stueck, 6.0)
|
product = self._make_product(uom_stueck, 6.0)
|
||||||
self._add_lines(product, [(3.0, 9.0)])
|
self._add_lines(product, [(3.0, 9.0)])
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||||
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):
|
def test_wizard_default_precision_for_kg_uom(self):
|
||||||
"""UOMs without reconcile_whole_units default use_whole_units to False."""
|
"""UOMs with default batch_fill_precision use 0.1."""
|
||||||
product = self._make_product(self.uom_kg, 10.0)
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
self._add_lines(product, [(23.0, 30.0)])
|
self._add_lines(product, [(23.0, 30.0)])
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||||
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."""
|
"""End-to-end: confirming the wizard brings open_packaging_qty to zero."""
|
||||||
product = self._make_product(self.uom_kg, 10.0)
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
bp = self._add_lines(product, [(23.0, 30.0)])
|
bp = self._add_lines(product, [(23.0, 30.0)])
|
||||||
self.assertGreater(bp.open_packaging_qty, 0.0)
|
self.assertGreater(bp.open_packaging_qty, 0.0)
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||||
action["res_id"]
|
wizard.action_fill()
|
||||||
)
|
|
||||||
wizard.action_reconcile()
|
|
||||||
|
|
||||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
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."""
|
"""action_restore_ordered_qty on the batch sets every line back to ordered qty."""
|
||||||
product = self._make_product(self.uom_kg, 10.0)
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
bp = self._add_lines(product, [(23.0, 30.0)])
|
bp = self._add_lines(product, [(23.0, 30.0)])
|
||||||
bp._reconcile_packaging(0.1)
|
bp._fill_packaging(0.1)
|
||||||
# reconciliation must have increased some qty
|
# fill must have increased some qty
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
any(
|
any(
|
||||||
line.product_uom_qty != line.product_uom_ordered_qty
|
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)
|
product_b = self._make_product(self.uom_kg, 10.0)
|
||||||
bp_a = self._add_lines(product_a, [(23.0, 30.0)])
|
bp_a = self._add_lines(product_a, [(23.0, 30.0)])
|
||||||
bp_b = self._add_lines(product_b, [(23.0, 30.0)])
|
bp_b = self._add_lines(product_b, [(23.0, 30.0)])
|
||||||
bp_a._reconcile_packaging(0.1)
|
bp_a._fill_packaging(0.1)
|
||||||
bp_b._reconcile_packaging(0.1)
|
bp_b._fill_packaging(0.1)
|
||||||
|
|
||||||
bp_a.action_restore_ordered_qty()
|
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."""
|
"""has_qty_adjusted is False when no line has been modified."""
|
||||||
product = self._make_product(self.uom_kg, 10.0)
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
self._add_lines(product, [(10.0, 15.0)])
|
self._add_lines(product, [(10.0, 15.0)])
|
||||||
self.assertFalse(self.batch.has_qty_adjusted)
|
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."""
|
"""has_qty_adjusted becomes True once a line quantity is increased."""
|
||||||
product = self._make_product(self.uom_kg, 10.0)
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
bp = self._add_lines(product, [(23.0, 30.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)
|
self.assertTrue(self.batch.has_qty_adjusted)
|
||||||
|
|
||||||
def test_has_qty_adjusted_false_after_restore(self):
|
def test_has_qty_adjusted_false_after_restore(self):
|
||||||
"""has_qty_adjusted returns to False after restoring ordered quantities."""
|
"""has_qty_adjusted returns to False after restoring ordered quantities."""
|
||||||
product = self._make_product(self.uom_kg, 10.0)
|
product = self._make_product(self.uom_kg, 10.0)
|
||||||
bp = self._add_lines(product, [(23.0, 30.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.batch.action_restore_ordered_qty()
|
||||||
self.assertFalse(self.batch.has_qty_adjusted)
|
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)
|
bp = self.batch.product_ids.filtered(lambda p: p.product_id == ineligible)
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||||
action["res_id"]
|
|
||||||
)
|
|
||||||
self.assertFalse(wizard.zero_line_ids.keep_qty)
|
self.assertFalse(wizard.zero_line_ids.keep_qty)
|
||||||
|
|
||||||
wizard.action_reconcile()
|
wizard.action_fill()
|
||||||
|
|
||||||
for line in bp.sale_order_line_ids:
|
for line in bp.sale_order_line_ids:
|
||||||
self.assertAlmostEqual(line.product_uom_qty, 0.0, places=3)
|
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}
|
orig_qtys = {line.id: line.product_uom_qty for line in bp.sale_order_line_ids}
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||||
action["res_id"]
|
|
||||||
)
|
|
||||||
wizard.zero_line_ids.write({"keep_qty": True})
|
wizard.zero_line_ids.write({"keep_qty": True})
|
||||||
wizard.action_reconcile()
|
wizard.action_fill()
|
||||||
|
|
||||||
for line in bp.sale_order_line_ids:
|
for line in bp.sale_order_line_ids:
|
||||||
self.assertAlmostEqual(line.product_uom_qty, orig_qtys[line.id], places=3)
|
self.assertAlmostEqual(line.product_uom_qty, orig_qtys[line.id], places=3)
|
||||||
|
|
||||||
def test_wizard_respects_use_whole_units_override(self):
|
def test_wizard_respects_precision_override(self):
|
||||||
"""Toggling use_whole_units on a wizard line changes the applied rounding."""
|
"""Overriding batch_fill_precision on a wizard line changes the applied rounding."""
|
||||||
product = self._make_product(self.uom_kg, 10.0)
|
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
|
# total = 7, open = 3; use 2 lines (rooms: 7 and 6)
|
||||||
# but with rounding=0.1 each gets 1.0 exactly — use 2 lines (rooms: 7 and 6)
|
|
||||||
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
|
bp = self._add_lines(product, [(3.0, 10.0), (4.0, 10.0)])
|
||||||
|
|
||||||
action = self.batch.action_fill_packages()
|
action = self.batch.action_fill_packages()
|
||||||
wizard = self.env["sale.order.batch.packaging.reconcile"].browse(
|
wizard = self.env["sale.order.batch.packaging.fill"].browse(action["res_id"])
|
||||||
action["res_id"]
|
|
||||||
)
|
|
||||||
# Force whole-units even though UOM is kg
|
# Force whole-units even though UOM is kg
|
||||||
wizard.line_ids.write({"use_whole_units": True})
|
wizard.line_ids.write({"batch_fill_precision": 1.0})
|
||||||
wizard.action_reconcile()
|
wizard.action_fill()
|
||||||
|
|
||||||
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
self.assertAlmostEqual(bp.open_packaging_qty, 0.0, places=2)
|
||||||
for line in bp.sale_order_line_ids:
|
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(
|
self.assertAlmostEqual(
|
||||||
line.product_uom_qty, round(line.product_uom_qty), places=6
|
line.product_uom_qty, round(line.product_uom_qty), places=6
|
||||||
)
|
)
|
||||||
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 +1 @@
|
||||||
from . import sale_order_batch_packaging_reconcile
|
from . import sale_order_batch_packaging_fill
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
from odoo import api, fields, models
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
class SaleOrderBatchPackagingReconcile(models.TransientModel):
|
class SaleOrderBatchPackagingFill(models.TransientModel):
|
||||||
_name = "sale.order.batch.packaging.reconcile"
|
_name = "sale.order.batch.packaging.fill"
|
||||||
_description = "Batch Packaging Auto-Reconcile Wizard"
|
_description = "Batch Packaging Auto-Fill Wizard"
|
||||||
|
|
||||||
batch_id = fields.Many2one("sale.order.batch", readonly=True)
|
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(
|
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_fill_lines = fields.Boolean(compute="_compute_has_fill_lines")
|
||||||
has_zero_lines = fields.Boolean(compute="_compute_has_zero_lines")
|
has_zero_lines = fields.Boolean(compute="_compute_has_zero_lines")
|
||||||
|
|
@ -23,22 +23,19 @@ class SaleOrderBatchPackagingReconcile(models.TransientModel):
|
||||||
for wizard in self:
|
for wizard in self:
|
||||||
wizard.has_zero_lines = bool(wizard.zero_line_ids)
|
wizard.has_zero_lines = bool(wizard.zero_line_ids)
|
||||||
|
|
||||||
def action_reconcile(self):
|
def action_fill(self):
|
||||||
for line in self.line_ids:
|
for line in self.line_ids:
|
||||||
rounding = 1.0 if line.use_whole_units else 0.1
|
line.batch_product_id._fill_packaging(line.batch_fill_precision)
|
||||||
line.batch_product_id._reconcile_packaging(rounding)
|
|
||||||
for zero_line in self.zero_line_ids.filtered(lambda l: not l.keep_qty):
|
for zero_line in self.zero_line_ids.filtered(lambda l: not l.keep_qty):
|
||||||
zero_line.batch_product_id._zero_packaging()
|
zero_line.batch_product_id._zero_packaging()
|
||||||
return {"type": "ir.actions.act_window_close"}
|
return {"type": "ir.actions.act_window_close"}
|
||||||
|
|
||||||
|
|
||||||
class SaleOrderBatchPackagingReconcileLine(models.TransientModel):
|
class SaleOrderBatchPackagingFillLine(models.TransientModel):
|
||||||
_name = "sale.order.batch.packaging.reconcile.line"
|
_name = "sale.order.batch.packaging.fill.line"
|
||||||
_description = "Batch Packaging Auto-Reconcile Wizard Line"
|
_description = "Batch Packaging Auto-Fill Wizard Line"
|
||||||
|
|
||||||
wizard_id = fields.Many2one(
|
wizard_id = fields.Many2one("sale.order.batch.packaging.fill", ondelete="cascade")
|
||||||
"sale.order.batch.packaging.reconcile", ondelete="cascade"
|
|
||||||
)
|
|
||||||
batch_product_id = fields.Many2one("sale.order.batch.product", required=True)
|
batch_product_id = fields.Many2one("sale.order.batch.product", required=True)
|
||||||
product_id = fields.Many2one(
|
product_id = fields.Many2one(
|
||||||
related="batch_product_id.product_id", string="Product"
|
related="batch_product_id.product_id", string="Product"
|
||||||
|
|
@ -48,29 +45,27 @@ class SaleOrderBatchPackagingReconcileLine(models.TransientModel):
|
||||||
string="Open Packaging Qty",
|
string="Open Packaging Qty",
|
||||||
digits=[12, 3],
|
digits=[12, 3],
|
||||||
)
|
)
|
||||||
use_whole_units = fields.Boolean(
|
batch_fill_precision = fields.Float(
|
||||||
string="Round to 1",
|
string="Precision",
|
||||||
compute="_compute_use_whole_units",
|
compute="_compute_batch_fill_precision",
|
||||||
precompute=True,
|
precompute=True,
|
||||||
store=True,
|
store=True,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends("batch_product_id.product_id.uom_id.reconcile_whole_units")
|
@api.depends("batch_product_id.product_id.uom_id.batch_fill_precision")
|
||||||
def _compute_use_whole_units(self):
|
def _compute_batch_fill_precision(self):
|
||||||
for line in self:
|
for line in self:
|
||||||
line.use_whole_units = (
|
line.batch_fill_precision = (
|
||||||
line.batch_product_id.product_id.uom_id.reconcile_whole_units
|
line.batch_product_id.product_id.uom_id.batch_fill_precision
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SaleOrderBatchPackagingReconcileZeroLine(models.TransientModel):
|
class SaleOrderBatchPackagingFillZeroLine(models.TransientModel):
|
||||||
_name = "sale.order.batch.packaging.reconcile.zero.line"
|
_name = "sale.order.batch.packaging.fill.zero.line"
|
||||||
_description = "Batch Packaging Auto-Reconcile Zero-Out Line"
|
_description = "Batch Packaging Auto-Fill Zero-Out Line"
|
||||||
|
|
||||||
wizard_id = fields.Many2one(
|
wizard_id = fields.Many2one("sale.order.batch.packaging.fill", ondelete="cascade")
|
||||||
"sale.order.batch.packaging.reconcile", ondelete="cascade"
|
|
||||||
)
|
|
||||||
batch_product_id = fields.Many2one("sale.order.batch.product", required=True)
|
batch_product_id = fields.Many2one("sale.order.batch.product", required=True)
|
||||||
product_id = fields.Many2one(
|
product_id = fields.Many2one(
|
||||||
related="batch_product_id.product_id", string="Product"
|
related="batch_product_id.product_id", string="Product"
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<record id="view_sale_order_batch_packaging_reconcile_form" model="ir.ui.view">
|
<record id="view_sale_order_batch_packaging_fill_form" model="ir.ui.view">
|
||||||
<field name="name">sale.order.batch.packaging.reconcile.form</field>
|
<field name="name">sale.order.batch.packaging.fill.form</field>
|
||||||
<field name="model">sale.order.batch.packaging.reconcile</field>
|
<field name="model">sale.order.batch.packaging.fill</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form string="Fill Packages">
|
<form>
|
||||||
<sheet>
|
<sheet>
|
||||||
<notebook>
|
<notebook>
|
||||||
<page
|
<page
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
<tree editable="bottom">
|
<tree editable="bottom">
|
||||||
<field name="product_id" readonly="1"/>
|
<field name="product_id" readonly="1"/>
|
||||||
<field name="open_packaging_qty" readonly="1"/>
|
<field name="open_packaging_qty" readonly="1"/>
|
||||||
<field name="use_whole_units"/>
|
<field name="batch_fill_precision"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</page>
|
</page>
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
<footer>
|
<footer>
|
||||||
<button name="action_reconcile" type="object" string="Reconcile" class="btn-primary"/>
|
<button name="action_fill" type="object" string="Confirm" class="btn-primary"/>
|
||||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||||
</footer>
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
Loading…
Reference in a new issue